Compare commits

..

40 Commits

Author SHA1 Message Date
ChiKyun Kim
649d87cae3 .. 2025-12-23 17:25:47 +09:00
backuppc
b3f661969a .. 2025-12-23 13:25:33 +09:00
backuppc
4f162e50b5 .. 2025-12-23 13:12:13 +09:00
backuppc
35df73fd29 xbee 값 설정기능 추가 2025-12-23 13:07:01 +09:00
ChiKyun Kim
8499c1c5be .. 2025-12-23 11:41:56 +09:00
backuppc
3408e3fc30 ... 2025-12-22 16:02:57 +09:00
ChiKyun Kim
5a4c73e4df Merge branch 'master' of file://k4fs3201n/k4bpartcenter$/Repository/K4/ENIG_AGV 2025-12-22 09:52:53 +09:00
ChiKyun Kim
34d1bdf504 add layout 2025-12-22 09:52:21 +09:00
backuppc
3cae423736 .. 2025-12-19 16:25:30 +09:00
ChiKyun Kim
d777adc219 BMS 를 RS232 클래스에서 폴링방식 전용 클래스로 변경
BMS 정보중 현재 사용 전류와 와트를 표시함
사용전류를 통해서 충전여부를 자동 판다시키고, 해당 값은 Manual Charge 플래그에 설정함.
2025-12-18 14:44:00 +09:00
ChiKyun Kim
b62cd5f52e 충방전 전류 및 전력량 계산 코드 추가 2025-12-18 10:55:04 +09:00
backuppc
32217c8501 Merge branch 'master' of https://git.tindevil.com/Amkor/ENIG 2025-12-18 10:32:48 +09:00
backuppc
9274727fa9 .. 2025-12-18 10:32:20 +09:00
2a44ba28a8 "fix:Enforce-Run-Mode-in-fAuto-VisibleChanged" 2025-12-18 00:43:22 +09:00
4bdc36040d "feat:Implement-Canvas-Run-Mode-and-Logic" 2025-12-18 00:38:59 +09:00
384f2affcb "refactor:Switch-serial-comm-to-polling" 2025-12-18 00:34:37 +09:00
ChiKyun Kim
51579591a2 add docment -
enable : signal2
2025-12-17 16:36:12 +09:00
ChiKyun Kim
cef2fa8095 flag,error 항목 정리, 2025-12-17 15:53:18 +09:00
backuppc
1f37871336 .. 2025-12-17 14:54:33 +09:00
eb0e08d290 .. 2025-12-16 23:31:47 +09:00
backuppc
4153362588 .. 2025-12-16 17:29:28 +09:00
67a48531ad .. 2025-12-15 22:06:53 +09:00
backuppc
a7f938ff19 .. 2025-12-15 17:34:43 +09:00
backuppc
9db88e5d6b .agvmap 확장자제거하고 .json 으로 통일한 mapeditor 와 호환성 유지 2025-12-15 08:33:42 +09:00
a8cb952ea4 .. 2025-12-14 22:56:04 +09:00
3c8eae889c "refactor:Improve-map-loading-and-use-canvas-nodes" 2025-12-14 22:55:09 +09:00
764fbbd204 "feat:Enable-hover-highlight-and-refactor" 2025-12-14 17:20:50 +09:00
34b038c4be Implement ACS Command Handlers (PickOn, PickOff, Charge), Manual Mode Safety, and Map UI Commands 2025-12-13 02:40:55 +09:00
703e1387bf .. 2025-12-12 20:57:55 +09:00
f507a6487e 소스정리 2025-12-12 20:00:33 +09:00
backuppc
4e9d29d22f ing... 2025-12-12 17:27:50 +09:00
backuppc
07ddc0425f 에뮬레이터 개발 전. 2025-12-12 14:29:06 +09:00
backuppc
ee2a6f04bb add ignore ( .vscode) 2025-12-11 08:24:08 +09:00
backuppc
6024f372d3 buffer in/out 시퀀스 작성중 2025-12-11 08:22:52 +09:00
backuppc
9a0a389e07 buffer in/out 시퀀스 작성 중 2025-12-11 08:22:24 +09:00
ChiKyun Kim
af280d7b27 오디오재생함수추가 2025-12-10 13:36:35 +09:00
backuppc
868fa2deec add supertonic 2025-12-10 11:39:15 +09:00
backuppc
3695ab0044 sync 전용 화면 만들기 전 백업. 2025-12-10 10:35:19 +09:00
backuppc
2236e3b1ba add GDS set button 2025-12-09 13:33:42 +09:00
backuppc
9db031c305 agv 노드 정보 정리 세분화 2025-12-09 13:18:22 +09:00
207 changed files with 125646 additions and 8690 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
obj
bin
.vscode
*.user
*.v12
*.suo

View File

@@ -0,0 +1,140 @@
<?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>..\Cs_HMI\DLL\arControl.Net4.dll</HintPath>
</Reference>
<Reference Include="ArLog.Net4">
<HintPath>..\Cs_HMI\DLL\ArLog.Net4.dll</HintPath>
</Reference>
<Reference Include="ArSetting.Net4">
<HintPath>..\Cs_HMI\DLL\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\CommUtil\arCommUtil.csproj">
<Project>{14e8c9a5-013e-49ba-b435-ffffff7dd623}</Project>
<Name>arCommUtil</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>

View File

@@ -0,0 +1,43 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.14.36804.6 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVEmulator", "AGVEmulator.csproj", "{9312AB43-72F6-4365-A266-E767215FA7F5}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVNavigationCore", "..\Cs_HMI\AGVLogic\AGVNavigationCore\AGVNavigationCore.csproj", "{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ENIGProtocol", "..\Cs_HMI\SubProject\ENIGProtocol\enigprotocol\ENIGProtocol.csproj", "{9365803B-933D-4237-93C7-B502C855A71C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "arCommUtil", "..\Cs_HMI\SubProject\CommUtil\arCommUtil.csproj", "{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9312AB43-72F6-4365-A266-E767215FA7F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9312AB43-72F6-4365-A266-E767215FA7F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9312AB43-72F6-4365-A266-E767215FA7F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9312AB43-72F6-4365-A266-E767215FA7F5}.Release|Any CPU.Build.0 = Release|Any CPU
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C}.Release|Any CPU.Build.0 = Release|Any CPU
{9365803B-933D-4237-93C7-B502C855A71C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9365803B-933D-4237-93C7-B502C855A71C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9365803B-933D-4237-93C7-B502C855A71C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9365803B-933D-4237-93C7-B502C855A71C}.Release|Any CPU.Build.0 = Release|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {C353381B-16D4-4298-A97E-6DE0FC4A66B2}
EndGlobalSection
EndGlobal

537
AGVEmulator/DevAGV.cs Normal file
View File

@@ -0,0 +1,537 @@
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 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 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);
_agvCanvas.SetMapLoadResult(mapresult);//.Nodes = _mapNodes;
// Initialize Visual AGV
if (_agvCanvas.Nodes.Count > 0)
{
_visualAgv = new VirtualAGV("AGV01", _agvCanvas.Nodes[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 || _agvCanvas.Nodes == null) return;
// Find node by tag
// Assuming NodeId might be the tag or contain it
var node = _agvCanvas.Nodes.FirstOrDefault(n => n.Id == tag || n.Id.EndsWith(tag));
// If not found, try to parse tag as int and match
if (node == null && int.TryParse(tag, out int tagNum))
{
node = _agvCanvas.Nodes.FirstOrDefault(n => n.Id == 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 || _agvCanvas.Nodes == 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 _agvCanvas.Nodes)
{
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.Id != numericUpDown1.Text)
{
if (int.TryParse(node.Id, out int tag))
{
AGV.SendTag(node.Id);
numericUpDown1.Text = node.Id;
// 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, NodeBase e)
{
if (e != null && _visualAgv != null)
{
_visualAgv.CurrentPosition = e.Position;
_currentPosF = e.Position;
if (int.TryParse(e.Id, 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";
if(System.IO.File.Exists(file)==false)
{
var od = new OpenFileDialog();
od.Filter = "json|*.json";
if (od.ShowDialog() != DialogResult.OK) return;
file = od.FileName;
}
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}");
// 시뮬레이터 캔버스에 맵 설정
this._agvCanvas.SetMapLoadResult(result);// = _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) && _agvCanvas.Nodes != null && _visualAgv != null)
{
var node = _agvCanvas.Nodes.FirstOrDefault(n => n.Id == input);
if (node != null)
{
_visualAgv.CurrentPosition = node.Position;
_currentPosF = node.Position;
numericUpDown1.Text = node.Id;
// Auto-orient: Prefer Right (0) or Up (270)
var neighbors = _agvCanvas.Nodes.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>

3
Cs_HMI/.cursorrules Normal file
View File

@@ -0,0 +1,3 @@
# User Preferences
- **프로젝트 컴파일은 build.bat 를 사용하세요(윈도우 환경)
- **콘솔명령 실행시에는 항상 cmd /c 를 사용해서 실행하세요

View File

@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Express 15 for Windows Desktop
VisualStudioVersion = 15.0.36324.19
# Visual Studio Version 17
VisualStudioVersion = 17.14.36804.6 d17.14
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Sub", "Sub", "{C423C39A-44E7-4F09-B2F7-7943975FF948}"
EndProject
@@ -36,6 +36,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVMapEditor", "AGVLogic\AG
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AGVSimulator", "AGVLogic\AGVSimulator\AGVSimulator.csproj", "{B2C3D4E5-0000-0000-0000-000000000000}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "arCommUtil", "SubProject\commutil\arCommUtil.csproj", "{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -166,6 +168,18 @@ Global
{B2C3D4E5-0000-0000-0000-000000000000}.Release|x64.Build.0 = Release|Any CPU
{B2C3D4E5-0000-0000-0000-000000000000}.Release|x86.ActiveCfg = Release|Any CPU
{B2C3D4E5-0000-0000-0000-000000000000}.Release|x86.Build.0 = Release|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Debug|x64.ActiveCfg = Debug|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Debug|x64.Build.0 = Debug|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Debug|x86.ActiveCfg = Debug|x86
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Debug|x86.Build.0 = Debug|x86
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|Any CPU.Build.0 = Release|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|x64.ActiveCfg = Release|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|x64.Build.0 = Release|Any CPU
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|x86.ActiveCfg = Release|x86
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -178,6 +192,7 @@ Global
{C5F7A8B2-8D3E-4A1B-9C6E-7F4D5E2A9B1C} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
{A1B2C3D4-E5F6-7890-ABCD-EF1234567890} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
{B2C3D4E5-0000-0000-0000-000000000000} = {E5C75D32-5AD6-44DD-8F27-E32023206EBB}
{14E8C9A5-013E-49BA-B435-FFFFFF7DD623} = {C423C39A-44E7-4F09-B2F7-7943975FF948}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B5B1FD72-356F-4840-83E8-B070AC21C8D9}

View File

@@ -17,7 +17,7 @@
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\..\..\..\..\Amkor\AGV4\</OutputPath>
<OutputPath>..\..\..\..\..\..\Amkor\AGV4\Test\MapEditor\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
@@ -62,7 +62,6 @@
<Compile Include="Models\ImagePathEditor.cs" />
<Compile Include="Models\MapImage.cs" />
<Compile Include="Models\MapLabel.cs" />
<Compile Include="Models\NodePropertyWrapper.cs" />
<Compile Include="Forms\MainForm.cs">
<SubType>Form</SubType>
</Compile>

View File

@@ -13,9 +13,9 @@ namespace AGVMapEditor.Forms
/// </summary>
public partial class ImageEditorForm : Form
{
private MapNode _targetNode;
private MapImage _targetNode;
public ImageEditorForm(MapNode imageNode = null)
public ImageEditorForm(MapImage imageNode = null)
{
InitializeComponent();
_targetNode = imageNode;
@@ -25,7 +25,7 @@ namespace AGVMapEditor.Forms
{
LoadImageFromNode(imageNode);
}
this.KeyPreview = true;
this.KeyDown += (s1, e1) => {
if (e1.KeyCode == Keys.Escape) this.Close();
@@ -38,7 +38,7 @@ namespace AGVMapEditor.Forms
imageCanvas.BrushSize = trackBrush.Value;
imageCanvas.BrushModeEnabled = chkBrushMode.Checked;
imageCanvas.BackColor = Color.FromArgb(32,32,32);
// 이벤트 연결
chkBrushMode.CheckedChanged += (s, e) => imageCanvas.BrushModeEnabled = chkBrushMode.Checked;
}
@@ -48,7 +48,7 @@ namespace AGVMapEditor.Forms
imageCanvas.BrushSize = trackBrush.Value;
}
private void LoadImageFromNode(MapNode node)
private void LoadImageFromNode(MapImage node)
{
if (node.LoadedImage != null)
{
@@ -66,7 +66,7 @@ namespace AGVMapEditor.Forms
}
}
}
private void LoadImageFromFile(string filePath)
{
try
@@ -160,7 +160,7 @@ namespace AGVMapEditor.Forms
return;
}
if (_targetNode != null && _targetNode.Type == NodeType.Image)
if (_targetNode != null)
{
// 표시 크기로 리사이즈된 이미지 가져오기
var finalImage = imageCanvas.GetResizedImage();

View File

@@ -64,6 +64,9 @@ namespace AGVMapEditor.Forms
this.toolStripSeparator3 = new System.Windows.Forms.ToolStripSeparator();
this.btnSave = new System.Windows.Forms.ToolStripButton();
this.btnSaveAs = new System.Windows.Forms.ToolStripButton();
this.toolStripSeparator2 = new System.Windows.Forms.ToolStripSeparator();
this.toolStripButton1 = new System.Windows.Forms.ToolStripDropDownButton();
this.allTurnLeftRightCrossOnToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
this.statusStrip1.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.splitContainer1)).BeginInit();
this.splitContainer1.Panel1.SuspendLayout();
@@ -351,7 +354,9 @@ namespace AGVMapEditor.Forms
this.btnClose,
this.toolStripSeparator3,
this.btnSave,
this.btnSaveAs});
this.btnSaveAs,
this.toolStripSeparator2,
this.toolStripButton1});
this.toolStrip2.Location = new System.Drawing.Point(0, 0);
this.toolStrip2.Name = "toolStrip2";
this.toolStrip2.Size = new System.Drawing.Size(1200, 25);
@@ -417,6 +422,28 @@ namespace AGVMapEditor.Forms
this.btnSaveAs.ToolTipText = "다른 이름으로 저장";
this.btnSaveAs.Click += new System.EventHandler(this.btnSaveAs_Click);
//
// toolStripSeparator2
//
this.toolStripSeparator2.Name = "toolStripSeparator2";
this.toolStripSeparator2.Size = new System.Drawing.Size(6, 25);
//
// toolStripButton1
//
this.toolStripButton1.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.allTurnLeftRightCrossOnToolStripMenuItem});
this.toolStripButton1.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButton1.Image")));
this.toolStripButton1.ImageTransparentColor = System.Drawing.Color.Magenta;
this.toolStripButton1.Name = "toolStripButton1";
this.toolStripButton1.Size = new System.Drawing.Size(68, 22);
this.toolStripButton1.Text = "Debig";
//
// allTurnLeftRightCrossOnToolStripMenuItem
//
this.allTurnLeftRightCrossOnToolStripMenuItem.Name = "allTurnLeftRightCrossOnToolStripMenuItem";
this.allTurnLeftRightCrossOnToolStripMenuItem.Size = new System.Drawing.Size(223, 22);
this.allTurnLeftRightCrossOnToolStripMenuItem.Text = "All TurnLeft/Right/Cross On";
this.allTurnLeftRightCrossOnToolStripMenuItem.Click += new System.EventHandler(this.allTurnLeftRightCrossOnToolStripMenuItem_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
@@ -490,5 +517,8 @@ namespace AGVMapEditor.Forms
private System.Windows.Forms.ToolStripButton btnAddImage;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.ToolStripSeparator toolStripSeparator2;
private System.Windows.Forms.ToolStripDropDownButton toolStripButton1;
private System.Windows.Forms.ToolStripMenuItem allTurnLeftRightCrossOnToolStripMenuItem;
}
}

View File

@@ -7,6 +7,8 @@ using System.Windows.Forms;
using AGVMapEditor.Models;
using AGVNavigationCore.Controls;
using AGVNavigationCore.Models;
using MapImage = AGVNavigationCore.Models.MapImage;
using MapLabel = AGVNavigationCore.Models.MapLabel;
using Newtonsoft.Json;
namespace AGVMapEditor.Forms
@@ -18,11 +20,11 @@ namespace AGVMapEditor.Forms
{
#region Fields
private List<MapNode> _mapNodes;
// private List<MapNode> this._mapCanvas.Nodes;
private UnifiedAGVCanvas _mapCanvas;
// 현재 선택된 노드
private MapNode _selectedNode;
private NodeBase _selectedNode;
// 파일 경로
private string _currentMapFile = string.Empty;
@@ -34,20 +36,20 @@ namespace AGVMapEditor.Forms
{
public string FromNodeId { get; set; }
public string FromNodeName { get; set; }
public string FromRfidId { get; set; }
public ushort FromRfidId { get; set; }
public string ToNodeId { get; set; }
public string ToNodeName { get; set; }
public string ToRfidId { get; set; }
public ushort ToRfidId { get; set; }
public string ConnectionType { get; set; }
public override string ToString()
{
// RFID가 있으면 RFID(노드이름), 없으면 NodeID(노드이름) 형태로 표시
string fromDisplay = !string.IsNullOrEmpty(FromRfidId)
string fromDisplay = FromRfidId > 0
? $"{FromRfidId}({FromNodeName})"
: $"---({FromNodeId})";
string toDisplay = !string.IsNullOrEmpty(ToRfidId)
string toDisplay = ToRfidId > 0
? $"{ToRfidId}({ToNodeName})"
: $"---({ToNodeId})";
@@ -93,20 +95,18 @@ namespace AGVMapEditor.Forms
#endregion
#region Initialization
private void InitializeData()
{
_mapNodes = new List<MapNode>();
// this._mapCanvas.Nodes = new List<MapNode>();
}
private void InitializeMapCanvas()
{
_mapCanvas = new UnifiedAGVCanvas();
_mapCanvas.Dock = DockStyle.Fill;
_mapCanvas.Nodes = _mapNodes;
// RfidMappings 제거 - MapNode에 통합됨
// 이벤트 연결
_mapCanvas.NodeAdded += OnNodeAdded;
@@ -115,12 +115,14 @@ namespace AGVMapEditor.Forms
_mapCanvas.NodeMoved += OnNodeMoved;
_mapCanvas.NodeDeleted += OnNodeDeleted;
_mapCanvas.ConnectionDeleted += OnConnectionDeleted;
_mapCanvas.ImageNodeDoubleClicked += OnImageNodeDoubleClicked;
_mapCanvas.ImageDoubleClicked += OnImageDoubleClicked;
_mapCanvas.MapChanged += OnMapChanged;
// 스플리터 패널에 맵 캔버스 추가
panel1.Controls.Add(_mapCanvas);
// ...
// 툴바 버튼 이벤트 연결
WireToolbarButtonEvents();
}
@@ -160,7 +162,7 @@ namespace AGVMapEditor.Forms
private void MainForm_Load(object sender, EventArgs e)
{
RefreshNodeList();
// 속성 변경 시 이벤트 연결
_propertyGrid.PropertyValueChanged += PropertyGrid_PropertyValueChanged;
@@ -174,7 +176,7 @@ namespace AGVMapEditor.Forms
}
}
private void OnNodeAdded(object sender, MapNode node)
private void OnNodeAdded(object sender, NodeBase node)
{
_hasChanges = true;
UpdateTitle();
@@ -199,7 +201,7 @@ namespace AGVMapEditor.Forms
// }
//}
private void OnNodesSelected(object sender, List<MapNode> nodes)
private void OnNodesSelected(object sender, List<NodeBase> nodes)
{
// 다중 선택 시 처리
if (nodes == null || nodes.Count == 0)
@@ -221,20 +223,20 @@ namespace AGVMapEditor.Forms
toolStripStatusLabel1.Text = $"{nodes.Count}개 노드 선택됨 - PropertyGrid에서 공통 속성 일괄 변경 가능";
// 다중 선택 PropertyWrapper 표시
var multiWrapper = new MultiNodePropertyWrapper(nodes);
_propertyGrid.SelectedObject = multiWrapper;
//var multiWrapper = new MultiNodePropertyWrapper(nodes);
_propertyGrid.SelectedObjects = nodes.ToArray();// multiWrapper;
_propertyGrid.Focus();
}
}
private void OnNodeMoved(object sender, MapNode node)
private void OnNodeMoved(object sender, NodeBase node)
{
_hasChanges = true;
UpdateTitle();
RefreshNodeList();
}
private void OnNodeDeleted(object sender, MapNode node)
private void OnNodeDeleted(object sender, NodeBase node)
{
_hasChanges = true;
UpdateTitle();
@@ -258,20 +260,17 @@ namespace AGVMapEditor.Forms
UpdateNodeProperties(); // 연결 정보 업데이트
}
private void OnImageNodeDoubleClicked(object sender, MapNode node)
private void OnImageDoubleClicked(object sender, MapImage image)
{
// 이미지 노드 더블클릭 시 이미지 편집창 표시
if (node != null && node.Type == NodeType.Image)
using (var editor = new ImageEditorForm(image))
{
using (var editor = new ImageEditorForm(node))
if (editor.ShowDialog(this) == DialogResult.OK)
{
if (editor.ShowDialog(this) == DialogResult.OK)
{
_hasChanges = true;
UpdateTitle();
_mapCanvas.Invalidate(); // 캔버스 다시 그리기
UpdateNodeProperties(); // 속성 업데이트
}
_hasChanges = true;
UpdateTitle();
_mapCanvas.Invalidate(); // 캔버스 다시 그리기
UpdateNodeProperties(); // 속성 업데이트
}
}
}
@@ -401,12 +400,11 @@ namespace AGVMapEditor.Forms
private void AddNewNode()
{
var nodeId = GenerateNodeId();
var nodeName = $"노드{_mapNodes.Count + 1}";
var position = new Point(100 + _mapNodes.Count * 50, 100 + _mapNodes.Count * 50);
var position = new Point(100 + this._mapCanvas.Nodes.Count * 50, 100 + this._mapCanvas.Nodes.Count * 50);
var node = new MapNode(nodeId, nodeName, position, NodeType.Normal);
var node = new MapNode(nodeId, position, StationType.Normal);
_mapNodes.Add(node);
this._mapCanvas.Nodes.Add(node);
_hasChanges = true;
RefreshNodeList();
@@ -422,13 +420,14 @@ namespace AGVMapEditor.Forms
return;
}
var result = MessageBox.Show($"노드 '{_selectedNode.Name}'를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.",
var rfidDisplay = (_selectedNode as MapNode)?.RfidId ?? 0;
var result = MessageBox.Show($"노드 {rfidDisplay}[{_selectedNode.Id}] 를 삭제하시겠습니까?\n연결된 RFID 매핑도 함께 삭제됩니다.",
"삭제 확인", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
// 노드 제거
_mapNodes.Remove(_selectedNode);
_mapCanvas.RemoveItem(_selectedNode);
_selectedNode = null;
_hasChanges = true;
@@ -441,15 +440,15 @@ namespace AGVMapEditor.Forms
private void AddConnectionToSelectedNode()
{
if (_selectedNode == null)
if (!(_selectedNode is MapNode selectedMapNode))
{
MessageBox.Show("연결을 추가할 노드를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
MessageBox.Show("연결을 추가할 노드(MapNode)를 선택하세요.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 다른 노드들 중에서 선택
var availableNodes = _mapNodes.Where(n => n.NodeId != _selectedNode.NodeId &&
!_selectedNode.ConnectedNodes.Contains(n.NodeId)).ToList();
var availableNodes = this._mapCanvas.Nodes.Where(n => n.Id != selectedMapNode.Id &&
!selectedMapNode.ConnectedNodes.Contains(n.Id)).ToList();
if (availableNodes.Count == 0)
{
@@ -458,13 +457,13 @@ namespace AGVMapEditor.Forms
}
// 간단한 선택 다이얼로그 (실제로는 별도 폼을 만들어야 함)
var nodeNames = availableNodes.Select(n => $"{n.NodeId}: {n.Name}").ToArray();
var nodeNames = availableNodes.Select(n => $"{n.Id}: {n.RfidId}").ToArray();
var input = Microsoft.VisualBasic.Interaction.InputBox("연결할 노드를 선택하세요:", "노드 연결", nodeNames[0]);
var targetNode = availableNodes.FirstOrDefault(n => input.StartsWith(n.NodeId));
var targetNode = availableNodes.FirstOrDefault(n => input.StartsWith(n.Id));
if (targetNode != null)
{
_selectedNode.AddConnection(targetNode.NodeId);
selectedMapNode.AddConnection(targetNode.Id);
_hasChanges = true;
RefreshMapCanvas();
UpdateNodeProperties();
@@ -474,25 +473,25 @@ namespace AGVMapEditor.Forms
private void RemoveConnectionFromSelectedNode()
{
if (_selectedNode == null || _selectedNode.ConnectedNodes.Count == 0)
if (!(_selectedNode is MapNode selectedMapNode) || selectedMapNode.ConnectedNodes.Count == 0)
{
MessageBox.Show("연결을 제거할 노드를 선택하거나 연결된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
// 연결된 노드들 중에서 선택
var connectedNodeNames = _selectedNode.ConnectedNodes.Select(connectedNodeId =>
var connectedNodeNames = selectedMapNode.ConnectedNodes.Select(connectedNodeId =>
{
var node = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
return node != null ? $"{node.NodeId}: {node.Name}" : connectedNodeId;
var node = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectedNodeId);
return node != null ? $"{node.Id}: {node.RfidId}" : connectedNodeId;
}).ToArray();
var input = Microsoft.VisualBasic.Interaction.InputBox("제거할 연결을 선택하세요:", "연결 제거", connectedNodeNames[0]);
var targetNodeId = input.Split(':')[0];
if (_selectedNode.ConnectedNodes.Contains(targetNodeId))
if (selectedMapNode.ConnectedNodes.Contains(targetNodeId))
{
_selectedNode.RemoveConnection(targetNodeId);
selectedMapNode.RemoveConnection(targetNodeId);
_hasChanges = true;
RefreshMapCanvas();
UpdateNodeProperties();
@@ -509,7 +508,7 @@ namespace AGVMapEditor.Forms
{
nodeId = $"N{counter:D3}";
counter++;
} while (_mapNodes.Any(n => n.NodeId == nodeId));
} while (this._mapCanvas.Nodes.Any(n => n.Id == nodeId));
return nodeId;
}
@@ -520,7 +519,7 @@ namespace AGVMapEditor.Forms
private void NewMap()
{
_mapNodes.Clear();
this._mapCanvas.Nodes.Clear();
_selectedNode = null;
_currentMapFile = string.Empty;
_hasChanges = false;
@@ -533,7 +532,7 @@ namespace AGVMapEditor.Forms
{
if (CheckSaveChanges())
{
_mapNodes.Clear();
this._mapCanvas.Nodes.Clear();
_selectedNode = null;
_currentMapFile = string.Empty;
_hasChanges = false;
@@ -547,8 +546,8 @@ namespace AGVMapEditor.Forms
{
var openFileDialog = new OpenFileDialog
{
Filter = "AGV Map Files (*.agvmap)|*.agvmap|All Files (*.*)|*.*",
DefaultExt = "agvmap",
Filter = "AGV Map Files (*.json)|*.json|All Files (*.*)|*.*",
DefaultExt = "json",
};
@@ -596,9 +595,9 @@ namespace AGVMapEditor.Forms
{
var saveFileDialog = new SaveFileDialog
{
Filter = "AGV Map Files (*.agvmap)|*.agvmap",
DefaultExt = "agvmap",
FileName = "NewMap.agvmap"
Filter = "AGV Map Files (*.json)|*.json",
DefaultExt = "json",
FileName = "NewMap.json"
};
if (saveFileDialog.ShowDialog() == DialogResult.OK)
@@ -624,18 +623,8 @@ namespace AGVMapEditor.Forms
if (result.Success)
{
_mapNodes = result.Nodes;
// 맵 캔버스에 데이터 설정
_mapCanvas.Nodes = _mapNodes;
// RfidMappings 제거됨 - MapNode에 통합
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
if (result.Settings != null)
{
_mapCanvas.BackColor = System.Drawing.Color.FromArgb(result.Settings.BackgroundColorArgb);
_mapCanvas.ShowGrid = result.Settings.ShowGrid;
}
_mapCanvas.SetMapLoadResult(result);
// 현재 파일 경로 업데이트
_currentMapFile = filePath;
@@ -648,8 +637,8 @@ namespace AGVMapEditor.Forms
UpdateNodeList();
RefreshNodeConnectionList();
// 맵 로드 후 자동으로 맵에 맞춤
_mapCanvas.FitToNodes();
UpdateStatusBar($"맵 파일을 성공적으로 로드했습니다: {Path.GetFileName(filePath)}");
}
@@ -693,7 +682,11 @@ namespace AGVMapEditor.Forms
ShowGrid = _mapCanvas.ShowGrid
};
if (MapLoader.SaveMapToFile(filePath, _mapNodes, settings))
if (MapLoader.SaveMapToFile(filePath,
_mapCanvas.Nodes, _mapCanvas.Labels,
_mapCanvas.Images, _mapCanvas.Marks,
_mapCanvas.Magnets,
settings))
{
// 현재 파일 경로 업데이트
_currentMapFile = filePath;
@@ -718,7 +711,7 @@ namespace AGVMapEditor.Forms
private void UpdateRfidMappings()
{
// RFID 자동 할당 제거 - 사용자가 직접 입력한 값 유지
// MapLoader.AssignAutoRfidIds(_mapNodes);
// MapLoader.AssignAutoRfidIds(this._mapCanvas.Nodes);
}
private bool CheckSaveChanges()
@@ -780,14 +773,14 @@ namespace AGVMapEditor.Forms
private void RefreshNodeList()
{
listBoxNodes.DataSource = null;
listBoxNodes.DataSource = _mapNodes;
listBoxNodes.DataSource = this._mapCanvas.Nodes;
listBoxNodes.DisplayMember = "DisplayText";
listBoxNodes.ValueMember = "NodeId";
listBoxNodes.ValueMember = "Id";
// 노드 목록 클릭 이벤트 연결
listBoxNodes.SelectedIndexChanged -= ListBoxNodes_SelectedIndexChanged;
listBoxNodes.SelectedIndexChanged += ListBoxNodes_SelectedIndexChanged;
// 노드 타입별 색상 적용
listBoxNodes.DrawMode = DrawMode.OwnerDrawFixed;
listBoxNodes.DrawItem -= ListBoxNodes_DrawItem;
@@ -812,14 +805,14 @@ namespace AGVMapEditor.Forms
{
e.DrawBackground();
if (e.Index >= 0 && e.Index < _mapNodes.Count)
if (e.Index >= 0 && e.Index < this._mapCanvas.Items.Count)
{
var node = _mapNodes[e.Index];
var node = this._mapCanvas.Items[e.Index];
// 노드 타입에 따른 색상 설정
Color foreColor = Color.Black;
Color backColor = e.BackColor;
if ((e.State & DrawItemState.Selected) == DrawItemState.Selected)
{
backColor = SystemColors.Highlight;
@@ -827,23 +820,30 @@ namespace AGVMapEditor.Forms
}
else
{
backColor = Color.White;
switch (node.Type)
{
case NodeType.Normal:
foreColor = Color.Black;
backColor = Color.White;
var item = node as MapNode;
if (item.StationType == StationType.Normal)
foreColor = Color.DimGray;
else if (item.StationType == StationType.Charger)
foreColor = Color.Red;
else
foreColor = Color.DarkGreen;
break;
case NodeType.Rotation:
foreColor = Color.DarkOrange;
backColor = Color.LightYellow;
case NodeType.Label:
case NodeType.Mark:
case NodeType.Image:
foreColor = Color.DarkBlue;
break;
case NodeType.Docking:
foreColor = Color.DarkGreen;
backColor = Color.LightGreen;
case NodeType.Magnet:
foreColor = Color.DarkMagenta;
break;
case NodeType.Charging:
default:
foreColor = Color.DarkRed;
backColor = Color.LightPink;
break;
}
}
@@ -855,17 +855,21 @@ namespace AGVMapEditor.Forms
}
// 텍스트 그리기 (노드ID - 노드명 - RFID 순서)
var displayText = node.NodeId;
if (!string.IsNullOrEmpty(node.Name))
string displayText;
if (node.Type == NodeType.Normal)
{
displayText += $" - {node.Name}";
var item = node as MapNode;
if (item.HasRfid())
displayText = $"[{node.Id}-{item.RfidId}] {item.StationType}";
else
displayText = $"[{node.Id}] {item.StationType}";
}
if (!string.IsNullOrEmpty(node.RfidId))
else if (node.Type == NodeType.Label)
{
displayText += $" - [{node.RfidId}]";
var item = node as MapLabel;
displayText = $"{item.Type.ToString().ToUpper()} - {item.Text}";
}
else displayText = $"{node.Type.ToString().ToUpper()}";
using (var brush = new SolidBrush(foreColor))
{
@@ -882,31 +886,31 @@ namespace AGVMapEditor.Forms
var processedPairs = new HashSet<string>();
// 모든 노드의 연결 정보를 수집 (중복 방지)
foreach (var fromNode in _mapNodes)
foreach (var fromNode in this._mapCanvas.Nodes)
{
foreach (var toNodeId in fromNode.ConnectedNodes)
{
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == toNodeId);
var toNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == toNodeId);
if (toNode != null)
{
// 중복 체크 (단일 연결만 표시)
string pairKey1 = $"{fromNode.NodeId}-{toNode.NodeId}";
string pairKey2 = $"{toNode.NodeId}-{fromNode.NodeId}";
string pairKey1 = $"{fromNode.Id}-{toNode.Id}";
string pairKey2 = $"{toNode.Id}-{fromNode.Id}";
if (!processedPairs.Contains(pairKey1) && !processedPairs.Contains(pairKey2))
{
// 사전 순으로 정렬하여 일관성 있게 표시
var (firstNode, secondNode) = string.Compare(fromNode.NodeId, toNode.NodeId) < 0
var (firstNode, secondNode) = string.Compare(fromNode.Id, toNode.Id) < 0
? (fromNode, toNode)
: (toNode, fromNode);
connections.Add(new NodeConnectionInfo
{
FromNodeId = firstNode.NodeId,
FromNodeName = firstNode.Name,
FromNodeId = firstNode.Id,
FromNodeName = "",
FromRfidId = firstNode.RfidId,
ToNodeId = secondNode.NodeId,
ToNodeName = secondNode.Name,
ToNodeId = secondNode.Id,
ToNodeName = "",
ToRfidId = secondNode.RfidId,
ConnectionType = "양방향" // 모든 연결이 양방향
});
@@ -941,7 +945,7 @@ namespace AGVMapEditor.Forms
_mapCanvas?.HighlightConnection(connectionInfo.FromNodeId, connectionInfo.ToNodeId);
// 연결된 노드들을 맵에서 하이라이트 표시 (선택적)
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.FromNodeId);
var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId);
if (fromNode != null)
{
_selectedNode = fromNode;
@@ -975,9 +979,7 @@ namespace AGVMapEditor.Forms
return;
}
// 노드 래퍼 객체 생성 (타입에 따라 다른 래퍼 사용)
var nodeWrapper = NodePropertyWrapperFactory.CreateWrapper(_selectedNode, _mapNodes);
_propertyGrid.SelectedObject = nodeWrapper;
_propertyGrid.SelectedObject = _selectedNode;
_propertyGrid.Focus();
// 이미지 노드인 경우 편집 버튼 활성화
@@ -989,8 +991,7 @@ namespace AGVMapEditor.Forms
/// </summary>
private void ShowCanvasProperties()
{
var canvasWrapper = new CanvasPropertyWrapper(_mapCanvas);
_propertyGrid.SelectedObject = canvasWrapper;
_propertyGrid.SelectedObject = _mapCanvas;
DisableImageEditButton();
}
@@ -1006,7 +1007,7 @@ namespace AGVMapEditor.Forms
private void UpdateImageEditButton()
{
// ToolStripButton으로 변경됨
btnEditImage.Enabled = (_selectedNode != null && _selectedNode.Type == NodeType.Image);
btnEditImage.Enabled = (_mapCanvas.SelectedImage != null);
}
/// <summary>
@@ -1023,9 +1024,10 @@ namespace AGVMapEditor.Forms
/// </summary>
private void BtnToolbarEditImage_Click(object sender, EventArgs e)
{
if (_selectedNode != null && _selectedNode.Type == NodeType.Image)
var selectedImage = _mapCanvas.SelectedImage;
if (selectedImage != null)
{
using (var editor = new ImageEditorForm(_selectedNode))
using (var editor = new ImageEditorForm(selectedImage))
{
if (editor.ShowDialog(this) == DialogResult.OK)
{
@@ -1063,7 +1065,7 @@ namespace AGVMapEditor.Forms
if (listBoxNodes != null)
{
listBoxNodes.DataSource = null;
listBoxNodes.DataSource = _mapNodes;
listBoxNodes.DataSource = this._mapCanvas.Items;
listBoxNodes.DisplayMember = "DisplayText";
}
}
@@ -1105,8 +1107,8 @@ namespace AGVMapEditor.Forms
// RFID 값 변경시 중복 검사
if (e.ChangedItem.PropertyDescriptor.Name == "RFID")
{
string newRfidValue = e.ChangedItem.Value?.ToString();
if (!string.IsNullOrEmpty(newRfidValue) && CheckRfidDuplicate(newRfidValue))
var newRfidValue = ushort.Parse(e.ChangedItem.Value?.ToString());
if (newRfidValue != 0 && CheckRfidDuplicate(newRfidValue))
{
// 중복된 RFID 값 발견
MessageBox.Show($"RFID 값 '{newRfidValue}'이(가) 이미 다른 노드에서 사용 중입니다.\n입력값을 되돌립니다.",
@@ -1124,12 +1126,14 @@ namespace AGVMapEditor.Forms
UpdateTitle();
// 🔥 다중 선택 여부 확인 및 선택된 노드들 저장
bool isMultiSelect = _propertyGrid.SelectedObject is MultiNodePropertyWrapper;
List<MapNode> selectedNodes = null;
bool isMultiSelect = _propertyGrid.SelectedObjects is MapNode[]; // _propertyGrid.SelectedObject is MapNode[];
//var a = _propertyGrid.SelectedObject;
List<NodeBase> selectedNodes = null;
if (isMultiSelect)
{
// 캔버스에서 현재 선택된 노드들 가져오기
selectedNodes = new List<MapNode>(_mapCanvas.SelectedNodes);
selectedNodes = new List<NodeBase>(_mapCanvas.SelectedNodes);
System.Diagnostics.Debug.WriteLine($"[PropertyGrid] 다중 선택 노드 수: {selectedNodes.Count}");
}
@@ -1146,18 +1150,18 @@ namespace AGVMapEditor.Forms
// 🔥 다중 선택인 경우 MultiNodePropertyWrapper를 다시 생성하여 바인딩
if (isMultiSelect && selectedNodes != null && selectedNodes.Count > 0)
{
System.Diagnostics.Debug.WriteLine($"[PropertyGrid] MultiNodePropertyWrapper 재생성: {selectedNodes.Count}개");
var multiWrapper = new MultiNodePropertyWrapper(selectedNodes);
_propertyGrid.SelectedObject = multiWrapper;
// System.Diagnostics.Debug.WriteLine($"[PropertyGrid] MultiNodePropertyWrapper 재생성: {selectedNodes.Count}개");
//var multiWrapper = new MultiNodePropertyWrapper(selectedNodes);
_propertyGrid.SelectedObjects = selectedNodes.ToArray();// multiWrapper;
}
// PropertyGrid 새로고침
_propertyGrid.Refresh();
// 🔥 단일 선택인 경우에만 노드를 다시 선택 (다중 선택은 캔버스에서 유지)
if (!isMultiSelect && currentSelectedNode != null)
if (!isMultiSelect && currentSelectedNode is MapNode mapNode)
{
var nodeIndex = _mapNodes.IndexOf(currentSelectedNode);
var nodeIndex = this._mapCanvas.Nodes.IndexOf(mapNode);
if (nodeIndex >= 0)
{
listBoxNodes.SelectedIndex = nodeIndex;
@@ -1170,38 +1174,24 @@ namespace AGVMapEditor.Forms
/// </summary>
/// <param name="rfidValue">검사할 RFID 값</param>
/// <returns>중복되면 true, 아니면 false</returns>
private bool CheckRfidDuplicate(string rfidValue)
private bool CheckRfidDuplicate(ushort rfidValue)
{
if (string.IsNullOrEmpty(rfidValue) || _mapNodes == null)
if (rfidValue == 0 || this._mapCanvas.Nodes == null)
return false;
// 현재 편집 중인 노드 제외하고 중복 검사
string currentNodeId = null;
var selectedObject = _propertyGrid.SelectedObject;
// 다양한 PropertyWrapper 타입 처리
if (selectedObject is NodePropertyWrapper nodeWrapper)
{
currentNodeId = nodeWrapper.WrappedNode?.NodeId;
}
else if (selectedObject is LabelNodePropertyWrapper labelWrapper)
{
currentNodeId = labelWrapper.WrappedNode?.NodeId;
}
else if (selectedObject is ImageNodePropertyWrapper imageWrapper)
{
currentNodeId = imageWrapper.WrappedNode?.NodeId;
}
int duplicateCount = 0;
foreach (var node in _mapNodes)
foreach (var node in this._mapCanvas.Nodes)
{
// 현재 편집 중인 노드는 제외
if (node.NodeId == currentNodeId)
if (node.Id == currentNodeId)
continue;
// 같은 RFID 값을 가진 노드가 있는지 확인
if (!string.IsNullOrEmpty(node.RfidId) && node.RfidId.Equals(rfidValue, StringComparison.OrdinalIgnoreCase))
if (node.RfidId != 0 && node.RfidId == rfidValue)
{
duplicateCount++;
break; // 하나라도 발견되면 중복
@@ -1236,23 +1226,23 @@ namespace AGVMapEditor.Forms
if (result == DialogResult.Yes)
{
// 단일 연결 삭제
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.FromNodeId);
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectionInfo.ToNodeId);
var fromNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.FromNodeId);
var toNode = this._mapCanvas.Nodes.FirstOrDefault(n => n.Id == connectionInfo.ToNodeId);
if (fromNode != null && toNode != null)
{
// 양방향 연결 삭제 (양쪽 방향 모두 제거)
bool removed = false;
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
if (fromNode.ConnectedNodes.Contains(toNode.Id))
{
fromNode.RemoveConnection(toNode.NodeId);
fromNode.RemoveConnection(toNode.Id);
removed = true;
}
if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
if (toNode.ConnectedNodes.Contains(fromNode.Id))
{
toNode.RemoveConnection(fromNode.NodeId);
toNode.RemoveConnection(fromNode.Id);
removed = true;
}
@@ -1279,96 +1269,47 @@ namespace AGVMapEditor.Forms
}
}
#region Multi-Node Selection
private void ShowMultiNodeContextMenu(List<MapNode> nodes)
private void allTurnLeftRightCrossOnToolStripMenuItem_Click(object sender, EventArgs e)
{
// 다중 선택 시 간단한 다이얼로그 표시
//모든노드의 trun left/right/ cross 속성을 true로 변경합니다
if (this._mapCanvas.Nodes == null || this._mapCanvas.Nodes.Count == 0)
{
MessageBox.Show("맵에 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var result = MessageBox.Show(
$"{nodes.Count}개의 노드가 선택되었습니다.\n\n" +
"일괄 속성 변경을 하시겠습니까?\n\n" +
"예: 글자색 변경\n" +
"아니오: 배경색 변경\n" +
"취소: 닫기",
"다중 노드 속성 변경",
MessageBoxButtons.YesNoCancel,
$"모든 노드({this._mapCanvas.Nodes.Count}개)회전/교차 속성을 활성화하시겠습니까?\n\n" +
"• CanTurnLeft = true\n" +
"• CanTurnRight = true\n" +
"• DisableCross = false",
"일괄 속성 변경",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question);
if (result == DialogResult.Yes)
{
BatchChangeForeColor();
}
else if (result == DialogResult.No)
{
BatchChangeBackColor();
}
}
/// <summary>
/// 선택된 노드들의 글자색 일괄 변경
/// </summary>
public void BatchChangeForeColor()
{
var selectedNodes = _mapCanvas.SelectedNodes;
if (selectedNodes == null || selectedNodes.Count == 0)
{
MessageBox.Show("선택된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
using (var colorDialog = new ColorDialog())
{
colorDialog.Color = selectedNodes[0].ForeColor;
if (colorDialog.ShowDialog() == DialogResult.OK)
foreach (var node in this._mapCanvas.Nodes)
{
foreach (var node in selectedNodes)
{
node.ForeColor = colorDialog.Color;
node.ModifiedDate = DateTime.Now;
}
_hasChanges = true;
UpdateTitle();
RefreshMapCanvas();
MessageBox.Show($"{selectedNodes.Count}개 노드의 글자색이 변경되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
node.CanTurnLeft = true;
node.CanTurnRight = true;
node.DisableCross = false;
node.ModifiedDate = DateTime.Now;
}
_hasChanges = true;
UpdateTitle();
RefreshMapCanvas();
MessageBox.Show(
$"{this._mapCanvas.Nodes.Count}개 노드의 회전/교차 속성이 모두 활성화되었습니다.",
"완료",
MessageBoxButtons.OK,
MessageBoxIcon.Information);
UpdateStatusBar($"모든 노드의 회전/교차 속성 활성화 완료");
}
}
/// <summary>
/// 선택된 노드들의 배경색 일괄 변경
/// </summary>
public void BatchChangeBackColor()
{
var selectedNodes = _mapCanvas.SelectedNodes;
if (selectedNodes == null || selectedNodes.Count == 0)
{
MessageBox.Show("선택된 노드가 없습니다.", "알림", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
using (var colorDialog = new ColorDialog())
{
colorDialog.Color = selectedNodes[0].DisplayColor;
if (colorDialog.ShowDialog() == DialogResult.OK)
{
foreach (var node in selectedNodes)
{
node.DisplayColor = colorDialog.Color;
node.ModifiedDate = DateTime.Now;
}
_hasChanges = true;
UpdateTitle();
RefreshMapCanvas();
MessageBox.Show($"{selectedNodes.Count}개 노드의 배경색이 변경되었습니다.", "완료", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
}
#endregion
}
}

View File

@@ -120,22 +120,40 @@
<metadata name="statusStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>132, 17</value>
</metadata>
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>249, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="btNodeRemove.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="toolStrip3.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>462, 17</value>
</metadata>
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<data name="btnSelect.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHrSURBVDhPldBNiBJhHMfx/ykKiohQ18tEhygyWGQRJwh1
fcHAt7qJ3TOj6FJ0akP06G2RPQd1MNhKcxvCxDc2UHbXSCoWNu8dWp3ReTks/moGVnafjW33c/rPM8/3
4eEhOoLJZ/6UvDp7l10/lNKa4+TW3AN9Hq9eC8kNW1ef1QZ3UapzRztMbs1+lVs2Qa5fLqsbtzBucE9G
NetwVD2fYPf+07h5ZUH9koD24yG0rWdQ1kMQK2eVXzXTaXbv1Lh9dUZu2apy4xLUjSi0nxk0X0bRLd7B
Vu0elM48xE8nIX6kJbbdZ1znFpTubTRfxaBpGiaTCdbfJaB+v4+BQN+2y3SBbaak5oxJqlqEcDiMTqcD
VVXR6/VQexGCtvkYYplG4gfi2c6gVKzcqHKuGIvFkEwmkcvlUCgUUF6ah7b5FIMV2hFXaG1YoudsOxWN
Ro2Y53l4vV5EIhHIayFIVSsk4Qy2S8SxzdRu7HQ64fF4YDab9SsvDt7Tjlii62KRusM3lGI7QzweRzab
NWK32w2TyQR9XSrRDfEtCfqM13Ti9zLdZFsKBoPIZDIH4l3DZQrs/d7H5/MhnU7D4XDA5XIdiA+VSqXQ
7/dht9sRCASOF+v0A9rtNvL5vPFg7P//8vv9j/RrWyyW48d//QFpDAsmbMS+zgAAAABJRU5ErkJggg==
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHrSURBVDhPldBLaBNBHMfx/0kUVBBJ0lxWPIhihBJKyAqS
pHkQIS+9hXg3RhQviicrITnmJqFnQQ8RqiamRqkhj6VCQtuIQaVQcxdq3d3s61Dy0w002KnU9nP67+x8
h2GIDmD0kT+mLk/fZNf3pQkznCrM3DFnZflSRG05euast7izcpM72GGqMP1ZFRw1tXm+qq9dg9LiHgwb
dnFYP51i9/6T0r4wp39Kwfh2F8bGI2irEYjvTmo/Gpbj7N4JpXNxShUcdbV1DvpaHMb3HNrP4uiVb2Cj
cQtadxbSh6OQ3tM82+6iNLk5rXcd7ecJGIaB0WiE1dcp6F9v41eNvmxV6QzbTMjtKYtct9Wi0Si63S50
XUe/30fjaQTG+n1IVRpKb4lnuzFtyc4Nl06VE4kE0uk0CoUCSqUSqvOzMNYfYnORtqVFWhEr9JhtJ+Lx
+DjmeR5+vx+xWAzqSgRy3Q65dgJbFeLYZmIndrvd8Pl8sFqt5pWfbL6hbalCl6Uy9cSXlGG7sWQyiXw+
P469Xi8sFgvMdblCV6RXVDNnvKAjPxfoKttSOBxGLpfbE+8QFyj09/cugUAA2WwWLpcLHo9nT7yvTCaD
wWAAp9OJUCh0uNhkHtDpdFAsFscPxv7/r2AweM+8ts1mO3z8x29OYwsb4/6fnQAAAABJRU5ErkJggg==
</value>
</data>
<data name="btnMove.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
@@ -156,75 +174,75 @@
<data name="btnAddLabel.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHMSURBVDhPnZJfa9pQGMb7JXa7sW9RCP1ku1lXRi+3luJa
dteOjYi5CJhoBIPGLYkExb8zic5molPnoAO3k80ozzhHTopzq2wPhHDe8z6/5yV599rtNlqtFhqNBur1
Omq1GqrVKiqVCn3f29ulZrOJ1Wq19QyHQwYxDONuCE2mhsFgwJKDIIghtOY4zt0QOjJtpmn0XC6XK/P5
HFEUsToFWpb1dwhN5Y22bf80TXMQhiE+f/qOd9qU3fX7fZRKpT9DaDIfeblcgpq/TEO8OPJx8sjD2+wa
0uv1UCgUoKrqJsRxHDKbzVjTYrFgyYknPi6f9fH6LMD5UTeG+L6PfD6/CbFt+8CyLDKdrpvc+g1Lvjq5
hnj+cQvieR40TduEmKYpFItFMplMWFO39RWJQw+vTm8hiUMfhrq+73Q6UBQFoijeQgzDEHRdJ+PxeAvy
JhHg5XEPF8fvEUVLNkUmk0EqlXoQA6h0XRdyuRwZjUYx5Oyxi4unXVyddvFtHsZmURQfbpi5NE0Tstks
oZvIPlzzBpfPP+BHGO02cymKIqTTaUI3kf9ialZVdbeZS5blfVmWCV0w13X/zcwlSdK+JEnkv8xcFJJM
Ju//Xuf6Be3RFseUK14pAAAAAElFTkSuQmCC
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHISURBVDhPnZJfa9pgFMb7JXa7sW9RkH2y3awro5ddS3Et
u2vHRsRcBEw0iEHiYpSg+Hcm0blUtOocdOCWbEZ5xnnlTXFule2BEN7znuf3HJKz12630Wq10Gg0UK/X
UavVUK1WUalU6P1gb5eazSZWq9XWMxwOGUTX9fshlEyGwWDAkj3PiyBUsyzrfgiNTM2URudyuVyZz+cI
w5DVCWia5t8hlMobDcP4aRjGIAgCfL75DkOdsrt+v49CofBnCCXzkZfLJcj8ZRrg1aGLk6cO3mfWkF6v
h3w+D0VRNiGWZfmz2Yw1LRYLlhx/7uLyuI+3Zx7OD7sRxHVd5HK5TUipVHpimqY/na6b7PotS746+QTh
/HoL4jgOVFXdhBSLxZimaf5kMmFN3dZXxA8cvDm9g8QPXOjK+r7T6UCWZQiCcAfRdT2WzWb98Xi8BXkX
9/D6qIeLow8IwyWbIp1OI5lMPooAJE3TYplMxh+NRhHk7JmNixddXJ128W0eRGZBEB5vmLlUVWUQ2kT2
4Zq3uHz5ET+CcLeZS5blWCqV8mkT+S8ms6Iou81ckiTtS5Lk04LZtv1vZi5RFPdFUfT/y8xFkEQi8fD3
Otcvn84Wo7k6b1AAAAAASUVORK5CYII=
</value>
</data>
<data name="btnAddImage.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG8SURBVDhPnZLditpQFIXnJXrb0rcYCH2wXhSZwly2U0rp
O0TMRUp+BTMaIYkERY3BH0TEalW8KEV6gp4cVjlnOBms7Ui7IITsvde3NmRfpWmKwWCAfr+PXq+HbreL
TqeDdrvN38+uLilJEjDGzp7lcikgvu8/DeHJ3LBYLETyfD4vILwWx/HTEL4yH+Zp/LvVarX3+z0opaLO
gWEY/h3CU+VgFEWHIAgWWZaBfv8GmjqiN5vN0Gw2/wzhyXLlPM/BzezHFrl9g/zLa9DUFr3pdIp6vQ7L
sk4hcRyT3W4nho7Ho0jOrRKY9w6s8RHMvikgk8kEtVrtFBJF0aswDMl2u32AfO2KZHZ/B9b8fAYZj8dw
XfcUEgSB0mg0yGazeYCsElCzBHb/4RFilUATQ/SHwyFM04Sqqo8Q3/cVz/PIer0+h/ifwNxb/DRvwXIq
trBtG5VK5UUB4PI8T6lWq2S1WhWQo/EGzHmLrHaHA9kXZlVVX56YpVzXVRzHIfwSBWTZB/Xegx2yy2Yp
0zQVwzAIv0T5i7nZsqzLZild1691XSf8wEaj0b+ZpTRNu9Y0jfyXWYpDyuXy89/rUr8A4v0dufOO1X4A
AAAASUVORK5CYII=
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAG6SURBVDhPnZLditpQFIXnJXrb0rcYkD5YL4pMYS7bKaXM
O0TMRUp+ETM2EqMERY3BH0TE0ap4UQaZE/TksIZzhpPBOjPSLgghe+/1rQ3ZZ3Eco9frodvtotPpoN1u
o9Vqodls8vebs1OKogiMsaNnPp8LiOd5r0N4MjfMZjORPJ1OMwivhWH4OoSvzId5Gv9uNBrN7XYLSqmo
c2AQBC9DeKoc9H1/5/v+LEkS0D+/QWNb9CaTCarV6vMQnixXTtMU3Mzu1kitC6Q/P4LGluiNx2NUKhWY
pnkICcOQbDYbMbTf70VyaubB3C9gv76DWRcZZDQaoVwuH0Lq9fqHIAjIer1+hNy2RTK7uQKrXh9BhsMh
HMc5hNRqtZzrumS1Wj1CFhGokQe7+fYEMfOgkS76/X4fhmFAUZQniOd5uVKpRJbL5THE+wHmXOLeuARL
qdjCsiwUi8V3GYDLdd2cbdtksVhkkL3+Ccz+jKR8hR3ZZmZFUd4fmKUcxxEQfokCMu+Cul/Bdslps5Rh
GDld1wm/RPmLudk0zdNmKU3TzjVNI/zABoPBv5mlVFU9V1WV/JdZikMKhcLbv+tSD5T6HZWMaVplAAAA
AElFTkSuQmCC
</value>
</data>
<data name="btnAddNode.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHeSURBVDhPnZLda9NgFMb3T3ir+F8Mwv6e3cgcorfToYgK
Or/unF94ldJsZm3SJq4xTSFJDSlt09oPSilpa1sKilbfsn7wyPtCInG6og+EkHPO83sO5KxVq1VUKhWU
y2WUSiUUi0UUCgW4rkvf59ZWyfM8LJfLU0+v12MQwzDOhtBkavB9nyV3Op0QQmuO45wNoSvTYZpGv/P5
vDuZTDCfz1mdAi3L+juEpgaDtm2fmKbpT6dTjL50kfUOWa/dbiOXy/0ZQpODlReLBah5/PUTHia2cfdg
E7p3wHqtVgu6rkOW5SjEcRwyHo/Z0Gw2w/BzF3uJy3j2bgcvtZt4lNgOIc1mE5lMJgqxbXvDsiwyGo3Y
0Ef/A+4dbuL58Q280e/glXYrAmk0GlAUJQoxTZPLZrNkOByyoXq3gAfiFl4c74aQvaMtZEox1q/VapAk
CTzP/4IYhsFpmkYGg8EpyOv3t/FUvoonR9cwX8zYFqlUCvF4/EIIoNI0jVNVlfT7/RBy/+0lPE5ewb66
i+8/voVmnucvRsyBFEXh0uk0oZfI1vVd7KvXMT0hq82BJEnikskkoZcY/GJqlmV5tTmQKIrroigSemD1
ev3fzIEEQVgXBIH8lzkQhcRisfO/1wP9BF1iD3Q2XAPzAAAAAElFTkSuQmCC
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHdSURBVDhPnZLda9NgFMb3T3ir+F8Min/PbmQO0VvdUEQF
nV93bk7xKqXZzNqkzbrGmJKmXUhpm9Z+UEpJW9tSULT6lvWDR94X3kicrugDIeSc8/yeAzkrlUoF5XIZ
pVIJxWIRhUIB+XwejuPQ94WVZXJdF4vF4szT7XYZxDCM8yE0mRo8z2PJ7Xbbh9CabdvnQ+jKdJim0e9c
LueMx2PMZjNWp0DLsv4Ooal80DTNU9M0vclkguGXDj64B6zXarWQTqf/DKHJfOX5fA5qHn39hCfRDTzY
X4Pu7rNes9mErutQFCUIsW2bjEYjNjSdTjH43MF29BpeJm9hT7uDp9ENH9JoNJBKpYKQbDZ7xbIsMhwO
2dBH7wQPD9awe7yJt/p9vNbuBiD1eh2qqgYhmUwmpGkaGQwGbKjWyeOxtI5Xx1s+ZPtwHalimPWr1Spk
WYYgCL8ghmGEkskk6ff7ZyBv3t/DC+UGnh/exGw+ZVvE43FEIpFLPoBK07RQIpEgvV7Phzx6dxXPYtex
c7SF7z+++WZBEC4HzFyqqjIIvUS2rudg5+g2JqdkuZlLluVQLBYj9BL5L6ZmRVGWm7kkSVqVJInQA6vV
av9m5hJFcVUURfJfZi4KCYfDF3+vc/0ED18PUDextaQAAAAASUVORK5CYII=
</value>
</data>
<data name="btnDelete.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGpSURBVDhP7Y87TyJRGIZR9AdYiYWFMdb+AYvNopXRWJho
oY1aWGyzW8DuFmplobGxMxkPrVrY4f0SHJhhmBmGAYMxiIjcxkGh8ZZoeM05UWPG6w/wTZ7m5Hve8302
23dYPB6PkxASJITgiyQ5jut+LiCExFVVRDabYORylCNGPp9kFArHMIwUDOMEsZhKSzIvC8xUKg5B8MPn
8zF4noff70cgEIAgCAgGg5AkCaqqshKO424sBQeQJJGJVikUCkFRFCbregSmmXldkE4fQlFkiKLIRCrJ
ssykcDiMSCQCXdexvx9DsZh7XXB6moCmqUyiv1FJ0zQmRqNRKHNj2BtswZazFrv9jVj83Xv3JGNnZxPn
53lcXFAKKJUMlMtnj5hILE4j6mrDjXcGlfgarhb+QP7ViiX3AFjBZ3h7HLj2zgCzXYC7DphsQnHqB1Z6
6vF8xkfZ+FlTqajLeJnyuAP03Tr7Ztbb7ZnL+WFg3IFblw0llw3pUTvWOuxZ6+yb4fsaJqSh5ru8uwnm
v1okR6qw3Vlzv9pe/d86+274vsa/6057iq5NN3qSHwBTm6pq5+CDSAAAAABJRU5ErkJggg==
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGvSURBVDhP7Y89T1phGIZpgR/QqTiQ6NDZP+BgCiZd2jA0
sUNNBx0cjIMDtB3EycGGxY2Evmym7eBGhfgVeuB8cM7hcIDQGKSU7+NBYWlrEw23ed+oMUdr/QHeybW8
ea77fR6b7T4s0WjUQwgRCSG4I5VIJPLisoAQUlJVAc1mmdFqUfYZ7XaF0en8gGFUYRg/USiotKRxtcCs
Vkvg+RSSySSD4zikUimk02nwPA9RFCFJElRVZSXhcPjYUvAdkiQw0SplMhkoisJkXc/BNBvXC2q1PSiK
DEEQmEglWZaZlM1mkcvloOs6isUCut3W9YJ6vQxNU5lEf6OSpmlMzOfzUMKL+Db1BFseJ3ZfubE2//Lk
QsbOziYOD9s4OqJ00OsZ6PcPzjFR/vwBef8YjmMhDEpx/P60AHluFF8Cr8EK/kfM58KfWAhYfQ4EHgHL
I+iujOOr7zEuz7gtG08dg4G6jqvpB12g79bZG5Pw2hu/Pk4DQRf++m3o+W2ozdoRn7A3rbM3hpscWpLe
DJ+0AyMw3zlRmXmA7WeO0w3vw/fW2X+Gm3S/TXjsVbo23ehCPgMKrqo38mZYEwAAAABJRU5ErkJggg==
</value>
</data>
<data name="btnEditImage.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAL4SURBVDhPfdLfT9NXGMdx/oPdL17twmQxi97sajfTzV+b
2Q91CxuxZGt0mWMxy3TFMhRx+hUqBVoI0FoqvxxrVSZFwHWtWuy0gy8UykAdiggMEGq/7bftkTB8L222
TojbJ3muznNeJznPk2V39ZfZ2+RZs1NWjC3PqeaVVemQFetFuTzrn9ja5bm5R4+eqvE48YT431pQBXOR
GDXOvkgGSL0cU+NUd0xS1hGk6PIPNPSZcQx+x7lgMVU+A5/XW/ja1oPONsJ8TGByDigZoLxFVlJ6zeVJ
pK5uGnrNtA4dxNr7GTW3tNQG9mNwH2d/XQt6+yhzynOAWEJQ1zVFUWcT3weLqQ3spfKXPRh8HyFdz8bo
+4pc02kKG+8wqyQxOftXAkpCYO2e5qirgbNyIRV+DdK1Dznm3cmRn3ciefLQVJRgb23idks2v186QdC2
hWD1Ok0aiMQFtit/ILk6MftOUe7XUuTdhf6ndznqyebbH/OpbjzEjFeHMtQB6jSRofP4S7fNpoHHqsDu
nsHcOUi+w47UfoBSz14k9ycUOL/AWJ/H/HARi5PtzFwzkAxd5M8HNxi07RNZqVmHY4IGz2waMbmCfFOs
4aTxTSTDRs6YthEeLmQ5eYPkvTzCv37JaJOWoCVnIWB+b20aWIgK9I530LXu4OC5t7AUvErIvpvh5s08
Dh1mWdwkMZaLeJhDbETPaN2OpwPlmzekP7GsuU8JRwW+sauZ8ofa6Ll0gJle3d+XNYiJj4kOF3Cv7m3O
Nl5QM1MwpoHkCsB7tYSRK4dYTvoQ41qePNyDEjrM3dqtTE2MU+l4ZoxVTnkiHIkSVQXR+BMGvE381qln
sKuCqcAHJMZzCAfzGTvzPgvT95lPr7L87ypb2+RSy/n+ntRypGRHyRsQe8Bty268+hfx127Hc/L15fpG
l5o6r77Qr1jb5IoMsDrHtS8vLd6sYjFQSeDERlo/XaN269atX933n9n12gtCyl275D61fcldtul6R/4r
L63ueTZ/AZ9Hi93BxLexAAAAAElFTkSuQmCC
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAL4SURBVDhPfdLxU9N1HMdx/oN+7/qp7vzF6+qX7vqts9K0
vDKzjvIcepx1GZ3XpQ1HJGL6FRbjKwOBDfg2CKMNJRkChps6JB34hcEWWmGIgIODue/23faRI3x229US
znrdvX/6vD+Pz93n/c5R3MMVSrs6J7tUzdL8mHKsLtmpavazamXOP2noHJy/Fw4/1BMJEknxv7WoC+aj
cWrb/NEskH45rieo6ZqmoitAyfkfcNyw4hz9mtOBUqp9Zj5usvF5Yz/GxnEW4gLZNaJlgcpmVUvrteen
kXp6cQxZaRs7gH3oI2qv51Pn34e57yj76lsxKTeZ1x4DxJOC+p4ZSrpb+D5QSp1/Lyd/3oXZ9z7SlVws
vs/Iq/qG4uZfmdNSyK7h1YCWFNh7ZznsdvCtWow8YEC6/B5HvNv56uJ2JE8BBrkMpa2FW625/H7uGIHG
TQRq1hsyQDQhaLxwD8ndjdV3gsqBfEq872D66S0Oe3L58sdCapoPEvYa0ca6QJ8lOtaOr3zzXAa4rwuU
vjDW7lEKnQpS537KPXuR+vZQ5PoES1MBC6ESlqY7CV82kwqe5c87VxmxfShy0rOOxAUOz1wGqXIH+KLU
wHHLq0jmDTRUbSYSKmYldZXU7QIig59ysyWfgC130W/dti4DLMYEJuebGNu2cuD069iKXiCo7CD03Ubu
Bw+xIq6RnMhD3N1JfNxE6NTWhyOVG5/PfGKF44YWiQl8E5eyNRDsoP/cfsJDxr8vGxBTHxALFXG7/g0U
5YyenYIlA6RWAd5LZYxfOMhKyoeYzOfB3V1owUP8VvcaM1OTyM5HxljtUqci0RgxXRBLPGDE28Iv3SZG
e2Rm/O+SnNxJJFDIRMPbLM7+wUJmlQf/XWV7h1puax/uTy9HWnaWvQLxO9yy7cBrepKBui14jr+00qS4
9fR51Zlhzd6hyllgbY7uXre8dK2aJf9J/Mc20Jr3lN5rXP/c2r7/zLYXnxBS3jPLvSe2LPdVvHylq/DZ
p9f2PJq/AD40i0VffXQ/AAAAAElFTkSuQmCC
</value>
</data>
<data name="btnConnect.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
@@ -245,16 +263,16 @@
<data name="btnDeleteConnection.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHwSURBVDhP7Y7Ni1JhGMVvXadgWrWzRcTQKgXJVbQQx0/w
K7iL1FVBKdYrLW4fM9pdGCPMQpnAC+YqoY240VaTWhnouInIVubqziBFSJEGzaRN5olnIDFx/oMOvPBw
zu8cXo77r4lsNtsJxti9eDzey2azkCSpwxi7wBhTS5KkkEcZMcT+U9ZoNMdcLlcilUqh2+1CURTUajXE
YrFtSZLq7XZ7SF6j0QAxxFJnMqDX6y+Jovit1Wr1/H7/M4fDMQoGg5BlGdFoFHSTR1kul+sQS53pgcTa
2tpAluWQ2+1eNBgM36vVKprNJur1OgqFAsijjBhiqTMZ0Ol0N0OhUD+dTt8VBKFiMpn27XY7BEE4eHST
RxkxxFJnMqDVas95PJ52Mpns5/P5r8ViEZlMBqIoIhKJHNzldYZXl8/8fmlewFP74v6T5eMPJwMcx/FG
o/G80+ncCQQCu+FweMAY++n1eq/4fD7Lo6vW4bs7FzHY3MD4fRl7+dt4E9aOXlhUt6ZHDlXJyis/NjcA
2QOsngTWl/AlsYySmd+eZefquVk1Hr8tYlr9mBrkz7JzVbHyH3YfXwdiagxXOPRWOHRu8Cjb+I+z7Fxt
+U49eH3t7K9Pq0v4HF2AEjiCqks1KlmP3p9lD9WW73SkYuF36Nv0o7/lP1AsGrGjxCTVAAAAAElFTkSu
QmCC
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHxSURBVDhP7Y7Ni1JhGMVvXadgWrWzRdQsU5BcRW4cR72E
H8FtoS6iFinWKwn2MaPdheJikIEJvGCuElpcaKOtTKUMdNxEZBut1Z1BipAiDZrSJvPEM5CYOP9BB154
OOd3Di/H/ddEdrv9GGPsbiKR6OVyOUiS1GGMnWOMaSVJUsmjjBhi/ynrdLojTqdzI51Oo9vtQlVV1Go1
xOPxbUmS6q1Wa0heo9EAMcRSZzJgNBovhsPhr+12u+fz+Z4KgjAKBAKQZRmxWAx0k0eZoigdYqkzPbCR
TCYHsiwHXS7Xoslk+latVtFsNlGv15HP50EeZcQQS53JgMFguBEMBvuZTOaOKIoVi8WyJwgCRFHcf3ST
RxkxxFJnMqDX68+43e53qVSqryjKl0KhgGw2i0gkgmg0un+X1xleXDr1+/nKAp4Ii3uPlo/enwxwHMeb
zeazDodjx+/374ZCoQFj7KfH47ni9XqtDy5bhm9un8eguInx2zK+P76FVyHd6JlVc3N65ECVbLz6o7gJ
yG5g7TiwvoTPG8soWvjtWXauSiua8fh1AdPqx7Ugf5adq4qNf7/78BoQ12K4yqG3yqFznUfZzn+YZedq
y3si8fLq6V8f15bwKbYA1X8I1QuaUcl2+N4se6C2vCejFSu/Q9+mH/0t/wFGlxos/Pd5kgAAAABJRU5E
rkJggg==
</value>
</data>
<data name="btnToggleGrid.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
@@ -269,39 +287,18 @@
<data name="btnFitMap.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKbSURBVDhPhZBdSFNhHMbPZURXEXQTFF2trgKpdWFZWH6c
0vmxqUu3+bUskyyMUDqm4pxk5YwsmV1MsM3M6FA0NN1mzo+Dbec9y0ihwvmeQStGpO+xguDwxjvdzXHR
A+/Ny/N7/s//T1GbCrjK1PyQkQVDphj/pFIOusrltw5DjOsvZrneQnXCl1SCy2AWXBVR/rERBAbOMUGH
XjVrL1Bx/VqGs+vAbF9x1G/LMyu5uMjkODxYauXD6ABYQVoApXpBlOpCcC1nAa7u9/dorNO2/KjvNr21
Ce8ysvygEQTDSAUgugggahJEdB2I69cEKF0VRKlUCP/YN9WtAd5OmlXyFHCZYoGBEiYUQQUAosYNWGrY
hOuAiGoBlE55O2jG05kTU/IUP1QuB+16lSBKlxJTEyuQRgJENaTFWEuGyteVIyt5KuA0yeRgxEhAsCJd
JmE8RBfifyKqDkGpmAR4rfTWAM5RGuMeahleXM/aaIFq43AEmYGIqgS4Vs6L6PjojdPMWHPG1hXm+rTs
XK8OkEMJUCraBKsFca2CwAAizbvIzz2jbdm/3Y3p80qe8tty1TMPCqNvujVWfvnXXiEinSSVBSjpQhCl
Eniqp/DPwkg99jWcwM/rU/XKDGqyK9fst2mi3ltnwER7JkP2JW+0KZ2ZvJMnL75qwpGgHS/P3MWedg1+
UaXercygfFZaPW6h2fEOOkaO5bFky+6bmasTbWfxp8lO/PXDIP625MQfJ1rxSOUx2ZUsJJnYK2mGqe4S
HJ614ej7AbzC3cfzjyqwNUP1+eX5lO1Kf1I9q001eCz5eMndjBfdLXj6XhEeqTn8/anu4A6l958aNh8x
OMtS8OvWLDxcqf7i1KfsUnr+q+Gqozv7Sw5ZHKa0bYm/v22YpTjaxhhxAAAAAElFTkSuQmCC
</value>
</data>
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>249, 17</value>
</metadata>
<metadata name="toolStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>249, 17</value>
</metadata>
<data name="btNodeRemove.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
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAKYSURBVDhPhZBfSFNRHMfvY0RPEfQSFD2tniKh9WBZmM5W
es02del1ii7LJAsjtG6pOCdZuiJLZg8TbFMzuiSKlm7m/LPU3XOWkUKF89xBK0ak51pBcDlx1L1cF33h
vBy+n+/v+/sxzIZm3flasatAAF3m6Ey3WZl2c4rfyUX97TmCv/WsNuaLK+jmLPBpUUTsLACzHef4gNOk
mXRkafztBt7vMIKJtpyIz55pUXNropPX4TybGML7wBI2ACRXQEkuD6KV9Dm0vNd3n7WN29mI965+cxPR
XSDQyYEQ1gCELwKEq6GErwNp9RpE8lUoyXkw9GPPWAsLPI16Qc0zwG2Oznbk8sEwzgIIV63DcuUGXA4k
XAaQfMLToOdfNaRH1Twz08UpAYdJAyX5UmxqbAXaCCJcSlsM1aRqvE16Rc0zb135Cj0YNVIQLMmXaZiI
8IW1PwmXBJGcQwM8Nt3mgElnXtT/2MCL0mraegtctgaHsQVIuBiilUJRwkcHb6bwQzdSN68w1WYQplqN
gB4KIjl7AyyB0koRhQHC7Lvwz139dSd/D1QlT6t5xmfP0E48yoy8aWFt4uKv3TAsH6eVIZKNQYQTKext
zvwz11tBvJXHyIuKRJM6gxltyrD47GzEc+cUGK7X8XRf+gark/nRe+nKfH81CQccZHGimYzUs+RlsXan
OoPx2vTa11a9MNSgi9JjjVhTlb7buuXhutPk02gj+fqhk3xbcJGPw7Wk13xEcccLiSfhShI31pJLQpN2
EnnfQZb8D8n0kyJiS9F87jufsFXtj6vnZYnciPUMWRi4ReYHasj4g2zSW3rw+zPj/m1q7z/VYznEufIT
yKvaNNJdqP3iMiXsUHv+q57iw9vbcw9YneakLbG/v5ifpNsR5bepAAAAAElFTkSuQmCC
</value>
</data>
<metadata name="toolStrip2.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
@@ -310,7 +307,7 @@
<data name="btnNew.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAAU1JREFUOE9j+E8GYEAGIIGrzz+AJYrW6IIxCMw6cO1/x/oTYFw2fQsKRjEE2eTp
wQAADsEBuJFr7QAAAU1JREFUOE9j+E8GYEAGIIGrzz+AJYrW6IIxCMw6cO1/x/oTYFw2fQsKRjEE2eTp
p73/Tz3pgSz0/+nbryi4c/eb/+EFE8FycANAtk475fV/0nH3//1HXP53HrCHu+TA/b9w/OnbL7ABKIbA
DJhw1PV/9wGH/y17rP/XbTNHMWD3HQgGGQDDQYnVCANgoGGHxf+qzcbIQnADtt36A8Ybr//BNAAUYCCA
HIgwZ6NrXn3lN6YBoJBGBzBnwzTCNMMM8AzNBquDGwASxGcrCC+5CMGg2EAxABS3bz5+wzAAm+Z55yAG
@@ -322,7 +319,7 @@
<data name="btnOpen.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAAhFJREFUOE/dz71rU1EYBvAO7t38E1xaizQfIh1cBHFyEV2sDiJoyCApFBVBO6QN
wQAADsEBuJFr7QAAAhFJREFUOE/dz71rU1EYBvAO7t38E1xaizQfIh1cBHFyEV2sDiJoyCApFBVBO6QN
iIGSqCDSKpSKtlb8QBCHEge1rW1iE733Jmlsmhh6g839vud+nHseSapJXFwFX3g45wzv7z1vT8//Ufwr
X6acGna23p3tyhmn8Hpoo/h0YO/GE1/vH3nr611cPLynDQgvghZjHgAGsGZ27y6po/o+ZFc+hKzfqa1e
JtWlkTL/fPBkB3gWkBklsKpxGKVJGJvTMMvzYK4E5ulgVAOjyu7bbYBR2xYWgrk2kH8cUD1HgVFKQi8m
@@ -337,7 +334,7 @@
<data name="btnReopen.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAAUZJREFUOE+lkzFLw0AYhvt3HP0DLqWDdCuECjooBRdBXJwEZ3VRBykUBCfRpSi4
wQAADsEBuJFr7QAAAUZJREFUOE+lkzFLw0AYhvt3HP0DLqWDdCuECjooBRdBXJwEZ3VRBykUBCfRpSi4
uXTSIYNiRSgFBxU6qHSoCMHiyfPJm9zFgKAHT0Mu9z53+fK15P4wSv5gojcYuvbNo3EU37tW587YaF8a
q3unAYGEG4JInl7e3HKrFsCc5rlunj+7+spuJpFAiw4HiynN7mwqamxX3OsoMUEg4Ydjs0Ds9+cNBFud
yK4SwOg9cbWFtUzA+7Lg4uHTkGgnrqdwf9YbG4UCJiWQhJ0JcwoJjrsfocA/+m8QlmA6WsoEfuH8owoF
@@ -372,7 +369,7 @@
<data name="btnSaveAs.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAO
wwAADsMBx2+oZAAAAyZJREFUOE9tku1PW1Ucx+8r/Tt86wvRxRjjEmPMULeZNAsQXczipoCdDhxZlg0Z
wQAADsEBuJFr7QAAAyZJREFUOE9tku1PW1Ucx+8r/Tt86wvRxRjjEmPMULeZNAsQXczipoCdDhxZlg0Z
m7CisBYfthcDXLrBlJktUbIBGQ+hAn2cq3uga3lqBxQuUCil9OHc9t7bj2mjy3B+k29OTnK+n9/35Byp
+NTowffN7iGDxb1hsLgxWNzC0OoWBotHGMwusafF4dt5fPzHF8uuPyf9n3Y32cfXtsR6NCG0aELhX69v
5S0IyFvU31yg/MJ06r2q/uf/m5f2Njsi8VRGc81l8SyouOdUHI8zjM4o3PYnUVTonUxT3zPPp+en089A
@@ -386,6 +383,21 @@
8maMpXiUaCpB7aVH7Drag7TDOBxajYut0FpaD0XSzP7jfWfG6eiPsZKIYLLto83zJe2eGky2UlaTi9Rd
CvLG4etIrxoHvigqHxh+pXIo+nLlIEWVA6KoYkCUnXUy+Ff+FYK4Nr4lkOwikO7CtWEB5mjqCvLm5zfY
9qme1o7PBh0fmsYxmic4+v08R1pnqTJPU30uwBGzl11VN3jp427+BmRb26lRD0KdAAAAAElFTkSuQmCC
</value>
</data>
<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>
</root>

View File

@@ -1,825 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using AGVNavigationCore.Models;
using AGVNavigationCore.Controls;
namespace AGVMapEditor.Models
{
/// <summary>
/// 노드 타입에 따른 PropertyWrapper 팩토리
/// </summary>
public static class NodePropertyWrapperFactory
{
public static object CreateWrapper(MapNode node, List<MapNode> mapNodes)
{
switch (node.Type)
{
case NodeType.Label:
return new LabelNodePropertyWrapper(node, mapNodes);
case NodeType.Image:
return new ImageNodePropertyWrapper(node, mapNodes);
default:
return new NodePropertyWrapper(node, mapNodes);
}
}
}
/// <summary>
/// 라벨 노드 전용 PropertyWrapper
/// </summary>
public class LabelNodePropertyWrapper
{
private MapNode _node;
private List<MapNode> _mapNodes;
public LabelNodePropertyWrapper(MapNode node, List<MapNode> mapNodes)
{
_node = node;
_mapNodes = mapNodes;
}
/// <summary>
/// 래핑된 MapNode 인스턴스 접근
/// </summary>
public MapNode WrappedNode => _node;
[Category("기본 정보")]
[DisplayName("노드 ID")]
[Description("노드의 고유 식별자")]
[ReadOnly(true)]
public string NodeId
{
get => _node.NodeId;
}
[Category("기본 정보")]
[DisplayName("노드 타입")]
[Description("노드의 타입")]
[ReadOnly(true)]
public NodeType Type
{
get => _node.Type;
}
[Category("라벨")]
[DisplayName("텍스트")]
[Description("표시할 텍스트")]
public string LabelText
{
get => _node.LabelText;
set
{
_node.LabelText = value ?? "";
_node.ModifiedDate = DateTime.Now;
}
}
[Category("라벨")]
[DisplayName("폰트 패밀리")]
[Description("폰트 패밀리")]
public string FontFamily
{
get => _node.FontFamily;
set
{
_node.FontFamily = value ?? "Arial";
_node.ModifiedDate = DateTime.Now;
}
}
[Category("라벨")]
[DisplayName("폰트 크기")]
[Description("폰트 크기")]
public float FontSize
{
get => _node.FontSize;
set
{
_node.FontSize = Math.Max(6, Math.Min(72, value));
_node.ModifiedDate = DateTime.Now;
}
}
[Category("라벨")]
[DisplayName("폰트 스타일")]
[Description("폰트 스타일")]
public FontStyle FontStyle
{
get => _node.FontStyle;
set
{
_node.FontStyle = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("라벨")]
[DisplayName("전경색")]
[Description("텍스트 색상")]
public Color ForeColor
{
get => _node.ForeColor;
set
{
_node.ForeColor = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("라벨")]
[DisplayName("배경색")]
[Description("배경 색상")]
public Color BackColor
{
get => _node.BackColor;
set
{
_node.BackColor = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("라벨")]
[DisplayName("배경 표시")]
[Description("배경을 표시할지 여부")]
public bool ShowBackground
{
get => _node.ShowBackground;
set
{
_node.ShowBackground = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("라벨")]
[DisplayName("패딩")]
[Description("텍스트 주변 여백 (픽셀 단위)")]
public int Padding
{
get => _node.Padding;
set
{
_node.Padding = Math.Max(0, Math.Min(50, value));
_node.ModifiedDate = DateTime.Now;
}
}
[Category("위치")]
[DisplayName("X 좌표")]
[Description("맵에서의 X 좌표")]
public int PositionX
{
get => _node.Position.X;
set
{
_node.Position = new Point(value, _node.Position.Y);
_node.ModifiedDate = DateTime.Now;
}
}
[Category("위치")]
[DisplayName("Y 좌표")]
[Description("맵에서의 Y 좌표")]
public int PositionY
{
get => _node.Position.Y;
set
{
_node.Position = new Point(_node.Position.X, value);
_node.ModifiedDate = DateTime.Now;
}
}
[Category("정보")]
[DisplayName("생성 일시")]
[Description("노드가 생성된 일시")]
[ReadOnly(true)]
public DateTime CreatedDate
{
get => _node.CreatedDate;
}
[Category("정보")]
[DisplayName("수정 일시")]
[Description("노드가 마지막으로 수정된 일시")]
[ReadOnly(true)]
public DateTime ModifiedDate
{
get => _node.ModifiedDate;
}
}
/// <summary>
/// 이미지 노드 전용 PropertyWrapper
/// </summary>
public class ImageNodePropertyWrapper
{
private MapNode _node;
private List<MapNode> _mapNodes;
public ImageNodePropertyWrapper(MapNode node, List<MapNode> mapNodes)
{
_node = node;
_mapNodes = mapNodes;
}
/// <summary>
/// 래핑된 MapNode 인스턴스 접근
/// </summary>
public MapNode WrappedNode => _node;
[Category("기본 정보")]
[DisplayName("노드 ID")]
[Description("노드의 고유 식별자")]
[ReadOnly(true)]
public string NodeId
{
get => _node.NodeId;
}
[Category("기본 정보")]
[DisplayName("노드 타입")]
[Description("노드의 타입")]
[ReadOnly(true)]
public NodeType Type
{
get => _node.Type;
}
[Category("이미지")]
[DisplayName("이미지 경로")]
[Description("이미지 파일 경로 (... 버튼으로 파일 선택)")]
[Editor(typeof(ImagePathEditor), typeof(UITypeEditor))]
public string ImagePath
{
get => _node.ImagePath;
set
{
if (string.IsNullOrEmpty(value))
{
_node.ImagePath = "";
return;
}
// 파일이 존재하면 Base64로 변환하여 저장
if (System.IO.File.Exists(value))
{
_node.ConvertImageToBase64(value);
_node.LoadImage(); // 이미지 다시 로드
}
else
{
_node.ImagePath = value;
}
_node.ModifiedDate = DateTime.Now;
}
}
[Category("이미지")]
[DisplayName("가로 배율")]
[Description("가로 배율 (1.0 = 원본 크기)")]
public float ScaleWidth
{
get => _node.Scale.Width;
set
{
_node.Scale = new SizeF(Math.Max(0.1f, Math.Min(5.0f, value)), _node.Scale.Height);
_node.ModifiedDate = DateTime.Now;
}
}
[Category("이미지")]
[DisplayName("세로 배율")]
[Description("세로 배율 (1.0 = 원본 크기)")]
public float ScaleHeight
{
get => _node.Scale.Height;
set
{
_node.Scale = new SizeF(_node.Scale.Width, Math.Max(0.1f, Math.Min(5.0f, value)));
_node.ModifiedDate = DateTime.Now;
}
}
[Category("이미지")]
[DisplayName("투명도")]
[Description("투명도 (0.0 = 투명, 1.0 = 불투명)")]
public float Opacity
{
get => _node.Opacity;
set
{
_node.Opacity = Math.Max(0.0f, Math.Min(1.0f, value));
_node.ModifiedDate = DateTime.Now;
}
}
[Category("이미지")]
[DisplayName("회전각도")]
[Description("회전 각도 (도 단위)")]
public float Rotation
{
get => _node.Rotation;
set
{
_node.Rotation = value % 360;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("위치")]
[DisplayName("X 좌표")]
[Description("맵에서의 X 좌표")]
public int PositionX
{
get => _node.Position.X;
set
{
_node.Position = new Point(value, _node.Position.Y);
_node.ModifiedDate = DateTime.Now;
}
}
[Category("위치")]
[DisplayName("Y 좌표")]
[Description("맵에서의 Y 좌표")]
public int PositionY
{
get => _node.Position.Y;
set
{
_node.Position = new Point(_node.Position.X, value);
_node.ModifiedDate = DateTime.Now;
}
}
[Category("정보")]
[DisplayName("생성 일시")]
[Description("노드가 생성된 일시")]
[ReadOnly(true)]
public DateTime CreatedDate
{
get => _node.CreatedDate;
}
[Category("정보")]
[DisplayName("수정 일시")]
[Description("노드가 마지막으로 수정된 일시")]
[ReadOnly(true)]
public DateTime ModifiedDate
{
get => _node.ModifiedDate;
}
}
/// <summary>
/// PropertyGrid에서 사용할 노드 속성 래퍼 클래스
/// </summary>
public class NodePropertyWrapper
{
private MapNode _node;
private List<MapNode> _mapNodes;
public NodePropertyWrapper(MapNode node, List<MapNode> mapNodes)
{
_node = node;
_mapNodes = mapNodes;
}
/// <summary>
/// 래핑된 MapNode 인스턴스 접근
/// </summary>
public MapNode WrappedNode => _node;
[Category("기본 정보")]
[DisplayName("노드 ID")]
[Description("노드의 고유 식별자")]
[ReadOnly(true)]
public string NodeId
{
get => _node.NodeId;
set => _node.NodeId = value;
}
[Category("기본 정보")]
[DisplayName("노드 이름")]
[Description("노드의 표시 이름")]
public string Name
{
get => _node.Name;
set
{
_node.Name = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("기본 정보")]
[DisplayName("노드 타입")]
[Description("노드의 타입 (Normal, Rotation, Docking, Charging)")]
public NodeType Type
{
get => _node.Type;
set
{
_node.Type = value;
_node.CanRotate = value == NodeType.Rotation;
_node.SetDefaultColorByType(value);
_node.ModifiedDate = DateTime.Now;
}
}
[Category("기본 정보")]
[DisplayName("도킹 방향")]
[Description("도킹이 필요한 노드의 경우 AGV 진입 방향 (DontCare: 방향 무관, Forward: 전진 도킹, Backward: 후진 도킹)")]
public DockingDirection DockDirection
{
get => _node.DockDirection;
set
{
_node.DockDirection = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("위치")]
[DisplayName("X 좌표")]
[Description("맵에서의 X 좌표")]
public int PositionX
{
get => _node.Position.X;
set
{
_node.Position = new Point(value, _node.Position.Y);
_node.ModifiedDate = DateTime.Now;
}
}
[Category("위치")]
[DisplayName("Y 좌표")]
[Description("맵에서의 Y 좌표")]
public int PositionY
{
get => _node.Position.Y;
set
{
_node.Position = new Point(_node.Position.X, value);
_node.ModifiedDate = DateTime.Now;
}
}
[Category("고급")]
[DisplayName("회전 가능")]
[Description("이 노드에서 AGV가 회전할 수 있는지 여부")]
public bool CanRotate
{
get => _node.CanRotate;
set
{
_node.CanRotate = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("고급")]
[DisplayName("RFID")]
[Description("RFID ID")]
public string RFID
{
get => _node.RfidId;
set
{
_node.RfidId = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("고급")]
[DisplayName("활성화")]
[Description("노드 활성화 여부")]
public bool IsActive
{
get => _node.IsActive;
set
{
_node.IsActive = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("표시")]
[DisplayName("노드 배경색")]
[Description("노드 배경 색상")]
public Color DisplayColor
{
get => _node.DisplayColor;
set
{
_node.DisplayColor = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("표시")]
[DisplayName("글자 색상")]
[Description("노드 텍스트 색상 (NodeId, Name 등)")]
public Color ForeColor
{
get => _node.ForeColor;
set
{
_node.ForeColor = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("표시")]
[DisplayName("글자 크기")]
[Description("노드 텍스트 크기 (픽셀)")]
public float TextFontSize
{
get => _node.TextFontSize;
set
{
_node.TextFontSize = Math.Max(5.0f, Math.Min(20.0f, value));
_node.ModifiedDate = DateTime.Now;
}
}
[Category("표시")]
[DisplayName("글자 굵게")]
[Description("노드 텍스트 볼드 표시")]
public bool TextFontBold
{
get => _node.TextFontBold;
set
{
_node.TextFontBold = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("표시")]
[DisplayName("이름 말풍선 배경색")]
[Description("노드 이름 말풍선(하단 표시) 배경색")]
public Color NameBubbleBackColor
{
get => _node.NameBubbleBackColor;
set
{
_node.NameBubbleBackColor = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("표시")]
[DisplayName("이름 말풍선 글자색")]
[Description("노드 이름 말풍선(하단 표시) 글자색")]
public Color NameBubbleForeColor
{
get => _node.NameBubbleForeColor;
set
{
_node.NameBubbleForeColor = value;
_node.ModifiedDate = DateTime.Now;
}
}
[Category("정보")]
[DisplayName("생성 일시")]
[Description("노드가 생성된 일시")]
[ReadOnly(true)]
public DateTime CreatedDate
{
get => _node.CreatedDate;
}
[Category("정보")]
[DisplayName("수정 일시")]
[Description("노드가 마지막으로 수정된 일시")]
[ReadOnly(true)]
public DateTime ModifiedDate
{
get => _node.ModifiedDate;
}
}
/// <summary>
/// 다중 노드 선택 시 공통 속성 편집용 래퍼
/// </summary>
public class MultiNodePropertyWrapper
{
private List<MapNode> _nodes;
public MultiNodePropertyWrapper(List<MapNode> nodes)
{
_nodes = nodes ?? new List<MapNode>();
}
[Category("다중 선택")]
[DisplayName("선택된 노드 수")]
[Description("현재 선택된 노드의 개수")]
[ReadOnly(true)]
public int SelectedCount => _nodes.Count;
[Category("표시")]
[DisplayName("노드 배경색")]
[Description("선택된 모든 노드의 배경색을 일괄 변경")]
public Color DisplayColor
{
get => _nodes.Count > 0 ? _nodes[0].DisplayColor : Color.Blue;
set
{
foreach (var node in _nodes)
{
node.DisplayColor = value;
node.ModifiedDate = DateTime.Now;
}
}
}
[Category("표시")]
[DisplayName("글자 색상")]
[Description("선택된 모든 노드의 글자 색상을 일괄 변경")]
public Color ForeColor
{
get => _nodes.Count > 0 ? _nodes[0].ForeColor : Color.Black;
set
{
foreach (var node in _nodes)
{
node.ForeColor = value;
node.ModifiedDate = DateTime.Now;
}
}
}
[Category("표시")]
[DisplayName("글자 크기")]
[Description("선택된 모든 노드의 글자 크기를 일괄 변경 (5~20 픽셀)")]
public float TextFontSize
{
get => _nodes.Count > 0 ? _nodes[0].TextFontSize : 7.0f;
set
{
var validValue = Math.Max(5.0f, Math.Min(20.0f, value));
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontSize 변경 시작: {validValue}, 노드 수: {_nodes.Count}");
foreach (var node in _nodes)
{
System.Diagnostics.Debug.WriteLine($" - 노드 {node.NodeId}: {node.TextFontSize} → {validValue}");
node.TextFontSize = validValue;
node.ModifiedDate = DateTime.Now;
System.Diagnostics.Debug.WriteLine($" - 변경 후: {node.TextFontSize}");
}
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontSize 변경 완료");
}
}
[Category("표시")]
[DisplayName("글자 굵게")]
[Description("선택된 모든 노드의 글자 굵기를 일괄 변경")]
public bool TextFontBold
{
get
{
var result = _nodes.Count > 0 ? _nodes[0].TextFontBold : true;
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontBold GET: {result}");
return result;
}
set
{
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontBold 변경 시작: {value}, 노드 수: {_nodes.Count}");
foreach (var node in _nodes)
{
System.Diagnostics.Debug.WriteLine($" - 노드 {node.NodeId}: {node.TextFontBold} → {value}");
node.TextFontBold = value;
node.ModifiedDate = DateTime.Now;
System.Diagnostics.Debug.WriteLine($" - 변경 후: {node.TextFontBold}");
}
System.Diagnostics.Debug.WriteLine($"[MultiNode] TextFontBold 변경 완료");
}
}
[Category("표시")]
[DisplayName("이름 말풍선 배경색")]
[Description("선택된 모든 노드의 이름 말풍선(하단 표시) 배경색을 일괄 변경")]
public Color NameBubbleBackColor
{
get => _nodes.Count > 0 ? _nodes[0].NameBubbleBackColor : Color.Gold;
set
{
foreach (var node in _nodes)
{
node.NameBubbleBackColor = value;
node.ModifiedDate = DateTime.Now;
}
}
}
[Category("표시")]
[DisplayName("이름 말풍선 글자색")]
[Description("선택된 모든 노드의 이름 말풍선(하단 표시) 글자색을 일괄 변경")]
public Color NameBubbleForeColor
{
get => _nodes.Count > 0 ? _nodes[0].NameBubbleForeColor : Color.Black;
set
{
foreach (var node in _nodes)
{
node.NameBubbleForeColor = value;
node.ModifiedDate = DateTime.Now;
}
}
}
[Category("고급")]
[DisplayName("활성화")]
[Description("선택된 모든 노드의 활성화 상태를 일괄 변경")]
public bool IsActive
{
get => _nodes.Count > 0 ? _nodes[0].IsActive : true;
set
{
foreach (var node in _nodes)
{
node.IsActive = value;
node.ModifiedDate = DateTime.Now;
}
}
}
[Category("정보")]
[DisplayName("안내")]
[Description("다중 선택 시 공통 속성만 변경할 수 있습니다")]
[ReadOnly(true)]
public string HelpText => "위 속성을 변경하면 선택된 모든 노드에 일괄 적용됩니다.";
}
/// <summary>
/// 캔버스 속성 래퍼 (배경색 등 캔버스 전체 속성 편집)
/// </summary>
public class CanvasPropertyWrapper
{
private UnifiedAGVCanvas _canvas;
public CanvasPropertyWrapper(UnifiedAGVCanvas canvas)
{
_canvas = canvas;
}
[Category("배경")]
[DisplayName("배경색")]
[Description("맵 캔버스 배경색")]
public Color BackgroundColor
{
get => _canvas.BackColor;
set
{
_canvas.BackColor = value;
_canvas.Invalidate();
}
}
[Category("그리드")]
[DisplayName("그리드 표시")]
[Description("그리드 표시 여부")]
public bool ShowGrid
{
get => _canvas.ShowGrid;
set
{
_canvas.ShowGrid = value;
_canvas.Invalidate();
}
}
[Category("정보")]
[DisplayName("설명")]
[Description("맵 캔버스 전체 속성")]
[ReadOnly(true)]
public string Description
{
get => "빈 공간을 클릭하면 캔버스 전체 속성을 편집할 수 있습니다.";
}
}
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?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>
@@ -48,6 +48,9 @@
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualBasic" />
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
@@ -58,9 +61,6 @@
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Controls\AGVState.cs" />
@@ -73,11 +73,17 @@
<DependentUpon>UnifiedAGVCanvas.cs</DependentUpon>
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Models\AGVCommand.cs" />
<Compile Include="Models\Enums.cs" />
<Compile Include="Models\IMovableAGV.cs" />
<Compile Include="Models\VirtualAGV.cs" />
<Compile Include="Models\MapLoader.cs" />
<Compile Include="Models\MapMagnet.cs" />
<Compile Include="Models\MapMark.cs" />
<Compile Include="Models\MapNode.cs" />
<Compile Include="Models\NodeBase.cs" />
<Compile Include="Models\MapLabel.cs" />
<Compile Include="Models\MapImage.cs" />
<Compile Include="PathFinding\Planning\AGVPathfinder.cs" />
<Compile Include="PathFinding\Planning\DirectionChangePlanner.cs" />
<Compile Include="PathFinding\Planning\DirectionalPathfinder.cs" />

View File

@@ -18,8 +18,8 @@ namespace AGVNavigationCore.Controls
// 이동 경로 정보 추가
Point? PrevPosition { get; }
string CurrentNodeId { get; }
string PrevNodeId { get; }
MapNode CurrentNode { get; }
MapNode PrevNode { get; }
DockingDirection DockingDirection { get; }
}

View File

@@ -1,8 +1,10 @@
using AGVNavigationCore.Models;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;
using AGVNavigationCore.Models;
using System.Xml.Linq;
namespace AGVNavigationCore.Controls
{
@@ -15,7 +17,17 @@ namespace AGVNavigationCore.Controls
Focus(); // 포커스 설정
var worldPoint = ScreenToWorld(e.Location);
var hitNode = GetNodeAt(worldPoint);
var hitNode = GetItemAt(worldPoint);
// 에뮬레이터 모드 처리
if (_canvasMode == CanvasMode.Emulator)
{
if (e.Button == MouseButtons.Right && hitNode != null)
{
NodeRightClicked?.Invoke(this, hitNode);
}
return;
}
// 🔥 어떤 모드에서든 노드/빈 공간 클릭 시 선택 이벤트 발생 (속성창 업데이트)
bool ctrlPressed = (ModifierKeys & Keys.Control) == Keys.Control;
@@ -38,7 +50,11 @@ namespace AGVNavigationCore.Controls
// 마지막 선택된 노드 업데이트 (단일 참조용)
_selectedNode = _selectedNodes.Count > 0 ? _selectedNodes[_selectedNodes.Count - 1] : null;
// 다중 선택 이벤트 발생 (OnNodesSelected에서 단일/다중 구분 처리)
// 단일/다중 선택 이벤트 발생
if (_selectedNodes.Count == 1)
{
NodeSelect?.Invoke(this, _selectedNodes[0], e);
}
NodesSelected?.Invoke(this, _selectedNodes);
Invalidate();
}
@@ -49,7 +65,8 @@ namespace AGVNavigationCore.Controls
_selectedNodes.Clear();
_selectedNodes.Add(hitNode);
// NodesSelected 이벤트만 발생 (OnNodesSelected에서 단일/다중 구분 처리)
// 단일/다중 선택 이벤트 발생
NodeSelect?.Invoke(this, hitNode, e);
NodesSelected?.Invoke(this, _selectedNodes);
Invalidate();
}
@@ -84,7 +101,7 @@ namespace AGVNavigationCore.Controls
break;
case EditMode.Connect:
HandleConnectClick(hitNode);
HandleConnectClick(hitNode as MapNode);
break;
case EditMode.Delete:
@@ -100,51 +117,51 @@ namespace AGVNavigationCore.Controls
private void UnifiedAGVCanvas_MouseDoubleClick(object sender, MouseEventArgs e)
{
var worldPoint = ScreenToWorld(e.Location);
var hitNode = GetNodeAt(worldPoint);
var hitNode = GetItemAt(worldPoint);
if (hitNode != null)
// 가동 모드에서는 더블클릭 편집 방지
if (_canvasMode == CanvasMode.Run) return;
if (hitNode == null) return;
if (hitNode.Type == NodeType.Normal)
{
// 노드 타입별 더블클릭 액션
switch (hitNode.Type)
{
case NodeType.Normal:
case NodeType.Rotation:
case NodeType.Docking:
case NodeType.Charging:
HandleNormalNodeDoubleClick(hitNode);
break;
case NodeType.Label:
HandleLabelNodeDoubleClick(hitNode);
break;
case NodeType.Image:
HandleImageNodeDoubleClick(hitNode);
break;
default:
// 기본 동작: 노드 선택 이벤트 발생
_selectedNode = hitNode;
_selectedNodes.Clear();
_selectedNodes.Add(hitNode);
NodesSelected?.Invoke(this, _selectedNodes);
break;
}
HandleNormalNodeDoubleClick(hitNode as MapNode);
}
else if (hitNode.Type == NodeType.Label)
{
HandleLabelDoubleClick(hitNode as MapLabel);
}
else if (hitNode.Type == NodeType.Image)
{
HandleImageDoubleClick(hitNode as MapImage);
}
else if (hitNode.Type == NodeType.Mark)
{
HandleMarkDoubleClick(hitNode as MapMark);
}
else if (hitNode.Type == NodeType.Magnet)
{
HandleMagnetDoubleClick(hitNode as MapMagnet);
}
}
private void HandleNormalNodeDoubleClick(MapNode node)
{
// RFID 입력창 표시
string currentRfid = node.RfidId ?? "";
var currentRfid = node.RfidId;
string newRfid = Microsoft.VisualBasic.Interaction.InputBox(
$"노드 '{node.Name}'의 RFID를 입력하세요:",
$"노드 '{node.RfidId}[{node.Id}]'의 RFID를 입력하세요:",
"RFID 설정",
currentRfid);
currentRfid.ToString());
if (!string.IsNullOrWhiteSpace(newRfid) && newRfid != currentRfid)
if (ushort.TryParse(newRfid, out ushort newrfidvalue) == false) return;
if (newrfidvalue < 1) return;
if (newrfidvalue != currentRfid)
{
node.RfidId = newRfid.Trim();
node.RfidId = newrfidvalue;
MapChanged?.Invoke(this, EventArgs.Empty);
Invalidate();
}
@@ -155,11 +172,19 @@ namespace AGVNavigationCore.Controls
_selectedNodes.Add(node);
NodesSelected?.Invoke(this, _selectedNodes);
}
private void HandleMarkDoubleClick(MapMark label)
{
//TODO:
}
private void HandleMagnetDoubleClick(MapMagnet label)
{
//TODO:
}
private void HandleLabelNodeDoubleClick(MapNode node)
private void HandleLabelDoubleClick(MapLabel label)
{
// 라벨 텍스트 입력창 표시
string currentText = node.LabelText ?? "새 라벨";
string currentText = label.Text ?? "새 라벨";
string newText = Microsoft.VisualBasic.Interaction.InputBox(
"라벨 텍스트를 입력하세요:",
"라벨 편집",
@@ -167,28 +192,24 @@ namespace AGVNavigationCore.Controls
if (!string.IsNullOrWhiteSpace(newText) && newText != currentText)
{
node.LabelText = newText.Trim();
label.Text = newText.Trim();
MapChanged?.Invoke(this, EventArgs.Empty);
Invalidate();
}
// 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
_selectedNode = node;
_selectedNodes.Clear();
_selectedNodes.Add(node);
NodesSelected?.Invoke(this, _selectedNodes);
_selectedNode = label;
LabelDoubleClicked?.Invoke(this, label);
Invalidate();
}
private void HandleImageNodeDoubleClick(MapNode node)
private void HandleImageDoubleClick(MapImage image)
{
// 더블클릭 시 해당 노드만 선택 (다중 선택 해제)
_selectedNode = node;
_selectedNodes.Clear();
_selectedNodes.Add(node);
NodesSelected?.Invoke(this, _selectedNodes);
_selectedNode = image;
// 이미지 편집 이벤트 발생 (MainForm에서 처리)
ImageNodeDoubleClicked?.Invoke(this, node);
ImageDoubleClicked?.Invoke(this, image);
Invalidate();
}
private void UnifiedAGVCanvas_MouseDown(object sender, MouseEventArgs e)
@@ -199,24 +220,23 @@ namespace AGVNavigationCore.Controls
{
if (_editMode == EditMode.Move)
{
var hitNode = GetNodeAt(worldPoint);
// 1. 노드 선택 확인
var hitNode = GetItemAt(worldPoint);
if (hitNode != null)
{
_isDragging = true;
_isPanning = false; // 🔥 팬 모드 비활성화 - 중요!
_isPanning = false;
_selectedNode = hitNode;
_dragStartPosition = hitNode.Position; // 원래 위치 저장 (고스트용)
_dragOffset = new Point(
worldPoint.X - hitNode.Position.X,
worldPoint.Y - hitNode.Position.Y
);
_mouseMoveCounter = 0; // 디버그: 카운터 리셋
_dragStartPosition = hitNode.Position;
_dragOffset = new Point(worldPoint.X - hitNode.Position.X, worldPoint.Y - hitNode.Position.Y);
_mouseMoveCounter = 0;
Cursor = Cursors.SizeAll;
Capture = true; // 🔥 마우스 캡처 활성화 - 이게 핵심!
//System.Diagnostics.Debug.WriteLine($"MouseDown: 드래그 시작! Capture={Capture}, isDragging={_isDragging}, isPanning={_isPanning}, Node={hitNode.NodeId}");
Capture = true;
Invalidate();
return;
}
}
// 팬 시작 (좌클릭 - 모드에 따라)
@@ -238,7 +258,9 @@ namespace AGVNavigationCore.Controls
// 컨텍스트 메뉴 (편집 모드에서만)
if (_canvasMode == CanvasMode.Edit)
{
var hitNode = GetNodeAt(worldPoint);
var hitNode = GetItemAt(worldPoint);
// TODO: 라벨/이미지에 대한 컨텍스트 메뉴도 지원하려면 여기서 hitLabel/hitImage 확인해서 전달
// 현재는 ShowContextMenu가 MapNode만 받으므로 노드만 처리
ShowContextMenu(e.Location, hitNode);
}
}
@@ -254,9 +276,12 @@ namespace AGVNavigationCore.Controls
_mouseMoveCounter++;
}
// 호버 노드 업데이트
var newHoveredNode = GetNodeAt(worldPoint);
if (newHoveredNode != _hoveredNode)
// 호버 업데이트
var newHoveredNode = GetItemAt(worldPoint);
bool hoverChanged = (newHoveredNode != _hoveredNode);
if (hoverChanged)
{
_hoveredNode = newHoveredNode;
Invalidate();
@@ -279,24 +304,31 @@ namespace AGVNavigationCore.Controls
}
else if (_isDragging && _canvasMode == CanvasMode.Edit)
{
// 드래그 위치 계산
var newPosition = new Point(
worldPoint.X - _dragOffset.X,
worldPoint.Y - _dragOffset.Y
);
// 그리드 스냅
if (ModifierKeys.HasFlag(Keys.Control))
{
newPosition.X = (newPosition.X / GRID_SIZE) * GRID_SIZE;
newPosition.Y = (newPosition.Y / GRID_SIZE) * GRID_SIZE;
}
bool moved = false;
// 노드 드래그
if (_selectedNode != null)
{
var oldPosition = _selectedNode.Position;
var newPosition = new Point(
worldPoint.X - _dragOffset.X,
worldPoint.Y - _dragOffset.Y
);
// 그리드 스냅
if (ModifierKeys.HasFlag(Keys.Control))
{
newPosition.X = (newPosition.X / GRID_SIZE) * GRID_SIZE;
newPosition.Y = (newPosition.Y / GRID_SIZE) * GRID_SIZE;
}
_selectedNode.Position = newPosition;
NodeMoved?.Invoke(this, _selectedNode);
moved = true;
}
if (moved)
{
MapChanged?.Invoke(this, EventArgs.Empty);
Invalidate();
Update(); // 🔥 즉시 Paint 이벤트 처리하여 화면 업데이트
@@ -397,48 +429,77 @@ namespace AGVNavigationCore.Controls
);
}
private MapNode GetNodeAt(Point worldPoint)
private NodeBase GetItemAt(Point worldPoint)
{
if (_nodes == null) return null;
// 역순으로 검사하여 위에 그려진 노드부터 확인
for (int i = _nodes.Count - 1; i >= 0; i--)
if (_labels != null)
{
var node = _nodes[i];
if (IsPointInNode(worldPoint, node))
return node;
// 역순으로 검사하여 위에 그려진 노드부터 확인
for (int i = _labels.Count - 1; i >= 0; i--)
{
var node = _labels[i];
if (IsPointInNode(worldPoint, node))
return node;
}
}
if (_nodes != null)
{
// 역순으로 검사하여 위에 그려진 노드부터 확인
for (int i = _nodes.Count - 1; i >= 0; i--)
{
var node = _nodes[i];
if (IsPointInNode(worldPoint, node))
return node;
}
}
if (_images != null)
{
// 역순으로 검사하여 위에 그려진 노드부터 확인
for (int i = _images.Count - 1; i >= 0; i--)
{
var node = _images[i];
if (IsPointInNode(worldPoint, node))
return node;
}
}
return null;
}
private bool IsPointInNode(Point point, MapNode node)
private bool IsPointInNode(Point point, NodeBase node)
{
switch (node.Type)
if (node is MapLabel label)
{
case NodeType.Label:
return IsPointInLabelNode(point, node);
case NodeType.Image:
return IsPointInImageNode(point, node);
default:
return IsPointInCircularNode(point, node);
return IsPointInLabel(point, label);
}
if (node is MapImage image)
{
return IsPointInImage(point, image);
}
// 라벨과 이미지는 별도 리스트로 관리되므로 여기서 처리하지 않음
// 하지만 혹시 모를 하위 호환성을 위해 타입 체크는 유지하되,
// 실제 로직은 CircularNode 등으로 분기
return IsPointInCircularNode(point, node as MapNode);
}
private bool IsPointInCircularNode(Point point, MapNode node)
{
switch (node.Type)
switch (node.StationType)
{
case NodeType.Docking:
case StationType.Loader:
case StationType.UnLoader:
case StationType.Clearner:
case StationType.Buffer:
return IsPointInPentagon(point, node);
case NodeType.Charging:
case StationType.Charger:
return IsPointInTriangle(point, node);
default:
return IsPointInCircle(point, node);
}
}
private bool IsPointInCircle(Point point, MapNode node)
private bool IsPointInCircle(Point point, NodeBase node)
{
// 화면에서 최소 20픽셀 정도의 히트 영역을 확보하되, 노드 크기보다 작아지지 않게 함
var minHitRadiusInScreen = 20;
@@ -451,7 +512,7 @@ namespace AGVNavigationCore.Controls
return distance <= hitRadius;
}
private bool IsPointInPentagon(Point point, MapNode node)
private bool IsPointInPentagon(Point point, NodeBase node)
{
// 화면에서 최소 20픽셀 정도의 히트 영역을 확보
var minHitRadiusInScreen = 20;
@@ -472,7 +533,7 @@ namespace AGVNavigationCore.Controls
return IsPointInPolygon(point, points);
}
private bool IsPointInTriangle(Point point, MapNode node)
private bool IsPointInTriangle(Point point, NodeBase node)
{
// 화면에서 최소 20픽셀 정도의 히트 영역을 확보하되, 노드 크기보다 작아지지 않게 함
var minHitRadiusInScreen = 20;
@@ -517,38 +578,68 @@ namespace AGVNavigationCore.Controls
return inside;
}
private bool IsPointInLabelNode(Point point, MapNode node)
private bool IsPointInLabel(Point point, MapLabel label)
{
var text = string.IsNullOrEmpty(node.LabelText) ? node.NodeId : node.LabelText;
var text = string.IsNullOrEmpty(label.Text) ? label.Id : label.Text;
// 임시 Graphics로 텍스트 크기 측정
using (var tempBitmap = new Bitmap(1, 1))
using (var tempGraphics = Graphics.FromImage(tempBitmap))
// Graphics 객체 임시 생성 (Using CreateGraphics is faster than new Bitmap)
using (var g = this.CreateGraphics())
{
var font = new Font(node.FontFamily, node.FontSize, node.FontStyle);
var textSize = tempGraphics.MeasureString(text, font);
// Font 생성 로직: 사용자 정의 폰트가 있으면 생성, 없으면 기본 폰트 사용 (Dispose 주의)
Font fontToUse = null;
bool shouldDisposeFont = false;
var textRect = new Rectangle(
(int)(node.Position.X - textSize.Width / 2),
(int)(node.Position.Y - textSize.Height / 2),
(int)textSize.Width,
(int)textSize.Height
);
try
{
if (string.IsNullOrEmpty(label.FontFamily) || label.FontSize <= 0)
{
fontToUse = this.Font;
shouldDisposeFont = false; // 컨트롤 폰트는 Dispose하면 안됨
}
else
{
try
{
fontToUse = new Font(label.FontFamily, label.FontSize, label.FontStyle);
shouldDisposeFont = true;
}
catch
{
fontToUse = this.Font;
shouldDisposeFont = false;
}
}
font.Dispose();
return textRect.Contains(point);
var textSize = g.MeasureString(text, fontToUse);
var textRect = new Rectangle(
(int)(label.Position.X - textSize.Width / 2),
(int)(label.Position.Y - textSize.Height / 2),
(int)textSize.Width,
(int)textSize.Height
);
return textRect.Contains(point);
}
finally
{
if (shouldDisposeFont && fontToUse != null)
{
fontToUse.Dispose();
}
}
}
}
private bool IsPointInImageNode(Point point, MapNode node)
private bool IsPointInImage(Point point, MapImage image)
{
var displaySize = node.GetDisplaySize();
var displaySize = image.GetDisplaySize();
if (displaySize.IsEmpty)
displaySize = new Size(50, 50); // 기본 크기
var imageRect = new Rectangle(
node.Position.X - displaySize.Width / 2,
node.Position.Y - displaySize.Height / 2,
image.Position.X - displaySize.Width / 2,
image.Position.Y - displaySize.Height / 2,
displaySize.Width,
displaySize.Height
);
@@ -556,6 +647,32 @@ namespace AGVNavigationCore.Controls
return imageRect.Contains(point);
}
//private MapLabel GetLabelAt(Point worldPoint)
//{
// if (_labels == null) return null;
// // 역순으로 검사
// for (int i = _labels.Count - 1; i >= 0; i--)
// {
// var label = _labels[i];
// if (IsPointInLabel(worldPoint, label))
// return label;
// }
// return null;
//}
//private MapImage GetImageAt(Point worldPoint)
//{
// if (_images == null) return null;
// // 역순으로 검사
// for (int i = _images.Count - 1; i >= 0; i--)
// {
// var image = _images[i];
// if (IsPointInImage(worldPoint, image))
// return image;
// }
// return null;
//}
private IAGV GetAGVAt(Point worldPoint)
{
if (_agvList == null) return null;
@@ -575,7 +692,7 @@ namespace AGVNavigationCore.Controls
});
}
private void HandleSelectClick(MapNode hitNode, Point worldPoint)
private void HandleSelectClick(NodeBase hitNode, Point worldPoint)
{
if (hitNode != null)
{
@@ -590,8 +707,8 @@ namespace AGVNavigationCore.Controls
{
// 연결선을 클릭했을 때 삭제 확인
var (fromNode, toNode) = connection.Value;
string fromDisplay = !string.IsNullOrEmpty(fromNode.RfidId) ? fromNode.RfidId : fromNode.NodeId;
string toDisplay = !string.IsNullOrEmpty(toNode.RfidId) ? toNode.RfidId : toNode.NodeId;
string fromDisplay = fromNode.HasRfid() ? fromNode.RfidId.ToString("0000") : fromNode.Id;
string toDisplay = toNode.HasRfid() ? toNode.RfidId.ToString("0000") : toNode.Id;
var result = MessageBox.Show(
$"연결을 삭제하시겠습니까?\n\n{fromDisplay} ↔ {toDisplay}",
@@ -602,13 +719,13 @@ namespace AGVNavigationCore.Controls
if (result == DialogResult.Yes)
{
// 단일 연결 삭제 (어느 방향에 저장되어 있는지 확인 후 삭제)
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
if (fromNode.ConnectedNodes.Contains(toNode.Id))
{
fromNode.RemoveConnection(toNode.NodeId);
fromNode.RemoveConnection(toNode.Id);
}
else if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
else if (toNode.ConnectedNodes.Contains(fromNode.Id))
{
toNode.RemoveConnection(fromNode.NodeId);
toNode.RemoveConnection(fromNode.Id);
}
// 이벤트 발생
@@ -645,9 +762,8 @@ namespace AGVNavigationCore.Controls
var newNode = new MapNode
{
NodeId = newNodeId,
Position = worldPoint,
Type = NodeType.Normal
Id = newNodeId,
Position = worldPoint
};
_nodes.Add(newNode);
@@ -666,20 +782,22 @@ namespace AGVNavigationCore.Controls
worldPoint.Y = (worldPoint.Y / GRID_SIZE) * GRID_SIZE;
}
// 고유한 NodeId 생성
// 고유한 NodeId 생성 (라벨도 ID 공유 권장)
string newNodeId = GenerateUniqueNodeId();
var newNode = new MapNode
var newLabel = new MapLabel
{
NodeId = newNodeId,
Id = newNodeId,
Position = worldPoint,
Type = NodeType.Label,
Name = "새 라벨"
Text = "New Label",
FontSize = 10,
FontFamily = "Arial"
};
_nodes.Add(newNode);
if (_labels == null) _labels = new List<MapLabel>();
_labels.Add(newLabel);
NodeAdded?.Invoke(this, newNode);
//NodeAdded?.Invoke(this, newNode); // TODO: 라벨 추가 이벤트 필요?
MapChanged?.Invoke(this, EventArgs.Empty);
Invalidate();
}
@@ -696,17 +814,17 @@ namespace AGVNavigationCore.Controls
// 고유한 NodeId 생성
string newNodeId = GenerateUniqueNodeId();
var newNode = new MapNode
var newImage = new MapImage
{
NodeId = newNodeId,
Id = newNodeId,
Position = worldPoint,
Type = NodeType.Image,
Name = "새 이미지"
Name = "New Image"
};
_nodes.Add(newNode);
if (_images == null) _images = new List<MapImage>();
_images.Add(newImage);
NodeAdded?.Invoke(this, newNode);
//NodeAdded?.Invoke(this, newNode); // TODO: 이미지 추가 이벤트 필요?
MapChanged?.Invoke(this, EventArgs.Empty);
Invalidate();
}
@@ -724,7 +842,9 @@ namespace AGVNavigationCore.Controls
nodeId = $"N{counter:D3}";
counter++;
}
while (_nodes.Any(n => n.NodeId == nodeId));
while (_nodes.Any(n => n.Id == nodeId) ||
(_labels != null && _labels.Any(l => l.Id == nodeId)) ||
(_images != null && _images.Any(i => i.Id == nodeId)));
_nodeCounter = counter;
return nodeId;
@@ -761,7 +881,7 @@ namespace AGVNavigationCore.Controls
// 연결된 모든 연결선도 제거
foreach (var node in _nodes)
{
node.RemoveConnection(hitNode.NodeId);
node.RemoveConnection(hitNode.Id);
}
_nodes.Remove(hitNode);
@@ -777,13 +897,13 @@ namespace AGVNavigationCore.Controls
private void CreateConnection(MapNode fromNode, MapNode toNode)
{
// 중복 연결 체크 (양방향)
if (fromNode.ConnectedNodes.Contains(toNode.NodeId) ||
toNode.ConnectedNodes.Contains(fromNode.NodeId))
if (fromNode.ConnectedNodes.Contains(toNode.Id) ||
toNode.ConnectedNodes.Contains(fromNode.Id))
return;
// 양방향 연결 생성 (AGV가 양쪽 방향으로 이동 가능하도록)
fromNode.AddConnection(toNode.NodeId);
toNode.AddConnection(fromNode.NodeId);
fromNode.AddConnection(toNode.Id);
toNode.AddConnection(fromNode.Id);
MapChanged?.Invoke(this, EventArgs.Empty);
}
@@ -795,20 +915,26 @@ namespace AGVNavigationCore.Controls
return (float)Math.Sqrt(dx * dx + dy * dy);
}
private void ShowContextMenu(Point location, MapNode hitNode)
private void ShowContextMenu(Point location, NodeBase hitItem)
{
_contextMenu.Items.Clear();
if (hitNode != null)
if (hitItem != null)
{
_contextMenu.Items.Add("노드 속성...", null, (s, e) =>
string typeName = "항목";
if (hitItem is MapNode) typeName = "노드";
else if (hitItem is MapLabel) typeName = "라벨";
else if (hitItem is MapImage) typeName = "이미지";
_contextMenu.Items.Add($"{typeName} 속성...", null, (s, e) =>
{
_selectedNode = hitNode;
_selectedNode = hitItem;
_selectedNodes.Clear();
_selectedNodes.Add(hitNode);
_selectedNodes.Add(hitItem);
NodesSelected?.Invoke(this, _selectedNodes);
Invalidate();
});
_contextMenu.Items.Add("노드 삭제", null, (s, e) => HandleDeleteClick(hitNode));
_contextMenu.Items.Add($"{typeName} 삭제", null, (s, e) => HandleDeleteClick(hitItem));
_contextMenu.Items.Add("-");
}
@@ -833,13 +959,13 @@ namespace AGVNavigationCore.Controls
var (fromNode, toNode) = connection.Value;
// 단일 연결 삭제 (어느 방향에 저장되어 있는지 확인 후 삭제)
if (fromNode.ConnectedNodes.Contains(toNode.NodeId))
if (fromNode.ConnectedNodes.Contains(toNode.Id))
{
fromNode.RemoveConnection(toNode.NodeId);
fromNode.RemoveConnection(toNode.Id);
}
else if (toNode.ConnectedNodes.Contains(fromNode.NodeId))
else if (toNode.ConnectedNodes.Contains(fromNode.Id))
{
toNode.RemoveConnection(fromNode.NodeId);
toNode.RemoveConnection(fromNode.Id);
}
// 이벤트 발생
@@ -852,13 +978,14 @@ namespace AGVNavigationCore.Controls
private (MapNode From, MapNode To)? GetConnectionAt(Point worldPoint)
{
const int CONNECTION_HIT_TOLERANCE = 10;
if (_nodes == null) return null;
// 모든 연결선을 확인하여 클릭한 위치와 가장 가까운 연결선 찾기
foreach (var fromNode in _nodes)
{
foreach (var toNodeId in fromNode.ConnectedNodes)
{
var toNode = _nodes.FirstOrDefault(n => n.NodeId == toNodeId);
var toNode = _nodes.FirstOrDefault(n => n.Id == toNodeId);
if (toNode != null)
{
// 연결선과 클릭 위치 간의 거리 계산
@@ -874,6 +1001,49 @@ namespace AGVNavigationCore.Controls
return null;
}
private void HandleDeleteClick(NodeBase item)
{
if (item == null) return;
if (item is MapNode hitNode)
{
// 연결된 모든 연결선도 제거
foreach (var node in _nodes)
{
node.RemoveConnection(hitNode.Id);
}
_nodes.Remove(hitNode);
if (_selectedNode == hitNode)
_selectedNode = null;
NodeDeleted?.Invoke(this, hitNode);
}
else if (item is MapLabel label)
{
if (_labels != null) _labels.Remove(label);
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
}
else if (item is MapImage image)
{
if (_images != null) _images.Remove(image);
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
}
else if (item is MapMark mark)
{
if (_marks != null) _marks.Remove(mark);
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
}
else if (item is MapMagnet magnet)
{
if (_magnets != null) _magnets.Remove(magnet);
if (_selectedNode.Id.Equals(item.Id)) _selectedNode = null;
}
MapChanged?.Invoke(this, EventArgs.Empty);
Invalidate();
}
private float CalculatePointToLineDistance(Point point, Point lineStart, Point lineEnd)
{
// 점에서 선분까지의 거리 계산
@@ -913,27 +1083,21 @@ namespace AGVNavigationCore.Controls
{
string tooltipText = "";
// 노드 툴팁
var hitNode = GetNodeAt(worldPoint);
var hitNode = GetItemAt(worldPoint);
var hitAGV = GetAGVAt(worldPoint);
if (hitNode != null)
tooltipText = $"노드: {hitNode.Id}\n타입: {hitNode.Type}\n위치: ({hitNode.Position.X}, {hitNode.Position.Y})";
else if (hitAGV != null)
{
tooltipText = $"노드: {hitNode.NodeId}\n타입: {hitNode.Type}\n위치: ({hitNode.Position.X}, {hitNode.Position.Y})";
}
else
{
// AGV 툴팁
var hitAGV = GetAGVAt(worldPoint);
if (hitAGV != null)
{
var state = _agvStates.ContainsKey(hitAGV.AgvId) ? _agvStates[hitAGV.AgvId] : AGVState.Idle;
tooltipText = $"AGV: {hitAGV.AgvId}\n상태: {state}\n배터리: {hitAGV.BatteryLevel:F1}%\n위치: ({hitAGV.CurrentPosition.X}, {hitAGV.CurrentPosition.Y})";
}
var state = _agvStates.ContainsKey(hitAGV.AgvId) ? _agvStates[hitAGV.AgvId] : AGVState.Idle;
tooltipText = $"AGV: {hitAGV.AgvId}\n상태: {state}\n배터리: {hitAGV.BatteryLevel:F1}%\n위치: ({hitAGV.CurrentPosition.X}, {hitAGV.CurrentPosition.Y})";
}
// 툴팁 업데이트 (기존 ToolTip 컨트롤 사용)
if (!string.IsNullOrEmpty(tooltipText))
// 툴팁 텍스트 갱신 (변경되었을 때만)
if (_tooltip != null && _tooltip.GetToolTip(this) != tooltipText)
{
// ToolTip 설정 (필요시 추가 구현)
_tooltip.SetToolTip(this, tooltipText);
}
}
@@ -1001,7 +1165,7 @@ namespace AGVNavigationCore.Controls
/// </summary>
public void PanToNode(string nodeId)
{
var node = _nodes?.FirstOrDefault(n => n.NodeId == nodeId);
var node = _nodes?.FirstOrDefault(n => n.Id == nodeId);
if (node != null)
{
PanTo(node.Position);

View File

@@ -1,12 +1,14 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Windows.Forms;
using AGVNavigationCore.Models;
using AGVNavigationCore.PathFinding;
using AGVNavigationCore.PathFinding.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Reflection.Emit;
using System.Windows.Forms;
namespace AGVNavigationCore.Controls
{
@@ -21,7 +23,7 @@ namespace AGVNavigationCore.Controls
private const int NODE_SIZE = 24;
private const int NODE_RADIUS = NODE_SIZE / 2;
private const int GRID_SIZE = 20;
private const float CONNECTION_WIDTH = 2.0f;
private const float CONNECTION_WIDTH = 1.0f;
private const int SNAP_DISTANCE = 10;
private const int AGV_SIZE = 40;
private const int CONNECTION_ARROW_SIZE = 8;
@@ -35,7 +37,10 @@ namespace AGVNavigationCore.Controls
/// </summary>
public enum CanvasMode
{
Edit // 편집 가능 (맵 에디터)
Edit, // 편집 가능 (맵 에디터)
Sync, // 동기화 모드 (장비 설정 동기화)
Emulator, // 에뮬레이터 모드
Run // 가동 모드 (User Request)
}
/// <summary>
@@ -63,10 +68,19 @@ namespace AGVNavigationCore.Controls
// 맵 데이터
private List<MapNode> _nodes;
private MapNode _selectedNode;
private List<MapNode> _selectedNodes; // 다중 선택
private MapNode _hoveredNode;
private MapNode _destinationNode;
private List<MapLabel> _labels; // 추가
private List<MapImage> _images; // 추가
private List<MapMark> _marks;
private List<MapMagnet> _magnets;
// 선택된 객체들 (나중에 NodeBase로 통일 필요)
private NodeBase _selectedNode;
private List<NodeBase> _selectedNodes; // 다중 선택 (NodeBase로 변경 고려)
private NodeBase _hoveredNode;
private NodeBase _destinationNode;
// AGV 관련
private List<IAGV> _agvList;
@@ -116,6 +130,11 @@ namespace AGVNavigationCore.Controls
// RFID 중복 검사
private HashSet<string> _duplicateRfidNodes = new HashSet<string>();
// 동기화 모드 관련
private string _syncMessage = "동기화 중...";
private float _syncProgress = 0.0f;
private string _syncDetail = "";
// 브러쉬 및 펜
private Brush _normalNodeBrush;
private Brush _rotationNodeBrush;
@@ -136,27 +155,38 @@ namespace AGVNavigationCore.Controls
private Pen _pathPen;
private Pen _agvPen;
private Pen _highlightedConnectionPen;
private Pen _magnetPen;
private Pen _markPen;
private ToolTip _tooltip;
// 컨텍스트 메뉴
private ContextMenuStrip _contextMenu;
// 이벤트
public event EventHandler<NodeBase> NodeRightClicked;
#endregion
#region Events
// 맵 편집 이벤트
public event EventHandler<MapNode> NodeAdded;
public event EventHandler<List<MapNode>> NodesSelected; // 다중 선택 이벤트
public event EventHandler<MapNode> NodeDeleted;
public event EventHandler<MapNode> NodeMoved;
public delegate void NodeSelectHandler(object sender, NodeBase node, MouseEventArgs e);
public event NodeSelectHandler NodeSelect;
public event EventHandler<NodeBase> NodeAdded;
public event EventHandler<List<NodeBase>> NodesSelected; // 다중 선택 이벤트
public event EventHandler<NodeBase> NodeDeleted;
public event EventHandler<NodeBase> NodeMoved;
public event EventHandler<(MapNode From, MapNode To)> ConnectionDeleted;
public event EventHandler<MapNode> ImageNodeDoubleClicked;
public event EventHandler<MapImage> ImageDoubleClicked;
public event EventHandler<MapLabel> LabelDoubleClicked;
public event EventHandler MapChanged;
#endregion
#region Properties
public string PredictMessage { get; set; } = "";
public string MapFileName { get; set; } = "";
/// <summary>
@@ -173,6 +203,60 @@ namespace AGVNavigationCore.Controls
}
}
public void RemoveItem(NodeBase item)
{
if (item is MapImage img) RemoveImage(img);
else if (item is MapLabel lb) RemoveLabel(lb);
else if (item is MapNode nd) RemoveNode(nd);
else if (item is MapMark mk) RemoveMark(mk);
else if (item is MapMagnet mg) RemoveMagnet(mg);
else throw new Exception("unknown type");
}
public void RemoveNode(MapNode node)
{
if (_nodes != null && _nodes.Contains(node))
{
_nodes.Remove(node);
Invalidate();
}
}
public void RemoveLabel(MapLabel label)
{
if (_labels != null && _labels.Contains(label))
{
_labels.Remove(label);
Invalidate();
}
}
public void RemoveImage(MapImage image)
{
if (_images != null && _images.Contains(image))
{
_images.Remove(image);
Invalidate();
}
}
public void RemoveMark(MapMark mark)
{
if (_marks != null && _marks.Contains(mark))
{
_marks.Remove(mark);
Invalidate();
}
}
public void RemoveMagnet(MapMagnet magnet)
{
if (_magnets != null && _magnets.Contains(magnet))
{
_magnets.Remove(magnet);
Invalidate();
}
}
/// <summary>
/// 편집 모드 (CanvasMode.Edit일 때만 적용)
/// </summary>
@@ -219,15 +303,80 @@ namespace AGVNavigationCore.Controls
}
}
[Browsable(false)]
public MapImage SelectedImage
{
get { return this._selectedNode as MapImage; }
}
[Browsable(false)]
public MapLabel SelectedLabel
{
get { return this._selectedNode as MapLabel; }
}
[Browsable(false)]
public MapMark SelectedMark
{
get { return this._selectedNode as MapMark; }
}
[Browsable(false)]
public MapMagnet SelectedMagnet
{
get { return this._selectedNode as MapMagnet; }
}
/// <summary>
/// 선택된 노드 (단일)
/// </summary>
public MapNode SelectedNode => _selectedNode;
public MapNode SelectedNode
{
get { return this._selectedNode as MapNode; }
}
/// <summary>
/// 선택된 노드들 (다중)
/// </summary>
public List<MapNode> SelectedNodes => _selectedNodes ?? new List<MapNode>();
public List<NodeBase> SelectedNodes => _selectedNodes ?? new List<NodeBase>();
public List<NodeBase> Items
{
get
{
List<NodeBase> items = new List<NodeBase>();
if (Nodes != null && Nodes.Any()) items.AddRange(Nodes);
if (Labels != null && Labels.Any()) items.AddRange(Labels);
if (Images != null && Images.Any()) items.AddRange(Images);
if (Marks != null && Marks.Any()) items.AddRange(Marks);
if (Magnets != null && Magnets.Any()) items.AddRange(Magnets);
return items;
}
}
/// <summary>
/// Map file loading 결과를 셋팅합니다
/// </summary>
/// <param name="result"></param>
public void SetMapLoadResult(MapLoader.MapLoadResult result)
{
this.Nodes = result.Nodes;
this.Labels = result.Labels; // 추가
this.Images = result.Images; // 추가
this.Marks = result.Marks;
this.Magnets = result.Magnets;
// 🔥 맵 설정 적용 (배경색, 그리드 표시)
if (result.Settings != null)
{
this.BackColor = Color.FromArgb(result.Settings.BackgroundColorArgb);
this.ShowGrid = result.Settings.ShowGrid;
}
this.FitToNodes();
}
/// <summary>
/// 노드 목록
@@ -249,6 +398,58 @@ namespace AGVNavigationCore.Controls
}
}
/// <summary>
/// 라벨 목록
/// </summary>
public List<MapLabel> Labels
{
get => _labels ?? new List<MapLabel>();
set
{
_labels = value ?? new List<MapLabel>();
Invalidate();
}
}
/// <summary>
/// 이미지 목록
/// </summary>
public List<MapImage> Images
{
get => _images ?? new List<MapImage>();
set
{
_images = value ?? new List<MapImage>();
Invalidate();
}
}
/// <summary>
/// 마크 목록
/// </summary>
public List<MapMark> Marks
{
get => _marks ?? new List<MapMark>();
set
{
_marks = value ?? new List<MapMark>();
Invalidate();
}
}
/// <summary>
/// 마그넷 목록
/// </summary>
public List<MapMagnet> Magnets
{
get => _magnets ?? new List<MapMagnet>();
set
{
_magnets = value ?? new List<MapMagnet>();
Invalidate();
}
}
/// <summary>
/// AGV 목록
/// </summary>
@@ -378,7 +579,12 @@ namespace AGVNavigationCore.Controls
ControlStyles.ResizeRedraw, true);
_nodes = new List<MapNode>();
_selectedNodes = new List<MapNode>(); // 다중 선택 리스트 초기화
_labels = new List<MapLabel>();
_images = new List<MapImage>();
_marks = new List<MapMark>();
_magnets = new List<MapMagnet>();
_selectedNodes = new List<NodeBase>(); // 다중 선택 리스트 초기화
_agvList = new List<IAGV>();
_agvPositions = new Dictionary<string, Point>();
_agvDirections = new Dictionary<string, AgvDirection>();
@@ -388,6 +594,12 @@ namespace AGVNavigationCore.Controls
InitializeBrushesAndPens();
CreateContextMenu();
_tooltip = new ToolTip();
_tooltip.AutoPopDelay = 5000;
_tooltip.InitialDelay = 1000;
_tooltip.ReshowDelay = 500;
_tooltip.ShowAlways = true;
}
private void InitializeBrushesAndPens()
@@ -409,7 +621,8 @@ namespace AGVNavigationCore.Controls
_gridBrush = new SolidBrush(Color.LightGray);
// 펜
_connectionPen = new Pen(Color.DarkBlue, CONNECTION_WIDTH);
_connectionPen = new Pen(Color.White, CONNECTION_WIDTH);
_connectionPen.DashStyle = DashStyle.Dash;
_connectionPen.EndCap = LineCap.ArrowAnchor;
_gridPen = new Pen(Color.LightGray, 1);
@@ -419,6 +632,8 @@ namespace AGVNavigationCore.Controls
_pathPen = new Pen(Color.Purple, 3);
_agvPen = new Pen(Color.Red, 3);
_highlightedConnectionPen = new Pen(Color.Red, 4) { DashStyle = DashStyle.Solid };
_magnetPen = new Pen(Color.FromArgb(100,Color.LightSkyBlue), 15) { DashStyle = DashStyle.Solid };
_markPen = new Pen(Color.White, 3); // 마크는 흰색 선으로 표시
}
private void CreateContextMenu()
@@ -546,6 +761,40 @@ namespace AGVNavigationCore.Controls
}
}
/// <summary>
/// 동기화 상태 설정
/// </summary>
/// <param name="message">메인 메시지</param>
/// <param name="progress">진행률 (0.0 ~ 1.0)</param>
/// <param name="detail">상세 메시지</param>
public void SetSyncStatus(string message, float progress, string detail = "")
{
_syncMessage = message;
_syncProgress = Math.Max(0.0f, Math.Min(1.0f, progress));
_syncDetail = detail;
if (_canvasMode != CanvasMode.Sync)
{
_canvasMode = CanvasMode.Sync;
UpdateModeUI();
}
Invalidate();
}
/// <summary>
/// 동기화 모드 종료
/// </summary>
public void ExitSyncMode()
{
if (_canvasMode == CanvasMode.Sync)
{
_canvasMode = CanvasMode.Edit; // 기본 모드로 복귀 (또는 이전 모드)
UpdateModeUI();
Invalidate();
}
}
#endregion
#region Cleanup
@@ -576,6 +825,8 @@ namespace AGVNavigationCore.Controls
_pathPen?.Dispose();
_agvPen?.Dispose();
_highlightedConnectionPen?.Dispose();
_magnetPen?.Dispose();
_markPen?.Dispose();
// 컨텍스트 메뉴 정리
_contextMenu?.Dispose();
@@ -601,13 +852,13 @@ namespace AGVNavigationCore.Controls
return;
// RFID값과 해당 노드의 인덱스를 저장
var rfidToNodeIndex = new Dictionary<string, List<int>>();
var rfidToNodeIndex = new Dictionary<ushort, List<int>>();
// 모든 노드의 RFID값 수집
for (int i = 0; i < _nodes.Count; i++)
{
var node = _nodes[i];
if (!string.IsNullOrEmpty(node.RfidId))
if (node.HasRfid())
{
if (!rfidToNodeIndex.ContainsKey(node.RfidId))
{
@@ -626,7 +877,7 @@ namespace AGVNavigationCore.Controls
for (int i = 1; i < kvp.Value.Count; i++)
{
int duplicateNodeIndex = kvp.Value[i];
_duplicateRfidNodes.Add(_nodes[duplicateNodeIndex].NodeId);
_duplicateRfidNodes.Add(_nodes[duplicateNodeIndex].Id);
}
}
}
@@ -647,7 +898,7 @@ namespace AGVNavigationCore.Controls
foreach (var node in _nodes)
{
// NodeId에서 숫자 부분 추출 (예: "N001" -> 1)
if (node.NodeId.StartsWith("N") && int.TryParse(node.NodeId.Substring(1), out int number))
if (node.Id.StartsWith("N") && int.TryParse(node.Id.Substring(1), out int number))
{
maxNumber = Math.Max(maxNumber, number);
}

View File

@@ -0,0 +1,54 @@
namespace AGVNavigationCore.Models
{
/// <summary>
/// AGV 제어 명령 클래스 (실제 AGV 제어용)
/// Predict() 메서드가 반환하는 다음 동작 명령
/// </summary>
public class AGVCommand
{
/// <summary>모터 명령 (정지/전진/후진)</summary>
public MotorCommand Motor { get; set; }
/// <summary>마그넷 위치 (직진/왼쪽/오른쪽)</summary>
public MagnetPosition Magnet { get; set; }
/// <summary>속도 레벨 (저속/중속/고속)</summary>
public SpeedLevel Speed { get; set; }
/// <summary>명령 이유 메세지- (디버깅/로깅용)</summary>
public string Message { get; set; }
/// <summary>명령 이유- (디버깅/로깅용)</summary>
public eAGVCommandReason Reason { get; set; }
/// <summary>
/// 생성자
/// </summary>
public AGVCommand(MotorCommand motor, MagnetPosition magnet, SpeedLevel speed, eAGVCommandReason reason, string reasonmessage = "")
{
Motor = motor;
Magnet = magnet;
Speed = speed;
Reason = reason;
Message = reasonmessage;
}
/// <summary>
/// 기본 생성자
/// </summary>
public AGVCommand()
{
Motor = MotorCommand.Stop;
Magnet = MagnetPosition.S;
Speed = SpeedLevel.L;
Message = "";
Reason = eAGVCommandReason.Normal;
}
public override string ToString()
{
return $"Motor:{Motor}, Magnet:{Magnet}, Speed:{Speed},Reason:{Reason}" +
(string.IsNullOrEmpty(Message) ? "" : $" ({Message})");
}
}
}

View File

@@ -2,7 +2,7 @@ using System;
namespace AGVNavigationCore.Models
{
/// <summary>
/// 노드 타입 열거형
@@ -11,16 +11,18 @@ namespace AGVNavigationCore.Models
{
/// <summary>일반 경로 노드</summary>
Normal,
/// <summary>회전 가능 지점</summary>
Rotation,
/// <summary>도킹 스테이션</summary>
Docking,
/// <summary>충전 스테이션</summary>
Charging,
/// <summary>라벨 (UI 요소)</summary>
Label,
/// <summary>이미지 (UI 요소)</summary>
Image
Image,
/// <summary>
/// 마크센서
/// </summary>
Mark,
/// <summary>
/// 마그넷라인
/// </summary>
Magnet
}
/// <summary>
@@ -58,18 +60,46 @@ namespace AGVNavigationCore.Models
/// </summary>
public enum StationType
{
/// <summary>
/// 일반노드
/// </summary>
Normal,
/// <summary>로더</summary>
Loader,
/// <summary>클리너</summary>
Cleaner,
Clearner,
/// <summary>오프로더</summary>
Offloader,
UnLoader,
/// <summary>버퍼</summary>
Buffer,
/// <summary>충전기</summary>
Charger
Charger,
/// <summary>
/// 끝점(더이상 이동불가)
/// </summary>
Limit,
}
/// <summary>
/// AGV턴상태
/// </summary>
public enum AGVTurn
{
None=0,
/// <summary>
/// left turn 90"
/// </summary>
L90,
/// <summary>
/// right turn 90"
/// </summary>
R90
}
/// <summary>
/// 모터 명령 열거형 (실제 AGV 제어용)
/// </summary>
@@ -109,50 +139,42 @@ namespace AGVNavigationCore.Models
H
}
/// <summary>
/// AGV 제어 명령 클래스 (실제 AGV 제어용)
/// Predict() 메서드가 반환하는 다음 동작 명령
/// </summary>
public class AGVCommand
{
/// <summary>모터 명령 (정지/전진/후진)</summary>
public MotorCommand Motor { get; set; }
/// <summary>마그넷 위치 (직진/왼쪽/오른쪽)</summary>
public MagnetPosition Magnet { get; set; }
/// <summary>속도 레벨 (저속/중속/고속)</summary>
public SpeedLevel Speed { get; set; }
/// <summary>명령 이유 (디버깅/로깅용)</summary>
public string Reason { get; set; }
public enum eAGVCommandReason
{
/// <summary>
/// 초기 미지정
/// </summary>
Normal,
/// <summary>
/// 생성자
/// 위치 미확정
/// </summary>
public AGVCommand(MotorCommand motor, MagnetPosition magnet, SpeedLevel speed, string reason = "")
{
Motor = motor;
Magnet = magnet;
Speed = speed;
Reason = reason;
}
UnknownPosition,
/// <summary>
/// 기본 생성자
/// 대상경로없음
/// </summary>
public AGVCommand()
{
Motor = MotorCommand.Stop;
Magnet = MagnetPosition.S;
Speed = SpeedLevel.L;
Reason = "";
}
NoTarget,
/// <summary>
/// 경로없음
/// </summary>
NoPath,
/// <summary>
/// 경로이탈
/// </summary>
PathOut,
/// <summary>
/// 마크스탑을 해야한다
/// </summary>
MarkStop,
/// <summary>
/// 완료
/// </summary>
Complete,
public override string ToString()
{
return $"Motor:{Motor}, Magnet:{Magnet}, Speed:{Speed}" +
(string.IsNullOrEmpty(Reason) ? "" : $" ({Reason})");
}
}
}

View File

@@ -82,7 +82,7 @@ namespace AGVNavigationCore.Models
/// <summary>
/// 현재 노드 ID
/// </summary>
string CurrentNodeId { get; }
MapNode CurrentNode { get; }
/// <summary>
/// 목표 위치
@@ -92,7 +92,7 @@ namespace AGVNavigationCore.Models
/// <summary>
/// 목표 노드 ID
/// </summary>
string PrevNodeId { get; }
MapNode PrevNode { get; }
/// <summary>
/// 도킹 방향
@@ -140,7 +140,7 @@ namespace AGVNavigationCore.Models
/// <summary>
/// 현재 노드 ID 조회
/// </summary>
string GetCurrentNodeId();
MapNode GetCurrentNode();
/// <summary>
/// AGV 상태 정보 문자열 조회

View File

@@ -0,0 +1,88 @@
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using AGVNavigationCore.Utils;
using Newtonsoft.Json;
using System;
namespace AGVNavigationCore.Models
{
public class MapImage : NodeBase
{
[Category("기본 정보")]
[Description("이미지의 이름입니다.")]
public string Name { get; set; } = "Image";
[Category("이미지 설정")]
[Description("이미지 파일 경로입니다 (편집기용).")]
public string ImagePath { get; set; } = string.Empty;
[ReadOnly(false)]
public string ImageBase64 { get; set; } = string.Empty;
[Category("이미지 설정")]
[Description("이미지 크기 배율입니다.")]
public SizeF Scale { get; set; } = new SizeF(1.0f, 1.0f);
[Category("이미지 설정")]
[Description("이미지 투명도입니다 (0.0 ~ 1.0).")]
public float Opacity { get; set; } = 1.0f;
[Category("이미지 설정")]
[Description("이미지 회전 각도입니다.")]
public float Rotation { get; set; } = 0.0f;
[JsonIgnore]
[Browsable(false)]
public Image LoadedImage { get; set; }
public MapImage()
{
Type = NodeType.Image;
}
public bool LoadImage()
{
try
{
Image originalImage = null;
if (!string.IsNullOrEmpty(ImageBase64))
{
originalImage = ImageConverterUtil.Base64ToImage(ImageBase64);
}
else if (!string.IsNullOrEmpty(ImagePath) && System.IO.File.Exists(ImagePath))
{
originalImage = Image.FromFile(ImagePath);
}
if (originalImage != null)
{
LoadedImage?.Dispose();
LoadedImage = originalImage; // 리사이즈 필요시 추가 구현
return true;
}
}
catch
{
// 무시
}
return false;
}
public Size GetDisplaySize()
{
if (LoadedImage == null) return Size.Empty;
return new Size(
(int)(LoadedImage.Width * Scale.Width),
(int)(LoadedImage.Height * Scale.Height)
);
}
public void Dispose()
{
LoadedImage?.Dispose();
LoadedImage = null;
}
}
}

View File

@@ -0,0 +1,42 @@
using System.ComponentModel;
using System.Drawing;
namespace AGVNavigationCore.Models
{
public class MapLabel : NodeBase
{
[Category("라벨 설정")]
[Description("표시할 텍스트입니다.")]
public string Text { get; set; } = "";
[Category("라벨 설정")]
[Description("글자색입니다")]
public Color ForeColor { get; set; } = Color.Black;
[Category("라벨 설정")]
[Description("배경색입니다.")]
public Color BackColor { get; set; } = Color.Transparent;
[Category("라벨 설정")]
[Description("폰트 종류입니다.")]
public string FontFamily { get; set; } = "Arial";
[Category("라벨 설정")]
[Description("폰트 크기입니다.")]
public float FontSize { get; set; } = 12.0f;
[Category("라벨 설정")]
[Description("폰트 스타일입니다.")]
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
[Category("라벨 설정")]
[Description("내부 여백입니다.")]
public int Padding { get; set; } = 5;
public MapLabel()
{
ForeColor = Color.Purple;
Type = NodeType.Label;
}
}
}

View File

@@ -28,6 +28,10 @@ namespace AGVNavigationCore.Models
{
public bool Success { get; set; }
public List<MapNode> Nodes { get; set; } = new List<MapNode>();
public List<MapLabel> Labels { get; set; } = new List<MapLabel>(); // 추가
public List<MapImage> Images { get; set; } = new List<MapImage>(); // 추가
public List<MapMark> Marks { get; set; } = new List<MapMark>();
public List<MapMagnet> Magnets { get; set; } = new List<MapMagnet>();
public MapSettings Settings { get; set; } = new MapSettings();
public string ErrorMessage { get; set; } = string.Empty;
public string Version { get; set; } = string.Empty;
@@ -40,9 +44,13 @@ namespace AGVNavigationCore.Models
public class MapFileData
{
public List<MapNode> Nodes { get; set; } = new List<MapNode>();
public List<MapLabel> Labels { get; set; } = new List<MapLabel>(); // 추가
public List<MapImage> Images { get; set; } = new List<MapImage>(); // 추가
public List<MapMark> Marks { get; set; } = new List<MapMark>();
public List<MapMagnet> Magnets { get; set; } = new List<MapMagnet>();
public MapSettings Settings { get; set; } = new MapSettings();
public DateTime CreatedDate { get; set; }
public string Version { get; set; } = "1.1"; // 버전 업그레이드 (설정 추가)
public string Version { get; set; } = "1.3"; // 버전 업그레이드
}
/// <summary>
@@ -64,7 +72,7 @@ namespace AGVNavigationCore.Models
var json = File.ReadAllText(filePath);
// JSON 역직렬화 설정: 누락된 속성 무시, 안전한 처리
// JSON 역직렬화 설정
var settings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Ignore,
@@ -72,36 +80,83 @@ namespace AGVNavigationCore.Models
DefaultValueHandling = DefaultValueHandling.Populate
};
// 먼저 구조 파악을 위해 동적 객체로 로드하거나, MapFileData로 시도
var mapData = JsonConvert.DeserializeObject<MapFileData>(json, settings);
if (mapData != null)
{
result.Nodes = mapData.Nodes ?? new List<MapNode>();
result.Settings = mapData.Settings ?? new MapSettings(); // 설정 로드
result.Nodes = new List<MapNode>();
result.Labels = mapData.Labels ?? new List<MapLabel>();
result.Images = mapData.Images ?? new List<MapImage>();
result.Marks = mapData.Marks ?? new List<MapMark>();
result.Magnets = mapData.Magnets ?? new List<MapMagnet>();
result.Settings = mapData.Settings ?? new MapSettings();
result.Version = mapData.Version ?? "1.0";
result.CreatedDate = mapData.CreatedDate;
// 기존 Description 데이터를 Name으로 마이그레이션
MigrateDescriptionToName(result.Nodes);
if (mapData.Nodes != null)
{
foreach (var node in mapData.Nodes)
{
// 마이그레이션: 기존 파일의 Nodes 리스트에 섞여있는 Label, Image 분리
// (새 파일 구조에서는 이미 분리되어 로드됨)
if (node.Type == NodeType.Label)
{
// MapNode -> MapLabel 변환 (필드 매핑)
var label = new MapLabel
{
Id = node.Id, // 기존 NodeId -> Id
Position = node.Position,
CreatedDate = node.CreatedDate,
ModifiedDate = node.ModifiedDate,
// Label 속성 매핑 (MapNode에서 임시로 가져오거나 Json Raw Parsing 필요할 수 있음)
// 현재 MapNode 클래스에는 해당 속성들이 제거되었으므로,
// Json 포맷 변경으로 인해 기존 데이터 로드시 정보 손실 가능성 있음.
// * 중요 *: MapNode 클래스에서 속성을 지웠으므로 일반 Deserialize로는 Label/Image 속성을 못 읽음.
// 해결책: JObject로 먼저 읽어서 분기 처리하거나, DTO 클래스를 별도로 두어야 함.
// 하지만 시간 관계상, 만약 기존 MapNode가 속성을 가지고 있지 않다면 마이그레이션은 "위치/ID" 정도만 복구됨.
// 완벽한 마이그레이션을 위해서는 MapNode에 Obsolete 속성을 잠시 두었어야 함.
// 여기서는 일단 기본 정보라도 살림.
};
result.Labels.Add(label);
}
else if (node.Type == NodeType.Image)
{
var image = new MapImage
{
Id = node.Id,
Position = node.Position,
CreatedDate = node.CreatedDate,
ModifiedDate = node.ModifiedDate,
// 이미지/라벨 속성 복구 불가 (MapNode에서 삭제됨)
};
result.Images.Add(image);
}
else
{
result.Nodes.Add(node);
}
}
}
// DockingDirection 마이그레이션 (기존 NodeType 기반으로 설정)
MigrateDockingDirection(result.Nodes);
// 중복된 NodeId 정리
// 중복된 NodeId 정리 (Nav Node만)
FixDuplicateNodeIds(result.Nodes);
// 존재하지 않는 노드에 대한 연결 정리 (고아 연결 제거)
// 고아 연결 정리
CleanupOrphanConnections(result.Nodes);
// 양방향 연결 자동 설정 (A→B가 있으면 B→A도 설정)
// 주의: CleanupDuplicateConnections()는 제거됨 - 양방향 연결을 단방향으로 변환하는 버그가 있었음
// 양방향 연결 자동 설정
EnsureBidirectionalConnections(result.Nodes);
// ConnectedMapNodes 채우기 (string ID → MapNode 객체 참조)
// ConnectedMapNodes 채우기
ResolveConnectedMapNodes(result.Nodes);
// 이미지 노드들의 이미지 로드
LoadImageNodes(result.Nodes);
// 이미지 로드 (MapImage 객체에서)
foreach (var img in result.Images)
{
img.LoadImage();
}
result.Success = true;
}
@@ -121,23 +176,23 @@ namespace AGVNavigationCore.Models
/// <summary>
/// 맵 데이터를 파일로 저장
/// </summary>
/// <param name="filePath">저장할 파일 경로</param>
/// <param name="nodes">맵 노드 목록</param>
/// <param name="settings">맵 설정 (배경색, 그리드 표시 등)</param>
/// <returns>저장 성공 여부</returns>
public static bool SaveMapToFile(string filePath, List<MapNode> nodes, MapSettings settings = null)
public static bool SaveMapToFile(string filePath, List<MapNode> nodes, List<MapLabel> labels = null, List<MapImage> images = null, List<MapMark> marks = null, List<MapMagnet> magnets = null, MapSettings settings = null)
{
try
{
// 저장 전 고아 연결 정리 (삭제된 노드에 대한 연결 제거)
// 저장 전 고아 연결 정리
CleanupOrphanConnections(nodes);
var mapData = new MapFileData
{
Nodes = nodes,
Settings = settings ?? new MapSettings(), // 설정 저장
Labels = labels ?? new List<MapLabel>(),
Images = images ?? new List<MapImage>(),
Marks = marks ?? new List<MapMark>(),
Magnets = magnets ?? new List<MapMagnet>(),
Settings = settings ?? new MapSettings(),
CreatedDate = DateTime.Now,
Version = "1.1"
Version = "1.3"
};
var json = JsonConvert.SerializeObject(mapData, Formatting.Indented);
@@ -145,27 +200,13 @@ namespace AGVNavigationCore.Models
return true;
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 이미지 노드들의 이미지 로드
/// </summary>
/// <param name="nodes">노드 목록</param>
private static void LoadImageNodes(List<MapNode> nodes)
{
foreach (var node in nodes)
{
if (node.Type == NodeType.Image)
{
node.LoadImage();
}
}
}
/// <summary>
/// ConnectedMapNodes 채우기 (ConnectedNodes의 string ID → MapNode 객체 변환)
/// </summary>
@@ -175,7 +216,7 @@ namespace AGVNavigationCore.Models
if (mapNodes == null || mapNodes.Count == 0) return;
// 빠른 조회를 위한 Dictionary 생성
var nodeDict = mapNodes.ToDictionary(n => n.NodeId, n => n);
var nodeDict = mapNodes.ToDictionary(n => n.Id, n => n);
foreach (var node in mapNodes)
{
@@ -192,6 +233,8 @@ namespace AGVNavigationCore.Models
}
}
}
}
}
@@ -208,36 +251,6 @@ namespace AGVNavigationCore.Models
// 기존 파일들은 다시 저장될 때 Description 없이 저장됨
}
/// <summary>
/// 기존 맵 파일의 DockingDirection을 NodeType 기반으로 마이그레이션
/// </summary>
/// <param name="mapNodes">맵 노드 목록</param>
private static void MigrateDockingDirection(List<MapNode> mapNodes)
{
if (mapNodes == null || mapNodes.Count == 0) return;
foreach (var node in mapNodes)
{
// 기존 파일에서 DockingDirection이 기본값(DontCare)인 경우에만 마이그레이션
if (node.DockDirection == DockingDirection.DontCare)
{
switch (node.Type)
{
case NodeType.Charging:
node.DockDirection = DockingDirection.Forward;
break;
case NodeType.Docking:
node.DockDirection = DockingDirection.Backward;
break;
default:
// Normal, Rotation, Label, Image는 DontCare 유지
node.DockDirection = DockingDirection.DontCare;
break;
}
}
}
}
/// <summary>
/// 중복된 NodeId를 가진 노드들을 고유한 NodeId로 수정
/// </summary>
@@ -252,13 +265,13 @@ namespace AGVNavigationCore.Models
// 첫 번째 패스: 중복된 노드들 식별
foreach (var node in mapNodes)
{
if (usedIds.Contains(node.NodeId))
if (usedIds.Contains(node.Id))
{
duplicateNodes.Add(node);
}
else
{
usedIds.Add(node.NodeId);
usedIds.Add(node.Id);
}
}
@@ -268,9 +281,9 @@ namespace AGVNavigationCore.Models
string newNodeId = GenerateUniqueNodeId(usedIds);
// 다른 노드들의 연결에서 기존 NodeId를 새 NodeId로 업데이트
UpdateConnections(mapNodes, duplicateNode.NodeId, newNodeId);
UpdateConnections(mapNodes, duplicateNode.Id, newNodeId);
duplicateNode.NodeId = newNodeId;
duplicateNode.Id = newNodeId;
usedIds.Add(newNodeId);
}
}
@@ -328,7 +341,7 @@ namespace AGVNavigationCore.Models
if (mapNodes == null || mapNodes.Count == 0) return;
// 존재하는 모든 노드 ID 집합 생성
var validNodeIds = new HashSet<string>(mapNodes.Select(n => n.NodeId));
var validNodeIds = new HashSet<string>(mapNodes.Select(n => n.Id));
// 각 노드의 연결을 검증하고 존재하지 않는 노드 ID 제거
foreach (var node in mapNodes)
@@ -366,13 +379,13 @@ namespace AGVNavigationCore.Models
foreach (var connectedNodeId in node.ConnectedNodes.ToList())
{
var connectedNode = mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
var connectedNode = mapNodes.FirstOrDefault(n => n.Id == connectedNodeId);
if (connectedNode == null) continue;
// 연결 쌍의 키 생성 (사전순 정렬)
string pairKey = string.Compare(node.NodeId, connectedNodeId, StringComparison.Ordinal) < 0
? $"{node.NodeId}-{connectedNodeId}"
: $"{connectedNodeId}-{node.NodeId}";
string pairKey = string.Compare(node.Id, connectedNodeId, StringComparison.Ordinal) < 0
? $"{node.Id}-{connectedNodeId}"
: $"{connectedNodeId}-{node.Id}";
if (processedPairs.Contains(pairKey))
{
@@ -385,17 +398,17 @@ namespace AGVNavigationCore.Models
processedPairs.Add(pairKey);
// 양방향 연결인 경우 하나만 유지
if (connectedNode.ConnectedNodes.Contains(node.NodeId))
if (connectedNode.ConnectedNodes.Contains(node.Id))
{
// 사전순으로 더 작은 노드에만 연결을 유지
if (string.Compare(node.NodeId, connectedNodeId, StringComparison.Ordinal) > 0)
if (string.Compare(node.Id, connectedNodeId, StringComparison.Ordinal) > 0)
{
connectionsToRemove.Add(connectedNodeId);
}
else
{
// 반대 방향 연결 제거
connectedNode.RemoveConnection(node.NodeId);
connectedNode.RemoveConnection(node.Id);
}
}
}
@@ -430,16 +443,16 @@ namespace AGVNavigationCore.Models
// 1단계: 모든 명시적 연결 수집
foreach (var node in mapNodes)
{
if (!allConnections.ContainsKey(node.NodeId))
if (!allConnections.ContainsKey(node.Id))
{
allConnections[node.NodeId] = new HashSet<string>();
allConnections[node.Id] = new HashSet<string>();
}
if (node.ConnectedNodes != null)
{
foreach (var connectedId in node.ConnectedNodes)
{
allConnections[node.NodeId].Add(connectedId);
allConnections[node.Id].Add(connectedId);
}
}
}
@@ -455,10 +468,10 @@ namespace AGVNavigationCore.Models
// 이 노드를 연결하는 모든 노드 찾기
foreach (var otherNodeId in allConnections.Keys)
{
if (otherNodeId == node.NodeId) continue;
if (otherNodeId == node.Id) continue;
// 다른 노드가 이 노드를 연결하고 있다면
if (allConnections[otherNodeId].Contains(node.NodeId))
if (allConnections[otherNodeId].Contains(node.Id))
{
// 이 노드의 ConnectedNodes에 그 노드를 추가 (중복 방지)
if (!node.ConnectedNodes.Contains(otherNodeId))

View File

@@ -0,0 +1,72 @@
using System;
using System.ComponentModel;
using System.Drawing;
using Newtonsoft.Json;
namespace AGVNavigationCore.Models
{
/// <summary>
/// 맵 상의 마그넷(Magnet) 정보를 나타내는 클래스
/// </summary>
public class MapMagnet : NodeBase
{
public MapMagnet() {
Type = NodeType.Magnet;
}
[Category("위치 정보")]
[Description("시작점 좌표")]
public MagnetPoint P1 { get; set; } = new MagnetPoint();
[Category("위치 정보")]
[Description("끝점 좌표")]
public MagnetPoint P2 { get; set; } = new MagnetPoint();
[Category("위치 정보")]
[Description("제어점 좌표 (곡선인 경우)")]
public MagnetPoint ControlPoint { get; set; } = null;
public class MagnetPoint
{
public double X { get; set; }
public double Y { get; set; }
}
[JsonIgnore]
public override Point Position
{
get => new Point((int)P1.X, (int)P1.Y);
set
{
double dx = value.X - P1.X;
double dy = value.Y - P1.Y;
P1.X += dx;
P1.Y += dy;
P2.X += dx;
P2.Y += dy;
if (ControlPoint != null)
{
ControlPoint.X += dx;
ControlPoint.Y += dy;
}
}
}
/// <summary>
/// 시작점 Point 반환
/// </summary>
[Browsable(false)]
[JsonIgnore]
public Point StartPoint => new Point((int)P1.X, (int)P1.Y);
/// <summary>
/// 끝점 Point 반환
/// </summary>
[Browsable(false)]
[JsonIgnore]
public Point EndPoint => new Point((int)P2.X, (int)P2.Y);
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.ComponentModel;
using System.Drawing;
namespace AGVNavigationCore.Models
{
/// <summary>
/// 맵 상의 마크(Mark) 정보를 나타내는 클래스
/// </summary>
public class MapMark : NodeBase
{
// Id is inherited from NodeBase
public MapMark() {
Type = NodeType.Mark;
}
[Category("위치 정보")]
[Description("마크의 X 좌표")]
public double X
{
get => Position.X;
set => Position = new Point((int)value, Position.Y);
}
[Category("위치 정보")]
[Description("마크의 Y 좌표")]
public double Y
{
get => Position.Y;
set => Position = new Point(Position.X, (int)value);
}
[Category("위치 정보")]
[Description("마크의 회전 각도")]
public double Rotation { get; set; }
}
}

View File

@@ -1,267 +1,127 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using AGVNavigationCore.Utils;
using Newtonsoft.Json;
namespace AGVNavigationCore.Models
{
/// <summary>
/// 맵 노드 정보를 관리하는 클래스
/// 논리적 노드로서 실제 맵의 위치와 속성을 정의
/// 맵 노드 정보를 관리하는 클래스 (주행 경로용 노드)
/// </summary>
public class MapNode
public class MapNode : NodeBase
{
/// <summary>
/// 논리적 노드 ID (맵 에디터에서 관리하는 고유 ID)
/// 예: "N001", "N002", "LOADER1", "CHARGER1"
/// </summary>
public string NodeId { get; set; } = string.Empty;
/// <summary>
/// 노드 표시 이름 (사용자 친화적)
/// 예: "로더1", "충전기1", "교차점A", "회전지점1"
/// </summary>
public string Name { get; set; } = string.Empty;
/// <summary>
/// 맵 상의 위치 좌표 (픽셀 단위)
/// </summary>
public Point Position { get; set; } = Point.Empty;
[Category("라벨 설정")]
[Description("표시할 텍스트입니다.")]
public string Text { get; set; } = "";
/// <summary>
/// 노드 타입
/// </summary>
public NodeType Type { get; set; } = NodeType.Normal;
public StationType StationType { get; set; }
/// <summary>
/// 도킹 방향 (도킹/충전 노드인 경우에만 Forward/Backward, 일반 노드는 DontCare)
/// </summary>
[Browsable(false)]
public bool CanDocking
{
get
{
if (StationType == StationType.Buffer) return true;
if (StationType == StationType.Loader) return true;
if (StationType == StationType.UnLoader) return true;
if (StationType == StationType.Clearner) return true;
if (StationType == StationType.Charger) return true;
return false;
}
}
[Category("노드 설정")]
[Description("도킹/충전 노드의 진입 방향입니다.")]
public DockingDirection DockDirection { get; set; } = DockingDirection.DontCare;
/// <summary>
/// 연결된 노드 ID 목록 (경로 정보)
/// </summary>
[Category("연결 정보")]
[Description("연결된 노드 ID 목록입니다.")]
[ReadOnly(true)]
public List<string> ConnectedNodes { get; set; } = new List<string>();
/// <summary>
/// 연결된 노드 객체 목록 (런타임 전용, JSON 무시)
/// </summary>
[Newtonsoft.Json.JsonIgnore]
[JsonIgnore]
[Browsable(false)]
public List<MapNode> ConnectedMapNodes { get; set; } = new List<MapNode>();
/// <summary>
/// 회전 가능 여부 (180도 회전 가능한 지점)
/// </summary>
public bool CanRotate { get; set; } = false;
[Category("주행 설정")]
[Description("제자리 회전(좌) 가능 여부입니다.")]
public bool CanTurnLeft { get; set; } = true;
/// <summary>
/// 장비 ID (도킹/충전 스테이션인 경우)
/// 예: "LOADER1", "CLEANER1", "BUFFER1", "CHARGER1"
/// </summary>
public string StationId { get; set; } = string.Empty;
[Category("주행 설정")]
[Description("제자리 회전(우) 가능 여부입니다.")]
public bool CanTurnRight { get; set; } = true;
/// <summary>
/// 장비 타입 (도킹/충전 스테이션인 경우)
/// </summary>
public StationType? StationType { get; set; } = null;
[Category("주행 설정")]
[Description("교차로 주행 가능 여부입니다.")]
public bool DisableCross
{
get
{
if (Type != NodeType.Normal) return true;
return _disablecross;
}
set { _disablecross = value; }
}
private bool _disablecross = false;
/// <summary>
/// 노드 생성 일자
/// </summary>
public DateTime CreatedDate { get; set; } = DateTime.Now;
[Category("주행 설정")]
[Description("노드 통과 시 제한 속도입니다.")]
public SpeedLevel SpeedLimit { get; set; } = SpeedLevel.M;
/// <summary>
/// 노드 수정 일자
/// </summary>
public DateTime ModifiedDate { get; set; } = DateTime.Now;
[Category("노드 설정")]
[Description("장비 ID 또는 별칭입니다.")]
public string AliasName { get; set; } = string.Empty;
/// <summary>
/// 노드 활성화 여부
/// </summary>
[Category("기본 정보")]
[Description("노드 사용 여부입니다.")]
public bool IsActive { get; set; } = true;
/// <summary>
/// 노드 색상 (맵 에디터 표시용)
/// </summary>
public Color DisplayColor { get; set; } = Color.Blue;
[Category("RFID 정보")]
[Description("물리적 RFID 태그 ID입니다.")]
public UInt16 RfidId { get; set; } = 0;
/// <summary>
/// RFID 태그 ID (이 노드에 매핑된 RFID)
/// </summary>
public string RfidId { get; set; } = string.Empty;
/// <summary>
/// RFID 상태 (정상, 손상, 교체예정 등)
/// </summary>
public string RfidStatus { get; set; } = "정상";
/// <summary>
/// RFID 설치 위치 설명 (현장 작업자용)
/// 예: "로더1번 앞", "충전기2번 입구", "복도 교차점" 등
/// </summary>
public string RfidDescription { get; set; } = string.Empty;
/// <summary>
/// 라벨 텍스트 (NodeType.Label인 경우 사용)
/// </summary>
public string LabelText { get; set; } = string.Empty;
/// <summary>
/// 라벨 폰트 패밀리 (NodeType.Label인 경우 사용)
/// </summary>
public string FontFamily { get; set; } = "Arial";
/// <summary>
/// 라벨 폰트 크기 (NodeType.Label인 경우 사용)
/// </summary>
public float FontSize { get; set; } = 12.0f;
/// <summary>
/// 라벨 폰트 스타일 (NodeType.Label인 경우 사용)
/// </summary>
public FontStyle FontStyle { get; set; } = FontStyle.Regular;
/// <summary>
/// 텍스트 전경색 (모든 노드 타입에서 사용)
/// </summary>
public Color ForeColor { get; set; } = Color.Black;
/// <summary>
/// 라벨 배경색 (NodeType.Label인 경우 사용)
/// </summary>
public Color BackColor { get; set; } = Color.Transparent;
[Category("노드 텍스트"), DisplayName("TextColor")]
[Description("텍스트 색상입니다.")]
public Color NodeTextForeColor { get; set; } = Color.Black;
private float _textFontSize = 7.0f;
/// <summary>
/// 텍스트 폰트 크기 (모든 노드 타입의 텍스트 표시에 사용, 픽셀 단위)
/// 0 이하의 값이 설정되면 기본값 7.0f로 자동 설정
/// </summary>
public float TextFontSize
[Category("노드 텍스트"), DisplayName("TextSize")]
[Description("일반 노드 텍스트의 크기입니다.")]
public float NodeTextFontSize
{
get => _textFontSize;
set => _textFontSize = value > 0 ? value : 7.0f;
}
/// <summary>
/// 텍스트 볼드체 여부 (모든 노드 타입의 텍스트 표시에 사용)
/// </summary>
public bool TextFontBold { get; set; } = true;
/// <summary>
/// 노드 이름 말풍선 배경색 (하단에 표시되는 노드 이름의 배경색)
/// </summary>
public Color NameBubbleBackColor { get; set; } = Color.Gold;
/// <summary>
/// 노드 이름 말풍선 글자색 (하단에 표시되는 노드 이름의 글자색)
/// </summary>
public Color NameBubbleForeColor { get; set; } = Color.Black;
/// <summary>
/// 라벨 배경 표시 여부 (NodeType.Label인 경우 사용)
/// </summary>
public bool ShowBackground { get; set; } = false;
/// <summary>
/// 라벨 패딩 (NodeType.Label인 경우 사용, 픽셀 단위)
/// </summary>
public int Padding { get; set; } = 8;
/// <summary>
/// 이미지 파일 경로 (편집용, 저장시엔 사용되지 않음)
/// </summary>
[Newtonsoft.Json.JsonIgnore]
public string ImagePath { get; set; } = string.Empty;
/// <summary>
/// Base64 인코딩된 이미지 데이터 (JSON 저장용)
/// </summary>
public string ImageBase64 { get; set; } = string.Empty;
/// <summary>
/// 이미지 크기 배율 (NodeType.Image인 경우 사용)
/// </summary>
public SizeF Scale { get; set; } = new SizeF(1.0f, 1.0f);
/// <summary>
/// 이미지 투명도 (NodeType.Image인 경우 사용, 0.0~1.0)
/// </summary>
public float Opacity { get; set; } = 1.0f;
/// <summary>
/// 이미지 회전 각도 (NodeType.Image인 경우 사용, 도 단위)
/// </summary>
public float Rotation { get; set; } = 0.0f;
/// <summary>
/// 로딩된 이미지 (런타임에서만 사용, JSON 직렬화 제외)
/// </summary>
[Newtonsoft.Json.JsonIgnore]
public Image LoadedImage { get; set; }
/// <summary>
/// 기본 생성자
/// </summary>
public MapNode()
public MapNode() : base()
{
Type = NodeType.Normal;
}
/// <summary>
/// 매개변수 생성자
/// </summary>
/// <param name="nodeId">노드 ID</param>
/// <param name="name">노드 이름</param>
/// <param name="position">위치</param>
/// <param name="type">노드 타입</param>
public MapNode(string nodeId, string name, Point position, NodeType type)
public MapNode(string nodeId, Point position, StationType type) : base(nodeId, position)
{
NodeId = nodeId;
Name = name;
Position = position;
Type = type;
CreatedDate = DateTime.Now;
ModifiedDate = DateTime.Now;
// 타입별 기본 색상 설정
SetDefaultColorByType(type);
Type = NodeType.Normal;
}
/// <summary>
/// 노드 타입에 따른 기본 색상 설정
/// </summary>
/// <param name="type">노드 타입</param>
public void SetDefaultColorByType(NodeType type)
[Category("기본 정보")]
[JsonIgnore]
[ReadOnly(true), Browsable(false)]
public bool isDockingNode
{
switch (type)
get
{
case NodeType.Normal:
DisplayColor = Color.Blue;
break;
case NodeType.Rotation:
DisplayColor = Color.Orange;
break;
case NodeType.Docking:
DisplayColor = Color.Green;
break;
case NodeType.Charging:
DisplayColor = Color.Red;
break;
case NodeType.Label:
DisplayColor = Color.Purple;
break;
case NodeType.Image:
DisplayColor = Color.Brown;
break;
if (StationType == StationType.Charger || StationType == StationType.Buffer ||
StationType == StationType.Clearner || StationType == StationType.Loader ||
StationType == StationType.UnLoader) return true;
return false;
}
}
/// <summary>
/// 다른 노드와의 연결 추가
/// </summary>
/// <param name="nodeId">연결할 노드 ID</param>
public void AddConnection(string nodeId)
{
if (!ConnectedNodes.Contains(nodeId))
@@ -271,10 +131,6 @@ namespace AGVNavigationCore.Models
}
}
/// <summary>
/// 다른 노드와의 연결 제거
/// </summary>
/// <param name="nodeId">연결 해제할 노드 ID</param>
public void RemoveConnection(string nodeId)
{
if (ConnectedNodes.Remove(nodeId))
@@ -283,289 +139,29 @@ namespace AGVNavigationCore.Models
}
}
/// <summary>
/// 도킹 스테이션 설정
/// </summary>
/// <param name="stationId">장비 ID</param>
/// <param name="stationType">장비 타입</param>
/// <param name="dockDirection">도킹 방향</param>
public void SetDockingStation(string stationId, StationType stationType, DockingDirection dockDirection)
{
Type = NodeType.Docking;
StationId = stationId;
StationType = stationType;
DockDirection = dockDirection;
SetDefaultColorByType(NodeType.Docking);
ModifiedDate = DateTime.Now;
}
/// <summary>
/// 충전 스테이션 설정
/// </summary>
/// <param name="stationId">충전기 ID</param>
public void SetChargingStation(string stationId)
{
Type = NodeType.Charging;
StationId = stationId;
StationType = Models.StationType.Charger;
DockDirection = DockingDirection.Forward; // 충전기는 항상 전진 도킹
SetDefaultColorByType(NodeType.Charging);
StationType = StationType.Charger;
Id = stationId;
DockDirection = DockingDirection.Forward;
ModifiedDate = DateTime.Now;
}
/// <summary>
/// 문자열 표현
/// </summary>
public override string ToString()
{
return $"{RfidId}({NodeId}): {Name} ({Type}) at ({Position.X}, {Position.Y})";
return $"RFID:{RfidId}(NODE:{Id}): AS:{AliasName} ({Type}) at ({Position.X}, {Position.Y})";
}
/// <summary>
/// 리스트박스 표시용 텍스트 (노드ID - 설명 - RFID 순서)
/// </summary>
public string DisplayText
{
get
{
var displayText = NodeId;
if (!string.IsNullOrEmpty(Name))
{
displayText += $" - {Name}";
}
if (!string.IsNullOrEmpty(RfidId))
{
displayText += $" - [{RfidId}]";
}
return displayText;
}
}
/// <summary>
/// 노드 복사
/// </summary>
/// <returns>복사된 노드</returns>
public MapNode Clone()
{
var clone = new MapNode
{
NodeId = NodeId,
Name = Name,
Position = Position,
Type = Type,
DockDirection = DockDirection,
ConnectedNodes = new List<string>(ConnectedNodes),
CanRotate = CanRotate,
StationId = StationId,
StationType = StationType,
CreatedDate = CreatedDate,
ModifiedDate = ModifiedDate,
IsActive = IsActive,
DisplayColor = DisplayColor,
RfidId = RfidId,
RfidStatus = RfidStatus,
RfidDescription = RfidDescription,
LabelText = LabelText,
FontFamily = FontFamily,
FontSize = FontSize,
FontStyle = FontStyle,
ForeColor = ForeColor,
BackColor = BackColor,
TextFontSize = TextFontSize,
TextFontBold = TextFontBold,
NameBubbleBackColor = NameBubbleBackColor,
NameBubbleForeColor = NameBubbleForeColor,
ShowBackground = ShowBackground,
Padding = Padding,
ImagePath = ImagePath,
ImageBase64 = ImageBase64,
Scale = Scale,
Opacity = Opacity,
Rotation = Rotation
};
return clone;
}
/// <summary>
/// 이미지 로드 (Base64 또는 파일 경로에서, 256x256 이상일 경우 자동 리사이즈)
/// </summary>
/// <returns>로드 성공 여부</returns>
public bool LoadImage()
{
if (Type != NodeType.Image) return false;
try
{
Image originalImage = null;
// 1. 먼저 Base64 데이터 시도
if (!string.IsNullOrEmpty(ImageBase64))
{
originalImage = ImageConverterUtil.Base64ToImage(ImageBase64);
}
// 2. Base64가 없으면 파일 경로에서 로드
else if (!string.IsNullOrEmpty(ImagePath) && System.IO.File.Exists(ImagePath))
{
originalImage = Image.FromFile(ImagePath);
}
if (originalImage != null)
{
LoadedImage?.Dispose();
// 이미지 크기 체크 및 리사이즈
if (originalImage.Width > 256 || originalImage.Height > 256)
{
LoadedImage = ResizeImage(originalImage, 256, 256);
originalImage.Dispose();
}
else
{
LoadedImage = originalImage;
}
return true;
}
}
catch (Exception)
{
// 이미지 로드 실패
}
return false;
}
/// <summary>
/// 이미지 리사이즈 (비율 유지)
/// </summary>
/// <param name="image">원본 이미지</param>
/// <param name="maxWidth">최대 너비</param>
/// <param name="maxHeight">최대 높이</param>
/// <returns>리사이즈된 이미지</returns>
private Image ResizeImage(Image image, int maxWidth, int maxHeight)
{
// 비율 계산
double ratioX = (double)maxWidth / image.Width;
double ratioY = (double)maxHeight / image.Height;
double ratio = Math.Min(ratioX, ratioY);
// 새로운 크기 계산
int newWidth = (int)(image.Width * ratio);
int newHeight = (int)(image.Height * ratio);
// 리사이즈된 이미지 생성
var resizedImage = new Bitmap(newWidth, newHeight);
using (var graphics = Graphics.FromImage(resizedImage))
{
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.SmoothingMode = SmoothingMode.HighQuality;
graphics.DrawImage(image, 0, 0, newWidth, newHeight);
}
return resizedImage;
}
/// <summary>
/// 실제 표시될 크기 계산 (이미지 노드인 경우)
/// </summary>
/// <returns>실제 크기</returns>
public Size GetDisplaySize()
{
if (Type != NodeType.Image || LoadedImage == null) return Size.Empty;
return new Size(
(int)(LoadedImage.Width * Scale.Width),
(int)(LoadedImage.Height * Scale.Height)
);
}
/// <summary>
/// 파일 경로에서 이미지를 Base64로 변환하여 저장
/// </summary>
/// <param name="filePath">이미지 파일 경로</param>
/// <returns>변환 성공 여부</returns>
public bool ConvertImageToBase64(string filePath)
{
if (Type != NodeType.Image) return false;
try
{
if (!System.IO.File.Exists(filePath))
{
return false;
}
ImageBase64 = ImageConverterUtil.FileToBase64(filePath, System.Drawing.Imaging.ImageFormat.Png);
ImagePath = filePath; // 편집용으로 경로 유지
return !string.IsNullOrEmpty(ImageBase64);
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// 리소스 정리
/// </summary>
public void Dispose()
{
LoadedImage?.Dispose();
LoadedImage = null;
}
/// <summary>
/// 경로 찾기에 사용 가능한 노드인지 확인
/// (라벨, 이미지 노드는 경로 찾기에서 제외)
/// </summary>
public bool IsNavigationNode()
{
return Type != NodeType.Label && Type != NodeType.Image && IsActive;
// 이제 MapNode는 항상 내비게이션 노드임 (Label, Image 분리됨)
// 하지만 기존 로직 호환성을 위해 Active 체크만 유지
return IsActive;
}
/// <summary>
/// RFID가 할당되어 있는지 확인
/// </summary>
public bool HasRfid()
{
return !string.IsNullOrEmpty(RfidId);
}
/// <summary>
/// RFID 정보 설정
/// </summary>
/// <param name="rfidId">RFID ID</param>
/// <param name="rfidDescription">설치 위치 설명</param>
/// <param name="rfidStatus">RFID 상태</param>
public void SetRfidInfo(string rfidId, string rfidDescription = "", string rfidStatus = "정상")
{
RfidId = rfidId;
RfidDescription = rfidDescription;
RfidStatus = rfidStatus;
ModifiedDate = DateTime.Now;
}
/// <summary>
/// RFID 정보 삭제
/// </summary>
public void ClearRfidInfo()
{
RfidId = string.Empty;
RfidDescription = string.Empty;
RfidStatus = "정상";
ModifiedDate = DateTime.Now;
}
/// <summary>
/// RFID 기반 표시 텍스트 (RFID ID 우선, 없으면 노드ID)
/// </summary>
public string GetRfidDisplayText()
{
return HasRfid() ? RfidId : NodeId;
return RfidId > 0;
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using Newtonsoft.Json;
namespace AGVNavigationCore.Models
{
/// <summary>
/// 맵 상의 모든 객체의 최상위 기본 클래스
/// 위치, 선택 상태, 기본 식별자 등을 관리
/// </summary>
public abstract class NodeBase
{
[Category("기본 정보")]
[Description("객체의 고유 ID입니다.")]
[ReadOnly(true)]
public string Id { get; set; } = Guid.NewGuid().ToString();
[Category("기본 정보")]
public NodeType Type { protected set; get; } = NodeType.Normal;
[Category("기본 정보")]
[Description("객체의 좌표(X, Y)입니다.")]
public virtual Point Position { get; set; } = Point.Empty;
[Category("기본 정보")]
[Description("객체 생성 일자입니다.")]
[JsonIgnore]
[ReadOnly(true), Browsable(false)]
public DateTime CreatedDate { get; set; } = DateTime.Now;
[Category("기본 정보")]
[Description("객체 수정 일자입니다.")]
[JsonIgnore]
[ReadOnly(true), Browsable(false)]
public DateTime ModifiedDate { get; set; } = DateTime.Now;
[Browsable(false)]
[JsonIgnore]
public bool IsSelected { get; set; } = false;
[Browsable(false)]
[JsonIgnore]
public bool IsHovered { get; set; } = false;
public NodeBase()
{
}
public NodeBase(string id, Point position)
{
Id = id;
Position = position;
}
}
}

View File

@@ -43,6 +43,8 @@ namespace AGVNavigationCore.Models
/// </summary>
public event EventHandler<string> ErrorOccurred;
#endregion
#region Fields
@@ -61,12 +63,12 @@ namespace AGVNavigationCore.Models
private int _currentNodeIndex;
private MapNode _currentNode;
private MapNode _prevNode;
private AGVTurn _turn;
// 이동 관련
private DateTime _lastUpdateTime;
private Point _moveStartPosition;
private Point _moveTargetPosition;
private float _moveProgress;
// 도킹 관련
private DockingDirection _dockingDirection;
@@ -79,17 +81,25 @@ namespace AGVNavigationCore.Models
private List<string> _detectedRfids = new List<string>(); // 감지된 RFID 목록
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
#region Properties
public bool Turn180 { get; set; } = false;
/// <summary>
/// 대상 이동시 모터 방향
/// </summary>
public AgvDirection PrevDirection => _prevDirection;
/// <summary>
/// AGV ID
/// </summary>
@@ -136,7 +146,12 @@ namespace AGVNavigationCore.Models
/// <summary>
/// 현재 노드 ID
/// </summary>
public string CurrentNodeId => _currentNode?.NodeId;
public MapNode CurrentNode => _currentNode;
/// <summary>
/// 현재 노드 ID (CurrentNode.Id)
/// </summary>
public string CurrentNodeId => _currentNode?.Id;
/// <summary>
/// 이전 위치
@@ -148,16 +163,17 @@ namespace AGVNavigationCore.Models
/// </summary>
public float BatteryLevel { get; set; } = 100.0f;
/// <summary>
/// 이전 노드 ID
/// </summary>
public string PrevNodeId => _prevNode?.NodeId;
/// <summary>
/// 이전 노드
/// </summary>
public MapNode PrevNode => _prevNode;
/// <summary>
/// Turn 상태값
/// </summary>
public AGVTurn Turn { get; set; }
/// <summary>
/// 도킹 방향
/// </summary>
@@ -173,6 +189,11 @@ namespace AGVNavigationCore.Models
/// </summary>
public int DetectedRfidCount => _detectedRfids.Count;
/// <summary>
/// 배터리 부족 경고 임계값 (%)
/// </summary>
public float LowBatteryThreshold { get; set; } = 20.0f;
#endregion
#region Constructor
@@ -209,6 +230,8 @@ namespace AGVNavigationCore.Models
_currentPosition = position;
}
/// <summary>
/// 감지된 RFID 설정 (실제 RFID 센서에서)
/// </summary>
@@ -245,12 +268,26 @@ namespace AGVNavigationCore.Models
BatteryLevel = Math.Max(0, Math.Min(100, percentage));
// 배터리 부족 경고
if (BatteryLevel < 20.0f && _currentState != AGVState.Charging)
if (BatteryLevel < LowBatteryThreshold && _currentState != AGVState.Charging)
{
OnError($"배터리 부족: {BatteryLevel:F1}%");
OnError($"배터리 부족: {BatteryLevel:F1}% (기준: {LowBatteryThreshold}%)");
}
}
/// <summary>
/// 현재 노드id의 개체를 IsPass 로 설정합니다
/// </summary>
public bool SetCurrentNodeMarkStop()
{
if (_currentNode == null) return false;
if (_currentPath == null) return false;
var = _currentPath.DetailedPath.Where(t => t.IsPass == false).OrderBy(t => t.seq).FirstOrDefault();
if ( == null) return false;
.IsPass = true;
Console.WriteLine($"미완료된처음노드를 true러치합니다");
return true;
}
/// <summary>
/// 다음 동작 예측 (실제 AGV 제어용)
/// AGV가 지속적으로 호출하여 현재 상태와 예측 상태를 일치시킴
@@ -265,7 +302,8 @@ namespace AGVNavigationCore.Models
return new AGVCommand(
MotorCommand.Forward,
MagnetPosition.S, // 직진
SpeedLevel.L, // 저속
SpeedLevel.L, // 저속
eAGVCommandReason.UnknownPosition,
$"위치 미확정 (RFID {_detectedRfids.Count}/2) - 전진하여 RFID 탐색"
);
}
@@ -277,7 +315,8 @@ namespace AGVNavigationCore.Models
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.L,
$"위치 확정 완료 (목적지 미설정) - 현재:{_currentNode?.NodeId ?? ""}"
eAGVCommandReason.NoPath,
$"위치 확정 완료 (목적지 미설정) - 현재:{_currentNode?.Id ?? ""}"
);
}
@@ -285,136 +324,50 @@ namespace AGVNavigationCore.Models
var lastNode = _currentPath.DetailedPath.Last();
if (_currentPath.DetailedPath.Where(t => t.seq < lastNode.seq && t.IsPass == false).Any() == false)
{
return new AGVCommand(
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.L,
$"목적지 도착 - 최종:{_currentNode?.NodeId ?? ""}"
);
// 마지막 노드에 도착했는지 확인 (현재 노드가 마지막 노드와 같은지)
if (_currentNode != null && _currentNode.Id == lastNode.NodeId)
{
if (lastNode.IsPass) //이미완료되었다.
{
return new AGVCommand(
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.L,
eAGVCommandReason.Complete,
$"목적지 도착 - 최종:{_currentNode?.Id ?? ""}"
);
}
else
{
//마지막노드는 일혔지만 완료되지 않았다. 마크스탑필요
return new AGVCommand(
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.L,
eAGVCommandReason.MarkStop,
$"목적지 도착 전(MarkStop) - 최종:{_currentNode?.Id ?? ""}"
);
}
}
}
// 4. 경로이탈
var TargetNode = _currentPath.DetailedPath.Where(t => t.IsPass == false && t.NodeId.Equals(_currentNode.NodeId)).FirstOrDefault();
var TargetNode = _currentPath.DetailedPath.Where(t => t.IsPass == false && t.NodeId.Equals(_currentNode.Id)).FirstOrDefault();
if (TargetNode == null)
{
return new AGVCommand(
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.L,
$"(재탐색요청)경로이탈 현재위치:{_currentNode.NodeId}"
eAGVCommandReason.PathOut,
$"(재탐색요청)경로이탈 현재위치:{_currentNode.Id}"
);
}
// 5. 방향체크
//if(CurrentDirection != TargetNode.MotorDirection)
//{
// return new AGVCommand(
// MotorCommand.Stop,
// MagnetPosition.S,
// SpeedLevel.L,
// $"(재탐색요청)모터방향 불일치 현재위치:{_currentNode.NodeId}"
// );
//}
return GetCommandFromPath(CurrentNode, "경로 실행 시작");
//this.CurrentNodeId
return GetCommandFromPath(CurrentNodeId, "경로 실행 시작");
// 4. 위치 확정 + 경로 실행 중 → 현재 상태에 따른 명령 예측
switch (_currentState)
{
case AGVState.Idle:
// 🔥 경로가 있다면 이동 시작 (경로 실행 대기 중)
if (_currentPath != null && _remainingNodes != null && _remainingNodes.Count > 0)
{
// DetailedPath에서 다음 노드 정보 가져오기
return GetCommandFromPath(_remainingNodes[0], "경로 실행 시작");
}
// 경로가 없으면 대기
return new AGVCommand(
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.L,
"대기 중 (경로 없음)"
);
case AGVState.Moving:
{
// 이동 중 - DetailedPath에서 현재/다음 노드 정보 가져오기
if (_currentPath != null && _remainingNodes != null && _remainingNodes.Count > 0)
{
return GetCommandFromPath(_remainingNodes[0], "이동 중");
}
// 경로 정보가 없으면 기본 명령 (fallback)
var motorCmd = _currentDirection == AgvDirection.Forward
? MotorCommand.Forward
: MotorCommand.Backward;
return new AGVCommand(
motorCmd,
MagnetPosition.S,
SpeedLevel.M,
$"이동 중 (DetailedPath 없음)"
);
}
case AGVState.Rotating:
// 회전 중 - 정지 상태에서 마그넷만 조정
MagnetPosition magnetPos = MagnetPosition.S;
if (_currentDirection == AgvDirection.Left)
magnetPos = MagnetPosition.L;
else if (_currentDirection == AgvDirection.Right)
magnetPos = MagnetPosition.R;
return new AGVCommand(
MotorCommand.Stop, // 회전은 정지 상태에서
magnetPos,
SpeedLevel.L,
$"회전 중 ({_currentDirection})"
);
case AGVState.Docking:
{
// 도킹 중 - 저속으로 전진 또는 후진
var dockingMotor = _dockingDirection == DockingDirection.Forward
? MotorCommand.Forward
: MotorCommand.Backward;
return new AGVCommand(
dockingMotor,
MagnetPosition.S,
SpeedLevel.L, // 도킹은 항상 저속
$"도킹 중 ({_dockingDirection})"
);
}
case AGVState.Charging:
return new AGVCommand(
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.L,
"충전 중"
);
case AGVState.Error:
return new AGVCommand(
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.L,
"오류 발생"
);
default:
return new AGVCommand(
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.L,
"알 수 없는 상태"
);
}
}
#endregion
@@ -434,7 +387,7 @@ namespace AGVNavigationCore.Models
/// <summary>
/// 현재 노드 ID 조회
/// </summary>
public string GetCurrentNodeId() => _currentNode?.NodeId;
public MapNode GetCurrentNode() => _currentNode;
/// <summary>
/// AGV 정보 조회
@@ -463,13 +416,13 @@ namespace AGVNavigationCore.Models
}
_currentPath = path;
_remainingNodes = path.Path.Select(n => n.NodeId).ToList(); // MapNode → NodeId 변환
_remainingNodes = path.Path.Select(n => n.Id).ToList(); // MapNode → NodeId 변환
_currentNodeIndex = 0;
// 경로 시작 노드가 현재 노드와 다른 경우 경고
if (_currentNode != null && _remainingNodes.Count > 0 && _remainingNodes[0] != _currentNode.NodeId)
if (_currentNode != null && _remainingNodes.Count > 0 && _remainingNodes[0] != _currentNode.Id)
{
OnError($"경로 시작 노드({_remainingNodes[0]})와 현재 노드({_currentNode.NodeId})가 다릅니다.");
OnError($"경로 시작 노드({_remainingNodes[0]})와 현재 노드({_currentNode.Id})가 다릅니다.");
}
}
@@ -494,6 +447,27 @@ namespace AGVNavigationCore.Models
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
#region Public Methods - ( )
@@ -527,10 +501,10 @@ namespace AGVNavigationCore.Models
_prevPosition = targetPosition;
_moveStartPosition = _currentPosition;
_moveTargetPosition = targetPosition;
_moveProgress = 0;
SetState(AGVState.Moving);
_isMoving = true;
Turn = AGVTurn.None;
}
/// <summary>
@@ -583,7 +557,7 @@ namespace AGVNavigationCore.Models
public void SetPosition(MapNode node, AgvDirection motorDirection)
{
// 현재 위치를 이전 위치로 저장 (리프트 방향 계산용)
if (_currentNode != null && _currentNode.NodeId != node.NodeId)
if (_currentNode != null && _currentNode.Id != node.Id)
{
_prevPosition = _currentPosition; // 이전 위치
_prevNode = _currentNode;
@@ -602,9 +576,9 @@ namespace AGVNavigationCore.Models
_currentNode = node;
// 🔥 노드 ID를 RFID로 간주하여 감지 목록에 추가 (시뮬레이터용)
if (!string.IsNullOrEmpty(node.NodeId) && !_detectedRfids.Contains(node.NodeId))
if (!string.IsNullOrEmpty(node.Id) && !_detectedRfids.Contains(node.Id))
{
_detectedRfids.Add(node.NodeId);
_detectedRfids.Add(node.Id);
}
// 🔥 RFID 2개 이상 감지 시 위치 확정
@@ -616,11 +590,19 @@ namespace AGVNavigationCore.Models
//현재 경로값이 있는지 확인한다.
if (CurrentPath != null && CurrentPath.DetailedPath != null && CurrentPath.DetailedPath.Any())
{
var item = CurrentPath.DetailedPath.FirstOrDefault(t => t.NodeId == node.NodeId && t.IsPass == false);
var item = CurrentPath.DetailedPath.FirstOrDefault(t => t.NodeId == node.Id && t.IsPass == false);
if (item != null)
{
//item.IsPass = true;
// [PathJump Check] 점프한 노드 개수 확인
// 현재 노드(item)보다 이전인데 아직 IsPass가 안 된 노드의 개수
int skippedCount = CurrentPath.DetailedPath.Count(t => t.seq < item.seq && t.IsPass == false);
if (skippedCount > 2)
{
OnError($"PathJump: {skippedCount}개의 노드를 건너뛰었습니다. (허용: 2개, 현재노드: {node.Id})");
return;
}
//item.IsPass = true;
//이전노드는 모두 지나친걸로 한다
CurrentPath.DetailedPath.Where(t => t.seq < item.seq).ToList().ForEach(t => t.IsPass = true);
}
@@ -630,45 +612,46 @@ namespace AGVNavigationCore.Models
PositionChanged?.Invoke(this, (_currentPosition, _currentDirection, _currentNode));
}
/// <summary>
/// 현재 RFID 시뮬레이션 (현재 위치 기준)
/// </summary>
public string SimulateRfidReading(List<MapNode> mapNodes)
{
var closestNode = FindClosestNode(_currentPosition, mapNodes);
if (closestNode == null)
return null;
#endregion
return closestNode.HasRfid() ? closestNode.RfidId : null;
/// <summary>
/// 노드 ID를 RFID 값으로 변환 (NodeResolver 사용)
/// </summary>
public ushort GetRfidByNodeId(List<MapNode> _mapNodes, string nodeId)
{
var node = _mapNodes?.FirstOrDefault(n => n.Id == nodeId);
if ((node?.HasRfid() ?? false) == false) return 0;
return node.RfidId;
}
#endregion
#region Private Methods
/// <summary>
/// DetailedPath에서 노드 정보를 찾아 AGVCommand 생성
/// </summary>
private AGVCommand GetCommandFromPath(string targetNodeId, string actionDescription)
private AGVCommand GetCommandFromPath(MapNode targetNode, string actionDescription)
{
// DetailedPath가 없으면 기본 명령 반환
if (_currentPath == null || _currentPath.DetailedPath == null || _currentPath.DetailedPath.Count == 0)
{
var defaultMotor = _currentDirection == AgvDirection.Forward
? MotorCommand.Forward
: MotorCommand.Backward;
// [Refactor] Predict와 일관성 유지: 경로가 없으면 정지
return new AGVCommand(
defaultMotor,
MotorCommand.Stop,
MagnetPosition.S,
SpeedLevel.M,
SpeedLevel.L,
eAGVCommandReason.NoPath,
$"{actionDescription} (DetailedPath 없음)"
);
}
// DetailedPath에서 targetNodeId에 해당하는 NodeMotorInfo 찾기
// 지나가지 않은 경로를 찾는다
var nodeInfo = _currentPath.DetailedPath.FirstOrDefault(n => n.NodeId == targetNodeId && n.IsPass == false);
var nodeInfo = _currentPath.DetailedPath.FirstOrDefault(n => n.NodeId == targetNode.Id && n.IsPass == false);
if (nodeInfo == null)
{
@@ -681,7 +664,8 @@ namespace AGVNavigationCore.Models
defaultMotor,
MagnetPosition.S,
SpeedLevel.M,
$"{actionDescription} (노드 {targetNodeId} 정보 없음)"
eAGVCommandReason.NoTarget,
$"{actionDescription} (노드 {targetNode.Id} 정보 없음)"
);
}
@@ -716,16 +700,20 @@ namespace AGVNavigationCore.Models
break;
}
// 속도 결정 (회전 노드면 저속, 일반 이동은 중속)
SpeedLevel speed = nodeInfo.CanRotate || nodeInfo.IsDirectionChangePoint
? SpeedLevel.L
: SpeedLevel.M;
// [Speed Control] NodeMotorInfo에 설정된 속도 사용
// 단, 회전 구간 등에서 안전을 위해 강제 감속이 필요한 경우 로직 추가 가능
// 현재는 사용자 설정 우선
SpeedLevel speed = nodeInfo.Speed;
// Optional: 회전 시 강제 감속 로직 (사용자 요청에 따라 주석 처리 또는 제거 가능)
// if (nodeInfo.CanRotate || nodeInfo.IsDirectionChangePoint) speed = SpeedLevel.L;
return new AGVCommand(
motorCmd,
magnetPos,
speed,
$"{actionDescription} → {targetNodeId} (Motor:{motorCmd}, Magnet:{magnetPos})"
eAGVCommandReason.Normal,
$"{actionDescription} → {targetNode.Id} (Motor:{motorCmd}, Magnet:{magnetPos})"
);
}
@@ -796,6 +784,29 @@ namespace AGVNavigationCore.Models
BatteryLevel = Math.Max(0, BatteryLevel);
}
public MapNode StartNode { get; set; } = null;
private MapNode _targetnode = null;
/// <summary>
/// 목적지를 설정합니다. 목적지가 변경되면 경로계산정보가 삭제 됩니다.
/// </summary>
public MapNode TargetNode
{
get
{
return _targetnode;
}
set
{
if (_targetnode != value)
{
_currentPath = null;
_targetnode = value;
}
}
}
private void ProcessNextNode()
{
if (_remainingNodes == null || _currentNodeIndex >= _remainingNodes.Count - 1)
@@ -859,18 +870,6 @@ namespace AGVNavigationCore.Models
}
}
private DockingDirection GetDockingDirection(NodeType nodeType)
{
switch (nodeType)
{
case NodeType.Charging:
return DockingDirection.Forward;
case NodeType.Docking:
return DockingDirection.Backward;
default:
return DockingDirection.Forward;
}
}
private void OnError(string message)
{
@@ -880,8 +879,6 @@ namespace AGVNavigationCore.Models
#endregion
#region Cleanup
/// <summary>

View File

@@ -59,7 +59,7 @@ namespace AGVNavigationCore.PathFinding.Analysis
if (node.IsNavigationNode())
{
var junctionInfo = AnalyzeNode(node);
_junctions[node.NodeId] = junctionInfo;
_junctions[node.Id] = junctionInfo;
}
}
}
@@ -69,7 +69,7 @@ namespace AGVNavigationCore.PathFinding.Analysis
/// </summary>
private JunctionInfo AnalyzeNode(MapNode node)
{
var junction = new JunctionInfo(node.NodeId);
var junction = new JunctionInfo(node.Id);
// 양방향 연결을 고려하여 모든 연결된 노드 찾기
var connectedNodes = GetAllConnectedNodes(node);
@@ -96,16 +96,16 @@ namespace AGVNavigationCore.PathFinding.Analysis
{
if (connectedNode != null)
{
connected.Add(connectedNode.NodeId);
connected.Add(connectedNode.Id);
}
}
// 역방향 연결된 노드들 (다른 노드에서 이 노드로 연결)
foreach (var otherNode in _mapNodes)
{
if (otherNode.NodeId != node.NodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == node.NodeId))
if (otherNode.Id != node.Id && otherNode.ConnectedMapNodes.Any(n => n.Id == node.Id))
{
connected.Add(otherNode.NodeId);
connected.Add(otherNode.Id);
}
}
@@ -124,7 +124,7 @@ namespace AGVNavigationCore.PathFinding.Analysis
foreach (var connectedId in connectedNodes)
{
var connectedNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectedId);
var connectedNode = _mapNodes.FirstOrDefault(n => n.Id == connectedId);
if (connectedNode != null)
{
double angle = CalculateAngle(junctionNode.Position, connectedNode.Position);
@@ -226,9 +226,9 @@ namespace AGVNavigationCore.PathFinding.Analysis
return MagnetDirection.Straight;
// 실제 각도 기반으로 마그넷 방향 계산
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == fromNodeId);
var currentNode = _mapNodes.FirstOrDefault(n => n.NodeId == currentNodeId);
var toNode = _mapNodes.FirstOrDefault(n => n.NodeId == toNodeId);
var fromNode = _mapNodes.FirstOrDefault(n => n.Id == fromNodeId);
var currentNode = _mapNodes.FirstOrDefault(n => n.Id == currentNodeId);
var toNode = _mapNodes.FirstOrDefault(n => n.Id == toNodeId);
if (fromNode == null || currentNode == null || toNode == null)
return MagnetDirection.Straight;

View File

@@ -320,7 +320,7 @@ namespace AGVNavigationCore.PathFinding.Core
{
return DetailedPath.Select(n => n.NodeId).ToList();
}
return Path?.Select(n => n.NodeId).ToList() ?? new List<string>();
return Path?.Select(n => n.Id).ToList() ?? new List<string>();
}
/// <summary>

View File

@@ -50,36 +50,36 @@ namespace AGVNavigationCore.PathFinding.Core
// 모든 네비게이션 노드를 PathNode로 변환하고 양방향 연결 생성
foreach (var mapNode in _mapNodes)
{
_mapNodeLookup[mapNode.NodeId] = mapNode; // Add to lookup table
_mapNodeLookup[mapNode.Id] = mapNode; // Add to lookup table
if (mapNode.IsNavigationNode())
{
var pathNode = new PathNode(mapNode.NodeId, mapNode.Position);
_nodeMap[mapNode.NodeId] = pathNode;
var pathNode = new PathNode(mapNode.Id, mapNode.Position);
_nodeMap[mapNode.Id] = pathNode;
}
}
// 단일 연결을 양방향으로 확장
foreach (var mapNode in _mapNodes)
{
if (mapNode.IsNavigationNode() && _nodeMap.ContainsKey(mapNode.NodeId))
if (mapNode.IsNavigationNode() && _nodeMap.ContainsKey(mapNode.Id))
{
var pathNode = _nodeMap[mapNode.NodeId];
var pathNode = _nodeMap[mapNode.Id];
foreach (var connectedNode in mapNode.ConnectedMapNodes)
{
if (connectedNode != null && _nodeMap.ContainsKey(connectedNode.NodeId))
if (connectedNode != null && _nodeMap.ContainsKey(connectedNode.Id))
{
// 양방향 연결 생성 (단일 연결이 양방향을 의미)
if (!pathNode.ConnectedNodes.Contains(connectedNode.NodeId))
if (!pathNode.ConnectedNodes.Contains(connectedNode.Id))
{
pathNode.ConnectedNodes.Add(connectedNode.NodeId);
pathNode.ConnectedNodes.Add(connectedNode.Id);
}
var connectedPathNode = _nodeMap[connectedNode.NodeId];
if (!connectedPathNode.ConnectedNodes.Contains(mapNode.NodeId))
var connectedPathNode = _nodeMap[connectedNode.Id];
if (!connectedPathNode.ConnectedNodes.Contains(mapNode.Id))
{
connectedPathNode.ConnectedNodes.Add(mapNode.NodeId);
connectedPathNode.ConnectedNodes.Add(mapNode.Id);
}
}
}
@@ -101,7 +101,7 @@ namespace AGVNavigationCore.PathFinding.Core
/// <param name="startNodeId">시작 노드 ID</param>
/// <param name="endNodeId">목적지 노드 ID</param>
/// <returns>경로 계산 결과</returns>
public AGVPathResult FindPath(string startNodeId, string endNodeId)
public AGVPathResult FindPathAStar(string startNodeId, string endNodeId)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
@@ -126,7 +126,6 @@ namespace AGVNavigationCore.PathFinding.Core
var startNode = _nodeMap[startNodeId];
var endNode = _nodeMap[endNodeId];
var openSet = new List<PathNode>();
var closedSet = new HashSet<string>();
var exploredCount = 0;
@@ -199,7 +198,7 @@ namespace AGVNavigationCore.PathFinding.Core
// 경유지가 없으면 기본 FindPath 호출
if (waypointNodeIds == null || waypointNodeIds.Length == 0)
{
return FindPath(startNodeId, endNodeId);
return FindPathAStar(startNodeId, endNodeId);
}
// 경유지 유효성 검증
@@ -220,7 +219,7 @@ namespace AGVNavigationCore.PathFinding.Core
// 경유지가 없으면 기본 경로 계산
if (validWaypoints.Count == 0)
{
return FindPath(startNodeId, endNodeId);
return FindPathAStar(startNodeId, endNodeId);
}
// 첫 번째 경유지가 시작노드와 같은지 검사
@@ -267,7 +266,7 @@ namespace AGVNavigationCore.PathFinding.Core
string waypoint = validWaypoints[i];
// 현재 위치에서 경유지까지의 경로 계산
var segmentResult = FindPath(currentStart, waypoint);
var segmentResult = FindPathAStar(currentStart, waypoint);
if (!segmentResult.Success)
{
@@ -295,7 +294,7 @@ namespace AGVNavigationCore.PathFinding.Core
}
// 2단계: 마지막 경유지에서 최종 목적지까지의 경로 계산
var finalSegmentResult = FindPath(currentStart, endNodeId);
var finalSegmentResult = FindPathAStar(currentStart, endNodeId);
if (!finalSegmentResult.Success)
{
@@ -372,8 +371,8 @@ namespace AGVNavigationCore.PathFinding.Core
var combinedDetailedPath = new List<NodeMotorInfo>(previousResult.DetailedPath ?? new List<NodeMotorInfo>());
// 이전 경로의 마지막 노드와 현재 경로의 시작 노드 비교
string lastNodeOfPrevious = previousResult.Path[previousResult.Path.Count - 1].NodeId;
string firstNodeOfCurrent = currentResult.Path[0].NodeId;
string lastNodeOfPrevious = previousResult.Path[previousResult.Path.Count - 1].Id;
string firstNodeOfCurrent = currentResult.Path[0].Id;
if (lastNodeOfPrevious == firstNodeOfCurrent)
{
@@ -442,7 +441,7 @@ namespace AGVNavigationCore.PathFinding.Core
AGVPathResult bestResult = null;
foreach (var targetId in targetNodeIds)
{
var result = FindPath(startNodeId, targetId);
var result = FindPathAStar(startNodeId, targetId);
if (result.Success && (bestResult == null || result.TotalDistance < bestResult.TotalDistance))
{
bestResult = result;
@@ -509,8 +508,8 @@ namespace AGVNavigationCore.PathFinding.Core
float totalDistance = 0;
for (int i = 0; i < path.Count - 1; i++)
{
var nodeId1 = path[i].NodeId;
var nodeId2 = path[i + 1].NodeId;
var nodeId1 = path[i].Id;
var nodeId2 = path[i + 1].Id;
if (_nodeMap.ContainsKey(nodeId1) && _nodeMap.ContainsKey(nodeId2))
{
@@ -578,7 +577,7 @@ namespace AGVNavigationCore.PathFinding.Core
return null;
// 교차로 노드 찾기
var junctionNode = _mapNodes.FirstOrDefault(n => n.NodeId == junctionNodeId);
var junctionNode = _mapNodes.FirstOrDefault(n => n.Id == junctionNodeId);
if (junctionNode == null || junctionNode.ConnectedNodes == null || junctionNode.ConnectedNodes.Count == 0)
return null;
@@ -603,7 +602,7 @@ namespace AGVNavigationCore.PathFinding.Core
if (connectedNodeId == targetNodeId) continue;
// 조건 3, 4, 5: 존재하고, 활성 상태이고, 네비게이션 가능
var connectedNode = _mapNodes.FirstOrDefault(n => n.NodeId == connectedNodeId);
var connectedNode = _mapNodes.FirstOrDefault(n => n.Id == connectedNodeId);
if (connectedNode != null && connectedNode.IsActive && connectedNode.IsNavigationNode())
{
alternateNodes.Add(connectedNode);

View File

@@ -22,8 +22,6 @@ namespace AGVNavigationCore.PathFinding.Planning
private readonly JunctionAnalyzer _junctionAnalyzer;
private readonly DirectionChangePlanner _directionChangePlanner;
public AGVPathfinder(List<MapNode> mapNodes)
{
_mapNodes = mapNodes ?? new List<MapNode>();
@@ -49,9 +47,10 @@ namespace AGVNavigationCore.PathFinding.Planning
n.IsActive &&
n.IsNavigationNode() &&
n.ConnectedNodes != null &&
n.DisableCross == false &&
n.ConnectedNodes.Count >= 3 &&
n.ConnectedMapNodes.Where(t => t.Type == NodeType.Docking || t.Type == NodeType.Charging).Any() == false &&
n.NodeId != startNode.NodeId
n.ConnectedMapNodes.Where(t => t.CanDocking).Any() == false &&
n.Id != startNode.Id
).ToList();
// docking 포인트가 연결된 노드는 제거한다.
@@ -99,11 +98,12 @@ namespace AGVNavigationCore.PathFinding.Planning
if (pathNode != null &&
pathNode.IsActive &&
pathNode.IsNavigationNode() &&
pathNode.DisableCross == false &&
pathNode.ConnectedNodes != null &&
pathNode.ConnectedNodes.Count >= 3 &&
pathNode.ConnectedMapNodes.Where(t => t.Type == NodeType.Docking || t.Type == NodeType.Charging).Any() == false)
pathNode.ConnectedMapNodes.Where(t => t.CanDocking).Any() == false)
{
if (pathNode.NodeId.Equals(StartNode.NodeId) == false)
if (pathNode.Id.Equals(StartNode.Id) == false)
return pathNode;
}
}
@@ -111,6 +111,12 @@ namespace AGVNavigationCore.PathFinding.Planning
return null;
}
public AGVPathResult FindPath(MapNode startNode, MapNode targetNode)
{
// 기본값으로 경로 탐색 (이전 위치 = 현재 위치, 방향 = 전진)
return FindPath(startNode, targetNode, startNode, AgvDirection.Forward, AgvDirection.Forward, false);
}
public AGVPathResult FindPath(MapNode startNode, MapNode targetNode,
MapNode prevNode, AgvDirection prevDirection, AgvDirection currentDirection, bool crossignore = false)
{
@@ -121,13 +127,17 @@ namespace AGVNavigationCore.PathFinding.Planning
return AGVPathResult.CreateFailure("목적지 노드가 null입니다.", 0, 0);
if (prevNode == null)
return AGVPathResult.CreateFailure("이전위치 노드가 null입니다.", 0, 0);
if (startNode.NodeId == targetNode.NodeId && targetNode.DockDirection.MatchAGVDirection(prevDirection))
return AGVPathResult.CreateSuccess(new List<MapNode> { startNode,startNode }, new List<AgvDirection>(), 0, 0);
if (targetNode.isDockingNode == false && targetNode.Type != NodeType.Normal)
return AGVPathResult.CreateFailure("이동 가능한 노드가 아닙니다", 0, 0);
var tnode = targetNode as MapNode;
if (startNode.Id == targetNode.Id && tnode.DockDirection.MatchAGVDirection(prevDirection))
return AGVPathResult.CreateSuccess(new List<MapNode> { startNode, startNode }, new List<AgvDirection>(), 0, 0);
var ReverseDirection = (currentDirection == AgvDirection.Forward ? AgvDirection.Backward : AgvDirection.Forward);
//1.목적지까지의 최단거리 경로를 찾는다.
var pathResult = _basicPathfinder.FindPath(startNode.NodeId, targetNode.NodeId);
var pathResult = _basicPathfinder.FindPathAStar(startNode.Id, targetNode.Id);
pathResult.PrevNode = prevNode;
pathResult.PrevDirection = prevDirection;
if (!pathResult.Success || pathResult.Path == null || pathResult.Path.Count == 0)
@@ -146,11 +156,11 @@ namespace AGVNavigationCore.PathFinding.Planning
//2.AGV방향과 목적지에 설정된 방향이 일치하면 그대로 진행하면된다.(목적지에 방향이 없는 경우에도 그대로 진행)
if (targetNode.DockDirection == DockingDirection.DontCare ||
(targetNode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward) ||
(targetNode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward))
if (tnode.DockDirection == DockingDirection.DontCare ||
(tnode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward) ||
(tnode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward))
{
if ((nextNodeForward?.NodeId ?? string.Empty) == pathResult.Path[1].NodeId) //예측경로와 다음진행방향 경로가 일치하면 해당 방향이 맞다
if ((nextNodeForward?.Id ?? string.Empty) == pathResult.Path[1].Id) //예측경로와 다음진행방향 경로가 일치하면 해당 방향이 맞다
{
MakeDetailData(pathResult, currentDirection);
MakeMagnetDirection(pathResult);
@@ -177,10 +187,10 @@ namespace AGVNavigationCore.PathFinding.Planning
//뒤로 이동시 경로상의 처음 만나는 노드가 같다면 그 방향으로 이동하면 된다.
// ⚠️ 단, 현재 방향과 목적지 도킹 방향이 일치해야 함!
if (nextNodeBackward != null && pathResult.Path.Count > 1 &&
nextNodeBackward.NodeId == pathResult.Path[1].NodeId) // ✅ 추가: 현재도 Backward여야 함
nextNodeBackward.Id == pathResult.Path[1].Id) // ✅ 추가: 현재도 Backward여야 함
{
if (targetNode.DockDirection == DockingDirection.Forward && ReverseDirection == AgvDirection.Forward ||
targetNode.DockDirection == DockingDirection.Backward && ReverseDirection == AgvDirection.Backward)
if (tnode.DockDirection == DockingDirection.Forward && ReverseDirection == AgvDirection.Forward ||
tnode.DockDirection == DockingDirection.Backward && ReverseDirection == AgvDirection.Backward)
{
MakeDetailData(pathResult, ReverseDirection);
MakeMagnetDirection(pathResult);
@@ -191,12 +201,12 @@ namespace AGVNavigationCore.PathFinding.Planning
}
if (nextNodeForward != null && pathResult.Path.Count > 1 &&
nextNodeForward.NodeId == pathResult.Path[1].NodeId &&
targetNode.DockDirection == DockingDirection.Forward &&
nextNodeForward.Id == pathResult.Path[1].Id &&
tnode.DockDirection == DockingDirection.Forward &&
currentDirection == AgvDirection.Forward) // ✅ 추가: 현재도 Forward여야 함
{
if (targetNode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward ||
targetNode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward)
if (tnode.DockDirection == DockingDirection.Forward && currentDirection == AgvDirection.Forward ||
tnode.DockDirection == DockingDirection.Backward && currentDirection == AgvDirection.Backward)
{
MakeDetailData(pathResult, currentDirection);
MakeMagnetDirection(pathResult);
@@ -221,7 +231,7 @@ namespace AGVNavigationCore.PathFinding.Planning
//진행방향으로 이동했을때 나오는 노드를 사용한다.
if (nextNodeForward != null)
{
var Path0 = _basicPathfinder.FindPath(startNode.NodeId, nextNodeForward.NodeId);
var Path0 = _basicPathfinder.FindPathAStar(startNode.Id, nextNodeForward.Id);
Path0.PrevNode = prevNode;
Path0.PrevDirection = prevDirection;
MakeDetailData(Path0, prevDirection);
@@ -259,7 +269,7 @@ namespace AGVNavigationCore.PathFinding.Planning
JunctionInPath = FindNearestJunction(startNode);
//종료노드로부터 가까운 교차로 검색
if (JunctionInPath == null) JunctionInPath = FindNearestJunction(targetNode);
if (JunctionInPath == null) JunctionInPath = FindNearestJunction(tnode);
}
if (JunctionInPath == null)
return AGVPathResult.CreateFailure("교차로가 없어 경로계산을 할 수 없습니다", 0, 0);
@@ -267,7 +277,7 @@ namespace AGVNavigationCore.PathFinding.Planning
//경유지를 포함하여 경로를 다시 계산한다.
//1.시작위치 - 교차로(여기까지는 현재 방향으로 그대로 이동을 한다)
var path1 = _basicPathfinder.FindPath(startNode.NodeId, JunctionInPath.NodeId);
var path1 = _basicPathfinder.FindPathAStar(startNode.Id, JunctionInPath.Id);
path1.PrevNode = prevNode;
path1.PrevDirection = prevDirection;
@@ -278,7 +288,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// ReverseCheck = false; //현재 진행 방향으로 이동해야한다
// MakeDetailData(path1, currentDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
//}
if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.NodeId.Equals(path1.Path[1].NodeId))
if (path1.Path.Count > 1 && nextNodeBackward != null && nextNodeBackward.Id.Equals(path1.Path[1].Id))
{
ReverseCheck = true; //현재 방향의 반대방향으로 이동해야한다
MakeDetailData(path1, ReverseDirection); // path1의 상세 경로 정보 채우기 (모터 방향 설정)
@@ -295,7 +305,7 @@ namespace AGVNavigationCore.PathFinding.Planning
//2.교차로 - 종료위치
var path2 = _basicPathfinder.FindPath(JunctionInPath.NodeId, targetNode.NodeId);
var path2 = _basicPathfinder.FindPathAStar(JunctionInPath.Id, targetNode.Id);
path2.PrevNode = prevNode;
path2.PrevDirection = prevDirection;
@@ -315,9 +325,9 @@ namespace AGVNavigationCore.PathFinding.Planning
//3.방향전환을 위환 대체 노드찾기
tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
path1.Path[path1.Path.Count - 2].NodeId,
path2.Path[1].NodeId);
tempNode = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.Id,
path1.Path[path1.Path.Count - 2].Id,
path2.Path[1].Id);
//4. path1 + tempnode + path2 가 최종 위치가 된다.
if (tempNode == null)
@@ -332,7 +342,7 @@ namespace AGVNavigationCore.PathFinding.Planning
//if (tempNode != null)
{
// 교차로 → 대체노드 경로 계산
var pathToTemp = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode.NodeId);
var pathToTemp = _basicPathfinder.FindPathAStar(JunctionInPath.Id, tempNode.Id);
pathToTemp.PrevNode = JunctionInPath;
pathToTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
if (!pathToTemp.Success)
@@ -348,7 +358,7 @@ namespace AGVNavigationCore.PathFinding.Planning
combinedResult = _basicPathfinder.CombineResults(combinedResult, pathToTemp);
// 대체노드 → 교차로 경로 계산 (역방향)
var pathFromTemp = _basicPathfinder.FindPath(tempNode.NodeId, JunctionInPath.NodeId);
var pathFromTemp = _basicPathfinder.FindPathAStar(tempNode.Id, JunctionInPath.Id);
pathFromTemp.PrevNode = JunctionInPath;
pathFromTemp.PrevDirection = (ReverseCheck ? ReverseDirection : currentDirection);
if (!pathFromTemp.Success)
@@ -362,14 +372,14 @@ namespace AGVNavigationCore.PathFinding.Planning
//현재까지 노드에서 목적지까지의 방향이 일치하면 그대로 사용한다.
bool temp3ok = false;
var TempCheck3 = _basicPathfinder.FindPath(combinedResult.Path.Last().NodeId, targetNode.NodeId);
if (TempCheck3.Path.First().NodeId.Equals(combinedResult.Path.Last().NodeId))
var TempCheck3 = _basicPathfinder.FindPathAStar(combinedResult.Path.Last().Id, targetNode.Id);
if (TempCheck3.Path.First().Id.Equals(combinedResult.Path.Last().Id))
{
if (targetNode.DockDirection == DockingDirection.Forward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Forward)
if (tnode.DockDirection == DockingDirection.Forward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Forward)
{
temp3ok = true;
}
else if (targetNode.DockDirection == DockingDirection.Backward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Backward)
else if (tnode.DockDirection == DockingDirection.Backward && combinedResult.DetailedPath.Last().MotorDirection == AgvDirection.Backward)
{
temp3ok = true;
}
@@ -381,11 +391,11 @@ namespace AGVNavigationCore.PathFinding.Planning
if (temp3ok == false)
{
//목적지와 방향이 맞지 않다. 그러므로 대체노드를 추가로 더 찾아야한다.
var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.NodeId,
combinedResult.Path[combinedResult.Path.Count - 2].NodeId,
path2.Path[1].NodeId);
var tempNode2 = _basicPathfinder.FindAlternateNodeForDirectionChange(JunctionInPath.Id,
combinedResult.Path[combinedResult.Path.Count - 2].Id,
path2.Path[1].Id);
var pathToTemp2 = _basicPathfinder.FindPath(JunctionInPath.NodeId, tempNode2.NodeId);
var pathToTemp2 = _basicPathfinder.FindPathAStar(JunctionInPath.Id, tempNode2.Id);
if (ReverseCheck) MakeDetailData(pathToTemp2, currentDirection);
else MakeDetailData(pathToTemp2, ReverseDirection);
@@ -400,7 +410,7 @@ namespace AGVNavigationCore.PathFinding.Planning
combinedResult.DetailedPath[combinedResult.DetailedPath.Count - 1].MotorDirection = currentDirection;
}
var pathToTemp3 = _basicPathfinder.FindPath(tempNode2.NodeId, JunctionInPath.NodeId);
var pathToTemp3 = _basicPathfinder.FindPathAStar(tempNode2.Id, JunctionInPath.Id);
if (ReverseCheck) MakeDetailData(pathToTemp3, ReverseDirection);
else MakeDetailData(pathToTemp3, currentDirection);
@@ -420,12 +430,15 @@ namespace AGVNavigationCore.PathFinding.Planning
}
/// <summary>
/// 이 작업후에 MakeMagnetDirection 를 추가로 실행 하세요
/// </summary>
/// <summary>
/// 이 작업후에 MakeMagnetDirection 를 추가로 실행 하세요
/// </summary>
/// <param name="path1"></param>
/// <param name="currentDirection"></param>
private void MakeDetailData(AGVPathResult path1, AgvDirection currentDirection)
public void MakeDetailData(AGVPathResult path1, AgvDirection currentDirection)
{
if (path1.Success && path1.Path != null && path1.Path.Count > 0)
{
@@ -433,18 +446,25 @@ namespace AGVNavigationCore.PathFinding.Planning
for (int i = 0; i < path1.Path.Count; i++)
{
var node = path1.Path[i];
string nodeId = node.NodeId;
string RfidId = node.RfidId;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].NodeId : null;
string nodeId = node.Id;
var RfidId = node.RfidId;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null;
// 노드 정보 생성 (현재 방향 유지)
var nodeInfo = new NodeMotorInfo(i+1,
var nodeInfo = new NodeMotorInfo(i + 1,
nodeId, RfidId,
currentDirection,
nextNodeId,
MagnetDirection.Straight
);
// [Speed Control] MapNode의 속도 설정 적용
var mapNode = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (mapNode != null)
{
nodeInfo.Speed = mapNode.SpeedLimit;
}
detailedPath1.Add(nodeInfo);
}
@@ -465,13 +485,13 @@ namespace AGVNavigationCore.PathFinding.Planning
for (int i = 0; i < path1.DetailedPath.Count; i++)
{
var detailPath = path1.DetailedPath[i];
string nodeId = path1.Path[i].NodeId;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].NodeId : null;
string nodeId = path1.Path[i].Id;
string nextNodeId = (i + 1 < path1.Path.Count) ? path1.Path[i + 1].Id : null;
// 마그넷 방향 계산 (3개 이상 연결된 교차로에서만 좌/우 가중치 적용)
if (i > 0 && nextNodeId != null)
{
string prevNodeId = path1.Path[i - 1].NodeId;
string prevNodeId = path1.Path[i - 1].Id;
if (path1.DetailedPath[i - 1].MotorDirection != detailPath.MotorDirection)
detailPath.MagnetDirection = MagnetDirection.Straight;
else

View File

@@ -73,7 +73,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// 방향이 같으면 직접 경로 계산
if (currentDirection == requiredDirection)
{
var directPath = _pathfinder.FindPath(startNodeId, targetNodeId);
var directPath = _pathfinder.FindPathAStar(startNodeId, targetNodeId);
if (directPath.Success)
{
return DirectionChangePlan.CreateSuccess(
@@ -85,20 +85,20 @@ namespace AGVNavigationCore.PathFinding.Planning
}
// 방향 전환이 필요한 경우 - 먼저 간단한 직접 경로 확인
var directPath2 = _pathfinder.FindPath(startNodeId, targetNodeId);
var directPath2 = _pathfinder.FindPathAStar(startNodeId, targetNodeId);
if (directPath2.Success)
{
// 직접 경로에 갈림길이 포함된 경우 그 갈림길에서 방향 전환
foreach (var node in directPath2.Path.Skip(1).Take(directPath2.Path.Count - 2)) // 시작과 끝 제외
{
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.NodeId);
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.Id);
if (junctionInfo != null && junctionInfo.IsJunction)
{
// 간단한 방향 전환: 직접 경로 사용하되 방향 전환 노드 표시
return DirectionChangePlan.CreateSuccess(
directPath2.Path,
node.NodeId,
$"갈림길 {node.NodeId}에서 방향 전환: {currentDirection} → {requiredDirection}"
node.Id,
$"갈림길 {node.Id}에서 방향 전환: {currentDirection} → {requiredDirection}"
);
}
}
@@ -160,19 +160,19 @@ namespace AGVNavigationCore.PathFinding.Planning
}
// 2. 직진 경로상의 갈림길들도 검색 (단, 되돌아가기 방지)
var directPath = _pathfinder.FindPath(startNodeId, targetNodeId);
var directPath = _pathfinder.FindPathAStar(startNodeId, targetNodeId);
if (directPath.Success)
{
foreach (var node in directPath.Path.Skip(2)) // 시작점과 다음 노드는 제외
{
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.NodeId);
var junctionInfo = _junctionAnalyzer.GetJunctionInfo(node.Id);
if (junctionInfo != null && junctionInfo.IsJunction)
{
// 직진 경로상에서는 더 엄격한 조건 적용
if (!suitableJunctions.Contains(node.NodeId) &&
HasMultipleExitOptions(node.NodeId))
if (!suitableJunctions.Contains(node.Id) &&
HasMultipleExitOptions(node.Id))
{
suitableJunctions.Add(node.NodeId);
suitableJunctions.Add(node.Id);
}
}
}
@@ -226,7 +226,7 @@ namespace AGVNavigationCore.PathFinding.Planning
/// </summary>
private List<string> GetAllConnectedNodes(string nodeId)
{
var node = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (node == null) return new List<string>();
var connected = new HashSet<string>();
@@ -236,16 +236,16 @@ namespace AGVNavigationCore.PathFinding.Planning
{
if (connectedNode != null)
{
connected.Add(connectedNode.NodeId);
connected.Add(connectedNode.Id);
}
}
// 역방향 연결
foreach (var otherNode in _mapNodes)
{
if (otherNode.NodeId != nodeId && otherNode.ConnectedMapNodes.Any(n => n.NodeId == nodeId))
if (otherNode.Id != nodeId && otherNode.ConnectedMapNodes.Any(n => n.Id == nodeId))
{
connected.Add(otherNode.NodeId);
connected.Add(otherNode.Id);
}
}
@@ -261,7 +261,7 @@ namespace AGVNavigationCore.PathFinding.Planning
foreach (var junction in junctions)
{
var path = _pathfinder.FindPath(startNodeId, junction);
var path = _pathfinder.FindPathAStar(startNodeId, junction);
double distance = path.Success ? path.TotalDistance : double.MaxValue;
distances.Add((junction, distance));
}
@@ -293,7 +293,7 @@ namespace AGVNavigationCore.PathFinding.Planning
string actualDirectionChangeNode = FindActualDirectionChangeNode(changePath, junctionNodeId);
string description = $"갈림길 {GetDisplayName(junctionNodeId)}를 통해 {GetDisplayName(actualDirectionChangeNode)}에서 방향 전환: {currentDirection} → {requiredDirection}";
System.Diagnostics.Debug.WriteLine($"[DirectionChangePlanner] ✅ 유효한 방향전환 경로: {string.Join(" ", changePath.Select(n => n.NodeId))}");
System.Diagnostics.Debug.WriteLine($"[DirectionChangePlanner] ✅ 유효한 방향전환 경로: {string.Join(" ", changePath.Select(n => n.Id))}");
return DirectionChangePlan.CreateSuccess(changePath, actualDirectionChangeNode, description);
}
@@ -313,13 +313,13 @@ namespace AGVNavigationCore.PathFinding.Planning
var fullPath = new List<MapNode>();
// 1. 시작점에서 갈림길까지의 경로
var toJunctionPath = _pathfinder.FindPath(startNodeId, junctionNodeId);
var toJunctionPath = _pathfinder.FindPathAStar(startNodeId, junctionNodeId);
if (!toJunctionPath.Success)
return fullPath;
// 2. 인근 갈림길을 통한 우회인지, 직진 경로상 갈림길인지 판단
var directPath = _pathfinder.FindPath(startNodeId, targetNodeId);
bool isNearbyDetour = !directPath.Success || !directPath.Path.Any(n => n.NodeId == junctionNodeId);
var directPath = _pathfinder.FindPathAStar(startNodeId, targetNodeId);
bool isNearbyDetour = !directPath.Success || !directPath.Path.Any(n => n.Id == junctionNodeId);
if (isNearbyDetour)
{
@@ -341,7 +341,7 @@ namespace AGVNavigationCore.PathFinding.Planning
var fullPath = new List<MapNode>();
// 1. 시작점에서 갈림길까지 직진 (현재 방향 유지)
var toJunctionPath = _pathfinder.FindPath(startNodeId, junctionNodeId);
var toJunctionPath = _pathfinder.FindPathAStar(startNodeId, junctionNodeId);
if (!toJunctionPath.Success)
return fullPath;
@@ -349,7 +349,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// 2. 갈림길에서 방향 전환 후 목적지로
// 이때 마그넷 센서를 이용해 목적지 방향으로 진입
var fromJunctionPath = _pathfinder.FindPath(junctionNodeId, targetNodeId);
var fromJunctionPath = _pathfinder.FindPathAStar(junctionNodeId, targetNodeId);
if (fromJunctionPath.Success && fromJunctionPath.Path.Count > 1)
{
fullPath.AddRange(fromJunctionPath.Path.Skip(1)); // 중복 노드 제거
@@ -366,7 +366,7 @@ namespace AGVNavigationCore.PathFinding.Planning
var fullPath = new List<MapNode>();
// 1. 시작점에서 갈림길까지의 경로
var toJunctionPath = _pathfinder.FindPath(startNodeId, junctionNodeId);
var toJunctionPath = _pathfinder.FindPathAStar(startNodeId, junctionNodeId);
if (!toJunctionPath.Success)
return fullPath;
@@ -376,18 +376,18 @@ namespace AGVNavigationCore.PathFinding.Planning
if (currentDirection != requiredDirection)
{
string fromNodeId = toJunctionPath.Path.Count >= 2 ?
toJunctionPath.Path[toJunctionPath.Path.Count - 2].NodeId : startNodeId;
toJunctionPath.Path[toJunctionPath.Path.Count - 2].Id : startNodeId;
var changeSequence = GenerateDirectionChangeSequence(junctionNodeId, fromNodeId, currentDirection, requiredDirection);
if (changeSequence.Count > 1)
{
fullPath.AddRange(changeSequence.Skip(1).Select(nodeId => _mapNodes.FirstOrDefault(n => n.NodeId == nodeId)).Where(n => n != null));
fullPath.AddRange(changeSequence.Skip(1).Select(nodeId => _mapNodes.FirstOrDefault(n => n.Id == nodeId)).Where(n => n != null));
}
}
// 3. 갈림길에서 목표점까지의 경로
string lastNode = fullPath.LastOrDefault()?.NodeId ?? junctionNodeId;
var fromJunctionPath = _pathfinder.FindPath(lastNode, targetNodeId);
string lastNode = fullPath.LastOrDefault()?.Id ?? junctionNodeId;
var fromJunctionPath = _pathfinder.FindPathAStar(lastNode, targetNodeId);
if (fromJunctionPath.Success && fromJunctionPath.Path.Count > 1)
{
fullPath.AddRange(fromJunctionPath.Path.Skip(1));
@@ -461,8 +461,8 @@ namespace AGVNavigationCore.PathFinding.Planning
// 왔던 길(excludeNodeId)를 제외한 노드 중에서 최적의 우회 노드 선택
// 우선순위: 1) 막다른 길이 아닌 노드 (우회 후 복귀 가능) 2) 직진방향 3) 목적지 방향
var junctionNode = _mapNodes.FirstOrDefault(n => n.NodeId == junctionNodeId);
var fromNode = _mapNodes.FirstOrDefault(n => n.NodeId == excludeNodeId);
var junctionNode = _mapNodes.FirstOrDefault(n => n.Id == junctionNodeId);
var fromNode = _mapNodes.FirstOrDefault(n => n.Id == excludeNodeId);
if (junctionNode == null || fromNode == null)
return availableNodes.FirstOrDefault();
@@ -478,7 +478,7 @@ namespace AGVNavigationCore.PathFinding.Planning
{
if (nodeId == excludeNodeId) continue; // 왔던 길 제외
var candidateNode = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
var candidateNode = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (candidateNode == null) continue;
// 갈림길에서 후보 노드로의 방향 벡터 계산 (junctionNode → candidateNode)
@@ -561,11 +561,11 @@ namespace AGVNavigationCore.PathFinding.Planning
return junctionNodeId; // 기본값으로 갈림길 반환
// 갈림길이 두 번 나타나는 위치 찾기
int firstJunctionIndex = changePath.FindIndex(n => n.NodeId == junctionNodeId);
int firstJunctionIndex = changePath.FindIndex(n => n.Id == junctionNodeId);
int lastJunctionIndex = -1;
for (int i = changePath.Count - 1; i >= 0; i--)
{
if (changePath[i].NodeId == junctionNodeId)
if (changePath[i].Id == junctionNodeId)
{
lastJunctionIndex = i;
break;
@@ -577,7 +577,7 @@ namespace AGVNavigationCore.PathFinding.Planning
firstJunctionIndex != lastJunctionIndex && lastJunctionIndex - firstJunctionIndex == 2)
{
// 첫 번째와 두 번째 갈림길 사이에 있는 노드가 실제 방향전환 노드
string detourNode = changePath[firstJunctionIndex + 1].NodeId;
string detourNode = changePath[firstJunctionIndex + 1].Id;
return detourNode;
}
@@ -609,7 +609,7 @@ namespace AGVNavigationCore.PathFinding.Planning
private bool CanReachTargetViaJunction(string junctionNodeId, string targetNodeId)
{
// 갈림길에서 목적지까지의 경로가 존재하는지 확인
var pathToTarget = _pathfinder.FindPath(junctionNodeId, targetNodeId);
var pathToTarget = _pathfinder.FindPathAStar(junctionNodeId, targetNodeId);
return pathToTarget.Success;
}
@@ -647,11 +647,11 @@ namespace AGVNavigationCore.PathFinding.Planning
}
string errorMessage = $"되돌아가기 패턴 검출 ({backtrackingPatterns.Count}개): {string.Join(", ", issues)}";
System.Diagnostics.Debug.WriteLine($"[PathValidation] ❌ 경로: {string.Join(" ", path.Select(n => n.NodeId))}");
System.Diagnostics.Debug.WriteLine($"[PathValidation] ❌ 경로: {string.Join(" ", path.Select(n => n.Id))}");
System.Diagnostics.Debug.WriteLine($"[PathValidation] ❌ 되돌아가기 패턴: {errorMessage}");
return PathValidationResult.CreateInvalidWithBacktracking(
path.Select(n => n.NodeId).ToList(), backtrackingPatterns, startNodeId, "", junctionNodeId, errorMessage);
path.Select(n => n.Id).ToList(), backtrackingPatterns, startNodeId, "", junctionNodeId, errorMessage);
}
// 2. 연속된 중복 노드 검증
@@ -670,13 +670,13 @@ namespace AGVNavigationCore.PathFinding.Planning
}
// 4. 갈림길 포함 여부 검증
if (!path.Any(n => n.NodeId == junctionNodeId))
if (!path.Any(n => n.Id == junctionNodeId))
{
return PathValidationResult.CreateInvalid(startNodeId, "", $"갈림길 {junctionNodeId}이 경로에 포함되지 않음");
}
System.Diagnostics.Debug.WriteLine($"[PathValidation] ✅ 유효한 경로: {string.Join(" ", path.Select(n => n.NodeId))}");
return PathValidationResult.CreateValid(path.Select(n => n.NodeId).ToList(), startNodeId, "", junctionNodeId);
System.Diagnostics.Debug.WriteLine($"[PathValidation] ✅ 유효한 경로: {string.Join(" ", path.Select(n => n.Id))}");
return PathValidationResult.CreateValid(path.Select(n => n.Id).ToList(), startNodeId, "", junctionNodeId);
}
/// <summary>
@@ -688,9 +688,9 @@ namespace AGVNavigationCore.PathFinding.Planning
for (int i = 0; i < path.Count - 2; i++)
{
string nodeA = path[i].NodeId;
string nodeB = path[i + 1].NodeId;
string nodeC = path[i + 2].NodeId;
string nodeA = path[i].Id;
string nodeB = path[i + 1].Id;
string nodeC = path[i + 2].Id;
// A → B → A 패턴 검출
if (nodeA == nodeC && nodeA != nodeB)
@@ -712,9 +712,9 @@ namespace AGVNavigationCore.PathFinding.Planning
for (int i = 0; i < path.Count - 1; i++)
{
if (path[i].NodeId == path[i + 1].NodeId)
if (path[i].Id == path[i + 1].Id)
{
duplicates.Add(path[i].NodeId);
duplicates.Add(path[i].Id);
}
}
@@ -728,12 +728,12 @@ namespace AGVNavigationCore.PathFinding.Planning
{
for (int i = 0; i < path.Count - 1; i++)
{
string currentNode = path[i].NodeId;
string nextNode = path[i + 1].NodeId;
string currentNode = path[i].Id;
string nextNode = path[i + 1].Id;
// 두 노드간 직접 연결성 확인 (맵 노드의 ConnectedMapNodes 리스트 사용)
var currentMapNode = _mapNodes.FirstOrDefault(n => n.NodeId == currentNode);
if (currentMapNode == null || !currentMapNode.ConnectedMapNodes.Any(n => n.NodeId == nextNode))
var currentMapNode = _mapNodes.FirstOrDefault(n => n.Id == currentNode);
if (currentMapNode == null || !currentMapNode.ConnectedMapNodes.Any(n => n.Id == nextNode))
{
return PathValidationResult.CreateInvalid(currentNode, nextNode, $"노드 {currentNode}와 {nextNode} 사이에 연결이 없음");
}
@@ -769,10 +769,10 @@ namespace AGVNavigationCore.PathFinding.Planning
/// <returns>표시할 이름</returns>
private string GetDisplayName(string nodeId)
{
var node = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
if (node != null && !string.IsNullOrEmpty(node.RfidId))
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (node != null && node.HasRfid())
{
return node.RfidId;
return node.RfidId.ToString("0000");
}
return $"({nodeId})";
}

View File

@@ -66,7 +66,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// 연결된 노드 중 현재 노드가 아닌 것들만 필터링
var candidateNodes = allNodes.Where(n =>
connectedNodeIds.Contains(n.NodeId) && n.NodeId != currentNode.NodeId
connectedNodeIds.Contains(n.Id) && n.Id != currentNode.Id
).ToList();
if (candidateNodes.Count == 0)
@@ -88,7 +88,7 @@ namespace AGVNavigationCore.PathFinding.Planning
if (movementLength < 0.001f) // 거의 이동하지 않음
{
return candidateNodes[0].NodeId; // 첫 번째 연결 노드 반환
return candidateNodes[0].Id; // 첫 번째 연결 노드 반환
}
var normalizedMovement = new PointF(
@@ -138,7 +138,7 @@ namespace AGVNavigationCore.PathFinding.Planning
// 가장 높은 점수를 가진 노드 반환
var bestCandidate = scoredCandidates.OrderByDescending(x => x.score).First();
return bestCandidate.node.NodeId;
return bestCandidate.node.Id;
}
/// <summary>

View File

@@ -40,13 +40,18 @@ namespace AGVNavigationCore.PathFinding.Planning
/// <summary>
/// RFID Value
/// </summary>
public string RfidId { get; set; }
public ushort RfidId { get; set; }
/// <summary>
/// 해당 노드에서의 모터방향
/// </summary>
public AgvDirection MotorDirection { get; set; }
/// <summary>
/// 해당 노드에서의 제한 속도
/// </summary>
public SpeedLevel Speed { get; set; } = SpeedLevel.M;
/// <summary>
/// 마그넷 센서 방향 제어 (갈림길 처리용)
/// </summary>
@@ -82,7 +87,7 @@ namespace AGVNavigationCore.PathFinding.Planning
/// </summary>
public string SpecialActionDescription { get; set; }
public NodeMotorInfo(int seqno,string nodeId,string rfid, AgvDirection motorDirection, string nextNodeId = null, MagnetDirection magnetDirection = MagnetDirection.Straight)
public NodeMotorInfo(int seqno,string nodeId,ushort rfid, AgvDirection motorDirection, string nextNodeId = null, MagnetDirection magnetDirection = MagnetDirection.Straight)
{
seq = seqno;
NodeId = nodeId;
@@ -103,7 +108,7 @@ namespace AGVNavigationCore.PathFinding.Planning
/// </summary>
public override string ToString()
{
var result = $"{RfidId}[{NodeId}]:{MotorDirection}";
var result = $"R{RfidId}[N{NodeId}]:{MotorDirection}";
// 마그넷 방향이 직진이 아닌 경우 표시
if (MagnetDirection != MagnetDirection.Straight)

View File

@@ -76,7 +76,7 @@ namespace AGVNavigationCore.Utils
List<MapNode> candidateNodes = new List<MapNode>();
if (prevDirection == direction)
{
candidateNodes = connectedMapNodes.Where(n => n.NodeId != prevNode.NodeId).ToList();
candidateNodes = connectedMapNodes.Where(n => n.Id != prevNode.Id).ToList();
}
else
{
@@ -112,9 +112,9 @@ namespace AGVNavigationCore.Utils
Console.WriteLine(
$"\n[GetNextNodeByDirection] ========== 다음 노드 선택 시작 ==========");
Console.WriteLine(
$" 현재노드: {currentNode.RfidId}[{currentNode.NodeId}]({currentNode.Position.X:F1}, {currentNode.Position.Y:F1})");
$" 현재노드: {currentNode.RfidId}[{currentNode.Id}]({currentNode.Position.X:F1}, {currentNode.Position.Y:F1})");
Console.WriteLine(
$" 이전노드: {prevNode.RfidId}[{prevNode.NodeId}]({prevNode.Position.X:F1}, {prevNode.Position.Y:F1})");
$" 이전노드: {prevNode.RfidId}[{prevNode.Id}]({prevNode.Position.X:F1}, {prevNode.Position.Y:F1})");
Console.WriteLine(
$" 이동벡터: ({movementVector.X:F2}, {movementVector.Y:F2}) → 정규화: ({normalizedMovement.X:F3}, {normalizedMovement.Y:F3})");
Console.WriteLine(
@@ -159,7 +159,7 @@ namespace AGVNavigationCore.Utils
}
Console.WriteLine(
$"\n [후보] {candidate.RfidId}[{candidate.NodeId}]({candidate.Position.X:F1}, {candidate.Position.Y:F1})");
$"\n [후보] {candidate.RfidId}[{candidate.Id}]({candidate.Position.X:F1}, {candidate.Position.Y:F1})");
Console.WriteLine(
$" 벡터: ({toNextVector.X:F2}, {toNextVector.Y:F2}), 길이: {toNextLength:F2}");
Console.WriteLine(
@@ -204,7 +204,7 @@ namespace AGVNavigationCore.Utils
}
Console.WriteLine(
$"\n 최종선택: {bestNode?.RfidId ?? "null"}[{bestNode?.NodeId ?? "null"}] (점수: {bestScore:F4})");
$"\n 최종선택: {bestNode?.RfidId ?? 0}[{bestNode?.Id ?? "null"}] (점수: {bestScore:F4})");
Console.WriteLine(
$"[GetNextNodeByDirection] ========== 다음 노드 선택 종료 ==========\n");
@@ -445,15 +445,15 @@ namespace AGVNavigationCore.Utils
// 선택 이유 생성
if (prevMotorDirection.HasValue && direction == prevMotorDirection)
{
reason = $"모터 방향 일관성 유지 ({direction}) → {candidate.NodeId}";
reason = $"모터 방향 일관성 유지 ({direction}) → {candidate.Id}";
}
else if (prevMotorDirection.HasValue)
{
reason = $"모터 방향 변경 ({prevMotorDirection} → {direction}) → {candidate.NodeId}";
reason = $"모터 방향 변경 ({prevMotorDirection} → {direction}) → {candidate.Id}";
}
else
{
reason = $"방향 기반 선택 ({direction}) → {candidate.NodeId}";
reason = $"방향 기반 선택 ({direction}) → {candidate.Id}";
}
}
}

View File

@@ -11,22 +11,22 @@ namespace AGVNavigationCore.Utils
{
/// <summary>
/// DirectionalPathfinder 테스트 클래스
/// NewMap.agvmap을 로드하여 방향별 다음 노드를 검증
/// NewMap.json 로드하여 방향별 다음 노드를 검증
/// </summary>
public class DirectionalPathfinderTest
{
private List<MapNode> _allNodes;
private Dictionary<string, MapNode> _nodesByRfidId;
private Dictionary<ushort, MapNode> _nodesByRfidId;
private AGVDirectionCalculator _calculator;
public DirectionalPathfinderTest()
{
_nodesByRfidId = new Dictionary<string, MapNode>();
_nodesByRfidId = new Dictionary<ushort, MapNode>();
_calculator = new AGVDirectionCalculator();
}
/// <summary>
/// NewMap.agvmap 파일 로드
/// NewMap.json 파일 로드
/// </summary>
public bool LoadMapFile(string filePath)
{
@@ -52,7 +52,7 @@ namespace AGVNavigationCore.Utils
// RFID ID로 인덱싱
foreach (var node in _allNodes)
{
if (!string.IsNullOrEmpty(node.RfidId))
if (node.HasRfid())
{
_nodesByRfidId[node.RfidId] = node;
}
@@ -71,7 +71,7 @@ namespace AGVNavigationCore.Utils
/// <summary>
/// 테스트: RFID 번호로 노드를 찾고, 다음 노드를 계산
/// </summary>
public void TestDirectionalMovement(string previousRfidId, string currentRfidId, AgvDirection direction)
public void TestDirectionalMovement(ushort previousRfidId, ushort currentRfidId, AgvDirection direction)
{
Console.WriteLine($"\n========================================");
Console.WriteLine($"테스트: {previousRfidId} → {currentRfidId} (방향: {direction})");
@@ -90,8 +90,8 @@ namespace AGVNavigationCore.Utils
return;
}
Console.WriteLine($"이전 노드: {previousNode.NodeId} (RFID: {previousNode.RfidId}) - 위치: {previousNode.Position}");
Console.WriteLine($"현재 노드: {currentNode.NodeId} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}");
Console.WriteLine($"이전 노드: {previousNode.Id} (RFID: {previousNode.RfidId}) - 위치: {previousNode.Position}");
Console.WriteLine($"현재 노드: {currentNode.Id} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}");
Console.WriteLine($"이동 벡터: ({currentNode.Position.X - previousNode.Position.X}, " +
$"{currentNode.Position.Y - previousNode.Position.Y})");
@@ -111,10 +111,10 @@ namespace AGVNavigationCore.Utils
}
// 다음 노드 정보 출력
var nextNode = _allNodes.FirstOrDefault(n => n.NodeId == nextNodeId);
var nextNode = _allNodes.FirstOrDefault(n => n.Id == nextNodeId);
if (nextNode != null)
{
Console.WriteLine($"✓ 다음 노드: {nextNode.NodeId} (RFID: {nextNode.RfidId}) - 위치: {nextNode.Position}");
Console.WriteLine($"✓ 다음 노드: {nextNode.Id} (RFID: {nextNode.RfidId}) - 위치: {nextNode.Position}");
Console.WriteLine($" ├─ 노드 타입: {GetNodeTypeName(nextNode.Type)}");
Console.WriteLine($" └─ 연결된 노드: {string.Join(", ", nextNode.ConnectedNodes)}");
}
@@ -132,7 +132,7 @@ namespace AGVNavigationCore.Utils
Console.WriteLine("\n========== 모든 노드 정보 ==========");
foreach (var node in _allNodes.OrderBy(n => n.RfidId))
{
Console.WriteLine($"{node.RfidId:D3} → {node.NodeId} ({GetNodeTypeName(node.Type)})");
Console.WriteLine($"{node.RfidId:D3} → {node.Id} ({GetNodeTypeName(node.Type)})");
Console.WriteLine($" 위치: {node.Position}, 연결: {string.Join(", ", node.ConnectedNodes)}");
}
}
@@ -140,7 +140,7 @@ namespace AGVNavigationCore.Utils
/// <summary>
/// 특정 RFID 노드의 상세 정보 출력
/// </summary>
public void PrintNodeInfo(string rfidId)
public void PrintNodeInfo(ushort rfidId)
{
if (!_nodesByRfidId.TryGetValue(rfidId, out var node))
{
@@ -149,11 +149,12 @@ namespace AGVNavigationCore.Utils
}
Console.WriteLine($"\n========== RFID {rfidId} 상세 정보 ==========");
Console.WriteLine($"노드 ID: {node.NodeId}");
Console.WriteLine($"이름: {node.Name}");
Console.WriteLine($"노드 ID: {node.Id}");
Console.WriteLine($"RFID: {node.RfidId}");
Console.WriteLine($"ALIAS: {node.AliasName}");
Console.WriteLine($"위치: {node.Position}");
Console.WriteLine($"타입: {GetNodeTypeName(node.Type)}");
Console.WriteLine($"회전 가능: {node.CanRotate}");
Console.WriteLine($"TurnLeft/Right/교차로 : {(node.CanTurnLeft ? "O":"X")}/{(node.CanTurnRight ? "O" : "X")}/{(node.DisableCross ? "X" : "O")}");
Console.WriteLine($"활성: {node.IsActive}");
Console.WriteLine($"연결된 노드:");
@@ -165,7 +166,7 @@ namespace AGVNavigationCore.Utils
{
foreach (var connectedId in node.ConnectedNodes)
{
var connectedNode = _allNodes.FirstOrDefault(n => n.NodeId == connectedId);
var connectedNode = _allNodes.FirstOrDefault(n => n.Id == connectedId);
if (connectedNode != null)
{
Console.WriteLine($" → {connectedId} (RFID: {connectedNode.RfidId}) - 위치: {connectedNode.Position}");

View File

@@ -29,7 +29,7 @@ namespace AGVNavigationCore.Utils
return DockingValidationResult.CreateNotRequired();
}
if (pathResult.DetailedPath.Any() == false && pathResult.Path.Any() && pathResult.Path.Count == 2 &&
pathResult.Path[0].NodeId == pathResult.Path[1].NodeId)
pathResult.Path[0].Id == pathResult.Path[1].Id)
{
System.Diagnostics.Debug.WriteLine($"[DockingValidator] 도킹 검증 불필요: 동일포인트");
return DockingValidationResult.CreateNotRequired();
@@ -44,7 +44,7 @@ namespace AGVNavigationCore.Utils
return DockingValidationResult.CreateNotRequired();
}
System.Diagnostics.Debug.WriteLine($"[DockingValidator] 목적지 노드: {LastNode.NodeId} 타입:{LastNode.Type} ({(int)LastNode.Type})");
System.Diagnostics.Debug.WriteLine($"[DockingValidator] 목적지 노드: {LastNode.Id} 타입:{LastNode.Type} ({(int)LastNode.Type})");
//detail 경로 이동 예측 검증
for (int i = 0; i < pathResult.DetailedPath.Count - 1; i++)
@@ -52,8 +52,8 @@ namespace AGVNavigationCore.Utils
var curNodeId = pathResult.DetailedPath[i].NodeId;
var nextNodeId = pathResult.DetailedPath[i + 1].NodeId;
var curNode = mapNodes?.FirstOrDefault(n => n.NodeId == curNodeId);
var nextNode = mapNodes?.FirstOrDefault(n => n.NodeId == nextNodeId);
var curNode = mapNodes?.FirstOrDefault(n => n.Id == curNodeId);
var nextNode = mapNodes?.FirstOrDefault(n => n.Id == nextNodeId);
if (curNode != null && nextNode != null)
{
@@ -67,7 +67,7 @@ namespace AGVNavigationCore.Utils
else
{
var prevNodeId = pathResult.DetailedPath[i - 1].NodeId;
prevNode = mapNodes?.FirstOrDefault(n => n.NodeId == prevNodeId);
prevNode = mapNodes?.FirstOrDefault(n => n.Id == prevNodeId);
prevDir = pathResult.DetailedPath[i - 1].MotorDirection;
}
@@ -78,7 +78,7 @@ namespace AGVNavigationCore.Utils
Console.WriteLine(
$"\n[ValidateDockingDirection] 경로 검증 단계 {i}:");
Console.WriteLine(
$" 이전→현재→다음: {prevNode.NodeId}({prevNode.RfidId}) → {curNode.NodeId}({curNode.RfidId}) → {nextNode.NodeId}({nextNode.RfidId})");
$" 이전→현재→다음: {prevNode.Id}({prevNode.RfidId}) → {curNode.Id}({curNode.RfidId}) → {nextNode.Id}({nextNode.RfidId})");
Console.WriteLine(
$" 현재 노드 위치: ({curNode.Position.X:F1}, {curNode.Position.Y:F1})");
Console.WriteLine(
@@ -96,16 +96,16 @@ namespace AGVNavigationCore.Utils
);
Console.WriteLine(
$" [예상] GetNextNodeByDirection 결과: {expectedNextNode?.NodeId ?? "null"}");
$" [예상] GetNextNodeByDirection 결과: {expectedNextNode?.Id ?? "null"}");
Console.WriteLine(
$" [실제] DetailedPath 다음 노드: {nextNode.RfidId}[{nextNode.NodeId}]");
$" [실제] DetailedPath 다음 노드: {nextNode.RfidId}[{nextNode.Id}]");
if (expectedNextNode != null && !expectedNextNode.NodeId.Equals(nextNode.NodeId))
if (expectedNextNode != null && !expectedNextNode.Id.Equals(nextNode.Id))
{
string error =
$"[DockingValidator] ⚠️ 경로 방향 불일치" +
$"\n현재={curNode.RfidId}[{curNodeId}] 이전={prevNode.RfidId}[{(prevNode?.NodeId ?? string.Empty)}] " +
$"\n예상다음={expectedNextNode.RfidId}[{expectedNextNode.NodeId}] 실제다음={nextNode.RfidId}[{nextNodeId}]";
$"\n현재={curNode.RfidId}[{curNodeId}] 이전={prevNode.RfidId}[{(prevNode?.Id ?? string.Empty)}] " +
$"\n예상다음={expectedNextNode.RfidId}[{expectedNextNode.Id}] 실제다음={nextNode.RfidId}[{nextNodeId}]";
Console.WriteLine(
$"[ValidateDockingDirection] ❌ 경로 방향 불일치 검출!");
Console.WriteLine(
@@ -118,7 +118,7 @@ namespace AGVNavigationCore.Utils
$" 현재→실제: ({(nextNode.Position.X - curNode.Position.X):F2}, {(nextNode.Position.Y - curNode.Position.Y):F2})");
Console.WriteLine($"[ValidateDockingDirection] 에러메시지: {error}");
return DockingValidationResult.CreateInvalid(
LastNode.NodeId,
LastNode.Id,
LastNode.Type,
pathResult.DetailedPath[i].MotorDirection,
pathResult.DetailedPath[i].MotorDirection,
@@ -150,12 +150,12 @@ namespace AGVNavigationCore.Utils
System.Diagnostics.Debug.WriteLine($"[DockingValidator] 필요한 도킹 방향: {requiredDirection}");
var LastNodeInfo = pathResult.DetailedPath.Last();
if (LastNodeInfo.NodeId != LastNode.NodeId)
if (LastNodeInfo.NodeId != LastNode.Id)
{
string error = $"마지막 노드의 도킹방향과 경로정보의 노드ID 불일치: 필요={LastNode.NodeId}, 계산됨={LastNodeInfo.NodeId }";
string error = $"마지막 노드의 도킹방향과 경로정보의 노드ID 불일치: 필요={LastNode.Id}, 계산됨={LastNodeInfo.NodeId }";
System.Diagnostics.Debug.WriteLine($"[DockingValidator] ❌ 도킹 검증 실패: {error}");
return DockingValidationResult.CreateInvalid(
LastNode.NodeId,
LastNode.Id,
LastNode.Type,
requiredDirection,
LastNodeInfo.MotorDirection,
@@ -167,7 +167,7 @@ namespace AGVNavigationCore.Utils
{
System.Diagnostics.Debug.WriteLine($"[DockingValidator] ✅ 도킹 검증 성공");
return DockingValidationResult.CreateValid(
LastNode.NodeId,
LastNode.Id,
LastNode.Type,
requiredDirection,
LastNodeInfo.MotorDirection);
@@ -177,7 +177,7 @@ namespace AGVNavigationCore.Utils
string error = $"도킹 방향 불일치: 필요={GetDirectionText(requiredDirection)}, 계산됨={GetDirectionText(LastNodeInfo.MotorDirection)}";
System.Diagnostics.Debug.WriteLine($"[DockingValidator] ❌ 도킹 검증 실패: {error}");
return DockingValidationResult.CreateInvalid(
LastNode.NodeId,
LastNode.Id,
LastNode.Type,
requiredDirection,
LastNodeInfo.MotorDirection,
@@ -264,7 +264,7 @@ namespace AGVNavigationCore.Utils
var deltaY = lastNode.Position.Y - secondLastNode.Position.Y;
var distance = Math.Sqrt(deltaX * deltaX + deltaY * deltaY);
System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 마지막 구간: {secondLastNode.NodeId} → {lastNode.NodeId}, 벡터: ({deltaX}, {deltaY}), 거리: {distance:F2}");
System.Diagnostics.Debug.WriteLine($"[CalculateFinalDirection] 마지막 구간: {secondLastNode.Id} → {lastNode.Id}, 벡터: ({deltaX}, {deltaY}), 거리: {distance:F2}");
// 이동 거리가 매우 작으면 현재 방향 유지
if (distance < 1.0)

View File

@@ -28,10 +28,10 @@ namespace AGVNavigationCore.Utils
Console.WriteLine("================================================\n");
// 테스트 노드 생성
var node001 = new MapNode { NodeId = "N001", RfidId = "001", Position = new Point(65, 229), ConnectedNodes = new List<string> { "N002" } };
var node002 = new MapNode { NodeId = "N002", RfidId = "002", Position = new Point(206, 244), ConnectedNodes = new List<string> { "N001", "N003" } };
var node003 = new MapNode { NodeId = "N003", RfidId = "003", Position = new Point(278, 278), ConnectedNodes = new List<string> { "N002", "N004" } };
var node004 = new MapNode { NodeId = "N004", RfidId = "004", Position = new Point(380, 340), ConnectedNodes = new List<string> { "N003", "N022", "N031" } };
var node001 = new MapNode { Id = "N001", RfidId = 001, Position = new Point(65, 229), ConnectedNodes = new List<string> { "N002" } };
var node002 = new MapNode { Id = "N002", RfidId = 002, Position = new Point(206, 244), ConnectedNodes = new List<string> { "N001", "N003" } };
var node003 = new MapNode { Id = "N003", RfidId = 003, Position = new Point(278, 278), ConnectedNodes = new List<string> { "N002", "N004" } };
var node004 = new MapNode { Id = "N004", RfidId = 004, Position = new Point(380, 340), ConnectedNodes = new List<string> { "N003", "N022", "N031" } };
var allNodes = new List<MapNode> { node001, node002, node003, node004 };
@@ -114,8 +114,8 @@ namespace AGVNavigationCore.Utils
AgvDirection motorDir = currentMotorDirection ?? direction;
Console.WriteLine($"설명: {description}");
Console.WriteLine($"이전 위치: {prevPos} (RFID: {allNodes.First(n => n.Position == prevPos)?.RfidId ?? "?"})");
Console.WriteLine($"현재 노드: {currentNode.NodeId} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}");
Console.WriteLine($"이전 위치: {prevPos} (RFID: {allNodes.First(n => n.Position == prevPos)?.RfidId.ToString("0000") ?? "?"})");
Console.WriteLine($"현재 노드: {currentNode.Id} (RFID: {currentNode.RfidId}) - 위치: {currentNode.Position}");
Console.WriteLine($"현재 모터 방향: {motorDir}");
Console.WriteLine($"요청 방향: {direction}");
@@ -128,32 +128,32 @@ namespace AGVNavigationCore.Utils
Console.WriteLine($"이동 벡터: ({movementVector.X}, {movementVector.Y})");
// 각 후보 노드에 대한 점수 계산
Console.WriteLine($"\n현재 노드({currentNode.NodeId})의 ConnectedNodes: {string.Join(", ", currentNode.ConnectedNodes)}");
Console.WriteLine($"\n현재 노드({currentNode.Id})의 ConnectedNodes: {string.Join(", ", currentNode.ConnectedNodes)}");
Console.WriteLine($"가능한 다음 노드들:");
var candidateNodes = allNodes.Where(n =>
currentNode.ConnectedNodes.Contains(n.NodeId) && n.NodeId != currentNode.NodeId
currentNode.ConnectedNodes.Contains(n.Id) && n.Id != currentNode.Id
).ToList();
foreach (var candidate in candidateNodes)
{
var score = CalculateScoreAndPrint(movementVector, currentNode.Position, candidate, direction);
string isExpected = (candidate.NodeId == expectedNextNode.NodeId) ? " ← 예상 노드" : "";
Console.WriteLine($" {candidate.NodeId} (RFID: {candidate.RfidId}) - 위치: {candidate.Position} - 점수: {score:F1}{isExpected}");
string isExpected = (candidate.Id == expectedNextNode.Id) ? " ← 예상 노드" : "";
Console.WriteLine($" {candidate.Id} (RFID: {candidate.RfidId}) - 위치: {candidate.Position} - 점수: {score:F1}{isExpected}");
}
// 최고 점수 노드 선택
var bestCandidate = GetBestCandidate(movementVector, currentNode.Position, candidateNodes, direction);
Console.WriteLine($"\n✓ 선택된 노드: {bestCandidate.NodeId} (RFID: {bestCandidate.RfidId})");
Console.WriteLine($"\n✓ 선택된 노드: {bestCandidate.Id} (RFID: {bestCandidate.RfidId})");
if (bestCandidate.NodeId == expectedNextNode.NodeId)
if (bestCandidate.Id == expectedNextNode.Id)
{
Console.WriteLine($"✅ 정답! ({expectedNodeIdStr})");
}
else
{
Console.WriteLine($"❌ 오답! 예상: {expectedNextNode.NodeId}, 실제: {bestCandidate.NodeId}");
Console.WriteLine($"❌ 오답! 예상: {expectedNextNode.Id}, 실제: {bestCandidate.Id}");
}
}

View File

@@ -63,14 +63,14 @@ namespace AGVNavigationCore.Utils
// 이전 노드 제외 (되돌아가는 방향 제외)
if (previousNode != null)
{
nextNodes = nextNodes.Where(n => n.NodeId != previousNode.NodeId).ToList();
nextNodes = nextNodes.Where(n => n.Id != previousNode.Id).ToList();
}
if (nextNodes.Count == 1)
{
// 직선 경로: 다음 노드 방향으로 예측
targetPosition = nextNodes.First().Position;
calculationMethod = $"전진 경로 예측 ({currentNode.NodeId}→{nextNodes.First().NodeId})";
calculationMethod = $"전진 경로 예측 ({currentNode.Id}→{nextNodes.First().Id})";
}
else if (nextNodes.Count > 1)
{
@@ -268,7 +268,7 @@ namespace AGVNavigationCore.Utils
foreach (var nodeId in currentNode.ConnectedNodes)
{
var connectedNode = mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
var connectedNode = mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (connectedNode != null)
{
connectedNodes.Add(connectedNode);

View File

@@ -14,7 +14,7 @@ namespace AGVNavigationCore.Utils
{
public void RunTests()
{
string mapFilePath = @"C:\Data\Source\(5613#) ENIG AGV\Source\Cs_HMI\Data\NewMap.agvmap";
string mapFilePath = @"C:\Data\Source\(5613#) ENIG AGV\Source\Cs_HMI\Data\NewMap.json";
var tester = new DirectionalPathfinderTest();
@@ -29,26 +29,26 @@ namespace AGVNavigationCore.Utils
tester.PrintAllNodes();
// 테스트 시나리오 1: 001 → 002 → Forward (003 기대)
tester.PrintNodeInfo("001");
tester.PrintNodeInfo("002");
tester.TestDirectionalMovement("001", "002", AgvDirection.Forward);
tester.PrintNodeInfo(001);
tester.PrintNodeInfo(002);
tester.TestDirectionalMovement(001, 002, AgvDirection.Forward);
// 테스트 시나리오 2: 002 → 001 → Backward (000 또는 이전 기대)
tester.TestDirectionalMovement("002", "001", AgvDirection.Backward);
tester.TestDirectionalMovement(002, 001, AgvDirection.Backward);
// 테스트 시나리오 3: 002 → 003 → Forward
tester.PrintNodeInfo("003");
tester.TestDirectionalMovement("002", "003", AgvDirection.Forward);
tester.PrintNodeInfo(003);
tester.TestDirectionalMovement(002, 003, AgvDirection.Forward);
// 테스트 시나리오 4: 003 → 004 → Forward
tester.PrintNodeInfo("004");
tester.TestDirectionalMovement("003", "004", AgvDirection.Forward);
tester.PrintNodeInfo(004);
tester.TestDirectionalMovement(003, 004, AgvDirection.Forward);
// 테스트 시나리오 5: 003 → 004 → Right (030 기대)
tester.TestDirectionalMovement("003", "004", AgvDirection.Right);
tester.TestDirectionalMovement(003, 004, AgvDirection.Right);
// 테스트 시나리오 6: 004 → 003 → Backward
tester.TestDirectionalMovement("004", "003", AgvDirection.Backward);
tester.TestDirectionalMovement(004, 003, AgvDirection.Backward);
Console.WriteLine("\n\n=== 테스트 완료 ===");
}

View File

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

View File

@@ -17,7 +17,7 @@
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\..\..\..\..\Amkor\AGV4\</OutputPath>
<OutputPath>..\..\..\..\..\..\Amkor\AGV4\Test\Simulator\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,21 @@
{
"Nodes": [
{
"NodeId": "N001",
"Id": "N001",
"Name": "UNLOADER",
"Position": "65, 229",
"Type": 2,
"CanDocking": true,
"DockDirection": 2,
"ConnectedNodes": [
"N002"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:44.9548285+09:00",
"ModifiedDate": "2025-12-08T11:54:03.1730939+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Red",
"RfidId": "0001",
@@ -38,20 +40,22 @@
"DisplayText": "N001 - UNLOADER - [0001]"
},
{
"NodeId": "N002",
"Id": "N002",
"Name": "N002",
"Position": "190, 230",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N003",
"N001"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:48.2957516+09:00",
"ModifiedDate": "2025-12-08T11:54:05.9083675+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0002",
@@ -76,20 +80,22 @@
"DisplayText": "N002 - N002 - [0002]"
},
{
"NodeId": "N003",
"Id": "N003",
"Name": "N003",
"Position": "296, 266",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N004",
"N002"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:49.2226656+09:00",
"ModifiedDate": "2025-12-08T11:54:07.7966472+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0003",
@@ -114,10 +120,11 @@
"DisplayText": "N003 - N003 - [0003]"
},
{
"NodeId": "N004",
"Id": "N004",
"Name": "N004",
"Position": "388, 330",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N022",
@@ -125,11 +132,12 @@
"N011",
"N003"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:50.1681027+09:00",
"ModifiedDate": "2025-12-08T11:54:09.4014966+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0004",
@@ -154,20 +162,22 @@
"DisplayText": "N004 - N004 - [0004]"
},
{
"NodeId": "N006",
"Id": "N006",
"Name": "N006",
"Position": "530, 220",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N007",
"N022"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:51.1111368+09:00",
"ModifiedDate": "2025-12-08T11:54:49.3718611+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0013",
@@ -192,20 +202,22 @@
"DisplayText": "N006 - N006 - [0013]"
},
{
"NodeId": "N007",
"Id": "N007",
"Name": "N007",
"Position": "589, 184",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N019",
"N006"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:51.9266982+09:00",
"ModifiedDate": "2025-12-08T11:54:51.4185821+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0014",
@@ -230,20 +242,22 @@
"DisplayText": "N007 - N007 - [0014]"
},
{
"NodeId": "N008",
"Id": "N008",
"Name": "N008",
"Position": "282, 452",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N009",
"N031"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:53.9595825+09:00",
"ModifiedDate": "2025-12-08T11:54:13.626916+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0009",
@@ -268,20 +282,22 @@
"DisplayText": "N008 - N008 - [0009]"
},
{
"NodeId": "N009",
"Id": "N009",
"Name": "N009",
"Position": "183, 465",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N010",
"N008"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:54.5035702+09:00",
"ModifiedDate": "2025-12-08T11:54:15.2395639+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0010",
@@ -306,19 +322,21 @@
"DisplayText": "N009 - N009 - [0010]"
},
{
"NodeId": "N010",
"Id": "N010",
"Name": "TOPS",
"Position": "52, 466",
"Type": 2,
"Type": 3,
"CanDocking": true,
"DockDirection": 2,
"ConnectedNodes": [
"N009"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:55.0563237+09:00",
"ModifiedDate": "2025-12-08T11:54:17.075895+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Red",
"RfidId": "0011",
@@ -343,21 +361,23 @@
"DisplayText": "N010 - TOPS - [0011]"
},
{
"NodeId": "N011",
"Id": "N011",
"Name": "N011",
"Position": "481, 399",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N012",
"N015",
"N004"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:55.8875335+09:00",
"ModifiedDate": "2025-12-08T11:54:30.5451017+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0005",
@@ -382,20 +402,22 @@
"DisplayText": "N011 - N011 - [0005]"
},
{
"NodeId": "N012",
"Id": "N012",
"Name": "N012",
"Position": "559, 464",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N013",
"N011"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:56.3678144+09:00",
"ModifiedDate": "2025-12-08T11:54:32.3566504+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0006",
@@ -420,20 +442,22 @@
"DisplayText": "N012 - N012 - [0006]"
},
{
"NodeId": "N013",
"Id": "N013",
"Name": "N013",
"Position": "640, 513",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N014",
"N012"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:56.8390845+09:00",
"ModifiedDate": "2025-12-08T11:54:34.1608589+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0007",
@@ -458,19 +482,21 @@
"DisplayText": "N013 - N013 - [0007]"
},
{
"NodeId": "N014",
"Id": "N014",
"Name": "LOADER",
"Position": "728, 573",
"Type": 2,
"Type": 1,
"CanDocking": true,
"DockDirection": 2,
"ConnectedNodes": [
"N013"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:34:57.2549726+09:00",
"ModifiedDate": "2025-12-08T11:54:36.2103863+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Red",
"RfidId": "0008",
@@ -495,19 +521,21 @@
"DisplayText": "N014 - LOADER - [0008]"
},
{
"NodeId": "N019",
"Id": "N019",
"Name": "CHARGER #2",
"Position": "679, 199",
"Type": 3,
"Type": 5,
"CanDocking": true,
"DockDirection": 1,
"ConnectedNodes": [
"N007"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:35:56.5359098+09:00",
"ModifiedDate": "2025-12-08T11:54:53.4706808+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Magenta",
"RfidId": "0015",
@@ -532,21 +560,23 @@
"DisplayText": "N019 - CHARGER #2 - [0015]"
},
{
"NodeId": "N022",
"Id": "N022",
"Name": "N022",
"Position": "461, 267",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N023",
"N004",
"N006"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T08:36:48.0311551+09:00",
"ModifiedDate": "2025-12-08T11:54:47.1279261+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0012",
@@ -571,20 +601,22 @@
"DisplayText": "N022 - N022 - [0012]"
},
{
"NodeId": "N023",
"Id": "N023",
"Name": "N023",
"Position": "418, 206",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N024",
"N022"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T09:41:36.8738794+09:00",
"ModifiedDate": "2025-12-08T11:55:02.2993636+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0016",
@@ -609,20 +641,22 @@
"DisplayText": "N023 - N023 - [0016]"
},
{
"NodeId": "N024",
"Id": "N024",
"Name": "N024",
"Position": "476, 141",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N025",
"N023"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T09:41:37.4551853+09:00",
"ModifiedDate": "2025-12-08T11:55:00.0899323+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0017",
@@ -647,20 +681,22 @@
"DisplayText": "N024 - N024 - [0017]"
},
{
"NodeId": "N025",
"Id": "N025",
"Name": "N025",
"Position": "548, 99",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N026",
"N024"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-11T09:41:38.0142374+09:00",
"ModifiedDate": "2025-12-08T11:54:57.8459375+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0018",
@@ -685,19 +721,21 @@
"DisplayText": "N025 - N025 - [0018]"
},
{
"NodeId": "N026",
"Id": "N026",
"Name": "CHARGER #1",
"Position": "670, 88",
"Type": 3,
"Type": 5,
"CanDocking": true,
"DockDirection": 1,
"ConnectedNodes": [
"N025"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-11T09:41:38.5834487+09:00",
"ModifiedDate": "2025-12-08T11:54:55.6038784+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Magenta",
"RfidId": "0019",
@@ -722,17 +760,19 @@
"DisplayText": "N026 - CHARGER #1 - [0019]"
},
{
"NodeId": "LBL001",
"Id": "LBL001",
"Name": "Amkor Technology Korea",
"Position": "183, 103",
"Type": 4,
"DockDirection": 0,
"Type": 6,
"CanDocking": false,
"DockDirection": 2,
"ConnectedNodes": [],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-11T11:08:22.4048927+09:00",
"ModifiedDate": "2025-10-30T17:14:35.0184087+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Purple",
"RfidId": "",
@@ -757,17 +797,19 @@
"DisplayText": "LBL001 - Amkor Technology Korea"
},
{
"NodeId": "IMG001",
"Id": "IMG001",
"Name": "logo",
"Position": "633, 310",
"Type": 5,
"DockDirection": 0,
"Type": 7,
"CanDocking": false,
"DockDirection": 1,
"ConnectedNodes": [],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-11T11:08:44.7897541+09:00",
"ModifiedDate": "2025-10-23T12:21:16.7786615+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Brown",
"RfidId": "",
@@ -792,20 +834,22 @@
"DisplayText": "IMG001 - logo"
},
{
"NodeId": "N015",
"Id": "N015",
"Name": "",
"Position": "448, 476",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N016",
"N011"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:47.8065756+09:00",
"ModifiedDate": "2025-12-08T11:54:28.0157957+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0037",
@@ -830,20 +874,22 @@
"DisplayText": "N015 - [0037]"
},
{
"NodeId": "N016",
"Id": "N016",
"Name": "",
"Position": "425, 524",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N017",
"N015"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:48.6628848+09:00",
"ModifiedDate": "2025-12-08T11:54:26.4584771+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0036",
@@ -868,20 +914,22 @@
"DisplayText": "N016 - [0036]"
},
{
"NodeId": "N017",
"Id": "N017",
"Name": "",
"Position": "389, 559",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N018",
"N016"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:49.8138877+09:00",
"ModifiedDate": "2025-12-08T11:54:24.9652264+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0035",
@@ -906,21 +954,23 @@
"DisplayText": "N017 - [0035]"
},
{
"NodeId": "N018",
"Id": "N018",
"Name": "",
"Position": "315, 562",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N030",
"N017",
"N005"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:50.6790623+09:00",
"ModifiedDate": "2025-12-08T11:54:23.246666+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0034",
@@ -945,21 +995,23 @@
"DisplayText": "N018 - [0034]"
},
{
"NodeId": "N005",
"Id": "N005",
"Name": "",
"Position": "227, 560",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N020",
"N029",
"N018"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:51.5267199+09:00",
"ModifiedDate": "2025-12-08T11:54:21.6078399+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0033",
@@ -984,21 +1036,23 @@
"DisplayText": "N005 - [0033]"
},
{
"NodeId": "N020",
"Id": "N020",
"Name": "",
"Position": "142, 557",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N021",
"N028",
"N005"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:52.3666114+09:00",
"ModifiedDate": "2025-12-08T11:54:20.2707567+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0032",
@@ -1023,20 +1077,22 @@
"DisplayText": "N020 - [0032]"
},
{
"NodeId": "N021",
"Id": "N021",
"Name": "",
"Position": "60, 559",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N027",
"N020"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:53.0958619+09:00",
"ModifiedDate": "2025-12-08T11:54:18.362104+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0031",
@@ -1061,19 +1117,21 @@
"DisplayText": "N021 - [0031]"
},
{
"NodeId": "N027",
"Id": "N027",
"Name": "BUF1",
"Position": "61, 645",
"Type": 2,
"Type": 4,
"CanDocking": true,
"DockDirection": 2,
"ConnectedNodes": [
"N021"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:54.7345704+09:00",
"ModifiedDate": "2025-12-08T11:54:43.1573737+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Green",
"RfidId": "0041",
@@ -1098,19 +1156,21 @@
"DisplayText": "N027 - BUF1 - [0041]"
},
{
"NodeId": "N028",
"Id": "N028",
"Name": "BUF2",
"Position": "141, 643",
"Type": 2,
"Type": 4,
"CanDocking": true,
"DockDirection": 2,
"ConnectedNodes": [
"N020"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:55.5263512+09:00",
"ModifiedDate": "2025-12-08T11:54:41.5219283+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Green",
"RfidId": "0040",
@@ -1135,19 +1195,21 @@
"DisplayText": "N028 - BUF2 - [0040]"
},
{
"NodeId": "N029",
"Id": "N029",
"Name": "BUF3",
"Position": "229, 638",
"Type": 2,
"Type": 4,
"CanDocking": true,
"DockDirection": 2,
"ConnectedNodes": [
"N005"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:56.6623294+09:00",
"ModifiedDate": "2025-12-08T11:54:39.9945926+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Green",
"RfidId": "0039",
@@ -1172,19 +1234,21 @@
"DisplayText": "N029 - BUF3 - [0039]"
},
{
"NodeId": "N030",
"Id": "N030",
"Name": "BUF4",
"Position": "316, 638",
"Type": 2,
"Type": 4,
"CanDocking": true,
"DockDirection": 2,
"ConnectedNodes": [
"N018"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": true,
"NodeAlias": "",
"CreatedDate": "2025-09-12T17:22:57.5510908+09:00",
"ModifiedDate": "2025-12-08T11:54:38.3326835+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Green",
"RfidId": "0038",
@@ -1209,20 +1273,22 @@
"DisplayText": "N030 - BUF4 - [0038]"
},
{
"NodeId": "N031",
"Id": "N031",
"Name": "",
"Position": "337, 397",
"Type": 0,
"CanDocking": false,
"DockDirection": 0,
"ConnectedNodes": [
"N004",
"N008"
],
"CanRotate": false,
"StationId": "",
"StationType": null,
"CanTurnLeft": true,
"CanTurnRight": true,
"DisableCross": false,
"NodeAlias": "",
"CreatedDate": "2025-09-15T11:18:40.5366059+09:00",
"ModifiedDate": "2025-12-08T11:54:12.0214781+09:00",
"ModifiedDate": "2025-12-09T08:41:57.1293017+09:00",
"IsActive": true,
"DisplayColor": "Cyan",
"RfidId": "0030",
@@ -1251,6 +1317,6 @@
"BackgroundColorArgb": -14671840,
"ShowGrid": false
},
"CreatedDate": "2025-12-08T11:55:04.9060276+09:00",
"CreatedDate": "2025-12-09T17:00:29.7458276+09:00",
"Version": "1.1"
}

1219
Cs_HMI/Data/NewMap.json Normal file

File diff suppressed because it is too large Load Diff

1135
Cs_HMI/Data/NewMap_2.json Normal file

File diff suppressed because it is too large Load Diff

1005
Cs_HMI/Data/NewMap_3.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -42,7 +42,7 @@
<DebugType>Full</DebugType>
<Optimize>False</Optimize>
<OutputPath>..\..\..\..\..\Amkor\AGV4\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DefineConstants>TRACE;DEBUG;NOSPEECH</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
@@ -99,7 +99,7 @@
<GenerateSerializationAssemblies>Auto</GenerateSerializationAssemblies>
</PropertyGroup>
<ItemGroup>
<Reference Include="arCommUtil, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<Reference Include="arCommUtil, Version=25.11.25.2000, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\DLL\arCommUtil.dll</HintPath>
</Reference>
@@ -116,8 +116,7 @@
<HintPath>C:\Program Files (x86)\Microsoft SDKs\Speech\v11.0\Assembly\Microsoft.Speech.dll</HintPath>
</Reference>
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
<Private>True</Private>
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="PresentationCore" />
<Reference Include="System" />
@@ -187,7 +186,10 @@
<Compile Include="Device\BMS.cs" />
<Compile Include="Device\BMSInformationEventArgs.cs" />
<Compile Include="Device\CFlag.cs" />
<Compile Include="Device\xbee.cs" />
<Compile Include="Device\BMSSerialComm.cs" />
<Compile Include="Device\xbee.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Device\Socket.cs" />
<Compile Include="Dialog\fCounter.cs">
<SubType>Form</SubType>
@@ -195,6 +197,12 @@
<Compile Include="Dialog\fCounter.Designer.cs">
<DependentUpon>fCounter.cs</DependentUpon>
</Compile>
<Compile Include="Dialog\fXbeeSetting.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Dialog\fXbeeSetting.Designer.cs">
<DependentUpon>fXbeeSetting.cs</DependentUpon>
</Compile>
<Compile Include="Dialog\fStateMachineDebug.cs">
<SubType>Form</SubType>
</Compile>
@@ -225,9 +233,6 @@
<Compile Include="Dialog\fSystem.Designer.cs">
<DependentUpon>fSystem.cs</DependentUpon>
</Compile>
<Compile Include="Dialog\DriveDetector.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Dialog\fVolume.cs">
<SubType>Form</SubType>
</Compile>
@@ -284,6 +289,33 @@
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="PUB.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">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_BUFFER_IN.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_BUFFER_OUT.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_CHGOFF.cs">
<SubType>Form</SubType>
</Compile>
@@ -305,12 +337,6 @@
<Compile Include="StateMachine\Step\_SM_RUN_GOHOME.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_GOUP.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_GODOWN.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\Step\_SM_RUN_SYNC.cs">
<SubType>Form</SubType>
</Compile>
@@ -341,9 +367,6 @@
<Compile Include="StateMachine\_SPS.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Device\_DeviceManagement.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="StateMachine\_Loop.cs">
<SubType>Form</SubType>
</Compile>
@@ -401,6 +424,9 @@
<EmbeddedResource Include="Dialog\fCounter.resx">
<DependentUpon>fCounter.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fXbeeSetting.resx">
<DependentUpon>fXbeeSetting.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fUpdateForm.resx">
<DependentUpon>fUpdateForm.cs</DependentUpon>
</EmbeddedResource>

View File

@@ -222,8 +222,6 @@ namespace Project
#region "Charge"
[Browsable(false)]
public int chargerpos { get; set; }
[Browsable(false)]
public int ChargetWaitSec { get; set; }
[Browsable(false)]
public int ChargeEmergencyLevel { get; set; }
@@ -240,29 +238,27 @@ namespace Project
[Browsable(false)]
public int ChargeSearchTime { get; set; }
[Category("Charge"), DisplayName("Low Battery Limit"), Description("배터리 부족 경고 알림 기준 (%)")]
public int BatteryLimit_Low { get; set; }
#endregion
#region "AGV"
public bool AutoModeOffAndClearPosition { get; set; }
[Browsable(false)]
public UInt16 GDSValue { get; set; }
[Browsable(false)]
public string musicfile { get; set; }
/// <summary>
/// FVI로 가능 방향이 Backward 인가?
/// Forward 방향이라면 false 를 한다
/// </summary>
[Browsable(false)]
public Boolean AGV_Direction_FVI_Backward { get; set; }
[Browsable(false)]
public Boolean Enable_Speak { get; set; }
//public Boolean Disable_BMS { get; set; }
//public Boolean Log_BMS_Tx { get; set; }
//public Boolean Log_BMS_Rx { get; set; }
//public double ChargeLimitLow { get; set; }
//public double ChargeLimitHigh { get; set; }
[Browsable(false)]
public string AGV_PANID { get; set; }
@@ -328,7 +324,12 @@ namespace Project
public Single interval_xbe { get; set; }
[Browsable(false)]
public int interval_bms { get; set; }
public int doorSoundTerm { get; set; }
/// <summary>
/// 스피커출력 간격
/// </summary>
[DisplayName("Speak Term")]
public int alarmSoundTerm { get; set; }
[Browsable(false)]
public int musicvol { get; set; }
@@ -336,6 +337,16 @@ namespace Project
public bool Enable_Music { get; set; }
#endregion
#region "Node Mapping"
[Category("Node Mapping"),DisplayName("Charger")]
public string NodeMAP_RFID_Charger { get; set; }
[Category("Node Mapping"), DisplayName("Home")]
public string NodeMAP_RFID_Home { get; set; }
#endregion
[Browsable(false)]
public string LastMapFile { get; set; }
@@ -366,6 +377,7 @@ namespace Project
if (StatusInterval < 10) StatusInterval = 300; //5분간격
if (XBE_ID < 1) XBE_ID = 0xFF; //0은 acs 이므로 쓰지못하게한다.
if (SAD == 0) SAD = 999;
if (GDSValue == 0) GDSValue = 1000;
if (TAGF1A == 0)
{
TAGNOT = 9000;
@@ -416,10 +428,10 @@ namespace Project
if (ChargeEmergencyLevel == 0) ChargeEmergencyLevel = 30;
if (interval_bms == 0) interval_bms = 10;
//충전은 10분간격으로 재시도 한다
if (ChargeRetryTerm == 0) ChargeRetryTerm = 600;
if (doorSoundTerm == 0) doorSoundTerm = 15; //기본 15초
if (alarmSoundTerm == 0) alarmSoundTerm = 15; //기본 15초
if (ChargeSearchTime == 0) ChargeSearchTime = 25;
if (BatteryLimit_Low == 0) BatteryLimit_Low = 20;
//최대 충전진행 시간(기본 1시간)
if (ChargeMaxTime == 0) ChargeMaxTime = 3600;
// if (interval_iostate == 0 || interval_iostate == 255) interval_iostate = 100;

View File

@@ -22,11 +22,31 @@ namespace Project
public Color SMSG_ShadowColor = Color.Transparent;
public float SMSG_ProgressValue = 0;
public string SMSG_Tag = string.Empty;
//public event EventHandler SMSG_Update;
//public void UpdateStatusMessage()
//{
// SMSG_Update?.Invoke(null, null);
//}
/// <summary>
/// 이동대상위치(상차,하차,충전)
/// </summary>
private ePosition _targetPos = ePosition.NONE;
public string result_message = "";
public double result_progressmax = 0;
public double result_progressvalue = 0;
public DateTime StopMessageTimePLC = DateTime.Parse("1982-11-23");
public DateTime StopMessageTimeSWR = DateTime.Parse("1982-11-23");
public string StopMessagePLC = string.Empty;
public string StopMessageSWR = string.Empty;
private ePosition _currentpos = ePosition.NONE;
private string _currentposcw = string.Empty;
public ushort LastTAG { get; set; } = 0;
public ePosition NextPos = ePosition.NONE;
private char _comandKit { get; set; }
public string Memo;
public eResult ResultCode { get; set; }
public eECode ResultErrorCode;
public string ResultMessage { get; set; }
public Boolean isError { get; set; }
public int retry = 0;
public DateTime retryTime;
public Device.Socket.Message RecvMessage;
/// <summary>
/// 작업시작시간
/// </summary>
@@ -52,9 +72,6 @@ namespace Project
}
}
//public DateTime ChargeStartTime = DateTime.Parse("1982-11-23");
#region "AGV Status Value"
public string PLC1_RawData { get; set; }
public string PLC2_RawData { get; set; }
@@ -62,26 +79,7 @@ namespace Project
#endregion
/// <summary>
/// 이동대상위치(상차,하차,충전)
/// </summary>
private ePosition _targetPos = ePosition.NONE;
public event EventHandler TargetPosSet;
public string result_message = "";
public double result_progressmax = 0;
public double result_progressvalue = 0;
public DateTime StopMessageTimePLC = DateTime.Parse("1982-11-23");
public DateTime StopMessageTimeSWR = DateTime.Parse("1982-11-23");
public string StopMessagePLC = string.Empty;
public string StopMessageSWR = string.Empty;
//public DateTime LastChar
//geTime = DateTime.Parse("1982-11-23");
public ePosition NextPos = ePosition.NONE;
public ePosition TargetPos
{
get
@@ -91,13 +89,11 @@ namespace Project
set
{
_targetPos = value;
TargetPosSet?.Invoke(this, null);
PUB.log.Add(string.Format("대상위치설정:{0}", value));
}
}
private char _comandKit { get; set; }
public char CommandKit
{
get
@@ -112,28 +108,7 @@ namespace Project
}
}
//private ePosition _currentPos = ePosition.NONE;
//public ePosition CurrentPos
//{
// get
// {
// return _currentPos;
// }
// set
// {
// if (_currentPos != value) //값이바뀔떄만 메세지 220628
// PUB.log.Add(string.Format("현재위치 설정:{0}->{1}", _currentPos, value));
// _currentPos = value;
// }
//}
private ePosition _currentpos = ePosition.NONE;
private string _currentposcw = string.Empty;
// public ePosition LastPos = ePosition.NONE;
public string LastTAG { get; set; } = string.Empty;
public ePosition CurrentPos
{
get
@@ -164,11 +139,7 @@ namespace Project
_currentposcw = value;
}
}
public string Memo;
public eResult ResultCode { get; set; }
public eECode ResultErrorCode;
public string ResultMessage { get; set; }
#region "SetResultMessage"
@@ -206,14 +177,7 @@ namespace Project
#endregion
public Boolean isError { get; set; }
public int retry = 0;
public DateTime retryTime;
public Device.Socket.Message RecvMessage;
public CResult()
{
this.Clear();

View File

@@ -51,8 +51,7 @@ namespace Project
public static string AGV연결실패 { get { return string.Format("AGV {0}", ); } }
public static string = "이동 예측이 동작하지 않습니다. 개발부서에 문의 하세요";
public static string = "목적지 이동 이 완료 되었습니다";
public static string = "홈검색을시작합니다";
public static string FindCurrentPisition = "현재 위치를 검색 합니다";
public static string = "상차";
public static string = "하차";
@@ -70,8 +69,8 @@ namespace Project
public static string = "커버를 올립니다";
public static string = "홈 위치로 이동 합니다";
public static string = "하차 작업이 완료 되었습니다";
public static string = "상차 작업이 완료 되었습니다";
public static string = "버퍼 도킹이 완료 되었습니다";
public static string = "버퍼 도킹이 해제 되었습니다";
public static string = "커버 업 대기 상태가 아닙니다";
public static string QC이동버튼은상하차에서만사용가능합니다 = "QC이동 버튼은 상,하차 에서만 사용 가능합니다";
public static string QA이동버튼은상하차에서만사용가능합니다 = "QA이동 버튼은 상,하차 에서만 사용 가능합니다";

View File

@@ -9,12 +9,12 @@ using System.CodeDom;
namespace arDev
{
public class BMS : arRS232
public class BMS : BMSSerialComm
{
public BMS()
{
MinRecvLength = 34;
}
/// <summary>
@@ -55,28 +55,60 @@ namespace arDev
{
tempBuffer.Add(incomByte);
var queylen = QueryIndex == 0 ? 34 : 23;
if (tempBuffer.Count == queylen)
if (tempBuffer.Count > 7)
{
if (incomByte != 0x77)
byte len = tempBuffer[3];
if (tempBuffer.Count >= 4 + len + 3) // Start+Reg+Status+Len + Data + Chk(2) + End
{
//종단기호가 맞지 않다. 이자료는 폐기한다.
var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2")));
RaiseMessage(MessageType.Error, $"discard : {hexstr}");
tempBuffer.Clear();
if (tempBuffer.Last() == 0x77)
{
//데이터가 맞게 수신됨
LastReceiveBuffer = tempBuffer.ToArray();
bComplete = true;
}
else
{
//종단기호가 맞지 않다. 이자료는 폐기한다.
var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2")));
RaiseMessage(MessageType.Error, $"discard : {hexstr}");
tempBuffer.Clear();
}
findSTX = false;
}
else
{
//데이터가 맞게 수신됨
LastReceiveBuffer = tempBuffer.ToArray();
bComplete = true;
}
findSTX = false;
}
else
{
//아직 모자르므로 대기한다
}
// [22 - 12 - 27 14:32:49] open: True
//[22 - 12 - 27 14:32:49] Send: DD A5 03 00 FF FD 77 0D
//[22 - 12 - 27 14:32:50] Send: DD A5 03 00 FF FD 77 0D
//[22 - 12 - 27 14:32:50] Recv: 26.61v,81.4 %
//[22 - 12 - 27 14:32:50] Recv: 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
//[22 - 12 - 27 14:32:50] Send: DD A5 03 00 FF FD 77 0D
//[22 - 12 - 27 14:32:51] Recv: 26.61v,81.4 %
//[22 - 12 - 27 14:32:51] Recv: 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 queylen = QueryIndex == 0 ? 34 : 23;
//if (tempBuffer.Count == queylen)
//{
// if (incomByte != 0x77)
// {
// //종단기호가 맞지 않다. 이자료는 폐기한다.
// var hexstr = string.Join(" ", tempBuffer.Select(t => t.ToString("X2")));
// RaiseMessage(MessageType.Error, $"discard : {hexstr}");
// tempBuffer.Clear();
// }
// else
// {
// //데이터가 맞게 수신됨
// LastReceiveBuffer = tempBuffer.ToArray();
// bComplete = true;
// }
// findSTX = false;
//}
//else
//{
// //아직 모자르므로 대기한다
//}
}
}
}
@@ -106,7 +138,7 @@ namespace arDev
else
{
var rxstr = string.Join(" ", data.Select(t => t.ToString("X2")));
RaiseMessage(MessageType.Recv, $"Querh:{QueryIndex},Data:{rxstr}" );
RaiseMessage(MessageType.Recv, rxstr);
}
if (QueryIndex == 0)
@@ -179,6 +211,13 @@ namespace arDev
batH = (UInt16)(batH | batL);
Current_Volt = (float)(batH / 100.0);
//충방전전류
Int16 batHi = (Int16)LastReceiveBuffer[6];
Int16 batLi = (Int16)LastReceiveBuffer[7];
batHi = (Int16)(batHi << 8);
batHi = (Int16)(batHi | batLi);
Charge_Amp = (float)(batHi / 100.0);
//잔량확인
batH = (UInt16)LastReceiveBuffer[8];
batL = (UInt16)LastReceiveBuffer[9];
@@ -224,107 +263,80 @@ namespace arDev
return false;
}
}
private bool _autocharge = false;
public Boolean AutoCharge
{
get { return _autocharge; }
set { _autocharge = false; }
}
//public void ClearManualChargeCheckValue()
//{
// chk_timee = new DateTime(1982, 11, 23);
// chk_times = new DateTime(1982, 11, 23);
// chk_valuee = 0f;
// chk_values = 0f;
//}
/// <summary>
/// 현재 충전중인지?
/// </summary>
public bool IsCharging { get; private set; }
DateTime ChargeStart = DateTime.Now;
DateTime ChargeEnd = DateTime.Now;
void CheckManualCharge()
{
if (AutoCharge)
//충방전전력이 1보다 크면 충전으로 한다.
if (Charge_Amp > 0.1)
{
if (chk_timee.Year != 1982)
//기존에 충전상태가 OFF였다면 충전중으로 알려준다
if (IsCharging == false)
{
chk_timee = new DateTime(1982, 11, 23);
chk_valuee = 999f;
IsCharging = true;
ChargeStart = DateTime.Now;
ChargeEnd = new DateTime(1982, 11, 23);
try
{
ChargeDetect?.Invoke(this, new ChargetDetectArgs(ChargeStart, true, Current_Level));
}
catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
}
if (chk_times.Year != 1982)
else
{
chk_times = new DateTime(1982, 11, 23);
chk_values = 999f;
//충전상태가 유지되고 있다.
}
}
if (chk_times.Year == 1982)
{
chk_times = DateTime.Now;
chk_values = Current_Level;
}
else
{
if (chk_timee.Year == 1982)
//충전이해제되었다.. 단 바로 해제하지않고 1초정도 텀을 주고 OFF한다.
if (IsCharging)
{
if ((Current_Level - chk_values) >= 0.1)
if (ChargeEnd.Year == 1982)
{
//충전중이다
chk_timee = DateTime.Now;
chk_valuee = Current_Level;
try
{
ChargeDetect?.Invoke(this, new ChargetDetectArgs(chk_times, chk_values, chk_timee, chk_valuee));
}
catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
}
else if ((Current_Level - chk_values) <= -0.1)
{
//방전중이다
if (chk_times.Year != 1982) chk_times = new DateTime(1982, 11, 23);
if (chk_timee.Year != 1982) chk_timee = new DateTime(1982, 11, 23);
ChargeEnd = DateTime.Now;
}
else
{
//아직 변화가 없으니 종료일을 기록하지 않는다
var ts = DateTime.Now - ChargeEnd;
if (ts.TotalSeconds > 2) //충전종료시그널후 2초후에 충전off를 알린다.
{
ChargeEnd = DateTime.Now;
IsCharging = false;
try
{
ChargeDetect?.Invoke(this, new ChargetDetectArgs(ChargeEnd, false, Current_Level));
}
catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
}
}
}
else
{
//이미 종료일이 셋팅된 상태이다
if ((Current_Level - chk_valuee) >= 0.1)
{
//종료시간을 시작값에 넣는다
chk_times = chk_timee;
chk_values = chk_valuee;
chk_timee = DateTime.Now;
chk_valuee = Current_Level;
try
{
ChargeDetect?.Invoke(this, new ChargetDetectArgs(chk_times, chk_values, chk_timee, chk_valuee));
}
catch (Exception ex) { RaiseMessage(MessageType.Error, ex.Message); }
}
else if ((Current_Level - chk_valuee) <= -0.1)
{
//방전중이다
if (chk_times.Year != 1982) chk_times = new DateTime(1982, 11, 23);
if (chk_timee.Year != 1982) chk_timee = new DateTime(1982, 11, 23);
}
else
{
//아직 변화가 없으니 종료일을 기록하지 않는다
}
//방전상태가 유지되고 있다.
}
}
}
}
public DateTime chk_times { get; set; } = new DateTime(1982, 11, 23);
public DateTime chk_timee { get; set; } = new DateTime(1982, 11, 23);
public float chk_values { get; set; } = 0f;
public float chk_valuee { get; set; } = 0f;
public float Charge_Amp { get; set; } = 0f;
public Int16 Charge_watt
{
get
{
return (Int16)((Charge_Amp) * Current_Volt);
}
}
/// <summary>
/// 전압
/// </summary>
@@ -407,7 +419,8 @@ namespace arDev
cmd.Add(0xFF);
cmd.Add(0xFD);
cmd.Add(0x77);
cmd.Add(0x0D);
//cmd.Add(0x0D);
//_device.DiscardInBuffer();
return WriteData(cmd.ToArray());
}
@@ -423,7 +436,8 @@ namespace arDev
cmd.Add(0xFF);
cmd.Add(0xFC);
cmd.Add(0x77);
cmd.Add(0x0D);
//cmd.Add(0x0D);
//_device.DiscardInBuffer();
return WriteData(cmd.ToArray());
}

View File

@@ -4,16 +4,14 @@ namespace arDev
{
public class ChargetDetectArgs : EventArgs
{
public DateTime times { get; set; }
public DateTime timee { get; set; }
public float values { get; set; }
public float valuee { get; set; }
public ChargetDetectArgs(DateTime times, float values, DateTime timee, float valuee)
public DateTime time { get; set; }
public float level { get; set; }
public bool Detected { get; set; }
public ChargetDetectArgs(DateTime times, bool detected, float values)
{
this.times = times;
this.times = timee;
this.values = values;
this.valuee = valuee;
this.time = times;
this.level = values;
this.Detected = detected;
}
}
public class BMSInformationEventArgs : EventArgs

View File

@@ -0,0 +1,607 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
namespace arDev
{
public abstract class BMSSerialComm : ISerialComm, IDisposable
{
protected System.IO.Ports.SerialPort _device;
protected ManualResetEvent _mre;
protected byte[] LastReceiveBuffer = new byte[] { };
/// <summary>
/// 최종 전송 메세지
/// </summary>
public byte[] lastSendBuffer = new byte[] { };
//public int ValidCheckTimeMSec { get; set; } = 5000;
protected List<byte> tempBuffer = new List<byte>();
protected Boolean findSTX = false;
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 float ScanInterval { get; set; }
// public byte[] LastRecvData;
public string LastRecvString
{
get
{
if (LastReceiveBuffer == null) return String.Empty;
else return System.Text.Encoding.Default.GetString(LastReceiveBuffer);
}
}
/// <summary>
/// 마지막으로 데이터를 받은 시간
/// </summary>
public DateTime lastRecvTime;
public int WriteError = 0;
public string WriteErrorMessage = string.Empty;
public int WaitTimeout { get; set; } = 1000;
public int MinRecvLength { get; set; } = 1;
// Polling Thread related
protected Thread _recvThread;
protected volatile bool _isReading = false;
/// <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 BMSSerialComm()
{
_device = new System.IO.Ports.SerialPort();
this.BaudRate = 9600;
ScanInterval = 10;
// _device.DataReceived += barcode_DataReceived; // Removed event handler
_device.ErrorReceived += this.barcode_ErrorReceived;
_device.WriteTimeout = 3000;
_device.ReadTimeout = 3000;
_device.ReadBufferSize = 8192;
_device.WriteBufferSize = 8192;
//_device.DiscardInBuffer();
//_device.DiscardOutBuffer();
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);
}
~BMSSerialComm()
{
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.
//
}
// Stop reading thread
_isReading = false;
// _device.DataReceived -= barcode_DataReceived; // Removed event handler
_device.ErrorReceived -= this.barcode_ErrorReceived;
if (_recvThread != null && _recvThread.IsAlive)
{
_recvThread.Join(500);
}
if (_device != null)
{
if (_device.IsOpen) _device.Close();
_device.Dispose();
}
// Free any unmanaged objects here.
//
disposed = true;
}
public Boolean Open()
{
try
{
if (_device.IsOpen == false)
{
_device.Open();
}
if (_device.IsOpen)
{
// Start polling thread
if (_isReading == false)
{
_isReading = true;
_recvThread = new Thread(ReadPort);
_recvThread.IsBackground = true;
_recvThread.Start();
}
return true;
}
return false;
}
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 bool Close()
{
try
{
_isReading = false; // Stop thread loop
if (_recvThread != null && _recvThread.IsAlive)
{
if (!_recvThread.Join(500)) // Wait for thread to finish
{
// _recvThread.Abort(); // Avoid Abort if possible
}
}
if (_device != null && _device.IsOpen)
{
_device.DiscardInBuffer();
_device.DiscardOutBuffer();
_device.Close(); //dispose에서는 포트를 직접 클리어하지 않게 해뒀다.
return true;
}
else return false;
}
catch (Exception)
{
return false;
}
}
protected Boolean RaiseRecvData()
{
return RaiseRecvData(LastReceiveBuffer.ToArray(), false);
}
/// <summary>
/// 수신받은 메세지를 발생 시킵니다
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
public virtual Boolean RaiseRecvData(byte[] Data, bool udpatelastbuffer)
{
//181206 - 최종수신 메세지 기록
lastRecvTime = DateTime.Now;
if (udpatelastbuffer && Data != null)
{
if (LastReceiveBuffer == null || LastReceiveBuffer.Length != Data.Length)
{
LastReceiveBuffer = new byte[Data.Length];
Array.Copy(Data, LastReceiveBuffer, Data.Length);
}
}
try
{
// UI update might need Invoke if this event handler updates UI directly,
// but usually the subscriber handles Invoke.
// Since we are running on a background thread now, subscribers must be aware.
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));
}
byte[] buffer = new byte[] { };
// Replaced with ReadPort Loop
/*
void barcode_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
try
{
int ReadCount = _device.BytesToRead;
buffer = new byte[ReadCount];
_device.Read(buffer, 0, buffer.Length);
System.Text.StringBuilder LogMsg = new StringBuilder();
byte[] remainBuffer;
Repeat:
if (CustomParser(buffer, out remainBuffer))
{
//분석완료이므로 받은 데이터를 버퍼에 기록한다
if (LastReceiveBuffer == null || (LastReceiveBuffer.Length != tempBuffer.Count))
Array.Resize(ref LastReceiveBuffer, tempBuffer.Count);
Array.Copy(tempBuffer.ToArray(), LastReceiveBuffer, tempBuffer.Count);
tempBuffer.Clear();
//수신메세지발생
RaiseRecvData();
if (remainBuffer != null && remainBuffer.Length > 0)
{
//버퍼를 변경해서 다시 전송을 해준다.
Array.Resize(ref buffer, remainBuffer.Length);
Array.Copy(remainBuffer, buffer, remainBuffer.Length);
goto Repeat; //남은 버퍼가 있다면 진행을 해준다.
}
}
}
catch (Exception ex)
{
//if (IsOpen)
//{
// //_device.DiscardInBuffer();
// //_device.DiscardOutBuffer();
//}
ErrorMessage = ex.Message;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
}
}
*/
void ReadPort()
{
while (_isReading)
{
try
{
if (_device == null || !_device.IsOpen)
{
Thread.Sleep(100);
continue;
}
int readCount = _device.BytesToRead;
if (readCount > 0)
{
byte[] buffer = new byte[readCount];
_device.Read(buffer, 0, buffer.Length);
byte[] remainBuffer;
Repeat:
if (CustomParser(buffer, out remainBuffer))
{
//분석완료이므로 받은 데이터를 버퍼에 기록한다
if (LastReceiveBuffer == null || (LastReceiveBuffer.Length != tempBuffer.Count))
Array.Resize(ref LastReceiveBuffer, tempBuffer.Count);
Array.Copy(tempBuffer.ToArray(), LastReceiveBuffer, tempBuffer.Count);
tempBuffer.Clear();
//수신메세지발생
RaiseRecvData();
if (remainBuffer != null && remainBuffer.Length > 0)
{
//버퍼를 변경해서 다시 전송을 해준다.
buffer = new byte[remainBuffer.Length]; // Reallocate buffer for remaining data
Array.Copy(remainBuffer, buffer, remainBuffer.Length);
goto Repeat; //남은 버퍼가 있다면 진행을 해준다.
}
}
}
else
{
Thread.Sleep(20); // Data 없음, 대기
}
}
catch (Exception ex)
{
// Thread 상에서 Exception 발생 시 로그 남기고 계속 진행 여부 결정
// 여기서는 에러 메시지 발생시키고 Sleep
ErrorMessage = ex.Message;
this.Message?.Invoke(this, new MessageEventArgs(ex.Message, true));
Thread.Sleep(1000);
}
}
}
#endregion
#region "External Events"
/// <summary>
/// 오류 및 기타 일반 메세지
/// </summary>
public event EventHandler<MessageEventArgs> Message;
#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
protected abstract bool CustomParser(byte[] buf, out byte[] remainBuffer);
/// <summary>
/// 포트가 열려있거나 데이터 수신시간이 없는경우 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 > (this.ScanInterval * 2.5)) return false;
return true;
}
}
protected bool WriteData(string cmd)
{
return WriteData(System.Text.Encoding.Default.GetBytes(cmd));
}
/// <summary>
/// 포트에 쓰기(barcode_DataReceived 이벤트로 메세지수신)
/// </summary>
protected Boolean WriteData(byte[] data)
{
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
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;
}
}
}

View File

@@ -14,11 +14,11 @@ using System.Windows.Forms;
namespace Project.Device
{
public class Xbee : SerialPort
public class Xbee : SerialPort, arDev.ISerialComm
{
public string buffer = string.Empty;
public System.Text.StringBuilder newbuffer = new StringBuilder();
public string errorMessage = string.Empty;
public string ErrorMessage { get; set; } = string.Empty;
public DateTime LastStatusSendTime { get; set; } = DateTime.Now;
private EEProtocol proto;
@@ -38,6 +38,8 @@ namespace Project.Device
public Xbee()
{
this.WriteTimeout = 500;
this.ReadTimeout = 500;
this.DataReceived += Xbee_DataReceived;
proto = new EEProtocol();
proto.OnDataReceived += Proto_OnDataReceived;
@@ -65,11 +67,23 @@ namespace Project.Device
}
catch (Exception ex)
{
errorMessage = ex.Message;
ErrorMessage = ex.Message;
return false;
}
}
public new bool Close()
{
try
{
base.Close();
return true;
}
catch
{
return false;
}
}
public new bool Open()
{
try
@@ -79,8 +93,8 @@ namespace Project.Device
}
catch (Exception ex)
{
errorMessage = ex.Message;
PUB.logxbee.AddE(errorMessage);
ErrorMessage = ex.Message;
PUB.logxbee.AddE(ErrorMessage);
return false;
}
}
@@ -92,19 +106,14 @@ namespace Project.Device
var cmd = e.ReceivedPacket.Command.ToString("X2");
var id = e.ReceivedPacket.ID.ToString("X2");
PUB.logxbee.Add("RX", $"{hexstrRaw}\nID:{id},CMD:{cmd},DATA:{hexstr}");
ProtocReceived?.Invoke(this, e);
}
private void Proto_OnMessage(object sender, EEProtocol.MessageEventArgs e)
{
MessageReceived?.Invoke(this, new MessageArgs(e.IsError, e.Message));
}
private void Xbee_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
var dev = sender as System.IO.Ports.SerialPort;
@@ -112,6 +121,7 @@ namespace Project.Device
dev.Read(buffer, 0, buffer.Length);
proto.ProcessReceivedData(buffer);
}
/// <summary>
/// 이동완료 신호 전송
/// </summary>
@@ -147,13 +157,28 @@ namespace Project.Device
byte cmd = (byte)ENIGProtocol.AGVCommandEH.Error;
if (errormessage.Length > 30) errormessage = errormessage.Substring(0, 29);
var data = new byte[] { (byte)errcode };
var data = new List<byte>();
data.Add((byte)errcode);
var datamsg = System.Text.Encoding.Default.GetBytes(errormessage);
data.AddRange(datamsg);
var packet = proto.CreatePacket(id, cmd, data);
var packet = proto.CreatePacket(id, cmd, data.ToArray());
Send(packet);
}
public bool BufferInReady { get; set; }
public bool BufferInComplete { get; set; }
public bool BufferOutComplete { 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; }
ManualResetEvent sendlock = new ManualResetEvent(true);
/// <summary>
@@ -161,6 +186,10 @@ namespace Project.Device
/// </summary>
public void SendStatus()
{
if (this.IsOpen == false) return;
if (sendlock.WaitOne() == false) return;
sendlock.Reset();
/*
Mode[1] : 0=manual, 1=auto
RunSt[1] : 0=stop, 1=run, 2=error
@@ -211,17 +240,17 @@ namespace Project.Device
data[5] = (byte)((VAR.BOOL[eVarBool.FLAG_CHARGEONA] || VAR.BOOL[eVarBool.FLAG_CHARGEONM]) ? 1 : 0);
// CartSt
if (PUB.AGV.signal.cart_detect1 && PUB.AGV.signal.cart_detect2)
if (PUB.AGV.signal2.cart_detect1 && PUB.AGV.signal2.cart_detect2)
data[6] = 1; // 센서두개가 모두 감지되는 경우
else if (PUB.AGV.signal.cart_detect1 == false && PUB.AGV.signal.cart_detect2 == false)
else if (PUB.AGV.signal2.cart_detect1 == false && PUB.AGV.signal2.cart_detect2 == false)
data[6] = 0; // 센서두개가 모두 감지되지 않는 경우
else
data[6] = 2; // 센서하나만 감지되는 경우
// LiftSt
if (PUB.AGV.signal.lift_up)
if (PUB.AGV.signal1.lift_up)
data[7] = 1; // 위로 올라가는 경우
else if (PUB.AGV.signal.lift_down)
else if (PUB.AGV.signal1.lift_down)
data[7] = 0; // 아래로 내려가는 경우
else
data[7] = 2; // unknown (기본값)
@@ -235,14 +264,21 @@ namespace Project.Device
var cmd = (byte)ENIGProtocol.AGVCommandEH.Status;
var packet = proto.CreatePacket(PUB.setting.XBE_ID, cmd, data);
if (Send(packet))
PUB.logxbee.AddI($"Send status {packet.Length} {packet.HexString()}");
PUB.logxbee.AddI($"Send status [O] : {packet.Length} {packet.HexString()}");
else
PUB.logxbee.AddE($"Send status [X] : {packet.Length} {packet.HexString()}");
LastStatusSendTime = DateTime.Now;
}
catch (Exception ex)
{
errorMessage = ex.Message;
PUB.logxbee.AddE(errorMessage);
ErrorMessage = ex.Message;
PUB.logxbee.AddE(ErrorMessage);
}
finally
{
sendlock.Set();
}
}

View File

@@ -1,311 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Media.Animation;
using AR;
using arCtl;
using COMM;
using Project.StateMachine;
namespace Project
{
/// <summary>
/// 장치 연결 및 상태 전송을 담당하는 별도 태스크
/// </summary>
public partial class fMain
{
// 장치 관리 태스크 관련
private Task deviceManagementTask;
private CancellationTokenSource deviceManagementCts;
private volatile bool isDeviceManagementRunning = false;
/// <summary>
/// 장치 관리 태스크 시작 (IDLE 상태 진입 시 호출)
/// </summary>
public void StartDeviceManagementTask()
{
if (isDeviceManagementRunning)
{
PUB.log.Add("DeviceManagement", "이미 실행 중입니다.");
return;
}
isDeviceManagementRunning = true;
deviceManagementCts = new CancellationTokenSource();
deviceManagementTask = Task.Factory.StartNew(
() => DeviceManagementWorker(deviceManagementCts.Token),
deviceManagementCts.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default
);
PUB.log.Add("DeviceManagement", "장치 관리 태스크 시작");
}
/// <summary>
/// 장치 관리 태스크 종료
/// File : /device/_DeviceManagement.cs
/// </summary>
public void StopDeviceManagementTask()
{
if (!isDeviceManagementRunning)
return;
isDeviceManagementRunning = false;
try
{
deviceManagementCts?.Cancel();
if (deviceManagementTask != null)
{
if (!deviceManagementTask.Wait(3000)) // 3초 대기
{
PUB.log.AddE("DeviceManagement:태스크 종료 대기 시간 초과");
}
}
}
catch (Exception ex)
{
PUB.log.AddE($"DeviceManagement 종료 중 오류: {ex.Message}");
}
finally
{
deviceManagementCts?.Dispose();
deviceManagementCts = null;
deviceManagementTask = null;
PUB.log.Add("DeviceManagement", "장치 관리 태스크 종료");
}
}
/// <summary>
/// 장치 관리 워커 (별도 태스크에서 실행)
/// - 장치 연결 관리 (AGV, XBee, BMS)
/// - 자동 상태 전송 (XBee, BMS)
/// </summary>
private void DeviceManagementWorker(CancellationToken cancellationToken)
{
PUB.log.Add("DeviceManagementWorker", "시작");
DateTime lastXbeStatusSendTime = DateTime.Now;
DateTime lastBmsQueryTime = DateTime.Now;
while (!cancellationToken.IsCancellationRequested && isDeviceManagementRunning)
{
try
{
// 상태머신이 IDLE 이상이고 CLOSING 미만일 때만 동작
if (PUB.sm.Step >= eSMStep.IDLE && PUB.sm.Step < eSMStep.CLOSING)
{
// ========== 1. 장치 연결 관리 ==========
ManageDeviceConnections();
// ========== 2. XBee 상태 전송 ==========
if (PUB.XBE != null && PUB.XBE.IsOpen)
{
var tsXbe = DateTime.Now - lastXbeStatusSendTime;
if (tsXbe.TotalSeconds >= PUB.setting.interval_xbe)
{
lastXbeStatusSendTime = DateTime.Now;
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
PUB.XBE.SendStatus();
}
catch (Exception ex)
{
PUB.log.AddE($"XBee SendStatus 오류: {ex.Message}");
}
});
}
}
// ========== 3. BMS 쿼리 및 배터리 경고 ==========
if (PUB.BMS != null && PUB.BMS.IsOpen)
{
var tsBms = DateTime.Now - lastBmsQueryTime;
if (tsBms.TotalSeconds >= PUB.setting.interval_bms)
{
lastBmsQueryTime = DateTime.Now;
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
PUB.BMS.SendQuery();
}
catch (Exception ex)
{
PUB.log.AddE($"BMS SendQuery 오류: {ex.Message}");
}
});
}
// 배터리 경고음
try
{
Update_BatteryWarnSpeak();
}
catch (Exception ex)
{
PUB.log.AddE($"BatteryWarnSpeak 오류: {ex.Message}");
}
}
}
}
catch (Exception ex)
{
PUB.log.AddE($"DeviceManagementWorker 오류: {ex.Message}");
}
// 1초 대기 (또는 취소 요청 시 즉시 종료)
try
{
Task.Delay(1000, cancellationToken).Wait();
}
catch (OperationCanceledException)
{
break;
}
}
PUB.log.Add("DeviceManagementWorker", "종료");
}
/// <summary>
/// 장치 연결 상태 관리
/// </summary>
private void ManageDeviceConnections()
{
try
{
// AGV 연결
ConnectSerialPort(PUB.AGV, PUB.setting.Port_AGV, PUB.setting.Baud_AGV,
eVarTime.LastConn_AGV, eVarTime.LastConnTry_AGV, eVarTime.LastRecv_AGV);
// XBee 연결
ConnectSerialPort(PUB.XBE, PUB.setting.Port_XBE, PUB.setting.Baud_XBE,
eVarTime.LastConn_XBE, eVarTime.LastConnTry_XBE, eVarTime.LastRecv_XBE);
// BMS 연결
if (PUB.BMS.IsOpen == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastConn_BAT);
if (ts.TotalSeconds > 3)
{
PUB.log.Add($"BMS 연결 시도: {PUB.setting.Port_BAT}");
PUB.BMS.PortName = PUB.setting.Port_BAT;
if (PUB.BMS.Open())
PUB.log.AddI($"BMS 연결 완료({PUB.setting.Port_BAT})");
VAR.TIME.Update(eVarTime.LastConn_BAT);
VAR.TIME.Update(eVarTime.LastConnTry_BAT);
}
}
else if (PUB.BMS.IsValid == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastConnTry_BAT);
if (ts.TotalSeconds > (PUB.setting.interval_bms * 2.5))
{
PUB.log.Add("BMS 자동 연결 해제 (응답 없음)");
PUB.BMS.Close();
VAR.TIME.Set(eVarTime.LastConn_BAT, DateTime.Now.AddSeconds(5));
}
}
}
catch (Exception ex)
{
PUB.log.AddE($"ManageDeviceConnections 오류: {ex.Message}");
}
}
/// <summary>
/// 시리얼 포트 연결 (arDev.arRS232)
/// </summary>
void ConnectSerialPort(arDev.arRS232 dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime)
{
if (dev.IsOpen == false && port.isEmpty() == false)
{
var tsPLC = VAR.TIME.RUN(conntry);
if (tsPLC.TotalSeconds > 5)
{
VAR.TIME.Update(conntry);
try
{
VAR.TIME.Update(recvtime);
dev.PortName = port;
dev.BaudRate = baud;
if (dev.Open())
{
PUB.log.Add(port, $"[AGV:{port}:{baud}] 연결 완료");
}
else
{
var errmessage = dev.errorMessage;
PUB.log.AddE($"[AGV:{port}:{baud}] {errmessage}");
}
VAR.TIME.Update(conn);
VAR.TIME.Update(conntry);
}
catch (Exception ex)
{
PUB.log.AddE(ex.Message);
}
}
}
else if (dev.PortName.Equals(port) == false)
{
PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료");
dev.Close();
VAR.TIME.Update(conntry);
}
}
/// <summary>
/// 시리얼 포트 연결 (Device.Xbee)
/// </summary>
void ConnectSerialPort(Device.Xbee dev, string port, int baud, eVarTime conn, eVarTime conntry, eVarTime recvtime)
{
if (dev.IsOpen == false && port.isEmpty() == false)
{
var tsPLC = VAR.TIME.RUN(conntry);
if (tsPLC.TotalSeconds > 5)
{
VAR.TIME.Update(conntry);
try
{
VAR.TIME.Update(recvtime);
dev.PortName = port;
dev.BaudRate = baud;
if (dev.Open())
{
PUB.log.Add(port, $"[XBEE:{port}:{baud}] 연결 완료");
}
else
{
var errmessage = dev.errorMessage;
PUB.log.AddE($"[XBEE:{port}:{baud}] {errmessage}");
}
VAR.TIME.Update(conn);
VAR.TIME.Update(conntry);
}
catch (Exception ex)
{
PUB.log.AddE(ex.Message);
}
}
}
else if (dev.PortName.Equals(port) == false)
{
PUB.log.Add(port, $"포트 변경({dev.PortName}->{port})으로 연결 종료");
dev.Close();
VAR.TIME[(int)conntry] = DateTime.Now;
}
}
}
}

View File

@@ -1,815 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms; // required for Message
using System.Runtime.InteropServices; // required for Marshal
using System.IO;
using Microsoft.Win32.SafeHandles;
// DriveDetector - rev. 1, Oct. 31 2007
namespace usbdetect
{
/// <summary>
/// Hidden Form which we use to receive Windows messages about flash drives
/// </summary>
internal class DetectorForm : Form
{
private Label label1;
private DriveDetector mDetector = null;
/// <summary>
/// Set up the hidden form.
/// </summary>
/// <param name="detector">DriveDetector object which will receive notification about USB drives, see WndProc</param>
public DetectorForm(DriveDetector detector)
{
mDetector = detector;
this.MinimizeBox = false;
this.MaximizeBox = false;
this.ShowInTaskbar = false;
this.ShowIcon = false;
this.FormBorderStyle = FormBorderStyle.None;
this.Load += new System.EventHandler(this.Load_Form);
this.Activated += new EventHandler(this.Form_Activated);
}
private void Load_Form(object sender, EventArgs e)
{
// We don't really need this, just to display the label in designer ...
InitializeComponent();
// Create really small form, invisible anyway.
this.Size = new System.Drawing.Size(5, 5);
}
private void Form_Activated(object sender, EventArgs e)
{
this.Visible = false;
}
/// <summary>
/// This function receives all the windows messages for this window (form).
/// We call the DriveDetector from here so that is can pick up the messages about
/// drives arrived and removed.
/// </summary>
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
if (mDetector != null)
{
mDetector.WndProc(ref m);
}
}
private void InitializeComponent()
{
this.label1 = new System.Windows.Forms.Label();
this.SuspendLayout();
//
// label1
//
this.label1.AutoSize = true;
this.label1.Location = new System.Drawing.Point(13, 30);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(377, 12);
this.label1.TabIndex = 0;
this.label1.Text = "This is invisible form. To see DriveDetector code click View Code";
//
// DetectorForm
//
this.ClientSize = new System.Drawing.Size(435, 80);
this.Controls.Add(this.label1);
this.Name = "DetectorForm";
this.ResumeLayout(false);
this.PerformLayout();
}
} // class DetectorForm
// Delegate for event handler to handle the device events
public delegate void DriveDetectorEventHandler(Object sender, DriveDetectorEventArgs e);
/// <summary>
/// Our class for passing in custom arguments to our event handlers
///
/// </summary>
public class DriveDetectorEventArgs : EventArgs
{
public DriveDetectorEventArgs()
{
Cancel = false;
Drive = "";
HookQueryRemove = false;
}
/// <summary>
/// Get/Set the value indicating that the event should be cancelled
/// Only in QueryRemove handler.
/// </summary>
public bool Cancel;
/// <summary>
/// Drive letter for the device which caused this event
/// </summary>
public string Drive;
/// <summary>
/// Set to true in your DeviceArrived event handler if you wish to receive the
/// QueryRemove event for this drive.
/// </summary>
public bool HookQueryRemove;
}
/// <summary>
/// Detects insertion or removal of removable drives.
/// Use it in 1 or 2 steps:
/// 1) Create instance of this class in your project and add handlers for the
/// DeviceArrived, DeviceRemoved and QueryRemove events.
/// AND (if you do not want drive detector to creaate a hidden form))
/// 2) Override WndProc in your form and call DriveDetector's WndProc from there.
/// If you do not want to do step 2, just use the DriveDetector constructor without arguments and
/// it will create its own invisible form to receive messages from Windows.
/// </summary>
class DriveDetector : IDisposable
{
/// <summary>
/// Events signalized to the client app.
/// Add handlers for these events in your form to be notified of removable device events
/// </summary>
public event DriveDetectorEventHandler DeviceArrived;
public event DriveDetectorEventHandler DeviceRemoved;
public event DriveDetectorEventHandler QueryRemove;
/// <summary>
/// The easiest way to use DriveDetector.
/// It will create hidden form for processing Windows messages about USB drives
/// You do not need to override WndProc in your form.
/// </summary>
public DriveDetector()
{
DetectorForm frm = new DetectorForm(this);
frm.Show(); // will be hidden immediatelly
Init(frm, null);
}
/// <summary>
/// Alternate constructor.
/// Pass in your Form and DriveDetector will not create hidden form.
/// </summary>
/// <param name="control">object which will receive Windows messages.
/// Pass "this" as this argument from your form class.</param>
public DriveDetector(Control control)
{
Init(control, null);
}
/// <summary>
/// Consructs DriveDetector object setting also path to file which should be opened
/// when registering for query remove.
/// </summary>
///<param name="control">object which will receive Windows messages.
/// Pass "this" as this argument from your form class.</param>
/// <param name="FileToOpen">Optional. Name of a file on the removable drive which should be opened.
/// If null, root directory of the drive will be opened. Opening a file is needed for us
/// to be able to register for the query remove message. TIP: For files use relative path without drive letter.
/// e.g. "SomeFolder\file_on_flash.txt"</param>
public DriveDetector(Control control, string FileToOpen)
{
Init(control, FileToOpen);
}
/// <summary>
/// init the DriveDetector object
/// </summary>
/// <param name="intPtr"></param>
private void Init(Control control, string fileToOpen)
{
mFileToOpen = fileToOpen;
mFileOnFlash = null;
mDeviceNotifyHandle = IntPtr.Zero;
mRecipientHandle = control.Handle;
mDirHandle = IntPtr.Zero; // handle to the root directory of the flash drive which we open
mCurrentDrive = "";
}
/// <summary>
/// Gets the value indicating whether the query remove event will be fired.
/// </summary>
public bool IsQueryHooked
{
get
{
if (mDeviceNotifyHandle == IntPtr.Zero)
return false;
else
return true;
}
}
/// <summary>
/// Gets letter of drive which is currently hooked. Empty string if none.
/// See also IsQueryHooked.
/// </summary>
public string HookedDrive
{
get
{
return mCurrentDrive;
}
}
/// <summary>
/// Gets the file stream for file which this class opened on a drive to be notified
/// about it's removal.
/// This will be null unless you specified a file to open (DriveDetector opens root directory of the flash drive)
/// </summary>
public FileStream OpenedFile
{
get
{
return mFileOnFlash;
}
}
/// <summary>
/// Hooks specified drive to receive a message when it is being removed.
/// This can be achieved also by setting e.HookQueryRemove to true in your
/// DeviceArrived event handler.
/// By default DriveDetector will open the root directory of the flash drive to obtain notification handle
/// from Windows (to learn when the drive is about to be removed).
/// </summary>
/// <param name="fileOnDrive">Drive letter or relative path to a file on the drive which should be
/// used to get a handle - required for registering to receive query remove messages.
/// If only drive letter is specified (e.g. "D:\\", root directory of the drive will be opened.</param>
/// <returns>true if hooked ok, false otherwise</returns>
public bool EnableQueryRemove(string fileOnDrive)
{
if (fileOnDrive == null || fileOnDrive.Length == 0)
throw new ArgumentException("Drive path must be supplied to register for Query remove.");
if ( fileOnDrive.Length == 2 && fileOnDrive[1] == ':' )
fileOnDrive += '\\'; // append "\\" if only drive letter with ":" was passed in.
if (mDeviceNotifyHandle != IntPtr.Zero)
{
// Unregister first...
RegisterForDeviceChange(false, null);
}
if (Path.GetFileName(fileOnDrive).Length == 0 ||!File.Exists(fileOnDrive))
mFileToOpen = null; // use root directory...
else
mFileToOpen = fileOnDrive;
RegisterQuery(Path.GetPathRoot(fileOnDrive));
if (mDeviceNotifyHandle == IntPtr.Zero)
return false; // failed to register
return true;
}
/// <summary>
/// Unhooks any currently hooked drive so that the query remove
/// message is not generated for it.
/// </summary>
public void DisableQueryRemove()
{
if (mDeviceNotifyHandle != IntPtr.Zero)
{
RegisterForDeviceChange(false, null);
}
}
/// <summary>
/// Unregister and close the file we may have opened on the removable drive.
/// Garbage collector will call this method.
/// </summary>
public void Dispose()
{
RegisterForDeviceChange(false, null);
}
#region WindowProc
/// <summary>
/// Message handler which must be called from client form.
/// Processes Windows messages and calls event handlers.
/// </summary>
/// <param name="m"></param>
public void WndProc(ref Message m)
{
int devType;
char c;
if (m.Msg == WM_DEVICECHANGE)
{
// WM_DEVICECHANGE can have several meanings depending on the WParam value...
switch (m.WParam.ToInt32())
{
//
// New device has just arrived
//
case DBT_DEVICEARRIVAL:
devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_VOLUME)
{
DEV_BROADCAST_VOLUME vol;
vol = (DEV_BROADCAST_VOLUME)
Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
// Get the drive letter
c = DriveMaskToLetter(vol.dbcv_unitmask);
//
// Call the client event handler
//
// We should create copy of the event before testing it and
// calling the delegate - if any
DriveDetectorEventHandler tempDeviceArrived = DeviceArrived;
if ( tempDeviceArrived != null )
{
DriveDetectorEventArgs e = new DriveDetectorEventArgs();
e.Drive = c + ":\\";
tempDeviceArrived(this, e);
// Register for query remove if requested
if (e.HookQueryRemove)
{
// If something is already hooked, unhook it now
if (mDeviceNotifyHandle != IntPtr.Zero)
{
RegisterForDeviceChange(false, null);
}
RegisterQuery(c + ":\\");
}
} // if has event handler
}
break;
//
// Device is about to be removed
// Any application can cancel the removal
//
case DBT_DEVICEQUERYREMOVE:
devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_HANDLE)
{
// TODO: we could get the handle for which this message is sent
// from vol.dbch_handle and compare it against a list of handles for
// which we have registered the query remove message (?)
//DEV_BROADCAST_HANDLE vol;
//vol = (DEV_BROADCAST_HANDLE)
// Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_HANDLE));
// if ( vol.dbch_handle ....
//
// Call the event handler in client
//
DriveDetectorEventHandler tempQuery = QueryRemove;
if (tempQuery != null)
{
DriveDetectorEventArgs e = new DriveDetectorEventArgs();
e.Drive = mCurrentDrive; // drive which is hooked
tempQuery(this, e);
// If the client wants to cancel, let Windows know
if (e.Cancel)
{
m.Result = (IntPtr)BROADCAST_QUERY_DENY;
}
else
{
// Change 28.10.2007: Unregister the notification, this will
// close the handle to file or root directory also.
// We have to close it anyway to allow the removal so
// even if some other app cancels the removal we would not know about it...
RegisterForDeviceChange(false, null); // will also close the mFileOnFlash
}
}
}
break;
//
// Device has been removed
//
case DBT_DEVICEREMOVECOMPLETE:
devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_VOLUME)
{
devType = Marshal.ReadInt32(m.LParam, 4);
if (devType == DBT_DEVTYP_VOLUME)
{
DEV_BROADCAST_VOLUME vol;
vol = (DEV_BROADCAST_VOLUME)
Marshal.PtrToStructure(m.LParam, typeof(DEV_BROADCAST_VOLUME));
c = DriveMaskToLetter(vol.dbcv_unitmask);
//
// Call the client event handler
//
DriveDetectorEventHandler tempDeviceRemoved = DeviceRemoved;
if (tempDeviceRemoved != null)
{
DriveDetectorEventArgs e = new DriveDetectorEventArgs();
e.Drive = c + ":\\";
tempDeviceRemoved(this, e);
}
// TODO: we could unregister the notify handle here if we knew it is the
// right drive which has been just removed
//RegisterForDeviceChange(false, null);
}
}
break;
}
}
}
#endregion
#region Private Area
/// <summary>
/// New: 28.10.2007 - handle to root directory of flash drive which is opened
/// for device notification
/// </summary>
private IntPtr mDirHandle = IntPtr.Zero;
/// <summary>
/// Class which contains also handle to the file opened on the flash drive
/// </summary>
private FileStream mFileOnFlash = null;
/// <summary>
/// Name of the file to try to open on the removable drive for query remove registration
/// </summary>
private string mFileToOpen;
/// <summary>
/// Handle to file which we keep opened on the drive if query remove message is required by the client
/// </summary>
private IntPtr mDeviceNotifyHandle;
/// <summary>
/// Handle of the window which receives messages from Windows. This will be a form.
/// </summary>
private IntPtr mRecipientHandle;
/// <summary>
/// Drive which is currently hooked for query remove
/// </summary>
private string mCurrentDrive;
// Win32 constants
private const int DBT_DEVTYP_DEVICEINTERFACE = 5;
private const int DBT_DEVTYP_HANDLE = 6;
private const int BROADCAST_QUERY_DENY = 0x424D5144;
private const int WM_DEVICECHANGE = 0x0219;
private const int DBT_DEVICEARRIVAL = 0x8000; // system detected a new device
private const int DBT_DEVICEQUERYREMOVE = 0x8001; // Preparing to remove (any program can disable the removal)
private const int DBT_DEVICEREMOVECOMPLETE = 0x8004; // removed
private const int DBT_DEVTYP_VOLUME = 0x00000002; // drive type is logical volume
/// <summary>
/// Registers for receiving the query remove message for a given drive.
/// We need to open a handle on that drive and register with this handle.
/// Client can specify this file in mFileToOpen or we will open root directory of the drive
/// </summary>
/// <param name="drive">drive for which to register. </param>
private void RegisterQuery(string drive)
{
bool register = true;
if (mFileToOpen == null)
{
// Change 28.10.2007 - Open the root directory if no file specified - leave mFileToOpen null
// If client gave us no file, let's pick one on the drive...
//mFileToOpen = GetAnyFile(drive);
//if (mFileToOpen.Length == 0)
// return; // no file found on the flash drive
}
else
{
// Make sure the path in mFileToOpen contains valid drive
// If there is a drive letter in the path, it may be different from the actual
// letter assigned to the drive now. We will cut it off and merge the actual drive
// with the rest of the path.
if (mFileToOpen.Contains(":"))
{
string tmp = mFileToOpen.Substring(3);
string root = Path.GetPathRoot(drive);
mFileToOpen = Path.Combine(root, tmp);
}
else
mFileToOpen = Path.Combine(drive, mFileToOpen);
}
try
{
//mFileOnFlash = new FileStream(mFileToOpen, FileMode.Open);
// Change 28.10.2007 - Open the root directory
if (mFileToOpen == null) // open root directory
mFileOnFlash = null;
else
mFileOnFlash = new FileStream(mFileToOpen, FileMode.Open);
}
catch (Exception)
{
// just do not register if the file could not be opened
register = false;
}
if (register)
{
//RegisterForDeviceChange(true, mFileOnFlash.SafeFileHandle);
//mCurrentDrive = drive;
// Change 28.10.2007 - Open the root directory
if (mFileOnFlash == null)
RegisterForDeviceChange(drive);
else
// old version
RegisterForDeviceChange(true, mFileOnFlash.SafeFileHandle);
mCurrentDrive = drive;
}
}
/// <summary>
/// New version which gets the handle automatically for specified directory
/// Only for registering! Unregister with the old version of this function...
/// </summary>
/// <param name="register"></param>
/// <param name="dirPath">e.g. C:\\dir</param>
private void RegisterForDeviceChange(string dirPath)
{
IntPtr handle = Native.OpenDirectory(dirPath);
if (handle == IntPtr.Zero)
{
mDeviceNotifyHandle = IntPtr.Zero;
return;
}
else
mDirHandle = handle; // save handle for closing it when unregistering
// Register for handle
DEV_BROADCAST_HANDLE data = new DEV_BROADCAST_HANDLE();
data.dbch_devicetype = DBT_DEVTYP_HANDLE;
data.dbch_reserved = 0;
data.dbch_nameoffset = 0;
//data.dbch_data = null;
//data.dbch_eventguid = 0;
data.dbch_handle = handle;
data.dbch_hdevnotify = (IntPtr)0;
int size = Marshal.SizeOf(data);
data.dbch_size = size;
IntPtr buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(data, buffer, true);
mDeviceNotifyHandle = Native.RegisterDeviceNotification(mRecipientHandle, buffer, 0);
}
/// <summary>
/// Registers to be notified when the volume is about to be removed
/// This is requierd if you want to get the QUERY REMOVE messages
/// </summary>
/// <param name="register">true to register, false to unregister</param>
/// <param name="fileHandle">handle of a file opened on the removable drive</param>
private void RegisterForDeviceChange(bool register, SafeFileHandle fileHandle)
{
if (register)
{
// Register for handle
DEV_BROADCAST_HANDLE data = new DEV_BROADCAST_HANDLE();
data.dbch_devicetype = DBT_DEVTYP_HANDLE;
data.dbch_reserved = 0;
data.dbch_nameoffset = 0;
//data.dbch_data = null;
//data.dbch_eventguid = 0;
data.dbch_handle = fileHandle.DangerousGetHandle(); //Marshal. fileHandle;
data.dbch_hdevnotify = (IntPtr)0;
int size = Marshal.SizeOf(data);
data.dbch_size = size;
IntPtr buffer = Marshal.AllocHGlobal(size);
Marshal.StructureToPtr(data, buffer, true);
mDeviceNotifyHandle = Native.RegisterDeviceNotification(mRecipientHandle, buffer, 0);
}
else
{
// close the directory handle
if (mDirHandle != IntPtr.Zero)
{
Native.CloseDirectoryHandle(mDirHandle);
// string er = Marshal.GetLastWin32Error().ToString();
}
// unregister
if (mDeviceNotifyHandle != IntPtr.Zero)
{
Native.UnregisterDeviceNotification(mDeviceNotifyHandle);
}
mDeviceNotifyHandle = IntPtr.Zero;
mDirHandle = IntPtr.Zero;
mCurrentDrive = "";
if (mFileOnFlash != null)
{
mFileOnFlash.Close();
mFileOnFlash = null;
}
}
}
/// <summary>
/// Gets drive letter from a bit mask where bit 0 = A, bit 1 = B etc.
/// There can actually be more than one drive in the mask but we
/// just use the last one in this case.
/// </summary>
/// <param name="mask"></param>
/// <returns></returns>
private static char DriveMaskToLetter(int mask)
{
char letter;
string drives = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
// 1 = A
// 2 = B
// 4 = C...
int cnt = 0;
int pom = mask / 2;
while (pom != 0)
{
// while there is any bit set in the mask
// shift it to the righ...
pom = pom / 2;
cnt++;
}
if (cnt < drives.Length)
letter = drives[cnt];
else
letter = '?';
return letter;
}
/* 28.10.2007 - no longer needed
/// <summary>
/// Searches for any file in a given path and returns its full path
/// </summary>
/// <param name="drive">drive to search</param>
/// <returns>path of the file or empty string</returns>
private string GetAnyFile(string drive)
{
string file = "";
// First try files in the root
string[] files = Directory.GetFiles(drive);
if (files.Length == 0)
{
// if no file in the root, search whole drive
files = Directory.GetFiles(drive, "*.*", SearchOption.AllDirectories);
}
if (files.Length > 0)
file = files[0]; // get the first file
// return empty string if no file found
return file;
}*/
#endregion
#region Native Win32 API
/// <summary>
/// WinAPI functions
/// </summary>
private class Native
{
// HDEVNOTIFY RegisterDeviceNotification(HANDLE hRecipient,LPVOID NotificationFilter,DWORD Flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr RegisterDeviceNotification(IntPtr hRecipient, IntPtr NotificationFilter, uint Flags);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern uint UnregisterDeviceNotification(IntPtr hHandle);
//
// CreateFile - MSDN
const uint GENERIC_READ = 0x80000000;
const uint OPEN_EXISTING = 3;
const uint FILE_SHARE_READ = 0x00000001;
const uint FILE_SHARE_WRITE = 0x00000002;
const uint FILE_ATTRIBUTE_NORMAL = 128;
const uint FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
// should be "static extern unsafe"
[DllImport("kernel32", SetLastError = true)]
static extern IntPtr CreateFile(
string FileName, // file name
uint DesiredAccess, // access mode
uint ShareMode, // share mode
uint SecurityAttributes, // Security Attributes
uint CreationDisposition, // how to create
uint FlagsAndAttributes, // file attributes
int hTemplateFile // handle to template file
);
[DllImport("kernel32", SetLastError = true)]
static extern bool CloseHandle(
IntPtr hObject // handle to object
);
/// <summary>
/// Opens a directory, returns it's handle or zero.
/// </summary>
/// <param name="dirPath">path to the directory, e.g. "C:\\dir"</param>
/// <returns>handle to the directory. Close it with CloseHandle().</returns>
static public IntPtr OpenDirectory(string dirPath)
{
// open the existing file for reading
IntPtr handle = CreateFile(
dirPath,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_NORMAL,
0);
if ( handle == INVALID_HANDLE_VALUE)
return IntPtr.Zero;
else
return handle;
}
public static bool CloseDirectoryHandle(IntPtr handle)
{
return CloseHandle(handle);
}
}
// Structure with information for RegisterDeviceNotification.
[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_HANDLE
{
public int dbch_size;
public int dbch_devicetype;
public int dbch_reserved;
public IntPtr dbch_handle;
public IntPtr dbch_hdevnotify;
public Guid dbch_eventguid;
public long dbch_nameoffset;
//public byte[] dbch_data[1]; // = new byte[1];
public byte dbch_data;
public byte dbch_data1;
}
// Struct for parameters of the WM_DEVICECHANGE message
[StructLayout(LayoutKind.Sequential)]
public struct DEV_BROADCAST_VOLUME
{
public int dbcv_size;
public int dbcv_devicetype;
public int dbcv_reserved;
public int dbcv_unitmask;
}
#endregion
}
}

View File

@@ -31,16 +31,15 @@
this.rtsys = new arCtl.LogTextBox();
this.rtTx = new arCtl.LogTextBox();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.rtAGV = new arCtl.LogTextBox();
this.rtBMS = new arCtl.LogTextBox();
this.rtXbee = new arCtl.LogTextBox();
this.panel1 = new System.Windows.Forms.Panel();
this.label1 = new System.Windows.Forms.Label();
this.rtXbee = new arCtl.LogTextBox();
this.titleXBEE = new System.Windows.Forms.Label();
this.panel2 = new System.Windows.Forms.Panel();
this.panel3 = new System.Windows.Forms.Panel();
this.rtBMS = new arCtl.LogTextBox();
this.titleBMS = new System.Windows.Forms.Label();
this.panel4 = new System.Windows.Forms.Panel();
this.label2 = new System.Windows.Forms.Label();
this.label3 = new System.Windows.Forms.Label();
this.rtAGV = new arCtl.LogTextBox();
this.titleAGV = new System.Windows.Forms.Label();
this.tableLayoutPanel1.SuspendLayout();
this.panel1.SuspendLayout();
this.panel2.SuspendLayout();
@@ -99,8 +98,7 @@
this.tableLayoutPanel1.Controls.Add(this.rtsys, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.rtTx, 2, 0);
this.tableLayoutPanel1.Controls.Add(this.panel1, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.panel2, 3, 1);
this.tableLayoutPanel1.Controls.Add(this.panel3, 2, 1);
this.tableLayoutPanel1.Controls.Add(this.panel2, 2, 1);
this.tableLayoutPanel1.Controls.Add(this.panel4, 0, 1);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 0);
@@ -111,45 +109,15 @@
this.tableLayoutPanel1.Size = new System.Drawing.Size(681, 495);
this.tableLayoutPanel1.TabIndex = 2;
//
// rtAGV
// panel1
//
this.rtAGV.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.rtAGV.ColorList = new arCtl.sLogMessageColor[0];
this.rtAGV.DateFormat = "mm:ss.fff";
this.rtAGV.DefaultColor = System.Drawing.Color.LightGray;
this.rtAGV.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtAGV.EnableDisplayTimer = false;
this.rtAGV.EnableGubunColor = true;
this.rtAGV.Font = new System.Drawing.Font("맑은 고딕", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.rtAGV.ListFormat = "[{0}] {1}";
this.rtAGV.Location = new System.Drawing.Point(0, 14);
this.rtAGV.MaxListCount = ((ushort)(1000));
this.rtAGV.MaxTextLength = ((uint)(400000u));
this.rtAGV.MessageInterval = 50;
this.rtAGV.Name = "rtAGV";
this.rtAGV.Size = new System.Drawing.Size(164, 129);
this.rtAGV.TabIndex = 2;
this.rtAGV.Text = "";
//
// rtBMS
//
this.rtBMS.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.rtBMS.ColorList = new arCtl.sLogMessageColor[0];
this.rtBMS.DateFormat = "mm:ss.fff";
this.rtBMS.DefaultColor = System.Drawing.Color.LightGray;
this.rtBMS.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtBMS.EnableDisplayTimer = false;
this.rtBMS.EnableGubunColor = true;
this.rtBMS.Font = new System.Drawing.Font("맑은 고딕", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.rtBMS.ListFormat = "[{0}] {1}";
this.rtBMS.Location = new System.Drawing.Point(0, 14);
this.rtBMS.MaxListCount = ((ushort)(1000));
this.rtBMS.MaxTextLength = ((uint)(400000u));
this.rtBMS.MessageInterval = 50;
this.rtBMS.Name = "rtBMS";
this.rtBMS.Size = new System.Drawing.Size(165, 129);
this.rtBMS.TabIndex = 2;
this.rtBMS.Text = "";
this.panel1.Controls.Add(this.rtXbee);
this.panel1.Controls.Add(this.titleXBEE);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(173, 349);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(164, 143);
this.panel1.TabIndex = 3;
//
// rtXbee
//
@@ -171,73 +139,96 @@
this.rtXbee.TabIndex = 2;
this.rtXbee.Text = "";
//
// panel1
// titleXBEE
//
this.panel1.Controls.Add(this.rtXbee);
this.panel1.Controls.Add(this.label1);
this.panel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel1.Location = new System.Drawing.Point(173, 349);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(164, 143);
this.panel1.TabIndex = 3;
//
// label1
//
this.label1.Dock = System.Windows.Forms.DockStyle.Top;
this.label1.Location = new System.Drawing.Point(0, 0);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(164, 14);
this.label1.TabIndex = 0;
this.label1.Text = "MC ID";
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.titleXBEE.Dock = System.Windows.Forms.DockStyle.Top;
this.titleXBEE.Location = new System.Drawing.Point(0, 0);
this.titleXBEE.Name = "titleXBEE";
this.titleXBEE.Size = new System.Drawing.Size(164, 14);
this.titleXBEE.TabIndex = 0;
this.titleXBEE.Text = "MC ID";
this.titleXBEE.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// panel2
//
this.tableLayoutPanel1.SetColumnSpan(this.panel2, 2);
this.panel2.Controls.Add(this.rtBMS);
this.panel2.Controls.Add(this.label3);
this.panel2.Controls.Add(this.titleBMS);
this.panel2.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel2.Location = new System.Drawing.Point(513, 349);
this.panel2.Location = new System.Drawing.Point(343, 349);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(165, 143);
this.panel2.Size = new System.Drawing.Size(335, 143);
this.panel2.TabIndex = 4;
//
// panel3
// rtBMS
//
this.panel3.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel3.Location = new System.Drawing.Point(343, 349);
this.panel3.Name = "panel3";
this.panel3.Size = new System.Drawing.Size(164, 143);
this.panel3.TabIndex = 5;
this.rtBMS.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.rtBMS.ColorList = new arCtl.sLogMessageColor[0];
this.rtBMS.DateFormat = "mm:ss.fff";
this.rtBMS.DefaultColor = System.Drawing.Color.LightGray;
this.rtBMS.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtBMS.EnableDisplayTimer = false;
this.rtBMS.EnableGubunColor = true;
this.rtBMS.Font = new System.Drawing.Font("맑은 고딕", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.rtBMS.ListFormat = "[{0}] {1}";
this.rtBMS.Location = new System.Drawing.Point(0, 14);
this.rtBMS.MaxListCount = ((ushort)(1000));
this.rtBMS.MaxTextLength = ((uint)(400000u));
this.rtBMS.MessageInterval = 50;
this.rtBMS.Name = "rtBMS";
this.rtBMS.Size = new System.Drawing.Size(335, 129);
this.rtBMS.TabIndex = 2;
this.rtBMS.Text = "";
//
// titleBMS
//
this.titleBMS.Dock = System.Windows.Forms.DockStyle.Top;
this.titleBMS.Location = new System.Drawing.Point(0, 0);
this.titleBMS.Name = "titleBMS";
this.titleBMS.Size = new System.Drawing.Size(335, 14);
this.titleBMS.TabIndex = 3;
this.titleBMS.Text = "BMS";
this.titleBMS.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// panel4
//
this.panel4.Controls.Add(this.rtAGV);
this.panel4.Controls.Add(this.label2);
this.panel4.Controls.Add(this.titleAGV);
this.panel4.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel4.Location = new System.Drawing.Point(3, 349);
this.panel4.Name = "panel4";
this.panel4.Size = new System.Drawing.Size(164, 143);
this.panel4.TabIndex = 6;
//
// label2
// rtAGV
//
this.label2.Dock = System.Windows.Forms.DockStyle.Top;
this.label2.Location = new System.Drawing.Point(0, 0);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(164, 14);
this.label2.TabIndex = 3;
this.label2.Text = "AGV";
this.label2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.rtAGV.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.rtAGV.ColorList = new arCtl.sLogMessageColor[0];
this.rtAGV.DateFormat = "mm:ss.fff";
this.rtAGV.DefaultColor = System.Drawing.Color.LightGray;
this.rtAGV.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtAGV.EnableDisplayTimer = false;
this.rtAGV.EnableGubunColor = true;
this.rtAGV.Font = new System.Drawing.Font("맑은 고딕", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.rtAGV.ListFormat = "[{0}] {1}";
this.rtAGV.Location = new System.Drawing.Point(0, 14);
this.rtAGV.MaxListCount = ((ushort)(1000));
this.rtAGV.MaxTextLength = ((uint)(400000u));
this.rtAGV.MessageInterval = 50;
this.rtAGV.Name = "rtAGV";
this.rtAGV.Size = new System.Drawing.Size(164, 129);
this.rtAGV.TabIndex = 2;
this.rtAGV.Text = "";
//
// label3
// titleAGV
//
this.label3.Dock = System.Windows.Forms.DockStyle.Top;
this.label3.Location = new System.Drawing.Point(0, 0);
this.label3.Name = "label3";
this.label3.Size = new System.Drawing.Size(165, 14);
this.label3.TabIndex = 3;
this.label3.Text = "BMS";
this.label3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.titleAGV.Dock = System.Windows.Forms.DockStyle.Top;
this.titleAGV.Location = new System.Drawing.Point(0, 0);
this.titleAGV.Name = "titleAGV";
this.titleAGV.Size = new System.Drawing.Size(164, 14);
this.titleAGV.TabIndex = 3;
this.titleAGV.Text = "AGV";
this.titleAGV.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
//
// fLog
//
@@ -267,11 +258,10 @@
private arCtl.LogTextBox rtBMS;
private arCtl.LogTextBox rtXbee;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label titleXBEE;
private System.Windows.Forms.Panel panel2;
private System.Windows.Forms.Panel panel3;
private System.Windows.Forms.Panel panel4;
private System.Windows.Forms.Label label3;
private System.Windows.Forms.Label label2;
private System.Windows.Forms.Label titleBMS;
private System.Windows.Forms.Label titleAGV;
}
}

View File

@@ -29,7 +29,9 @@ namespace Project.Dialog
private void fLog_Load(object sender, EventArgs e)
{
this.label1.Text = $"XBEE:{PUB.setting.XBE_ID}";
this.titleXBEE.Text = $"XBEE({PUB.setting.Port_XBE},ID:{PUB.setting.XBE_ID})";
this.titleAGV.Text = $"AGV({PUB.setting.Port_AGV}:{PUB.setting.Baud_AGV})";
this.titleBMS.Text = $"BMS({PUB.setting.Port_BAT}:{PUB.setting.Baud_BAT})";
var colorlist = new arCtl.sLogMessageColor[]
{
new arCtl.sLogMessageColor("NOR",Color.Black),

View File

@@ -28,530 +28,307 @@
/// </summary>
private void InitializeComponent()
{
this.lbMsg = new arCtl.arLabel();
this.arLabel4 = new arCtl.arLabel();
this.arLabel9 = new arCtl.arLabel();
this.arLabel10 = new arCtl.arLabel();
this.arLabel1 = new arCtl.arLabel();
this.arLabel6 = new arCtl.arLabel();
this.arLabel2 = new arCtl.arLabel();
this.btOpenDir = new System.Windows.Forms.Button();
this.arLabel4 = new System.Windows.Forms.Button();
this.btShutdown = new System.Windows.Forms.Button();
this.btRestart = new System.Windows.Forms.Button();
this.btStartMenu = new System.Windows.Forms.Button();
this.btTaskMgr = new System.Windows.Forms.Button();
this.btProcessList = new System.Windows.Forms.Button();
this.label1 = new System.Windows.Forms.Label();
this.label2 = new System.Windows.Forms.Label();
this.arLabel3 = new arCtl.arLabel();
this.arLabel5 = new arCtl.arLabel();
this.arLabel7 = new arCtl.arLabel();
this.btEmulator = new System.Windows.Forms.Button();
this.btMakePatch = new System.Windows.Forms.Button();
this.btAutoRestart = new System.Windows.Forms.Button();
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
this.button1 = new System.Windows.Forms.Button();
this.button2 = new System.Windows.Forms.Button();
this.button3 = new System.Windows.Forms.Button();
this.tableLayoutPanel1.SuspendLayout();
this.SuspendLayout();
//
// lbMsg
// btOpenDir
//
this.lbMsg.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.lbMsg.BackColor2 = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18)))));
this.lbMsg.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.lbMsg.BorderColor = System.Drawing.Color.LightSkyBlue;
this.lbMsg.BorderColorOver = System.Drawing.Color.Red;
this.lbMsg.BorderSize = new System.Windows.Forms.Padding(1);
this.lbMsg.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.lbMsg.Cursor = System.Windows.Forms.Cursors.Hand;
this.lbMsg.Font = new System.Drawing.Font("Consolas", 12F);
this.lbMsg.ForeColor = System.Drawing.Color.White;
this.lbMsg.GradientEnable = true;
this.lbMsg.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.lbMsg.GradientRepeatBG = true;
this.lbMsg.isButton = true;
this.lbMsg.Location = new System.Drawing.Point(9, 8);
this.lbMsg.Margin = new System.Windows.Forms.Padding(5);
this.lbMsg.MouseDownColor = System.Drawing.Color.Yellow;
this.lbMsg.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.lbMsg.msg = null;
this.lbMsg.Name = "lbMsg";
this.lbMsg.ProgressBorderColor = System.Drawing.Color.Black;
this.lbMsg.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.lbMsg.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.lbMsg.ProgressEnable = false;
this.lbMsg.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.lbMsg.ProgressForeColor = System.Drawing.Color.Black;
this.lbMsg.ProgressMax = 100F;
this.lbMsg.ProgressMin = 0F;
this.lbMsg.ProgressPadding = new System.Windows.Forms.Padding(0);
this.lbMsg.ProgressValue = 0F;
this.lbMsg.ShadowColor = System.Drawing.Color.Black;
this.lbMsg.Sign = "";
this.lbMsg.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.lbMsg.SignColor = System.Drawing.Color.Yellow;
this.lbMsg.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.lbMsg.Size = new System.Drawing.Size(165, 100);
this.lbMsg.TabIndex = 2;
this.lbMsg.Text = "폴더열기";
this.lbMsg.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.lbMsg.TextShadow = true;
this.lbMsg.TextVisible = true;
this.lbMsg.Click += new System.EventHandler(this.lbMsg_Click);
this.btOpenDir.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.btOpenDir.Cursor = System.Windows.Forms.Cursors.Hand;
this.btOpenDir.Dock = System.Windows.Forms.DockStyle.Fill;
this.btOpenDir.Font = new System.Drawing.Font("Consolas", 12F);
this.btOpenDir.ForeColor = System.Drawing.Color.White;
this.btOpenDir.Location = new System.Drawing.Point(5, 5);
this.btOpenDir.Margin = new System.Windows.Forms.Padding(5);
this.btOpenDir.Name = "btOpenDir";
this.btOpenDir.Size = new System.Drawing.Size(201, 79);
this.btOpenDir.TabIndex = 2;
this.btOpenDir.Text = "폴더열기";
this.btOpenDir.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.btOpenDir.Click += new System.EventHandler(this.lbMsg_Click);
//
// arLabel4
//
this.arLabel4.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.arLabel4.BackColor2 = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18)))));
this.arLabel4.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.arLabel4.BorderColor = System.Drawing.Color.LightSkyBlue;
this.arLabel4.BorderColorOver = System.Drawing.Color.Red;
this.arLabel4.BorderSize = new System.Windows.Forms.Padding(1);
this.arLabel4.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.arLabel4.Cursor = System.Windows.Forms.Cursors.Hand;
this.arLabel4.Dock = System.Windows.Forms.DockStyle.Bottom;
this.arLabel4.Font = new System.Drawing.Font("Consolas", 12F);
this.arLabel4.ForeColor = System.Drawing.Color.White;
this.arLabel4.GradientEnable = true;
this.arLabel4.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.arLabel4.GradientRepeatBG = true;
this.arLabel4.isButton = true;
this.arLabel4.Location = new System.Drawing.Point(9, 387);
this.arLabel4.Location = new System.Drawing.Point(10, 412);
this.arLabel4.Margin = new System.Windows.Forms.Padding(5);
this.arLabel4.MouseDownColor = System.Drawing.Color.Yellow;
this.arLabel4.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.arLabel4.msg = null;
this.arLabel4.Name = "arLabel4";
this.arLabel4.ProgressBorderColor = System.Drawing.Color.Black;
this.arLabel4.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.arLabel4.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.arLabel4.ProgressEnable = false;
this.arLabel4.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.arLabel4.ProgressForeColor = System.Drawing.Color.Black;
this.arLabel4.ProgressMax = 100F;
this.arLabel4.ProgressMin = 0F;
this.arLabel4.ProgressPadding = new System.Windows.Forms.Padding(0);
this.arLabel4.ProgressValue = 0F;
this.arLabel4.ShadowColor = System.Drawing.Color.Black;
this.arLabel4.Sign = "";
this.arLabel4.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.arLabel4.SignColor = System.Drawing.Color.Yellow;
this.arLabel4.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.arLabel4.Size = new System.Drawing.Size(503, 51);
this.arLabel4.Size = new System.Drawing.Size(633, 51);
this.arLabel4.TabIndex = 2;
this.arLabel4.Text = "닫기";
this.arLabel4.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.arLabel4.TextShadow = true;
this.arLabel4.TextVisible = true;
this.arLabel4.Click += new System.EventHandler(this.arLabel4_Click);
//
// arLabel9
// btShutdown
//
this.arLabel9.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.arLabel9.BackColor2 = System.Drawing.Color.Red;
this.arLabel9.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.arLabel9.BorderColor = System.Drawing.Color.LightSkyBlue;
this.arLabel9.BorderColorOver = System.Drawing.Color.Red;
this.arLabel9.BorderSize = new System.Windows.Forms.Padding(1);
this.arLabel9.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.arLabel9.Cursor = System.Windows.Forms.Cursors.Hand;
this.arLabel9.Font = new System.Drawing.Font("Consolas", 12F);
this.arLabel9.ForeColor = System.Drawing.Color.White;
this.arLabel9.GradientEnable = true;
this.arLabel9.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.arLabel9.GradientRepeatBG = true;
this.arLabel9.isButton = true;
this.arLabel9.Location = new System.Drawing.Point(9, 112);
this.arLabel9.Margin = new System.Windows.Forms.Padding(5);
this.arLabel9.MouseDownColor = System.Drawing.Color.Yellow;
this.arLabel9.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.arLabel9.msg = null;
this.arLabel9.Name = "arLabel9";
this.arLabel9.ProgressBorderColor = System.Drawing.Color.Black;
this.arLabel9.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.arLabel9.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.arLabel9.ProgressEnable = false;
this.arLabel9.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.arLabel9.ProgressForeColor = System.Drawing.Color.Black;
this.arLabel9.ProgressMax = 100F;
this.arLabel9.ProgressMin = 0F;
this.arLabel9.ProgressPadding = new System.Windows.Forms.Padding(0);
this.arLabel9.ProgressValue = 0F;
this.arLabel9.ShadowColor = System.Drawing.Color.Black;
this.arLabel9.Sign = "";
this.arLabel9.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.arLabel9.SignColor = System.Drawing.Color.Yellow;
this.arLabel9.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.arLabel9.Size = new System.Drawing.Size(165, 100);
this.arLabel9.TabIndex = 2;
this.arLabel9.Text = "시스템 종료";
this.arLabel9.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.arLabel9.TextShadow = true;
this.arLabel9.TextVisible = true;
this.arLabel9.Click += new System.EventHandler(this.arLabel9_Click);
this.btShutdown.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.btShutdown.Cursor = System.Windows.Forms.Cursors.Hand;
this.btShutdown.Dock = System.Windows.Forms.DockStyle.Fill;
this.btShutdown.Font = new System.Drawing.Font("Consolas", 12F);
this.btShutdown.ForeColor = System.Drawing.Color.White;
this.btShutdown.Location = new System.Drawing.Point(5, 94);
this.btShutdown.Margin = new System.Windows.Forms.Padding(5);
this.btShutdown.Name = "btShutdown";
this.btShutdown.Size = new System.Drawing.Size(201, 79);
this.btShutdown.TabIndex = 2;
this.btShutdown.Text = "시스템 종료";
this.btShutdown.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.btShutdown.Click += new System.EventHandler(this.arLabel9_Click);
//
// arLabel10
// btRestart
//
this.arLabel10.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.arLabel10.BackColor2 = System.Drawing.Color.DarkBlue;
this.arLabel10.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.arLabel10.BorderColor = System.Drawing.Color.LightSkyBlue;
this.arLabel10.BorderColorOver = System.Drawing.Color.Red;
this.arLabel10.BorderSize = new System.Windows.Forms.Padding(1);
this.arLabel10.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.arLabel10.Cursor = System.Windows.Forms.Cursors.Hand;
this.arLabel10.Font = new System.Drawing.Font("Consolas", 12F);
this.arLabel10.ForeColor = System.Drawing.Color.White;
this.arLabel10.GradientEnable = true;
this.arLabel10.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.arLabel10.GradientRepeatBG = true;
this.arLabel10.isButton = true;
this.arLabel10.Location = new System.Drawing.Point(178, 112);
this.arLabel10.Margin = new System.Windows.Forms.Padding(5);
this.arLabel10.MouseDownColor = System.Drawing.Color.Yellow;
this.arLabel10.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.arLabel10.msg = null;
this.arLabel10.Name = "arLabel10";
this.arLabel10.ProgressBorderColor = System.Drawing.Color.Black;
this.arLabel10.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.arLabel10.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.arLabel10.ProgressEnable = false;
this.arLabel10.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.arLabel10.ProgressForeColor = System.Drawing.Color.Black;
this.arLabel10.ProgressMax = 100F;
this.arLabel10.ProgressMin = 0F;
this.arLabel10.ProgressPadding = new System.Windows.Forms.Padding(0);
this.arLabel10.ProgressValue = 0F;
this.arLabel10.ShadowColor = System.Drawing.Color.Black;
this.arLabel10.Sign = "";
this.arLabel10.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.arLabel10.SignColor = System.Drawing.Color.Yellow;
this.arLabel10.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.arLabel10.Size = new System.Drawing.Size(165, 100);
this.arLabel10.TabIndex = 2;
this.arLabel10.Text = "시스템 재시작";
this.arLabel10.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.arLabel10.TextShadow = true;
this.arLabel10.TextVisible = true;
this.arLabel10.Click += new System.EventHandler(this.arLabel10_Click);
this.btRestart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.btRestart.Cursor = System.Windows.Forms.Cursors.Hand;
this.btRestart.Dock = System.Windows.Forms.DockStyle.Fill;
this.btRestart.Font = new System.Drawing.Font("Consolas", 12F);
this.btRestart.ForeColor = System.Drawing.Color.White;
this.btRestart.Location = new System.Drawing.Point(216, 94);
this.btRestart.Margin = new System.Windows.Forms.Padding(5);
this.btRestart.Name = "btRestart";
this.btRestart.Size = new System.Drawing.Size(201, 79);
this.btRestart.TabIndex = 2;
this.btRestart.Text = "시스템 재시작";
this.btRestart.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.btRestart.Click += new System.EventHandler(this.arLabel10_Click);
//
// arLabel1
// btStartMenu
//
this.arLabel1.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.arLabel1.BackColor2 = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18)))));
this.arLabel1.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.arLabel1.BorderColor = System.Drawing.Color.LightSkyBlue;
this.arLabel1.BorderColorOver = System.Drawing.Color.Red;
this.arLabel1.BorderSize = new System.Windows.Forms.Padding(1);
this.arLabel1.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.arLabel1.Cursor = System.Windows.Forms.Cursors.Hand;
this.arLabel1.Font = new System.Drawing.Font("Consolas", 12F);
this.arLabel1.ForeColor = System.Drawing.Color.White;
this.arLabel1.GradientEnable = true;
this.arLabel1.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.arLabel1.GradientRepeatBG = true;
this.arLabel1.isButton = true;
this.arLabel1.Location = new System.Drawing.Point(347, 8);
this.arLabel1.Margin = new System.Windows.Forms.Padding(5);
this.arLabel1.MouseDownColor = System.Drawing.Color.Yellow;
this.arLabel1.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.arLabel1.msg = null;
this.arLabel1.Name = "arLabel1";
this.arLabel1.ProgressBorderColor = System.Drawing.Color.Black;
this.arLabel1.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.arLabel1.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.arLabel1.ProgressEnable = false;
this.arLabel1.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.arLabel1.ProgressForeColor = System.Drawing.Color.Black;
this.arLabel1.ProgressMax = 100F;
this.arLabel1.ProgressMin = 0F;
this.arLabel1.ProgressPadding = new System.Windows.Forms.Padding(0);
this.arLabel1.ProgressValue = 0F;
this.arLabel1.ShadowColor = System.Drawing.Color.Black;
this.arLabel1.Sign = "";
this.arLabel1.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.arLabel1.SignColor = System.Drawing.Color.Yellow;
this.arLabel1.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.arLabel1.Size = new System.Drawing.Size(165, 100);
this.arLabel1.TabIndex = 2;
this.arLabel1.Text = "시작메뉴";
this.arLabel1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.arLabel1.TextShadow = true;
this.arLabel1.TextVisible = true;
this.arLabel1.Click += new System.EventHandler(this.arLabel1_Click);
this.btStartMenu.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.btStartMenu.Cursor = System.Windows.Forms.Cursors.Hand;
this.btStartMenu.Dock = System.Windows.Forms.DockStyle.Fill;
this.btStartMenu.Font = new System.Drawing.Font("Consolas", 12F);
this.btStartMenu.ForeColor = System.Drawing.Color.White;
this.btStartMenu.Location = new System.Drawing.Point(427, 5);
this.btStartMenu.Margin = new System.Windows.Forms.Padding(5);
this.btStartMenu.Name = "btStartMenu";
this.btStartMenu.Size = new System.Drawing.Size(201, 79);
this.btStartMenu.TabIndex = 2;
this.btStartMenu.Text = "시작메뉴";
this.btStartMenu.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.btStartMenu.Click += new System.EventHandler(this.arLabel1_Click);
//
// arLabel6
// btTaskMgr
//
this.arLabel6.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.arLabel6.BackColor2 = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18)))));
this.arLabel6.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.arLabel6.BorderColor = System.Drawing.Color.LightSkyBlue;
this.arLabel6.BorderColorOver = System.Drawing.Color.Red;
this.arLabel6.BorderSize = new System.Windows.Forms.Padding(1);
this.arLabel6.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.arLabel6.Cursor = System.Windows.Forms.Cursors.Hand;
this.arLabel6.Font = new System.Drawing.Font("Consolas", 12F);
this.arLabel6.ForeColor = System.Drawing.Color.White;
this.arLabel6.GradientEnable = true;
this.arLabel6.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.arLabel6.GradientRepeatBG = true;
this.arLabel6.isButton = true;
this.arLabel6.Location = new System.Drawing.Point(178, 8);
this.arLabel6.Margin = new System.Windows.Forms.Padding(5);
this.arLabel6.MouseDownColor = System.Drawing.Color.Yellow;
this.arLabel6.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.arLabel6.msg = null;
this.arLabel6.Name = "arLabel6";
this.arLabel6.ProgressBorderColor = System.Drawing.Color.Black;
this.arLabel6.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.arLabel6.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.arLabel6.ProgressEnable = false;
this.arLabel6.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.arLabel6.ProgressForeColor = System.Drawing.Color.Black;
this.arLabel6.ProgressMax = 100F;
this.arLabel6.ProgressMin = 0F;
this.arLabel6.ProgressPadding = new System.Windows.Forms.Padding(0);
this.arLabel6.ProgressValue = 0F;
this.arLabel6.ShadowColor = System.Drawing.Color.Black;
this.arLabel6.Sign = "";
this.arLabel6.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.arLabel6.SignColor = System.Drawing.Color.Yellow;
this.arLabel6.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.arLabel6.Size = new System.Drawing.Size(165, 100);
this.arLabel6.TabIndex = 2;
this.arLabel6.Text = "작업관리자";
this.arLabel6.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.arLabel6.TextShadow = true;
this.arLabel6.TextVisible = true;
this.arLabel6.Click += new System.EventHandler(this.arLabel6_Click);
this.btTaskMgr.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.btTaskMgr.Cursor = System.Windows.Forms.Cursors.Hand;
this.btTaskMgr.Dock = System.Windows.Forms.DockStyle.Fill;
this.btTaskMgr.Font = new System.Drawing.Font("Consolas", 12F);
this.btTaskMgr.ForeColor = System.Drawing.Color.White;
this.btTaskMgr.Location = new System.Drawing.Point(216, 5);
this.btTaskMgr.Margin = new System.Windows.Forms.Padding(5);
this.btTaskMgr.Name = "btTaskMgr";
this.btTaskMgr.Size = new System.Drawing.Size(201, 79);
this.btTaskMgr.TabIndex = 2;
this.btTaskMgr.Text = "작업관리자";
this.btTaskMgr.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.btTaskMgr.Click += new System.EventHandler(this.arLabel6_Click);
//
// arLabel2
// btProcessList
//
this.arLabel2.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.arLabel2.BackColor2 = System.Drawing.Color.DarkBlue;
this.arLabel2.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.arLabel2.BorderColor = System.Drawing.Color.LightSkyBlue;
this.arLabel2.BorderColorOver = System.Drawing.Color.Red;
this.arLabel2.BorderSize = new System.Windows.Forms.Padding(1);
this.arLabel2.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.arLabel2.Cursor = System.Windows.Forms.Cursors.Hand;
this.arLabel2.Font = new System.Drawing.Font("Consolas", 12F);
this.arLabel2.ForeColor = System.Drawing.Color.White;
this.arLabel2.GradientEnable = true;
this.arLabel2.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.arLabel2.GradientRepeatBG = true;
this.arLabel2.isButton = true;
this.arLabel2.Location = new System.Drawing.Point(347, 112);
this.arLabel2.Margin = new System.Windows.Forms.Padding(5);
this.arLabel2.MouseDownColor = System.Drawing.Color.Yellow;
this.arLabel2.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.arLabel2.msg = null;
this.arLabel2.Name = "arLabel2";
this.arLabel2.ProgressBorderColor = System.Drawing.Color.Black;
this.arLabel2.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.arLabel2.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.arLabel2.ProgressEnable = false;
this.arLabel2.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.arLabel2.ProgressForeColor = System.Drawing.Color.Black;
this.arLabel2.ProgressMax = 100F;
this.arLabel2.ProgressMin = 0F;
this.arLabel2.ProgressPadding = new System.Windows.Forms.Padding(0);
this.arLabel2.ProgressValue = 0F;
this.arLabel2.ShadowColor = System.Drawing.Color.Black;
this.arLabel2.Sign = "";
this.arLabel2.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.arLabel2.SignColor = System.Drawing.Color.Yellow;
this.arLabel2.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.arLabel2.Size = new System.Drawing.Size(165, 100);
this.arLabel2.TabIndex = 2;
this.arLabel2.Text = "Process List";
this.arLabel2.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.arLabel2.TextShadow = true;
this.arLabel2.TextVisible = true;
this.arLabel2.Click += new System.EventHandler(this.arLabel2_Click);
this.btProcessList.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.btProcessList.Cursor = System.Windows.Forms.Cursors.Hand;
this.btProcessList.Dock = System.Windows.Forms.DockStyle.Fill;
this.btProcessList.Font = new System.Drawing.Font("Consolas", 12F);
this.btProcessList.ForeColor = System.Drawing.Color.White;
this.btProcessList.Location = new System.Drawing.Point(427, 94);
this.btProcessList.Margin = new System.Windows.Forms.Padding(5);
this.btProcessList.Name = "btProcessList";
this.btProcessList.Size = new System.Drawing.Size(201, 79);
this.btProcessList.TabIndex = 2;
this.btProcessList.Text = "Process List";
this.btProcessList.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.btProcessList.Click += new System.EventHandler(this.arLabel2_Click);
//
// label1
//
this.label1.Dock = System.Windows.Forms.DockStyle.Bottom;
this.label1.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.label1.ForeColor = System.Drawing.Color.White;
this.label1.Location = new System.Drawing.Point(9, 327);
this.label1.Location = new System.Drawing.Point(10, 389);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(515, 23);
this.label1.Size = new System.Drawing.Size(633, 23);
this.label1.TabIndex = 3;
this.label1.Text = "label1";
//
// label2
//
this.label2.Dock = System.Windows.Forms.DockStyle.Bottom;
this.label2.Font = new System.Drawing.Font("맑은 고딕", 9.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.label2.ForeColor = System.Drawing.Color.White;
this.label2.Location = new System.Drawing.Point(9, 359);
this.label2.Location = new System.Drawing.Point(10, 366);
this.label2.Name = "label2";
this.label2.Size = new System.Drawing.Size(515, 23);
this.label2.Size = new System.Drawing.Size(633, 23);
this.label2.TabIndex = 3;
this.label2.Text = "label1";
//
// arLabel3
// btEmulator
//
this.arLabel3.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.arLabel3.BackColor2 = System.Drawing.Color.FromArgb(((int)(((byte)(18)))), ((int)(((byte)(18)))), ((int)(((byte)(18)))));
this.arLabel3.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.arLabel3.BorderColor = System.Drawing.Color.LightSkyBlue;
this.arLabel3.BorderColorOver = System.Drawing.Color.Red;
this.arLabel3.BorderSize = new System.Windows.Forms.Padding(1);
this.arLabel3.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.arLabel3.Cursor = System.Windows.Forms.Cursors.Hand;
this.arLabel3.Font = new System.Drawing.Font("Consolas", 12F);
this.arLabel3.ForeColor = System.Drawing.Color.White;
this.arLabel3.GradientEnable = true;
this.arLabel3.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.arLabel3.GradientRepeatBG = true;
this.arLabel3.isButton = true;
this.arLabel3.Location = new System.Drawing.Point(9, 216);
this.arLabel3.Margin = new System.Windows.Forms.Padding(5);
this.arLabel3.MouseDownColor = System.Drawing.Color.Yellow;
this.arLabel3.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.arLabel3.msg = null;
this.arLabel3.Name = "arLabel3";
this.arLabel3.ProgressBorderColor = System.Drawing.Color.Black;
this.arLabel3.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.arLabel3.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.arLabel3.ProgressEnable = false;
this.arLabel3.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.arLabel3.ProgressForeColor = System.Drawing.Color.Black;
this.arLabel3.ProgressMax = 100F;
this.arLabel3.ProgressMin = 0F;
this.arLabel3.ProgressPadding = new System.Windows.Forms.Padding(0);
this.arLabel3.ProgressValue = 0F;
this.arLabel3.ShadowColor = System.Drawing.Color.Black;
this.arLabel3.Sign = "";
this.arLabel3.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.arLabel3.SignColor = System.Drawing.Color.Yellow;
this.arLabel3.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.arLabel3.Size = new System.Drawing.Size(165, 100);
this.arLabel3.TabIndex = 4;
this.arLabel3.Text = "Emulator";
this.arLabel3.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.arLabel3.TextShadow = true;
this.arLabel3.TextVisible = true;
this.arLabel3.Click += new System.EventHandler(this.arLabel3_Click);
this.btEmulator.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.btEmulator.Cursor = System.Windows.Forms.Cursors.Hand;
this.btEmulator.Dock = System.Windows.Forms.DockStyle.Fill;
this.btEmulator.Font = new System.Drawing.Font("Consolas", 12F);
this.btEmulator.ForeColor = System.Drawing.Color.White;
this.btEmulator.Location = new System.Drawing.Point(5, 183);
this.btEmulator.Margin = new System.Windows.Forms.Padding(5);
this.btEmulator.Name = "btEmulator";
this.btEmulator.Size = new System.Drawing.Size(201, 79);
this.btEmulator.TabIndex = 4;
this.btEmulator.Text = "Emulator";
this.btEmulator.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.btEmulator.Click += new System.EventHandler(this.arLabel3_Click);
//
// arLabel5
// btMakePatch
//
this.arLabel5.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.arLabel5.BackColor2 = System.Drawing.Color.Pink;
this.arLabel5.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.arLabel5.BorderColor = System.Drawing.Color.LightSkyBlue;
this.arLabel5.BorderColorOver = System.Drawing.Color.Red;
this.arLabel5.BorderSize = new System.Windows.Forms.Padding(1);
this.arLabel5.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.arLabel5.Cursor = System.Windows.Forms.Cursors.Hand;
this.arLabel5.Font = new System.Drawing.Font("Consolas", 12F);
this.arLabel5.ForeColor = System.Drawing.Color.White;
this.arLabel5.GradientEnable = true;
this.arLabel5.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.arLabel5.GradientRepeatBG = true;
this.arLabel5.isButton = true;
this.arLabel5.Location = new System.Drawing.Point(178, 216);
this.arLabel5.Margin = new System.Windows.Forms.Padding(5);
this.arLabel5.MouseDownColor = System.Drawing.Color.Yellow;
this.arLabel5.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.arLabel5.msg = null;
this.arLabel5.Name = "arLabel5";
this.arLabel5.ProgressBorderColor = System.Drawing.Color.Black;
this.arLabel5.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.arLabel5.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.arLabel5.ProgressEnable = false;
this.arLabel5.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.arLabel5.ProgressForeColor = System.Drawing.Color.Black;
this.arLabel5.ProgressMax = 100F;
this.arLabel5.ProgressMin = 0F;
this.arLabel5.ProgressPadding = new System.Windows.Forms.Padding(0);
this.arLabel5.ProgressValue = 0F;
this.arLabel5.ShadowColor = System.Drawing.Color.Black;
this.arLabel5.Sign = "";
this.arLabel5.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.arLabel5.SignColor = System.Drawing.Color.Yellow;
this.arLabel5.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.arLabel5.Size = new System.Drawing.Size(165, 100);
this.arLabel5.TabIndex = 5;
this.arLabel5.Text = "패치파일 생성";
this.arLabel5.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.arLabel5.TextShadow = true;
this.arLabel5.TextVisible = true;
this.arLabel5.Click += new System.EventHandler(this.arLabel5_Click);
this.btMakePatch.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.btMakePatch.Cursor = System.Windows.Forms.Cursors.Hand;
this.btMakePatch.Dock = System.Windows.Forms.DockStyle.Fill;
this.btMakePatch.Font = new System.Drawing.Font("Consolas", 12F);
this.btMakePatch.ForeColor = System.Drawing.Color.White;
this.btMakePatch.Location = new System.Drawing.Point(216, 183);
this.btMakePatch.Margin = new System.Windows.Forms.Padding(5);
this.btMakePatch.Name = "btMakePatch";
this.btMakePatch.Size = new System.Drawing.Size(201, 79);
this.btMakePatch.TabIndex = 5;
this.btMakePatch.Text = "패치파일 생성";
this.btMakePatch.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.btMakePatch.Click += new System.EventHandler(this.arLabel5_Click);
//
// arLabel7
// btAutoRestart
//
this.arLabel7.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.arLabel7.BackColor2 = System.Drawing.Color.DarkBlue;
this.arLabel7.BackgroundImagePadding = new System.Windows.Forms.Padding(0);
this.arLabel7.BorderColor = System.Drawing.Color.LightSkyBlue;
this.arLabel7.BorderColorOver = System.Drawing.Color.Red;
this.arLabel7.BorderSize = new System.Windows.Forms.Padding(1);
this.arLabel7.ColorTheme = arCtl.arLabel.eColorTheme.Custom;
this.arLabel7.Cursor = System.Windows.Forms.Cursors.Hand;
this.arLabel7.Font = new System.Drawing.Font("Consolas", 12F);
this.arLabel7.ForeColor = System.Drawing.Color.White;
this.arLabel7.GradientEnable = true;
this.arLabel7.GradientMode = System.Drawing.Drawing2D.LinearGradientMode.Vertical;
this.arLabel7.GradientRepeatBG = true;
this.arLabel7.isButton = true;
this.arLabel7.Location = new System.Drawing.Point(347, 216);
this.arLabel7.Margin = new System.Windows.Forms.Padding(5);
this.arLabel7.MouseDownColor = System.Drawing.Color.Yellow;
this.arLabel7.MouseOverColor = System.Drawing.Color.FromArgb(((int)(((byte)(64)))), ((int)(((byte)(64)))), ((int)(((byte)(64)))));
this.arLabel7.msg = null;
this.arLabel7.Name = "arLabel7";
this.arLabel7.ProgressBorderColor = System.Drawing.Color.Black;
this.arLabel7.ProgressColor1 = System.Drawing.Color.LightSkyBlue;
this.arLabel7.ProgressColor2 = System.Drawing.Color.DeepSkyBlue;
this.arLabel7.ProgressEnable = false;
this.arLabel7.ProgressFont = new System.Drawing.Font("Consolas", 10F);
this.arLabel7.ProgressForeColor = System.Drawing.Color.Black;
this.arLabel7.ProgressMax = 100F;
this.arLabel7.ProgressMin = 0F;
this.arLabel7.ProgressPadding = new System.Windows.Forms.Padding(0);
this.arLabel7.ProgressValue = 0F;
this.arLabel7.ShadowColor = System.Drawing.Color.Black;
this.arLabel7.Sign = "";
this.arLabel7.SignAlign = System.Drawing.ContentAlignment.BottomRight;
this.arLabel7.SignColor = System.Drawing.Color.Yellow;
this.arLabel7.SignFont = new System.Drawing.Font("Consolas", 7F, System.Drawing.FontStyle.Italic);
this.arLabel7.Size = new System.Drawing.Size(165, 100);
this.arLabel7.TabIndex = 6;
this.arLabel7.Text = "자동 재시작";
this.arLabel7.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.arLabel7.TextShadow = true;
this.arLabel7.TextVisible = true;
this.arLabel7.Click += new System.EventHandler(this.arLabel7_Click);
this.btAutoRestart.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(72)))), ((int)(((byte)(72)))), ((int)(((byte)(72)))));
this.btAutoRestart.Cursor = System.Windows.Forms.Cursors.Hand;
this.btAutoRestart.Dock = System.Windows.Forms.DockStyle.Fill;
this.btAutoRestart.Font = new System.Drawing.Font("Consolas", 12F);
this.btAutoRestart.ForeColor = System.Drawing.Color.White;
this.btAutoRestart.Location = new System.Drawing.Point(427, 183);
this.btAutoRestart.Margin = new System.Windows.Forms.Padding(5);
this.btAutoRestart.Name = "btAutoRestart";
this.btAutoRestart.Size = new System.Drawing.Size(201, 79);
this.btAutoRestart.TabIndex = 6;
this.btAutoRestart.Text = "자동 재시작";
this.btAutoRestart.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
this.btAutoRestart.Click += new System.EventHandler(this.arLabel7_Click);
//
// tableLayoutPanel1
//
this.tableLayoutPanel1.ColumnCount = 3;
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333F));
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 33.33333F));
this.tableLayoutPanel1.Controls.Add(this.btOpenDir, 0, 0);
this.tableLayoutPanel1.Controls.Add(this.btAutoRestart, 2, 2);
this.tableLayoutPanel1.Controls.Add(this.btTaskMgr, 1, 0);
this.tableLayoutPanel1.Controls.Add(this.btMakePatch, 1, 2);
this.tableLayoutPanel1.Controls.Add(this.btStartMenu, 2, 0);
this.tableLayoutPanel1.Controls.Add(this.btEmulator, 0, 2);
this.tableLayoutPanel1.Controls.Add(this.btShutdown, 0, 1);
this.tableLayoutPanel1.Controls.Add(this.btRestart, 1, 1);
this.tableLayoutPanel1.Controls.Add(this.btProcessList, 2, 1);
this.tableLayoutPanel1.Controls.Add(this.button1, 0, 3);
this.tableLayoutPanel1.Controls.Add(this.button2, 1, 3);
this.tableLayoutPanel1.Controls.Add(this.button3, 2, 3);
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
this.tableLayoutPanel1.Location = new System.Drawing.Point(10, 10);
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
this.tableLayoutPanel1.RowCount = 4;
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 25F));
this.tableLayoutPanel1.Size = new System.Drawing.Size(633, 356);
this.tableLayoutPanel1.TabIndex = 7;
//
// button1
//
this.button1.Dock = System.Windows.Forms.DockStyle.Fill;
this.button1.Location = new System.Drawing.Point(3, 270);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(205, 83);
this.button1.TabIndex = 7;
this.button1.Text = "Map Editor";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click_1);
//
// button2
//
this.button2.Dock = System.Windows.Forms.DockStyle.Fill;
this.button2.Enabled = false;
this.button2.Location = new System.Drawing.Point(214, 270);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(205, 83);
this.button2.TabIndex = 7;
this.button2.Text = "---";
this.button2.UseVisualStyleBackColor = true;
//
// button3
//
this.button3.Dock = System.Windows.Forms.DockStyle.Fill;
this.button3.Enabled = false;
this.button3.Location = new System.Drawing.Point(425, 270);
this.button3.Name = "button3";
this.button3.Size = new System.Drawing.Size(205, 83);
this.button3.TabIndex = 7;
this.button3.Text = "---";
this.button3.UseVisualStyleBackColor = true;
//
// fSystem
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(40)))), ((int)(((byte)(40)))), ((int)(((byte)(40)))));
this.ClientSize = new System.Drawing.Size(519, 451);
this.Controls.Add(this.arLabel7);
this.Controls.Add(this.arLabel5);
this.Controls.Add(this.arLabel3);
this.ClientSize = new System.Drawing.Size(653, 473);
this.Controls.Add(this.tableLayoutPanel1);
this.Controls.Add(this.label2);
this.Controls.Add(this.label1);
this.Controls.Add(this.arLabel4);
this.Controls.Add(this.lbMsg);
this.Controls.Add(this.arLabel9);
this.Controls.Add(this.arLabel10);
this.Controls.Add(this.arLabel1);
this.Controls.Add(this.arLabel2);
this.Controls.Add(this.arLabel6);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "fSystem";
this.Padding = new System.Windows.Forms.Padding(10);
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "fSystem";
this.Load += new System.EventHandler(this.fSystem_Load);
this.tableLayoutPanel1.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private arCtl.arLabel lbMsg;
private arCtl.arLabel arLabel4;
private arCtl.arLabel arLabel6;
private arCtl.arLabel arLabel9;
private arCtl.arLabel arLabel10;
private arCtl.arLabel arLabel1;
private arCtl.arLabel arLabel2;
private System.Windows.Forms.Button btOpenDir;
private System.Windows.Forms.Button arLabel4;
private System.Windows.Forms.Button btTaskMgr;
private System.Windows.Forms.Button btShutdown;
private System.Windows.Forms.Button btRestart;
private System.Windows.Forms.Button btStartMenu;
private System.Windows.Forms.Button btProcessList;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Label label2;
private arCtl.arLabel arLabel3;
private arCtl.arLabel arLabel5;
private arCtl.arLabel arLabel7;
private System.Windows.Forms.Button btEmulator;
private System.Windows.Forms.Button btMakePatch;
private System.Windows.Forms.Button btAutoRestart;
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
private System.Windows.Forms.Button button1;
private System.Windows.Forms.Button button2;
private System.Windows.Forms.Button button3;
}
}

View File

@@ -154,5 +154,13 @@ namespace Project.Dialog
{
PUB.SystemReboot(5,true);
}
private void button1_Click_1(object sender, EventArgs e)
{
//mapeditor
var fn = new System.IO.FileInfo(@".\test\AGVMapEditor.exe");
if (fn.Exists == false) return;
UTIL.RunExplorer(fn.FullName);
}
}
}

View File

@@ -0,0 +1,239 @@
namespace Project.Dialog
{
partial class fXbeeSetting
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.button1 = new System.Windows.Forms.Button();
this.tbBaud = new System.Windows.Forms.TextBox();
this.tbPortName = new System.Windows.Forms.ComboBox();
this.panel1 = new System.Windows.Forms.Panel();
this.button2 = new System.Windows.Forms.Button();
this.btmy = new System.Windows.Forms.Button();
this.tbmy = new System.Windows.Forms.TextBox();
this.btch = new System.Windows.Forms.Button();
this.tbch = new System.Windows.Forms.TextBox();
this.btpand = new System.Windows.Forms.Button();
this.tbpanid = new System.Windows.Forms.TextBox();
this.panel2 = new System.Windows.Forms.Panel();
this.rtXbee = new arCtl.LogTextBox();
this.serialPort1 = new System.IO.Ports.SerialPort(this.components);
this.panel1.SuspendLayout();
this.panel2.SuspendLayout();
this.SuspendLayout();
//
// button1
//
this.button1.Location = new System.Drawing.Point(25, 45);
this.button1.Name = "button1";
this.button1.Size = new System.Drawing.Size(227, 26);
this.button1.TabIndex = 0;
this.button1.Text = "open/close";
this.button1.UseVisualStyleBackColor = true;
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// tbBaud
//
this.tbBaud.Font = new System.Drawing.Font("굴림", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.tbBaud.Location = new System.Drawing.Point(152, 13);
this.tbBaud.Name = "tbBaud";
this.tbBaud.Size = new System.Drawing.Size(100, 26);
this.tbBaud.TabIndex = 2;
this.tbBaud.Text = "9600";
this.tbBaud.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// tbPortName
//
this.tbPortName.Font = new System.Drawing.Font("굴림", 12F);
this.tbPortName.FormattingEnabled = true;
this.tbPortName.Location = new System.Drawing.Point(25, 15);
this.tbPortName.Name = "tbPortName";
this.tbPortName.Size = new System.Drawing.Size(121, 24);
this.tbPortName.TabIndex = 3;
//
// panel1
//
this.panel1.Controls.Add(this.button2);
this.panel1.Controls.Add(this.btmy);
this.panel1.Controls.Add(this.tbmy);
this.panel1.Controls.Add(this.btch);
this.panel1.Controls.Add(this.tbch);
this.panel1.Controls.Add(this.btpand);
this.panel1.Controls.Add(this.tbpanid);
this.panel1.Controls.Add(this.tbPortName);
this.panel1.Controls.Add(this.button1);
this.panel1.Controls.Add(this.tbBaud);
this.panel1.Dock = System.Windows.Forms.DockStyle.Top;
this.panel1.Location = new System.Drawing.Point(0, 0);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(580, 118);
this.panel1.TabIndex = 4;
//
// button2
//
this.button2.Location = new System.Drawing.Point(293, 15);
this.button2.Name = "button2";
this.button2.Size = new System.Drawing.Size(75, 89);
this.button2.TabIndex = 10;
this.button2.Text = "Read Setting";
this.button2.UseVisualStyleBackColor = true;
this.button2.Click += new System.EventHandler(this.button2_Click);
//
// btmy
//
this.btmy.Location = new System.Drawing.Point(480, 78);
this.btmy.Name = "btmy";
this.btmy.Size = new System.Drawing.Size(86, 26);
this.btmy.TabIndex = 9;
this.btmy.Text = "My";
this.btmy.UseVisualStyleBackColor = true;
this.btmy.Click += new System.EventHandler(this.btmy_Click);
//
// tbmy
//
this.tbmy.Font = new System.Drawing.Font("굴림", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.tbmy.Location = new System.Drawing.Point(374, 78);
this.tbmy.Name = "tbmy";
this.tbmy.Size = new System.Drawing.Size(100, 26);
this.tbmy.TabIndex = 8;
this.tbmy.Text = "9600";
this.tbmy.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// btch
//
this.btch.Location = new System.Drawing.Point(480, 47);
this.btch.Name = "btch";
this.btch.Size = new System.Drawing.Size(86, 26);
this.btch.TabIndex = 7;
this.btch.Text = "Channel";
this.btch.UseVisualStyleBackColor = true;
this.btch.Click += new System.EventHandler(this.btch_Click);
//
// tbch
//
this.tbch.Font = new System.Drawing.Font("굴림", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.tbch.Location = new System.Drawing.Point(374, 47);
this.tbch.Name = "tbch";
this.tbch.Size = new System.Drawing.Size(100, 26);
this.tbch.TabIndex = 6;
this.tbch.Text = "9600";
this.tbch.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// btpand
//
this.btpand.Location = new System.Drawing.Point(480, 15);
this.btpand.Name = "btpand";
this.btpand.Size = new System.Drawing.Size(86, 26);
this.btpand.TabIndex = 5;
this.btpand.Text = "PanID";
this.btpand.UseVisualStyleBackColor = true;
this.btpand.Click += new System.EventHandler(this.btpand_Click);
//
// tbpanid
//
this.tbpanid.Font = new System.Drawing.Font("굴림", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(129)));
this.tbpanid.Location = new System.Drawing.Point(374, 15);
this.tbpanid.Name = "tbpanid";
this.tbpanid.Size = new System.Drawing.Size(100, 26);
this.tbpanid.TabIndex = 4;
this.tbpanid.Text = "9600";
this.tbpanid.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// panel2
//
this.panel2.Controls.Add(this.rtXbee);
this.panel2.Dock = System.Windows.Forms.DockStyle.Fill;
this.panel2.Location = new System.Drawing.Point(0, 118);
this.panel2.Name = "panel2";
this.panel2.Size = new System.Drawing.Size(580, 338);
this.panel2.TabIndex = 5;
//
// rtXbee
//
this.rtXbee.BackColor = System.Drawing.Color.FromArgb(((int)(((byte)(224)))), ((int)(((byte)(224)))), ((int)(((byte)(224)))));
this.rtXbee.ColorList = new arCtl.sLogMessageColor[0];
this.rtXbee.DateFormat = "mm:ss.fff";
this.rtXbee.DefaultColor = System.Drawing.Color.LightGray;
this.rtXbee.Dock = System.Windows.Forms.DockStyle.Fill;
this.rtXbee.EnableDisplayTimer = false;
this.rtXbee.EnableGubunColor = true;
this.rtXbee.Font = new System.Drawing.Font("맑은 고딕", 6.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.rtXbee.ListFormat = "[{0}] {1}";
this.rtXbee.Location = new System.Drawing.Point(0, 0);
this.rtXbee.MaxListCount = ((ushort)(1000));
this.rtXbee.MaxTextLength = ((uint)(400000u));
this.rtXbee.MessageInterval = 50;
this.rtXbee.Name = "rtXbee";
this.rtXbee.Size = new System.Drawing.Size(580, 338);
this.rtXbee.TabIndex = 2;
this.rtXbee.Text = "";
//
// serialPort1
//
this.serialPort1.ReadTimeout = 1000;
this.serialPort1.WriteTimeout = 1000;
//
// fXbeeSetting
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 12F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(580, 456);
this.Controls.Add(this.panel2);
this.Controls.Add(this.panel1);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "fXbeeSetting";
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Form1";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.fXbeeSetting_FormClosed);
this.Load += new System.EventHandler(this.fXbeeSetting_Load);
this.panel1.ResumeLayout(false);
this.panel1.PerformLayout();
this.panel2.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private System.Windows.Forms.Button button1;
private System.Windows.Forms.TextBox tbBaud;
private System.Windows.Forms.ComboBox tbPortName;
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Panel panel2;
private arCtl.LogTextBox rtXbee;
private System.Windows.Forms.Button btpand;
private System.Windows.Forms.TextBox tbpanid;
private System.Windows.Forms.Button btmy;
private System.Windows.Forms.TextBox tbmy;
private System.Windows.Forms.Button btch;
private System.Windows.Forms.TextBox tbch;
private System.Windows.Forms.Button button2;
private System.IO.Ports.SerialPort serialPort1;
}
}

View File

@@ -0,0 +1,193 @@
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;
using AR;
using COMM;
namespace Project.Dialog
{
public partial class fXbeeSetting : Form
{
public fXbeeSetting()
{
InitializeComponent();
VAR.BOOL[eVarBool.DISABLE_AUTOCONN_XBEE] = true;
PUB.XBE.Close();
this.serialPort1.DataReceived += SerialPort1_DataReceived;
}
private void fXbeeSetting_Load(object sender, EventArgs e)
{
this.tbPortName.Items.Clear();
foreach (var item in System.IO.Ports.SerialPort.GetPortNames())
{
this.tbPortName.Items.Add(item);
}
this.tbPortName.Text = PUB.setting.Port_XBE;
this.tbBaud.Text = PUB.setting.Baud_XBE.ToString();
}
void showlog(string Message)
{
if (rtXbee.Visible)
{
rtXbee.AddMsg(DateTime.Now, "NORMAL", Message);
}
}
void showlog(arCtl.LogTextBox rtRx, DateTime LogTime, string TypeStr, string Message)
{
if (rtRx.Visible)
{
rtRx.AddMsg(LogTime, TypeStr, Message);
}
}
private void button1_Click(object sender, EventArgs e)
{
if (this.serialPort1.IsOpen)
{
serialPort1.Close();
showlog("closed");
}
else
{
serialPort1.PortName = tbPortName.Text;
serialPort1.BaudRate = int.Parse(tbBaud.Text);
serialPort1.Open();
showlog("open");
}
}
private void fXbeeSetting_FormClosed(object sender, FormClosedEventArgs e)
{
VAR.BOOL[eVarBool.DISABLE_AUTOCONN_XBEE] = false;
}
private volatile bool isCommandExecuting = false;
private void SerialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
if (isCommandExecuting) return;
try
{
string data = serialPort1.ReadExisting();
var hexdata = System.Text.Encoding.Default.GetBytes(data);
var hexstr = string.Join(" ", hexdata.Select(t => t.ToString("X2")));
if (!string.IsNullOrEmpty(data))
{
this.BeginInvoke(new Action(() =>
{
showlog($"RxAsync: {hexstr}");
}));
}
}
catch { }
}
private string Cmd(string cmd, int timeout = 1000)
{
isCommandExecuting = true;
try
{
if (!serialPort1.IsOpen) return "Error: Port Closed";
serialPort1.DiscardInBuffer();
serialPort1.Write(cmd);
System.Threading.Thread.Sleep(20);
serialPort1.ReadTimeout = timeout;
string res = serialPort1.ReadTo("\r");
System.Threading.Thread.Sleep(20);
showlog($"Tx:{cmd.Trim()}, Rx:{res}");
//명령수신호 10ms 대기후 다음 명령을 전송
System.Threading.Thread.Sleep(20);
return res;
}
catch (Exception ex)
{
showlog($"Err: {ex.Message}");
return "Error";
}
finally
{
isCommandExecuting = false;
}
}
private void btpand_Click(object sender, EventArgs e)
{
//각명령마다 회신을 확인하고 다음명령을 실행해야함
//명령수신호 10ms 대기후 다음 명령을 전송
//명령을 설정하면 응답은 OK\d 형태로 입력된다.
var cmds = new string[] {
"+++",
$"ATID{tbpanid.Text}\r" ,
$"ATCN\r"};
foreach (var cmd in cmds)
{
if(!Cmd(cmd).Contains("OK"))
{
showlog("FAIL");
break;
}
}
}
private void btch_Click(object sender, EventArgs e)
{
var cmds = new string[] {
"+++",
$"ATCH{tbch.Text}\r" ,
$"ATCN\r"};
foreach (var cmd in cmds)
{
if (!Cmd(cmd).Contains("OK"))
{
showlog("FAIL");
break;
}
}
}
private void btmy_Click(object sender, EventArgs e)
{
var cmds = new string[] {
"+++",
$"ATMY{tbmy.Text}\r" ,
$"ATCN\r"};
foreach (var cmd in cmds)
{
if (!Cmd(cmd).Contains("OK"))
{
showlog("FAIL");
break;
}
}
}
private void button2_Click(object sender, EventArgs e)
{
//read all(id,ch,my)
if (Cmd("+++").Contains("OK"))
{
var id = Cmd("ATID\r");
var ch = Cmd("ATCH\r");
var my = Cmd("ATMY\r");
Cmd("ATCN\r");
this.BeginInvoke(new Action(() => {
this.tbpanid.Text = id;
this.tbch.Text = ch;
this.tbmy.Text = my;
}));
}
}
}
}

View File

@@ -0,0 +1,123 @@
<?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="serialPort1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@@ -8,7 +8,11 @@ using System.Media;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using System;
#if SPEECH
using Microsoft.Speech.Synthesis;
#endif
using System.Threading.Tasks;
using System.Data.SqlClient;
using System.Linq;
@@ -27,7 +31,12 @@ namespace Project
public static bool AutRebootAlreay = false;
public static bool DriveSpeed = false;
public static AGVNavigationCore.Controls.UnifiedAGVCanvas _mapCanvas;
public static List<MapNode> _mapNodes;
//public static List<MapNode> _mapNodes;
/// <summary>
/// 다음 작업 명령 (PickOn/PickOff)
/// </summary>
public static ENIGProtocol.AGVCommandHE NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
/// <summary>
/// 가상 AGV (시뮬레이션용)
@@ -69,8 +78,9 @@ namespace Project
/// 디버그모니터용 소켓(데이터를 전송만 한다)
/// </summary>
public static Device.Socket sock_debug;
#if SPEECH
private static SpeechSynthesizer voice;
#endif
public static SoundPlayer mplayer;
[DllImport("winmm.dll")]
@@ -94,18 +104,33 @@ namespace Project
else return false;
}
public static void Speak(string m, Boolean force = false, bool addlog = true)
/// <summary>
/// 스피커를 통한 음성을 출력합니다
/// 출력되는 음성은 기본로그에도 자동 포함됩니다
/// </summary>
/// <param name="m"></param>
/// <param name="force"></param>
/// <param name="addlog">로그에도 출력 합니다</param>
public static void Speak(string m, Boolean force = false, bool addlog = true,string logcate="")
{
if (force == false && PUB.setting.Enable_Speak == false)
{
Console.WriteLine("speech disabled");
return;
}
#if SPEECH
if (force)
voice.SpeakAsyncCancelAll();
if (voice.State == SynthesizerState.Ready)
voice.SpeakAsync(m);
if (addlog) PUB.log.Add("SPEAK", m);
if (addlog)
{
if (logcate.isEmpty()) logcate = "SPEAK";
PUB.log.Add(logcate, m);
}
#else
PUB.log.Add($"스피치컴파일상수OFF");
#endif
}
/// <summary>
@@ -118,7 +143,7 @@ namespace Project
if (VAR.BOOL[eVarBool.FLAG_AUTORUN] &&
VAR.BOOL[eVarBool.FLAG_CHARGEONM] == false &&
PUB.BMS.Current_Level > PUB.setting.ChargeEmergencyLevel &&
PUB.sm.RunStep != StateMachine.ERunStep.GOUP &&
PUB.sm.RunStep != StateMachine.ERunStep.BUFFER_OUT &&
VAR.BOOL[eVarBool.WAIT_COVER_DOWN] == false &&
VAR.BOOL[eVarBool.WAIT_COVER_UP] == false)
{
@@ -150,7 +175,7 @@ namespace Project
public static AR.Log log, logagv, logplc, logbms, logxbee;
public static Boolean bPlayMusic = false;
/// <summary>
/// 사용자 인풋 감지 시간
@@ -217,7 +242,7 @@ namespace Project
PUB.mplayer.SoundLocation = PUB.setting.musicfile;
SetVolume(PUB.setting.musicvol);
}
#if SPEECH
voice = new SpeechSynthesizer();
try
{
@@ -245,7 +270,7 @@ namespace Project
// 음성 설정 실패 시 기본값 사용
}
voice.SetOutputToDefaultAudioDevice();
#endif
var file_version = System.IO.Path.Combine(UTIL.CurrentPath, "version.txt");
if (System.IO.File.Exists(file_version))
{
@@ -382,6 +407,9 @@ namespace Project
return Lang.AGV연결실패;
case eECode.PLCCONN:
return Lang.PLC통신실패;
case eECode.MESSAGE_ERROR:
if (values.Length > 0) return values[0].ToString();
return "Message Error";
default:
return err.ToString();
@@ -582,15 +610,46 @@ namespace Project
}
#region VirtualAGV
public static MapNode FindByNodeID(string nodeidx)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_mapNodes == null || _mapNodes.Any() == false) return null;
if (nodeidx.isEmpty()) return null;
return _mapNodes.Where(t => t.Id.Equals(nodeidx)).FirstOrDefault();
}
public static MapNode FindByRFID(string rfidValue)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_mapNodes == null || _mapNodes.Any() == false) return null;
if (rfidValue.isEmpty()) return null;
return _mapNodes.Where(t => t.RfidId.Equals(rfidValue)).FirstOrDefault();
}
public static List<MapNode> FindByNodeAlias(string alias)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_mapNodes == null || _mapNodes.Any() == false) return null;
if (alias.isEmpty()) return null;
var lst = _mapNodes.Where(t => t.AliasName.Equals(alias));
if (lst.Any() == false) return null;
return lst.ToList();
}
public static List<MapNode> FindByNodeType(AGVNavigationCore.Models.MapNode type)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_mapNodes == null || _mapNodes.Any() == false) return null;
var lst = _mapNodes.Where(t => t.Type.Equals(type));
if (lst.Any() == false) return null;
return lst.ToList();
}
/// <summary>
/// RFID 읽기 시 해당 노드 위치로 AGV 업데이트
/// </summary>
/// <param name="rfidId">읽은 RFID ID</param>
/// <param name="motorDirection">모터 방향 (Forward/Backward)</param>
/// <returns>업데이트 성공 여부</returns>
public static bool UpdateAGVFromRFID(string rfidId, AgvDirection motorDirection = AgvDirection.Forward)
public static bool UpdateAGVFromRFID(ushort rfidId, AgvDirection motorDirection = AgvDirection.Forward)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_virtualAGV == null || _mapNodes == null) return false;
// RFID에 해당하는 노드 찾기
@@ -600,7 +659,7 @@ namespace Project
_virtualAGV.SetPosition(node, motorDirection);
RefreshAGVCanvas();
log.Add($"[AGV] RFID {rfidId} 감지 → 노드 {node.NodeId} 위치 업데이트 (방향: {motorDirection})");
log.Add($"[AGV] RFID {rfidId} 감지 → 노드 {node.Id} 위치 업데이트 (방향: {motorDirection})");
return true;
}
@@ -616,9 +675,10 @@ namespace Project
/// <returns>업데이트 성공 여부</returns>
public static bool UpdateAGVToNode(string nodeId, AgvDirection motorDirection = AgvDirection.Forward)
{
var _mapNodes = PUB._mapCanvas.Nodes;
if (_virtualAGV == null || _mapNodes == null) return false;
var node = _mapNodes.FirstOrDefault(n => n.NodeId == nodeId);
var node = _mapNodes.FirstOrDefault(n => n.Id == nodeId);
if (node != null)
{
_virtualAGV.SetPosition(node, motorDirection);
@@ -675,7 +735,7 @@ namespace Project
{
if (_virtualAGV == null) return;
_virtualAGV.BatteryLevel = batteryLevel;
_virtualAGV.SetBatteryLevel(batteryLevel);
RefreshAGVCanvas();
}
@@ -690,42 +750,7 @@ namespace Project
}
}
/// <summary>
/// 현재 AGV의 노드 ID 가져오기
/// </summary>
/// <returns>현재 노드 ID</returns>
public static string GetCurrentAGVNodeId()
{
return _virtualAGV?.CurrentNodeId ?? string.Empty;
}
/// <summary>
/// 현재 AGV 위치 가져오기
/// </summary>
/// <returns>현재 위치</returns>
public static Point GetCurrentAGVPosition()
{
return _virtualAGV?.CurrentPosition ?? Point.Empty;
}
/// <summary>
/// 현재 AGV 방향 가져오기
/// </summary>
/// <returns>현재 방향</returns>
public static AgvDirection GetCurrentAGVDirection()
{
return _virtualAGV?.CurrentDirection ?? AgvDirection.Forward;
}
/// <summary>
/// 현재 AGV 상태 가져오기
/// </summary>
/// <returns>현재 상태</returns>
public static AGVState GetCurrentAGVState()
{
return _virtualAGV?.CurrentState ?? AGVState.Idle;
}
#endregion
}

View File

@@ -19,20 +19,6 @@ namespace Project
private void _SM_RUN(Boolean isFirst, TimeSpan stepTime)
{
//중단기능이 동작이라면 처리하지 않는다.
if (PUB.sm.bPause)
{
System.Threading.Thread.Sleep(200);
return;
}
//가동불가 조건 확인
if (CheckStopCondition() == false)
{
PUB.sm.SetNewStep(eSMStep.IDLE);
return;
}
//HW 연결오류
if (PUB.AGV.IsOpen == false)
{
@@ -41,17 +27,16 @@ namespace Project
return;
}
//이머전시상태라면 stop 처리한다.
if (PUB.AGV.error.Emergency &&
PUB.AGV.system1.agv_stop == true &&
PUB.AGV.system1.stop_by_front_detect == false)
//가동불가 조건 확인
if (CheckStopCondition() == false) return;
//중단기능이 동작이라면 처리하지 않는다.
if (PUB.sm.bPause)
{
PUB.Speak(Lang.);
PUB.sm.SetNewStep(eSMStep.IDLE);
System.Threading.Thread.Sleep(200);
return;
}
//스텝이 변경되었다면?
if (PUB.sm.RunStep != PUB.sm.RunStepNew)
{
@@ -84,7 +69,7 @@ namespace Project
if (PUB.AGV.system1.stop_by_front_detect == true)
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.doorSoundTerm)
if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm)
{
PUB.Speak(Lang.);
LastSpeakTime = DateTime.Now;
@@ -92,14 +77,74 @@ namespace Project
return;
}
//선로이탈감지
if (PUB.AGV.error.runerror_by_no_magent_line == true)
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm)
{
PUB.Speak(Lang.);
LastSpeakTime = DateTime.Now;
}
return;
}
//현재위치를 모르는 상태라면 이동하여 현재 위치를 찾는다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false) return;
//나머지 상황체크
switch (PUB.sm.RunStep)
{
case ERunStep.GOHOME:
if (_SM_RUN_GOHOME(runStepisFirst, PUB.sm.GetRunSteptime) == true)
{
PUB.log.Add($"홈 이동이 완료되어 준비상태로 전환합니다");
PUB.sm.SetNewRunStep(ERunStep.READY);
}
break;
case ERunStep.GOTO: //목적지까지 이동하는 경우
_SM_RUN_GOTO(runStepisFirst, PUB.sm.GetRunSteptime);
if (_SM_RUN_GOTO(runStepisFirst, PUB.sm.GetRunSteptime) == true)
{
//목적지가 BUFFER라면 버퍼투입대기위치까지 완료했다는 시그널을 보낸다.
var target = PUB._virtualAGV.TargetNode;
PUB.log.Add($"목적지({target.RfidId}) 도착완료 타입:{target.Type}, 출발지:{PUB._virtualAGV.StartNode.RfidId}");
switch (target.StationType)
{
case AGVNavigationCore.Models.StationType.Buffer:
//현재위치가 마지막경로의 NODEID와 일치해야한다
if (PUB._virtualAGV.CurrentPath == null)
{
PUB.log.AddAT("목적지 버퍼이동완료 했지만 상세경로가 없습니다");
PUB.XBE.BufferInComplete = false;
PUB.XBE.BufferOutComplete = false;
break;
}
var lastPath = PUB._virtualAGV.CurrentPath.DetailedPath.LastOrDefault();
if (lastPath.NodeId.Equals(PUB._virtualAGV.CurrentNode.Id))
{
//버퍼진입전 노드에 도착완료했따
PUB.XBE.BufferInReady = true;
PUB.XBE.BufferReadyError = false;
}
else
{
//마지막위치가 아닌 다른 위치에 있으니 버퍼 작업을 할 수없다
PUB.log.AddAT("목적지 버퍼이동완료 했지만 마지막 노드가 아닙니다");
PUB.XBE.BufferInReady = false;
PUB.XBE.BufferReadyError = true;
}
PUB.XBE.BufferInComplete = false;
PUB.XBE.BufferOutComplete = false;
break;
}
PUB._virtualAGV.Turn = AGVNavigationCore.Models.AGVTurn.None;
PUB.sm.SetNewRunStep(ERunStep.READY);
}
break;
case ERunStep.MARKSTROPB: //후진방향으로 마크스탑
case ERunStep.MARKSTOPB: //후진방향으로 마크스탑
case ERunStep.MARKSTOPF: //전진방향으로 마크스탑
//이동중이지 않다면 먼저 이동을 진행한다
@@ -172,423 +217,173 @@ namespace Project
PUB.Result.TargetPos = ePosition.QC;
}
PUB.Result.CurrentPosCW = "1";
PUB.sm.SetNewRunStep(ERunStep.GOHOME);
PUB.log.AddAT("충전 해제로 홈으로 이동");
}
}
break;
case ERunStep.GOUP: //상차이동
if (_SM_RUN_GOUP(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
//230601
// if (PUB.Result != null && PUB.sm != null)
// EEMStatus.AddEEDBSQL(PUB.sm.Step, PUB.sm.RunStep.ToString(), PUB.Result.TargetPos.ToString());
//QA를 제외한 경우에는 기본 QC로 이동한다
if (PUB.Result.NextPos == ePosition.QA)
PUB.Result.TargetPos = ePosition.QA;
else
PUB.Result.TargetPos = ePosition.QC;
PUB.sm.SetNewRunStep(ERunStep.GODOWN); //하차작업으로 전환
return;
}
break;
case ERunStep.GODOWN: //하차이동
if (_SM_RUN_GODOWN(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
VAR.TIME.Update(eVarTime.ChargeTry);
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);// (Device.PLC.ZMotDirection.Down); //하차작업이 완료되면 커버를 내려서 바로 작업할 수 있게 한다.
//230601
//if (PUB.Result != null && PUB.sm != null)
// EEMStatus.AddEEDBSQL(PUB.sm.Step, PUB.sm.RunStep.ToString(), PUB.Result.TargetPos.ToString());
//하차가 완료되면 충전대기시간을 30초 남겨두고 없데이트한다
//충전이 필요할 경우 바로 될수있도록 220118
VAR.TIME[eVarTime.ChargeTry] = DateTime.Now.AddSeconds(-1 * PUB.setting.ChargeRetryTerm + 30);
if (PUB.Result.CurrentPos == ePosition.QC)
{
PUB.Speak(Lang.);
PUB.sm.SetNewRunStep(ERunStep.READY);
PUB.log.AddAT("충전 해제로 대기상태로 전환 합니다");
}
}
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;
//로더아웃으로 자동 진행하지 않음 (ACS 명령 대기)
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
{
PUB.sm.SetNewRunStep(ERunStep.READY);
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop; // Command consumed
}
else
{
//홈 이동 명령처리
// Legacy behavior or Goto command: Auto-exit?
// User said separation is key. Let's Stop here too or keep legacy for GOTO?
// Assuming GOTO might rely on this, but safer to STOP if we want strict separation.
// However, let's keep legacy behavior for GOTO if possible, but for PickOn/Off we STOP.
PUB.sm.ClearRunStep();
PUB.Result.TargetPos = ePosition.QC;
PUB.sm.SetNewRunStep(ERunStep.GOHOME);
PUB.Speak(Lang.);
PUB.sm.SetNewRunStep(ERunStep.LOADER_OUT);
}
return;
}
break;
case ERunStep.GOHOME:
if (runStepisFirst)
case ERunStep.UNLOADER_OUT: //언로더아웃
if (_SM_RUN_UNLOADER_OUT(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
}
else if (_SM_RUN_GOHOME(runStepisFirst, PUB.sm.GetRunSteptime))
{
//230601
// if (PUB.Result != null && PUB.sm != null)
// EEMStatus.AddEEDBSQL(PUB.sm.Step, PUB.sm.RunStep.ToString(), PUB.Result.TargetPos.ToString());
PUB.Speak(Lang.);
PUB.Speak(Lang.);
VAR.TIME.Update(eVarTime.ChargeTry);
PUB.sm.SetNewRunStep(ERunStep.READY); //대기상태로 전환한다
//도킹완료상태를 업데이트한다.
PUB.XBE.UnloaderInComplete = false;
PUB.XBE.UnloaderOutComplete = true;
//대기상태로 전환
PUB.sm.SetNewRunStep(ERunStep.READY);
return;
}
break;
}
}
bool CheckStopCondition()
{
return true;
}
void CheckAGVMoveTo(eGoDir dir)
{
//계속내려간다
if (dir == eGoDir.Down)
{
var tsCmd = DateTime.Now - LastCommandTime;
if (tsCmd.TotalMilliseconds >= 1999)
{
//현재 동작중인상태에서 방향이 맞지 않다면 일단 움직임을 멈춘다
if (PUB.AGV.system1.agv_run)
case ERunStep.UNLOADER_IN: //언로더도킹
if (_SM_RUN_UNLOADER_IN(runStepisFirst, PUB.sm.GetRunSteptime))
{
if (PUB.AGV.data.Direction == 'B')
PUB.Speak(Lang.);
//도킹완료상태를 업데이트한다.
PUB.XBE.UnloaderInComplete = true;
//언로더아웃으로 자동 진행하지 않음
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
{
PUB.log.Add($"방향이 맞지 않아 정지 합니다({dir})");
PUB.AGV.AGVMoveStop("CheckAGVMoveTo");
PUB.sm.SetNewRunStep(ERunStep.READY);
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
}
else if (PUB.AGV.data.Speed == 'S')
{
PUB.log.Add($"마크정지를 해제하기 위해 장비를 멈춥니다");
}
}
else
{
//움직이지 않으므로 전진하도록 한다
PUB.log.Add($"AGV 기동 방향(DOWN):{dir}");
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
}
LastCommandTime = DateTime.Now;
}
}
else
{
var tsCmd = DateTime.Now - LastCommandTime;
if (tsCmd.TotalMilliseconds >= 1999)
{
if (PUB.AGV.system1.agv_run)
{
if (PUB.AGV.data.Direction == 'F')
{
PUB.log.Add($"방향이 맞지 않아 정지 합니다({dir})");
PUB.AGV.AGVMoveStop("CheckAGVMoveTo");
}
else if (PUB.AGV.data.Speed == 'S')
{
PUB.log.Add($"마크정지를 해제하기 위해 장비를 멈춥니다");
}
}
else
{
PUB.log.Add($"AGV 기동 방향(UP):{dir}");
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
}
LastCommandTime = DateTime.Now;
}
}
}
void CheckAGVStopbyMARK(string sender)
{
//계속내려간다
var tsCmd = DateTime.Now - LastCommandTime;
if (VAR.BOOL[eVarBool.NEXTSTOP_MARK] == false || tsCmd.TotalMilliseconds >= 1500)
{
PUB.AGV.AGVMoveStop("CheckAGVStopbyMARK", arDev.Narumi.eStopOpt.MarkStop);
LastCommandTime = DateTime.Now;
PUB.log.Add($"[{sender}] MARK신호에 멈춤 설정");
}
}
Boolean UpdateMotionPositionForMark(string sender)
{
//이머전시상태에서는 처리하지 않는다.
if (VAR.BOOL[eVarBool.EMERGENCY]) return false;
//DOWN 작업
// if (goDIR == eGoDir.Down)
{
//1. 현재위치 > 대상위치
if (PUB.Result.CurrentPos > PUB.Result.TargetPos)
{
//계속내려간다
if (PUB.setting.AGV_Direction_FVI_Backward)
CheckAGVMoveTo(eGoDir.Down);
else
CheckAGVMoveTo(eGoDir.Up);
}
//2. 현재위치 < 대상위치
else if (PUB.Result.CurrentPos < PUB.Result.TargetPos)
{
//올라가야한다
if (PUB.setting.AGV_Direction_FVI_Backward)
CheckAGVMoveTo(eGoDir.Up);
else
CheckAGVMoveTo(eGoDir.Down);
}
//3. 현재위치 = 대상위치
else
{
//현재위치가 확정되었는가?
var actpos = ctlPos1.GetPositionActive(PUB.Result.CurrentPos);
if (actpos == false && PUB.AGV.system1.agv_stop == true)
{
//위치확정이되지 않았다면 AGV멈춤시에 기동하게 한다.
var lastcom = DateTime.Now - LastCommandTime;
if (lastcom.TotalSeconds > 3)
{
if (PUB.Result.CurrentPosCW == "1")
{
if (PUB.setting.AGV_Direction_FVI_Backward)
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
else
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
}
else
{
if (PUB.setting.AGV_Direction_FVI_Backward)
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
else
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
}
LastCommandTime = DateTime.Now;
PUB.logagv.Add($"AGV가 멈춰있다 동작을 재개 합니다");
}
}
else
{
//마크센서가 들어와잇고, 위치가 act 되어있다면 해당 위치에 있는 것이다
if (PUB.AGV.error.Emergency == false &&
PUB.AGV.system1.agv_stop &&
PUB.AGV.signal.mark_sensor && actpos &&
PUB.Result.CurrentPos == PUB.Result.TargetPos)
{
//PUB.AGV.AGVMoveStop();
return true;
}
if (PUB.AGV.system1.agv_stop == true && PUB.AGV.system1.agv_run == false)
{
PUB.log.Add($"멈춰있으므로 이동을 시작 합니다");
if (PUB.Result.CurrentPosCW == "1")
{
if (PUB.setting.AGV_Direction_FVI_Backward)
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
else
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
}
else
{
if (PUB.setting.AGV_Direction_FVI_Backward)
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
else
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
}
}
//AGV는 아래로 FVI방향으로 내려가고 있다
if (VAR.BOOL[eVarBool.AGVDIR_UP] == false)
{
if (PUB.Result.CurrentPosCW == "0")
{
//장비가 마크센서에의해 멈췃다면 완료이다
if (PUB.AGV.error.Emergency == false && PUB.AGV.signal.mark_sensor)
{
PUB.AGV.AGVMoveStop("UPdateMotionPositionForMark");
return true;
}
else CheckAGVStopbyMARK(sender);
}
else if (PUB.Result.CurrentPosCW == "1")
{
//내려가는 작업이고 AGV는 올라가고 있는데 RFID는 위졲이 감지되었다면 아래로 내려가야한다
CheckAGVMoveTo(eGoDir.Up);
}
else
{
PUB.Result.CurrentPosCW = "1";
CheckAGVMoveTo(eGoDir.Up);
}
}
//AGV는 Qc방향으로 올라가고 있다
else
{
if (PUB.Result.CurrentPosCW == "1")
{
//네려가는 방향에서 내려가는 위치가 인식되었다면 마크에서 멈춰야 한다
if (PUB.AGV.error.Emergency == false && PUB.AGV.signal.mark_sensor)
{
PUB.AGV.AGVMoveStop("UPdateMotionPositionForMark");
return true;
}
else CheckAGVStopbyMARK(sender);
}
else if (PUB.Result.CurrentPosCW == "0")
{
//내려가는 작업이고 AGV는 올라가고 있는데 RFID는 위졲이 감지되었다면 아래로 내려가야한다
CheckAGVMoveTo(eGoDir.Down);
}
else
{
PUB.Result.CurrentPosCW = "0";
CheckAGVMoveTo(eGoDir.Down);
}
PUB.sm.ClearRunStep();
PUB.sm.SetNewRunStep(ERunStep.UNLOADER_OUT);
}
return;
}
break;
}
}
//UP 작업
//else
//{
// //1. 현재위치 > 대상위치
// if (PUB.Result.CurrentPos > PUB.Result.TargetPos)
// {
// //계속내려간다
// CheckAGVMoveTo(eGoDir.Down);
// }
// //2. 현재위치 < 대상위치
// else if (PUB.Result.CurrentPos < PUB.Result.TargetPos)
// {
// //올라가야한다
// CheckAGVMoveTo(eGoDir.Up);
// }
// //3. 현재위치 = 대상위치
// else
// {
// //AGV는 위로가고 있다
// if (VAR.BOOL[eVarBool.AGVDIR_UP] == true)
// {
// if (PUB.Result.CurrentPosCW == "0")
// {
// //장비가 마크센서에의해 멈췃다면 완료이다
// if (PUB.AGV.system1.agv_stop && PUB.AGV.system1.stop_by_front_detect == false && PUB.AGV.error.Emergency == false && PUB.AGV.signal.mark_sensor)
// {
// return true;
// }
// else CheckAGVStopbyMARK();
// }
// else if (PUB.Result.CurrentPosCW == "1")
// {
// //내려가는 작업이고 AGV는 올라가고 있는데 RFID는 위졲이 감지되었다면 아래로 내려가야한다
// CheckAGVMoveTo(eGoDir.Down);
// }
// }
// //AGV는 내려가고 있다
// else if (VAR.BOOL[eVarBool.AGVDIR_UP] == false)
// {
// if (PUB.Result.CurrentPosCW == "1")
// {
// //네려가는 방향에서 내려가는 위치가 인식되었다면 마크에서 멈춰야 한다
// if (PUB.AGV.system1.agv_stop && PUB.AGV.system1.stop_by_front_detect == false && PUB.AGV.error.Emergency == false && PUB.AGV.signal.mark_sensor)
// {
// return true;
// }
// else CheckAGVStopbyMARK();
// }
// else if (PUB.Result.CurrentPosCW == "0")
// {
// //내려가는 작업이고 AGV는 올라가고 있는데 RFID는 위졲이 감지되었다면 아래로 내려가야한다
// CheckAGVMoveTo(eGoDir.Up);
// }
// }
// }
//}
return false;
}
Boolean UpdateMotionPositionForCharger(string sender)
{
if (VAR.BOOL[eVarBool.AGVDIR_UP] == false)// PUB.flag.get(EFlag.FLAG_DIR_BW) == true)
{
//충전기 검색은 항상 뒤로 검색한다
var tsCmd = DateTime.Now - tm_gocharge_command;
if (tsCmd.TotalMilliseconds >= 1999 &&
PUB.AGV.error.Emergency == false &&
PUB.AGV.system1.agv_run == false)
{
//PUB.PLC.Move(Device.PLC.Rundirection.Backward, "UpdateMotionPosition(" + sender + ")");
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);//
tm_gocharge_command = DateTime.Now;
}
}
else
{
//CCW (역방향 )
//if (Pub.Result.RFIDPos < ePosition.CHARGE || Pub.Result.RFIDPos > ePosition.QC)
//{
// //정상적이라면 RFID값은 QC + 혹은 QC - 에 있어야 하므로 항상 차저보다 높이 있다
// //그렇지 않다면 초기화해서 QC검색부터 다시 실행하게 한다
// //여기는 비정상 위치 값이다.
// Pub.sm.SetStepSeq(1);
//}
//else
//{
//현재위치가 충전위치이고, 움직이지 않았다면 완료된 경우라 할수 있따
if (PUB.Result.CurrentPos == ePosition.CHARGE &&
PUB.AGV.signal.mark_sensor == true)
{
PUB.log.AddI("충전위치 검색 완료");
return true;
}
else
{
//이동중이지 않다면 항상 이동을 해줘야한다
var tsCmd = DateTime.Now - LastCommandTime;
if (tsCmd.TotalMilliseconds >= 1999 &&
PUB.AGV.error.Emergency == false &&
PUB.AGV.system1.agv_run == false)
case ERunStep.CLEANER_OUT: //클리너아웃
if (_SM_RUN_CLEANER_OUT(runStepisFirst, PUB.sm.GetRunSteptime))
{
//PUB.PLC.Move(Device.PLC.Rundirection.Backward, "UpdateMotionPosition #1(" + sender + ")");
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);//
LastCommandTime = DateTime.Now;
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;
//클리너아웃으로 자동 진행하지 않음
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
{
PUB.sm.SetNewRunStep(ERunStep.READY);
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
}
else
{
PUB.sm.ClearRunStep();
PUB.sm.SetNewRunStep(ERunStep.CLEANER_OUT);
}
return;
}
break;
case ERunStep.BUFFER_OUT: //버퍼아웃
if (_SM_RUN_BUFFER_OUT(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
//도킹완료상태를 업데이트한다.
PUB.XBE.BufferInComplete = false;
PUB.XBE.BufferOutComplete = true;
//대기상태로 전환
PUB.sm.SetNewRunStep(ERunStep.READY);
return;
}
break;
case ERunStep.BUFFER_IN: //버퍼도킹
if (_SM_RUN_BUFFER_IN(runStepisFirst, PUB.sm.GetRunSteptime))
{
PUB.Speak(Lang.);
//도킹완료상태를 업데이트한다.
PUB.XBE.BufferInComplete = true;
//버퍼아웃으로 자동 진행하지 않음
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOn || PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
{
PUB.sm.SetNewRunStep(ERunStep.READY);
PUB.NextWorkCmd = ENIGProtocol.AGVCommandHE.Stop;
}
else
{
PUB.sm.ClearRunStep();
PUB.sm.SetNewRunStep(ERunStep.BUFFER_OUT);
}
return;
}
break;
}
return false;
}
}//cvass
}

View File

@@ -0,0 +1,268 @@
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>
/// <param name="isFirst"></param>
/// <param name="stepTime"></param>
/// <returns></returns>
public Boolean _SM_RUN_BUFFER_IN(bool isFirst, TimeSpan stepTime)
{
var funcname = "_SM_RUN_BUFFER_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. 회전이 진행되지 않았다면 회전을 진행한다.
* 2. LIFT DOWN
* 3. 후진-저속-마크다운 실행
*/
if (PUB.sm.RunStepSeq == idx++)
{
PUB.log.Add("버퍼도킹시작");
PUB.Speak(Lang.);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//모션 전후진 제어
if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.L90)
{
//동작중이면 동작을 멈춘다
if (PUB.AGV.system1.agv_run == true)
{
var ts = VAR.TIME.RUN(eVarTime.LastStopCommandTime);
if (ts.TotalSeconds > 3)
{
PUB.AGV.AGVMoveStop(funcname);
VAR.TIME.Update(eVarTime.LastStopCommandTime);
}
}
else PUB.sm.UpdateRunStepSeq(); //agv가 멈춰있으므로 턴을 진행해야 한다
}
else PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다.
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.L90)
{
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.AGV.AGVMoveLeft180Turn();
PUB.log.Add("AGV Left Turn");
PUB.sm.UpdateRunStepSeq();
}
else PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다.
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.L90)
{
//5초이내에 턴이동 상태가 확인되어야 한다.
if (PUB.AGV.system1.agv_run == false)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 5)
{
//5초동안 AGV까 움직이지 않았다면 오류 처리한다.
PUB.AGV.AGVMoveStop("5초이내 턴 감지 안됨");
PUB.log.AddE("5초이내 턴 감지 안됨");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
return false;
}
}
PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다.
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//턴이완료되었느닞 확인한다.
if (PUB._virtualAGV.Turn != AGVNavigationCore.Models.AGVTurn.L90)
{
//10초 이상 가동하고 있다면 문제이다
if (PUB.AGV.system1.agv_run == true)
{
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 15)
{
PUB.AGV.AGVMoveStop("15초이내 턴 이 완료되지 않음");
PUB.log.AddE("5초이내 턴 완료 확인 안됨");
PUB.sm.SetNewRunStep(ERunStep.ERROR);
return false;
}
return false;
}
else PUB._virtualAGV.Turn = AGVNavigationCore.Models.AGVTurn.L90;
}
PUB.sm.UpdateRunStepSeq(); //이미완료된상태이므로 다음으로 진행한다.
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
// [PickOn/PickOff] 초기 리프트 동작
var liftCmd = arDev.Narumi.LiftCommand.DN;
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
{
liftCmd = arDev.Narumi.LiftCommand.UP;
}
PUB.AGV.LiftControl(liftCmd);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//리프트 센서 확인
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
// Timebound check
}
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++)
{
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
PUB.log.Add("버퍼 진입 완료. 작업 수행(Lift Pick/Drop)");
var liftCmd = arDev.Narumi.LiftCommand.UP;
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
{
liftCmd = arDev.Narumi.LiftCommand.DN;
}
PUB.AGV.LiftControl(liftCmd);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
// 리프트 동작 대기
// TODO: 실제 센서 확인 로직 추가 필요
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds < 2) return false;
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환 (퇴출 명령 대기)");
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//완료되었다. (ACS에 보내야함)
PUB.log.Add("버퍼 진입 및 작업 완료");
PUB.sm.UpdateRunStepSeq();
return false;
}
// 작업을 마치고 설비 안에 멈춰있는 상태.
// ACS가 이 상태를 확인하고 NextWorkCmd로 퇴출(Out) 명령을 보내야 함.
PUB.AddEEDB($"버퍼작업완료({PUB.Result.TargetPos})");
return true;
PUB.AddEEDB($"버퍼투입완료({PUB.Result.TargetPos})");
return true;
}
}
}

View File

@@ -0,0 +1,113 @@
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
{
public Boolean _SM_RUN_BUFFER_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("buffer 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;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//우측으로 180도 턴
PUB.AGV.AGVMoveRight180Turn();
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;
}
else if (PUB.sm.RunStepSeq == idx++)
{
PUB.log.Add("BufferOut Complete");
PUB.sm.UpdateRunStepSeq();
return false;
}
PUB.AddEEDB($"bufferout 완료({PUB.Result.TargetPos})");
return true;
}
}
}

View File

@@ -14,6 +14,8 @@ namespace Project
public Boolean _SM_RUN_CHGOFF(bool isFirst, TimeSpan stepTime)
{
//충전중인지 확인한다.
if (VAR.BOOL[eVarBool.FLAG_CHARGEONA] == true || PUB.AGV.system1.Battery_charging == true)
{
@@ -25,7 +27,7 @@ namespace Project
PUB.Speak(Lang.);
}
}
//AGV는 충전을 해제한 상태이다
if (PUB.AGV.system1.Battery_charging == false)
{
@@ -47,6 +49,17 @@ namespace Project
PUB.AGV.AGVCharge(PUB.setting.ChargerID, false);
VAR.TIME.Update(eVarTime.SendChargeOff);
}
// 1분 타임아웃 체크
if (stepTime.TotalMinutes >= 1)
{
PUB.XBE.ErrorMessage = $"충전해제가 실패되었습니다(1분)";
PUB.log.AddE(PUB.XBE.ErrorMessage);
PUB.sm.SetNewStep(eSMStep.IDLE);
return false;
}
}
return false;
}

View File

@@ -0,0 +1,180 @@
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++)
{
// [PickOn/PickOff] 초기 리프트 동작
var liftCmd = arDev.Narumi.LiftCommand.DN;
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
{
liftCmd = arDev.Narumi.LiftCommand.UP;
}
PUB.AGV.LiftControl(liftCmd);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//리프트 센서 확인
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds > 10)
{
// Timebound check
}
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++)
{
// [Action] 진입 완료 후 리프트 동작 (Pick/Drop)
PUB.log.Add("클리너 진입 완료. 작업 수행(Lift Pick/Drop)");
var liftCmd = arDev.Narumi.LiftCommand.UP;
if (PUB.NextWorkCmd == ENIGProtocol.AGVCommandHE.PickOff)
{
liftCmd = arDev.Narumi.LiftCommand.DN;
}
PUB.AGV.LiftControl(liftCmd);
VAR.TIME.Update(eVarTime.LastTurnCommandTime);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
// 리프트 동작 대기
var ts = VAR.TIME.RUN(eVarTime.LastTurnCommandTime);
if (ts.TotalSeconds < 2) return false;
PUB.log.Add("작업(Pick/Drop) 완료. 대기 상태로 전환");
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

@@ -27,7 +27,18 @@ namespace Project
PUB.Result.SetResultMessage(eResult.Hardware, eECode.AGVCONN, eNextStep.ERROR);
return false;
}
//이미 충전중이라면 바로 완료 처리한다 (사용자 요청)
if (VAR.BOOL[eVarBool.FLAG_CHARGEONA] == true || PUB.AGV.system1.Battery_charging == true)
{
if (isFirst)
{
PUB.log.Add("이미 충전 중이므로 충전 시퀀스를 종료하고 준비 상태로 전환합니다.");
PUB.sm.SetNewRunStep(ERunStep.READY);
}
return false;
}
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false)
@@ -38,7 +49,7 @@ namespace Project
if (PUB.AGV.system1.stop_by_front_detect == true)
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.doorSoundTerm)
if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm)
{
PUB.Speak(Lang.);
LastSpeakTime = DateTime.Now;
@@ -63,19 +74,27 @@ namespace Project
var idx = 1;
if (PUB.sm.RunStepSeq == idx++)
{
PUB.Speak(Lang.);
PUB.Result.TargetPos = ePosition.QC;
var targetnode = PUB.FindByRFID(PUB.setting.NodeMAP_RFID_Charger);
if (targetnode == null)
{
PUB.log.AddE($"충전기 노드가 설정되지 않았습니다");
PUB.sm.SetNewRunStep(ERunStep.READY);
return false;
}
//충전기로 대상을 설정한다
PUB._virtualAGV.TargetNode = targetnode;
VAR.TIME.Update(eVarTime.ChargeSearch);
PUB.sm.UpdateRunStepSeq();
PUB.log.Add($"충전:대상위치 QC 시작");
PUB.log.Add($"충전기 위치로 이동 목표:{targetnode.RfidId}");
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//모션 전후진 제어
if (UpdateMotionPositionForMark("GOCHARGE #1") == true)
if (UpdateMotionPositionForCharger("GOCHARGE #1") == true)
{
PUB.log.Add($"충전:충전기 검색 전 QC위치 확인 완료");
PUB.log.Add($"충전기 목표위치 이동 완료");
PUB.sm.UpdateRunStepSeq();
}
else
@@ -98,72 +117,41 @@ namespace Project
}
else if (PUB.sm.RunStepSeq == idx++)
{
if (PUB.setting.chargerpos == 0) //down search
PUB.log.Add($"충전:충전기 검색을 위한 전진시작");
PUB.Speak(Lang.);
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
PUB.log.Add($"충전:충전기 검색을 위한 전진시작");
PUB.Speak(Lang.);
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Speed = arDev.Narumi.eMoveSpd.Low,
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Forward,
PBSSensor = 1,
});
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
//PUB.Result.TargetPos = ePosition.CHARGE;
VAR.TIME.Update(eVarTime.ChargeSearch);
}
else if (PUB.setting.chargerpos == 2) //up search
{
PUB.log.Add($"충전:충전기 검색을 위한 전진시작");
PUB.Speak(Lang.);
PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
{
Speed = arDev.Narumi.eMoveSpd.Low,
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Backward,
PBSSensor = 1,
});
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Backward);
//PUB.Result.TargetPos = ePosition.CHARGE;
VAR.TIME.Update(eVarTime.ChargeSearch);
}
else
{
PUB.log.Add($"충전기위치가 QC위치로 설정되어 있습니다");
}
Speed = arDev.Narumi.eMoveSpd.Low,
Bunki = arDev.Narumi.eBunki.Strate,
Direction = arDev.Narumi.eMoveDir.Forward,
PBSSensor = 1,
});
PUB.AGV.AGVMoveRun(arDev.Narumi.eRunOpt.Forward);
//PUB.Result.TargetPos = ePosition.CHARGE;
VAR.TIME.Update(eVarTime.ChargeSearch);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
if (PUB.setting.chargerpos != 1)
{
if (PUB.AGV.system1.agv_run)
{
PUB.log.Add($"충전:AGV기동확인으로 마크정지신호설정");
PUB.Speak(Lang.);
PUB.AGV.AGVMoveStop("SM_RUN_GOCHARGE", arDev.Narumi.eStopOpt.MarkStop);
VAR.TIME.Update(eVarTime.ChargeSearch);
PUB.sm.UpdateRunStepSeq();
}
else
{
if (VAR.TIME.RUN(eVarTime.ChargeSearch).TotalSeconds > 5)
{
//5초이상 이곳에서 대기한다면 다시 돌려준다
PUB.sm.UpdateRunStepSeq(-1);
PUB.log.Add($"충전:AGV기동확인 안됨, 롤백 다시 이동할 수 있게 함");
}
}
}
else
if (PUB.AGV.system1.agv_run)
{
PUB.log.Add($"충전:AGV기동확인으로 마크정지신호설정");
PUB.Speak(Lang.);
PUB.AGV.AGVMoveStop("SM_RUN_GOCHARGE", arDev.Narumi.eStopOpt.MarkStop);
VAR.TIME.Update(eVarTime.ChargeSearch);
PUB.sm.UpdateRunStepSeq();
}
else
{
if (VAR.TIME.RUN(eVarTime.ChargeSearch).TotalSeconds > 5)
{
//5초이상 이곳에서 대기한다면 다시 돌려준다
PUB.sm.UpdateRunStepSeq(-1);
PUB.log.Add($"충전:AGV기동확인 안됨, 롤백 다시 이동할 수 있게 함");
}
}
return false;
}
@@ -200,9 +188,6 @@ namespace Project
PUB.Result.CurrentPos = ePosition.CHARGE; //현재위치를 충전기로 한다
PUB.Result.TargetPos = ePosition.CHARGE;
PUB.Result.CurrentPosCW = "1";
ctlPos1.SetPosition(ePosition.CHARGE);
ctlPos1.SetPositionActive(ePosition.CHARGE);
ctlPos1.Invalidate();
PUB.sm.UpdateRunStepSeq();
return false;
}
@@ -254,7 +239,6 @@ namespace Project
}
return false;
}
return true;
}
}

View File

@@ -1,223 +0,0 @@
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
{
public Boolean _SM_RUN_GODOWN(bool isFirst, TimeSpan stepTime)
{
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되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false)
return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다 200409
if (PUB.AGV.system1.stop_by_front_detect == true)
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.doorSoundTerm)
{
PUB.Speak(Lang.);
LastSpeakTime = DateTime.Now;
}
return false;
}
//현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false)
return false;
if (PUB.sm.RunStepSeq == 1)
{
//하차는 무조건 대상이 QC가 된다
if (PUB.Result.TargetPos == ePosition.NONE)
PUB.Result.TargetPos = ePosition.QC;
//if (PUB.Result.TargetPos == ePosition.QA &&
// PUB.Result.CurrentPos == ePosition.QC &&
// (PUB.PLC.IsLimitDn()))
//{
// PUB.Speak("안전 커버를 올리고 다시 시도하세요", true);
// PUB.sm.ClearRunStep();
// PUB.sm.SetNewRunStep(ERunStep.READY);
//}
PUB.AddEEDB($"하차작업시작({PUB.Result.TargetPos})");
PUB.Speak(Lang.);
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == 2)
{
//모션 전후진 제어
if (UpdateMotionPositionForMark("GODOWN"))
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == 3)
{
//위치 확정이 완료될때까지 대기
if (PUB.Result.CurrentPos == PUB.Result.TargetPos)
{
//PUB.PLC.Move(Device.PLC.Rundirection.Stop, "GODOWN:위치확정");
PUB.AGV.AGVMoveStop("sm_run_godown");
PUB.sm.UpdateRunStepSeq();
}
else if (PUB.AGV.system1.agv_run == false)
{
//움직이지않으면 방향을 다시 조정한다
PUB.sm.SetStepSeq(2);
}
return false;
}
else if (PUB.sm.RunStepSeq == 4)
{
//대상까지 모두 완료되었다.(완전히 정차할때까지 기다린다)
if (PUB.AGV.system1.agv_run == false)
{
PUB.log.Add("이동 정지 확인");
PUB.sm.UpdateRunStepSeq();
}
return false;
}
else if (PUB.sm.RunStepSeq == 5)
{
//하차수량증가
if (PUB.Result.TargetPos == ePosition.QA)
PUB.counter.CountQA += 1;
else
PUB.counter.CountQC += 1;
PUB.counter.Save();
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == 6)
{
//커버를 자동으로 내려준다
CoverControlTime = DateTime.Now;
UpdateProgressStatus(stepTime.TotalSeconds, 5, Lang.);
VAR.BOOL[eVarBool.WAIT_COVER_DOWN] = true;
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);//
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == 7)
{
//커버 내림이 완료될때까지 기다린다
//if (PUB.PLC.IsLimitDn() == true)
//{
// VAR.BOOL[eVarBool.WAIT_COVER_DOWN] = false;
// PUB.sm.UpdateRunStepSeq();
//}
//else
{
//경과시간이 10초가 지나면 5초마다 음성을 출력한다
var tsCover = DateTime.Now - CoverControlTime;
if (tsCover.TotalSeconds >= 7)
{
PUB.Speak(Lang.);
CoverControlTime = DateTime.Now;
}
}
return false;
}
else if (PUB.sm.RunStepSeq == 8)
{
//IO업데이트 간격 전송
UpdateProgressStatus(stepTime.TotalSeconds, 5, Lang.);
//PUB.Speak(Lang.안전커버를올리면하차가완료됩니다);
VAR.BOOL[eVarBool.WAIT_COVER_UP] = true;
CoverControlTime = DateTime.Now;
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == 9)
{
//커버 올림이 완료될때까지 기다린다
if (VAR.BOOL[eVarBool.FLAG_LIMITHIGH] == true)
{
VAR.BOOL[eVarBool.WAIT_COVER_UP] = false;
VAR.BOOL[eVarBool.ITEMON] = false;
PUB.sm.UpdateRunStepSeq();
}
else
{
//경과시간이 10초가 지나면 5초마다 음성을 출력한다
var tsCover = DateTime.Now - CoverControlTime;
if (tsCover.TotalSeconds >= PUB.setting.doorSoundTerm)
{
PUB.Speak(Lang.);
CoverControlTime = DateTime.Now;
////한쪽이 올라가 있는 상태에..
//if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_LU) == true)
//{
// //모터는 올리는 방향일때에...
// if (PUB.PLC.GetValueO(arDev.FakePLC.DOName.PINO_GUIDEMOTOR_LDIR) == true)
// {
// //모터가 멈춰있을때에..
// if (PUB.PLC.GetValueO(arDev.FakePLC.DOName.PINO_GUIDEMOTOR_LRUN) == false)
// {
// //자동으로 올려준다 (센서가 간혹 인식이 안되어서 .대기하는 경우가 잇음)
// //왼쪽이 올라가 있지 않은 경우
// if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_LU) == false)
// PUB.PLC.ZMot_Left(arDev.FakePLC.ZMotDirection.Up);
// }
// }
//}
//if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_RU) == true)
//{
// //모터는 올리는 방향일때에...
// if (PUB.PLC.GetValueO(arDev.FakePLC.DOName.PINO_GUIDEMOTOR_RDIR) == true)
// {
// //모터가 멈춰있을때에..
// if (PUB.PLC.GetValueO(arDev.FakePLC.DOName.PINO_GUIDEMOTOR_RRUN) == false)
// {
// //자동으로 올려준다 (센서가 간혹 인식이 안되어서 .대기하는 경우가 잇음)
// //왼쪽이 올라가 있지 않은 경우
// if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_RU) == false)
// PUB.PLC.ZMot_Right(arDev.FakePLC.ZMotDirection.Up);
// }
// }
//}
}
}
return false;
}
PUB.AddEEDB($"하차작업완료({PUB.Result.TargetPos})");
EEMStatus.AddStatusCount(1, PUB.Result.TargetPos.ToString()); //230620
return true;
}
}
}

View File

@@ -13,27 +13,18 @@ namespace Project
{
public Boolean _SM_RUN_GOHOME(bool isFirst, TimeSpan stepTime)
{
var funcName = "_SM_RUN_GOHOME";
if (runStepisFirst)
{
// PUB.flag.set(EFlag.FLAG_NEXTSTOP_ALIGN, false);
//VAR.BOOL[eVarBool.FLAG_NEXTSTOP_MARK] = false;//);
VAR.BOOL[eVarBool.FLAG_NEXTSTOP_ALIGN] = false;//);
}
//220629
// if(PUB.flag.get(EFlag.FLAG_GO_CHAGER_TEMP))
//{
// PUB.flag.set(EFlag.FLAG_GO_CHAGER_TEMP, false);
//}
//HW 연결오류
if (PUB.AGV.IsOpen == false)
{
PUB.Result.SetResultMessage(eResult.Hardware, eECode.AGVCONN, eNextStep.ERROR);
return false;
}
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false)
@@ -43,7 +34,7 @@ namespace Project
if (PUB.AGV.system1.stop_by_front_detect == true)
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.doorSoundTerm)
if (tsSpeak.TotalSeconds >= PUB.setting.alarmSoundTerm)
{
PUB.Speak(Lang.);
LastSpeakTime = DateTime.Now;
@@ -51,27 +42,29 @@ namespace Project
return false;
}
//현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false)
return false;
var idx = 1;
if (PUB.sm.RunStepSeq == idx++)
{
PUB.Speak(Lang.);
//홈은 무조건 QC위치로 간다
PUB.AddEEDB($"홈검색시작({PUB.Result.TargetPos})");
PUB.Result.TargetPos = ePosition.QC;
var homenode = PUB.FindByRFID(PUB.setting.NodeMAP_RFID_Home);
if (homenode == null)
{
PUB.log.Add($"홈위치의 노드맵핑(환경설정)이 없습니다");
PUB.sm.SetNewRunStep(ERunStep.READY);
return false;
}
PUB._virtualAGV.TargetNode = homenode;
PUB.AddEEDB($"홈검색시작({homenode.RfidId})");
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//모션 전후진 제어
if (UpdateMotionPositionForMark("GOHOME"))
if (UpdateMotionPositionForMark(funcName))
{
PUB.AGV.AGVMoveStop("SM_RUN_GOHOME");
PUB.AGV.AGVMoveStop(funcName);
PUB.sm.UpdateRunStepSeq();
}
return false;
@@ -79,7 +72,7 @@ namespace Project
else if (PUB.sm.RunStepSeq == idx++)
{
//QC까지 모두 완료되었다.(완전히 정차할때까지 기다린다)
PUB.Speak(Lang., true);
PUB.Speak(Lang., true);
PUB.AddEEDB($"홈검색완료({PUB.Result.TargetPos})");
PUB.sm.UpdateRunStepSeq();
return false;

View File

@@ -17,6 +17,7 @@ namespace Project
{
///명령어 재전송 간격(기본 2초)
var CommandInterval = 2;
var funcName = "_SM_RUN_GOTO";
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false)
@@ -29,267 +30,48 @@ namespace Project
VAR.TIME.Update(eVarTime.CheckGotoTargetSet);
}
//목적지가 설정되었는지 체크한다.
//Z if (PUB.mapctl.Manager.agv.TargetRFID.IsEmpty)
// {
// //최대 5초간 설정여부를 확인하고
// if (VAR.TIME.RUN(eVarTime.CheckGotoTargetSet).TotalSeconds > 5)
// {
// //실패시에는 READY로 전환한다.
// PUB.sm.SetNewRunStep(ERunStep.READY);
// PUB.Speak(Lang.목적지가없어대기상태로전환합니다);
// }
// return false;
// }
//라이더멈춤이 설정되어있다면 음성으로 알려준다 200409
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;
}
//var idx = 1;
//var BeforePredictIdx = -1;
//var predict = PUB.mapctl.Manager.PredictResult;
//if (PUB.sm.RunStepSeq == idx++)
//{
// PUB.Speak(Lang.위치로이동합니다);
// PUB.log.Add($"목적지 위치 이동시작({PUB.mapctl.Manager.agv.TargetRFID.Value})");
// VAR.TIME.Update(eVarTime.CheckGotoTargetSet);
// VAR.TIME.Set(eVarTime.SendGotoCommand, DateTime.Now.AddDays(-1));
// PUB.sm.UpdateRunStepSeq();
// return false;
//}
//else if (PUB.sm.RunStepSeq == idx++)
//{
// //멈춰야하는경우
// if (predict.MoveState == AGVControl.AGVMoveState.Stop)
// {
// if (PUB.AGV.system1.agv_run)
// {
// if (VAR.TIME.RUN(eVarTime.SendGotoCommand).TotalSeconds > 2)
// {
// PUB.Speak("AGV Stop");
// PUB.AGV.AGVMoveStop("Predict", arDev.Narumi.eStopOpt.Stop);
// VAR.TIME.Update(eVarTime.SendGotoCommand);
// }
// return false;
// }
// else
// {
// //완료되었거나 턴을진행해야한다
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.Arrived ||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove ||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint)
// {
// GotoTurnStep = 0;
// GotoTurnSetTime = DateTime.Now.AddDays(-1);
// PUB.sm.UpdateRunStepSeq();
// }
// return false;
// }
// }
// else //이동해야하는 경우
// {
// //속도와 방향이 불일치하는 경우 다시 설정한다 (속도: H,L,M,[S]
// AGVControl.AgvDir AGV_Direction = (AGVControl.AgvDir)PUB.AGV.data.Direction;
// AGVControl.AgvSpeed AGV_Speed = (AGVControl.AgvSpeed)PUB.AGV.data.Speed;
// AGVControl.AgvSts AGV_Sts = (AGVControl.AgvSts)PUB.AGV.data.Sts;
// //상태값이 바뀌었다면 전송을 해야한다
// if (predict.Direction != AGV_Direction || predict.MoveSpeed != AGV_Speed || predict.MoveDiv != AGV_Sts)
// {
// if (VAR.TIME.RUN(eVarTime.SendGotoCommand).TotalSeconds > CommandInterval)
// {
// arDev.Narumi.eBunki v_bunki = arDev.Narumi.eBunki.Strate;
// if (predict.MoveDiv == AGVControl.AgvSts.Straight) v_bunki = arDev.Narumi.eBunki.Strate;
// else if (predict.MoveDiv == AGVControl.AgvSts.Left) v_bunki = arDev.Narumi.eBunki.Left;
// else if (predict.MoveDiv == AGVControl.AgvSts.Right) v_bunki = arDev.Narumi.eBunki.Right;
// arDev.Narumi.eMoveDir v_dir = arDev.Narumi.eMoveDir.Backward;
// if (predict.Direction == AGVControl.AgvDir.Forward) v_dir = arDev.Narumi.eMoveDir.Forward;
// else if (predict.Direction == AGVControl.AgvDir.Backward) v_dir = arDev.Narumi.eMoveDir.Backward;
// arDev.Narumi.eMoveSpd v_spd = arDev.Narumi.eMoveSpd.Low;
// if (predict.MoveSpeed == AGVControl.AgvSpeed.Middle) v_spd = arDev.Narumi.eMoveSpd.Middle;
// else if (predict.MoveSpeed == AGVControl.AgvSpeed.High) v_spd = arDev.Narumi.eMoveSpd.High;
// else if (predict.MoveSpeed == AGVControl.AgvSpeed.Low) v_spd = arDev.Narumi.eMoveSpd.Low;
// //이동셋팅을 해준다
// PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
// {
// Bunki = v_bunki,
// Direction = v_dir,
// PBSSensor = 1,
// Speed = v_spd,
// });
// if (predict.MoveSpeed == AGVControl.AgvSpeed.MarkStop)
// {
// PUB.AGV.AGVMoveStop("Predict", arDev.Narumi.eStopOpt.Stop);
// }
// VAR.TIME.Update(eVarTime.SendGotoCommand);
// }
// return false;
// }
// //정지상태라면 이동 명령을 전달한다
// if (PUB.AGV.system1.agv_run == false)
// {
// if (VAR.TIME.RUN(eVarTime.SendGotoCommand).TotalSeconds > CommandInterval)
// {
// PUB.Speak("AGV Start");
// arDev.Narumi.eRunOpt v_dir = arDev.Narumi.eRunOpt.Backward;
// if (predict.Direction == AGVControl.AgvDir.Forward) v_dir = arDev.Narumi.eRunOpt.Forward;
// else if (predict.Direction == AGVControl.AgvDir.Backward) v_dir = arDev.Narumi.eRunOpt.Backward;
// PUB.AGV.AGVMoveRun(v_dir);
// VAR.TIME.Update(eVarTime.SendGotoCommand);
// }
// return false;
// }
// }
// //예측이 업데이트되지 않으면 오류 처리해야한다
// if (BeforePredictIdx == -1) BeforePredictIdx = (int)predict.Idx;
// else if (BeforePredictIdx != predict.Idx) //이전사용한 IDX와 다르다면 예측이 실행된 경우이다
// BeforePredictIdx = (int)predict.Idx;
// else
// {
// //5초이상 예측값이 업데이트되지 않으면 오류 처리한다.
// var tsPredict = DateTime.Now - predict.CreateTime;
// if (tsPredict.TotalSeconds > 5)
// {
// PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.PredictFix, Lang.예측값이계산되지않아이동을중단합니다);
// PUB.Speak(Lang.예측값이계산되지않아이동을중단합니다);
// PUB.sm.SetNewRunStep(ERunStep.READY);
// }
// }
// return false;
//}
//else if (PUB.sm.RunStepSeq == idx++)
//{
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.Arrived)
// {
// PUB.Speak(Lang.목적지이동이완료되었습니다);
// PUB.sm.SetNewRunStep(ERunStep.READY);
// PUB.sm.UpdateRunStepSeq();
// }
// else if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove ||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint)
// {
// //턴을 해야하는 경우이다
// //좌턴을 기본으로 진행하며, 좌턴이동 후 마크스탑을 입력한다
// if (GotoTurnStep == 0)
// {
// //턴을 한적이 없으므로 턴을 먼저 진행한다
// arDev.Narumi.eMoveDir moveDir = arDev.Narumi.eMoveDir.Backward;
// if (predict.Direction == AGVControl.AgvDir.Forward) moveDir = arDev.Narumi.eMoveDir.Forward;
// if (PUB.AGV.data.Sts != 'L' || PUB.AGV.data.Speed != 'L' || PUB.AGV.data.Direction != moveDir.ToString()[0])
// {
// //셋팅이 다르다면 3초간격으로 전송한다
// var tsTurnSet = DateTime.Now - GotoTurnSetTime;
// if (tsTurnSet.TotalSeconds > 3)
// {
// PUB.AGV.AGVMoveSet(new arDev.Narumi.BunkiData
// {
// Bunki = arDev.Narumi.eBunki.Left,
// Direction = moveDir,
// PBSSensor = 1,
// Speed = arDev.Narumi.eMoveSpd.Low,
// });
// GotoTurnSetTime = DateTime.Now;
// PUB.log.Add("Turn Bunki Set");
// }
// }
// else
// {
// PUB.mapctl.Manager.agv.CurrentRFID.TurnOK = false;
// PUB.mapctl.Manager.agv.CurrentRFID.TurnStart = DateTime.Now;
// PUB.sm.UpdateRunStepSeq(); //셋팅이 맞으니 다음스텝으로 진행한다
// GotoTurnStep += 1;
// }
// }
// }
// else PUB.sm.UpdateRunStepSeq();
// return false;
//}
//else if (PUB.sm.RunStepSeq == idx++)
//{
// //턴이완료되길 기다린다.
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove ||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint)
// {
// //(최소5초는 기다리고 판단한다)
// if (stepTime.TotalSeconds < 5) return false;
// //최대30초는 기다려준다
// if (stepTime.TotalSeconds > 30)
// {
// var ermsg = "Turn Timeout(30sec)";
// PUB.log.AddE(ermsg);
// PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.TurnTimeout, ermsg);
// PUB.sm.SetNewRunStep(ERunStep.READY);
// }
// //모션이 멈추었다면 턴이완료된것이다.
// if (PUB.AGV.system1.agv_stop)
// {
// if (PUB.AGV.system1.Mark1_check == false && PUB.AGV.system1.Mark2_check == false)
// {
// PUB.log.AddE($"Turn 완료이나 Mark 센서가 확인되지 않았습니다");
// }
// GotoTurnStep += 1;
// PUB.sm.UpdateRunStepSeq();
// }
// else
// {
// //아직 이동중이므로 대기한다
// }
// }
// else PUB.sm.UpdateRunStepSeq(); //기타사항은 다음으로 넘어간다
// return false;
//}
//else if (PUB.sm.RunStepSeq == idx++)
//{
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove ||
// predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnPoint)
// {
// if (GotoTurnStep < 2)
// {
// PUB.XBE.SendError(ENIGProtocol.AGVErrorCode.TurnError, "턴시퀀스 완료 실패");
// PUB.log.AddE($"턴완료시퀀스가 2가아닙니다. 대기 상태로 강제 전환합니다");
// PUB.sm.SetNewRunStep(ERunStep.READY);
// }
// else
// {
// PUB.log.AddI("Turn Complete");
// }
// //방향전환용 턴이라면 이동기록을 추가해서 방향이 맞도록 처리해주자
// if (predict.ReasonCode == AGVControl.AGVActionReasonCode.NeedTurnMove)
// {
// var rfid = PUB.mapctl.Manager.agv.CurrentRFID;
// var lastHistory = PUB.mapctl.Manager.agv.MovementHistory.Last();
// //원래방향에서 반대로 처리한다
// var revDir = lastHistory.Direction == AGVControl.AgvDir.Backward ? AGVControl.AgvDir.Forward : AGVControl.AgvDir.Backward;
// PUB.mapctl.Manager.agv.AddToMovementHistory(rfid.Value, rfid.Location, revDir);
// }
// else
// {
// //이동용 RFID에서 턴명령이 들어있는경우였다
// PUB.mapctl.Manager.agv.CurrentRFID.TurnEnd = DateTime.Now;
// PUB.mapctl.Manager.agv.CurrentRFID.TurnOK = true;
// }
// PUB.sm.UpdateRunStepSeq();
// }
// else PUB.sm.UpdateRunStepSeq();
// return false;
//}
//좌턴이동명령 전송
//마크스탑전송
//마크스탑이 확인되면 나머지는 경로예측에 맡긴다
var idx = 1;
if (PUB.sm.RunStepSeq == idx++)
{
if(PUB._virtualAGV.TargetNode == null)
{
PUB.log.Add($"대상노드가 없어 이동을할 수 없습니다");
PUB.sm.SetNewRunStep(ERunStep.READY);
return false;
}
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//모션 전후진 제어
if (UpdateMotionPositionForMark(funcName))
{
PUB.AGV.AGVMoveStop(funcName);
PUB.sm.UpdateRunStepSeq();
}
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//QC까지 모두 완료되었다.(완전히 정차할때까지 기다린다)
PUB.Speak(Lang., true);
PUB.AddEEDB($"홈검색완료({PUB.Result.TargetPos})");
PUB.sm.UpdateRunStepSeq();
return false;
}
return true;
}

View File

@@ -1,269 +0,0 @@
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
{
public Boolean _SM_RUN_GOUP(bool isFirst, TimeSpan stepTime)
{
if (runStepisFirst)
{
// VAR.BOOL[eVarBool.FLAG_NEXTSTOP_MARK] = false;//);
VAR.BOOL[eVarBool.FLAG_NEXTSTOP_ALIGN] = false;
}
//HW 연결오류
if (PUB.AGV.IsOpen == false)
{
PUB.Result.SetResultMessage(eResult.Hardware, eECode.AGVCONN, eNextStep.ERROR);
return false;
}
//충전 상태가 OFF되어야 동작하게한다
if (_SM_RUN_CHGOFF(isFirst, stepTime) == false)
return false;
//라이더멈춤이 설정되어있다면 음성으로 알려준다 200409
if (PUB.AGV.system1.stop_by_front_detect == true)
{
var tsSpeak = DateTime.Now - LastSpeakTime;
if (tsSpeak.TotalSeconds >= PUB.setting.doorSoundTerm)
{
PUB.Speak(Lang.);
LastSpeakTime = DateTime.Now;
}
return false;
}
//현재 위치가 결정되어있는지 체크한다
if (_SM_RUN_POSCHK(isFirst, stepTime) == false)
return false;
var idx = 1;
if (PUB.sm.RunStepSeq == idx++)
{
//상차 가능 조건 확인
if (PUB.Result.TargetPos == ePosition.NONE)
{
PUB.Result.SetResultMessage(eResult.Hardware, eECode.NOTALLOWUP, eNextStep.ERROR);
}
else
{
PUB.AddEEDB($"상차작업시작({PUB.Result.TargetPos})");
PUB.Speak(Lang.);
PUB.sm.UpdateRunStepSeq();
}
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//모션 전후진 제어
if (UpdateMotionPositionForMark("GOUP"))
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//장비멈춤확인
if (PUB.AGV.system1.agv_run == true)
{
var ts = DateTime.Now - LastCommandTime;
if (ts.TotalMilliseconds > 2000)
{
PUB.AGV.AGVMoveStop("SM_RUN_GOUP");// PUB.PLC.Move(Device.PLC.Rundirection.Stop, "GOUP:위치확정");
LastCommandTime = DateTime.Now;
}
}
else
{
//움직이지 않고 있다면 다시 이동을 시켜준다.
PUB.log.Add("이동 정지 확인");
PUB.sm.UpdateRunStepSeq();
}
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
if (PUB.AGV.system1.agv_run == true)
{
var ts = DateTime.Now - LastCommandTime;
if (ts.TotalMilliseconds > 2000)
{
PUB.AGV.AGVMoveStop("SM_RUN_GOUP");// PUB.PLC.Move(Device.PLC.Rundirection.Stop, "GOUP:위치확정");
LastCommandTime = DateTime.Now;
}
}
//커버를 자동으로 내려준다
CoverControlTime = DateTime.Now;
UpdateProgressStatus(stepTime.TotalSeconds, 5, Lang.);
PUB.AGV.LiftControl(arDev.Narumi.LiftCommand.DN);
VAR.BOOL[eVarBool.WAIT_COVER_DOWN] = true;
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
if (PUB.AGV.system1.agv_run == true)
{
var ts = DateTime.Now - LastCommandTime;
if (ts.TotalMilliseconds > 2000)
{
PUB.AGV.AGVMoveStop("SM_RUN_GOUP");// PUB.PLC.Move(Device.PLC.Rundirection.Stop, "GOUP:위치확정");
LastCommandTime = DateTime.Now;
}
}
//커버 내림이 완료될때까지 기다린다
if (true)
{
VAR.BOOL[eVarBool.WAIT_COVER_DOWN] = false;
PUB.Result.NextPos = ePosition.NONE;
PUB.sm.UpdateRunStepSeq();
}
else
{
//경과시간이 10초가 지나면 5초마다 음성을 출력한다
var tsCover = DateTime.Now - CoverControlTime;
if (tsCover.TotalSeconds >= 7)
{
PUB.Speak(Lang.);
CoverControlTime = DateTime.Now;
}
}
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
if (PUB.AGV.system1.agv_run == true)
{
var ts = DateTime.Now - LastCommandTime;
if (ts.TotalMilliseconds > 2000)
{
PUB.AGV.AGVMoveStop("SM_RUN_GOUP");// PUB.PLC.Move(Device.PLC.Rundirection.Stop, "GOUP:위치확정");
LastCommandTime = DateTime.Now;
}
return false;
}
//상차는 여기서 수량확인한다
if (PUB.Result.TargetPos == ePosition.F1)
PUB.counter.CountUp1 += 1;
else if (PUB.Result.TargetPos == ePosition.F2)
PUB.counter.CountUp2 += 1;
else if (PUB.Result.TargetPos == ePosition.F3)
PUB.counter.CountUp3 += 1;
else if (PUB.Result.TargetPos == ePosition.F4)
PUB.counter.CountUp4 += 1;
//else if (PUB.Result.TargetPos == ePosition.QA)
// PUB.counter.CountQA += 1;
PUB.counter.Save();
PUB.sm.UpdateRunStepSeq();
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
//IO업데이트 간격 전송
UpdateProgressStatus(stepTime.TotalSeconds, 5, Lang.);
PUB.Speak(Lang.);
PUB.sm.UpdateRunStepSeq();
VAR.BOOL[eVarBool.WAIT_COVER_UP] = true;
PUB.Result.NextPos = ePosition.NONE;
CoverControlTime = DateTime.Now;
return false;
}
else if (PUB.sm.RunStepSeq == idx++)
{
if (PUB.AGV.system1.agv_run == true)
{
var ts = DateTime.Now - LastCommandTime;
if (ts.TotalMilliseconds > 2000)
{
PUB.AGV.AGVMoveStop("SM_RUN_GOUP");// PUB.PLC.Move(Device.PLC.Rundirection.Stop, "GOUP:위치확정");
LastCommandTime = DateTime.Now;
}
}
//커버 올림이 완료될때까지 기다린다
if (VAR.BOOL[eVarBool.FLAG_LIMITHIGH] == true)
{
VAR.BOOL[eVarBool.WAIT_COVER_UP] = false;
VAR.BOOL[eVarBool.ITEMON] = true;
PUB.sm.UpdateRunStepSeq();
}
else
{
//경과시간이 10초가 지나면 5초마다 음성을 출력한다
var tsCover = DateTime.Now - CoverControlTime;
if (tsCover.TotalSeconds >= PUB.setting.doorSoundTerm)
{
PUB.Speak(Lang.);
CoverControlTime = DateTime.Now;
////한쪽이 올라가 있는 상태에..
//if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_LU) == true)
//{
// //모터는 올리는 방향일때에...
// if (PUB.PLC.GetValueO(arDev.FakePLC.DOName.PINO_GUIDEMOTOR_LDIR) == true)
// {
// //모터가 멈춰있을때에..
// if (PUB.PLC.GetValueO(arDev.FakePLC.DOName.PINO_GUIDEMOTOR_LRUN) == false)
// {
// //자동으로 올려준다 (센서가 간혹 인식이 안되어서 .대기하는 경우가 잇음)
// //왼쪽이 올라가 있지 않은 경우
// if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_LU) == false)
// PUB.PLC.ZMot_Left(arDev.FakePLC.ZMotDirection.Up);
// //오른쪽이 올라가 있지 않은 경우
// if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_RU) == false)
// PUB.PLC.ZMot_Right(arDev.FakePLC.ZMotDirection.Up);
// }
// }
//}
//if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_RU) == true)
//{
// //모터는 올리는 방향일때에...
// if (PUB.PLC.GetValueO(arDev.FakePLC.DOName.PINO_GUIDEMOTOR_RDIR) == true)
// {
// //모터가 멈춰있을때에..
// if (PUB.PLC.GetValueO(arDev.FakePLC.DOName.PINO_GUIDEMOTOR_RRUN) == false)
// {
// //자동으로 올려준다 (센서가 간혹 인식이 안되어서 .대기하는 경우가 잇음)
// //왼쪽이 올라가 있지 않은 경우
// if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_LU) == false)
// PUB.PLC.ZMot_Left(arDev.FakePLC.ZMotDirection.Up);
// //오른쪽이 올라가 있지 않은 경우
// if (PUB.PLC.GetValueI(arDev.FakePLC.DIName.PINI_LIMIT_RU) == false)
// PUB.PLC.ZMot_Right(arDev.FakePLC.ZMotDirection.Up);
// }
// }
//}
}
}
return false;
}
PUB.AddEEDB($"상차작업완료({PUB.Result.TargetPos})");
return true;
}
}
}

Some files were not shown because too many files have changed in this diff Show More