feat: Add real-time IO/interlock updates, HW status display, and history page

- Implement real-time IO value updates via IOValueChanged event
- Add interlock toggle and real-time interlock change events
- Fix ToggleLight to check return value of DIO.SetRoomLight
- Add HW status display in Footer matching WinForms HWState
- Implement GetHWStatus API and 250ms broadcast interval
- Create HistoryPage React component for work history viewing
- Add GetHistoryData API for database queries
- Add date range selection, search, filter, and CSV export
- Add History button in Header navigation
- Add PickerMoveDialog component for manage operations
- Fix DataSet column names (idx, PRNATTACH, PRNVALID, qtymax)

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-27 00:14:47 +09:00
parent bb67d04d90
commit 3bd35ad852
19 changed files with 2917 additions and 81 deletions

View File

@@ -6,6 +6,8 @@ using Microsoft.Web.WebView2.Core;
using Microsoft.Web.WebView2.WinForms;
using Newtonsoft.Json;
using Project.WebUI;
using AR;
using arDev.DIO;
namespace Project.Dialog
{
@@ -15,14 +17,16 @@ namespace Project.Dialog
private Timer plcTimer;
private WebSocketServer _wsServer;
// Machine State (Simulated PLC Memory)
// Machine State
private double currX = 0, currY = 0, currZ = 0;
private double targetX = 0, targetY = 0, targetZ = 0;
private bool[] inputs = new bool[32];
private bool[] outputs = new bool[32];
private string systemState = "IDLE";
private string currentRecipeId = "1"; // Default recipe
// IO 캐시 (변경된 값만 전송하기 위함)
private bool[] _lastInputs;
private bool[] _lastOutputs;
public fWebView()
{
InitializeComponent();
@@ -38,25 +42,149 @@ namespace Project.Dialog
MessageBox.Show("Failed to start WebSocket Server (Port 8081). Run as Admin or allow port.\n" + ex.Message);
}
// Set default inputs (Pressure OK, Estop OK)
inputs[4] = true;
inputs[6] = true;
// IO 캐시 초기화
int diCount = PUB.dio?.GetDICount ?? 32;
int doCount = PUB.dio?.GetDOCount ?? 32;
_lastInputs = new bool[diCount];
_lastOutputs = new bool[doCount];
// IO 값 변경 이벤트 구독 (DIOMonitor.cs와 동일)
if (PUB.dio != null)
{
PUB.dio.IOValueChanged += Dio_IOValueChanged;
}
// 인터락 값 변경 이벤트 구독 (DIOMonitor.cs와 동일)
if (PUB.iLock != null)
{
for (int i = 0; i < PUB.iLock.Length; i++)
{
PUB.iLock[i].ValueChanged += ILock_ValueChanged;
}
}
// Load event handler
this.Load += FWebView_Load;
this.FormClosed += FWebView_FormClosed;
}
private void FWebView_FormClosed(object sender, FormClosedEventArgs e)
{
// IO 이벤트 구독 해제
if (PUB.dio != null)
{
PUB.dio.IOValueChanged -= Dio_IOValueChanged;
}
// 인터락 이벤트 구독 해제
if (PUB.iLock != null)
{
for (int i = 0; i < PUB.iLock.Length; i++)
{
PUB.iLock[i].ValueChanged -= ILock_ValueChanged;
}
}
}
// 인터락 값 변경 이벤트 핸들러 (DIOMonitor.cs의 LockXF_ValueChanged와 동일)
private void ILock_ValueChanged(object sender, AR.InterfaceValueEventArgs e)
{
try
{
var item = sender as CInterLock;
if (item == null) return;
var axisIndex = item.idx;
var ilockUpdate = new
{
type = "INTERLOCK_CHANGED",
data = new
{
axisIndex = axisIndex,
lockIndex = (int)e.ArrIDX,
state = e.NewValue,
hexValue = item.Value().HexString()
}
};
string json = JsonConvert.SerializeObject(ilockUpdate);
// WebView2로 전송
if (webView != null && webView.CoreWebView2 != null)
{
this.BeginInvoke(new Action(() =>
{
try
{
webView.CoreWebView2.PostWebMessageAsJson(json);
}
catch { }
}));
}
// WebSocket으로 전송
_wsServer?.Broadcast(json);
}
catch (Exception ex)
{
Console.WriteLine($"[fWebView] Interlock change broadcast error: {ex.Message}");
}
}
// IO 값 변경 이벤트 핸들러 (DIOMonitor.cs의 dio_IOValueChanged와 동일)
private void Dio_IOValueChanged(object sender, IOValueEventArgs e)
{
try
{
// 변경된 IO만 즉시 전송
var ioUpdate = new
{
type = "IO_CHANGED",
data = new
{
id = e.ArrIDX,
ioType = e.Direction == eIOPINDIR.INPUT ? "input" : "output",
state = e.NewValue
}
};
string json = JsonConvert.SerializeObject(ioUpdate);
// WebView2로 전송
if (webView != null && webView.CoreWebView2 != null)
{
this.BeginInvoke(new Action(() =>
{
try
{
webView.CoreWebView2.PostWebMessageAsJson(json);
}
catch { }
}));
}
// WebSocket으로 전송
_wsServer?.Broadcast(json);
}
catch (Exception ex)
{
Console.WriteLine($"[fWebView] IO change broadcast error: {ex.Message}");
}
}
private void InitializeComponent()
{
this.SuspendLayout();
// Form
this.ClientSize = new System.Drawing.Size(1200, 800);
this.Text = "STD Label Attach - Web UI";
//
// fWebView
//
this.ClientSize = new System.Drawing.Size(1784, 961);
this.Name = "fWebView";
this.StartPosition = FormStartPosition.CenterScreen;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "STD Label Attach - Web UI";
this.ResumeLayout(false);
}
private async void InitializeWebView()
@@ -106,6 +234,9 @@ namespace Project.Dialog
}
}
// HW 상태 업데이트 카운터 (250ms 주기 = 50ms * 5)
private int _hwUpdateCounter = 0;
// --- Logic Loop ---
private void PlcTimer_Tick(object sender, EventArgs e)
{
@@ -114,36 +245,105 @@ namespace Project.Dialog
currY = Lerp(currY, targetY, 0.1);
currZ = Lerp(currZ, targetZ, 0.1);
// 2. Prepare Data Packet
// 2. 시스템 상태 업데이트
if (PUB.sm != null)
{
systemState = PUB.sm.Step.ToString();
}
// 3. Prepare Data Packet
var payload = new
{
type = "STATUS_UPDATE",
sysState = systemState,
position = new { x = currX, y = currY, z = currZ },
ioState = GetChangedIOs() // Function to return array of IO states
ioState = GetChangedIOs() // 변경된 IO만 전송
};
string json = JsonConvert.SerializeObject(payload);
// 3. Send to React via PostMessage (WebView2)
// 4. Send to React via PostMessage (WebView2)
if (webView != null && webView.CoreWebView2 != null)
{
webView.CoreWebView2.PostWebMessageAsJson(json);
}
// 4. Broadcast to WebSocket (Dev/HMR)
// 5. Broadcast to WebSocket (Dev/HMR)
_wsServer?.Broadcast(json);
// 6. HW 상태 업데이트 (250ms 주기 - 윈폼의 _Display_Interval_250ms와 동일)
_hwUpdateCounter++;
if (_hwUpdateCounter >= 5) // 50ms * 5 = 250ms
{
_hwUpdateCounter = 0;
BroadcastHWStatus();
}
}
// H/W 상태 브로드캐스트 (윈폼의 HWState 업데이트와 동일)
private void BroadcastHWStatus()
{
try
{
var bridge = new WebUI.MachineBridge(this);
string hwStatusJson = bridge.GetHWStatus();
var payload = new
{
type = "HW_STATUS_UPDATE",
data = JsonConvert.DeserializeObject(hwStatusJson)
};
string json = JsonConvert.SerializeObject(payload);
// WebView2로 전송
if (webView != null && webView.CoreWebView2 != null)
{
webView.CoreWebView2.PostWebMessageAsJson(json);
}
// WebSocket으로 전송
_wsServer?.Broadcast(json);
}
catch (Exception ex)
{
Console.WriteLine($"[fWebView] HW status broadcast error: {ex.Message}");
}
}
private List<object> GetChangedIOs()
{
// Simply return list of all active IOs or just send all for simplicity
var list = new List<object>();
for (int i = 0; i < 32; i++)
// 실제 DIO에서 값 읽기
if (PUB.dio != null)
{
list.Add(new { id = i, type = "input", state = inputs[i] });
list.Add(new { id = i, type = "output", state = outputs[i] });
int diCount = PUB.dio.GetDICount;
int doCount = PUB.dio.GetDOCount;
// DI (Digital Input) - 변경된 값만 추가
for (int i = 0; i < diCount && i < _lastInputs.Length; i++)
{
bool currentValue = PUB.dio.GetDIValue(i);
if (currentValue != _lastInputs[i])
{
list.Add(new { id = i, type = "input", state = currentValue });
_lastInputs[i] = currentValue;
}
}
// DO (Digital Output) - 변경된 값만 추가
for (int i = 0; i < doCount && i < _lastOutputs.Length; i++)
{
bool currentValue = PUB.dio.GetDOValue(i);
if (currentValue != _lastOutputs[i])
{
list.Add(new { id = i, type = "output", state = currentValue });
_lastOutputs[i] = currentValue;
}
}
}
return list;
}
@@ -164,9 +364,37 @@ namespace Project.Dialog
if (axis == "Z") targetZ = val;
}
public void SetOutput(int id, bool state)
// DO 출력 제어 (DIOMonitor.cs의 tblDO_ItemClick과 동일한 로직)
public bool SetOutput(int id, bool state)
{
if (id < 32) outputs[id] = state;
try
{
if (PUB.dio == null)
{
Console.WriteLine($"[fWebView] SetOutput failed: DIO not initialized");
return false;
}
if (PUB.dio.IsInit == false)
{
// DIO가 초기화되지 않은 경우 가상 신호 생성 (디버그 모드)
PUB.dio.RaiseEvent(eIOPINDIR.OUTPUT, id, state);
PUB.log.Add($"[Web] Fake DO: idx={id}, val={state}");
return true;
}
else
{
// 실제 출력 제어
PUB.dio.SetOutput(id, state);
PUB.log.Add($"[Web] Set output: idx={id}, val={state}");
return true;
}
}
catch (Exception ex)
{
Console.WriteLine($"[fWebView] SetOutput error: {ex.Message}");
return false;
}
}
public void HandleCommand(string cmd)
@@ -214,5 +442,34 @@ namespace Project.Dialog
Console.WriteLine($"PostMessage failed: {ex.Message}");
}
}
// 이벤트 브로드캐스트 (WebView2 + WebSocket)
public void BroadcastEvent(string eventType, object data = null)
{
try
{
var payload = new
{
type = eventType,
data = data
};
string json = JsonConvert.SerializeObject(payload);
// WebView2로 전송
if (webView != null && webView.CoreWebView2 != null)
{
webView.CoreWebView2.PostWebMessageAsJson(json);
}
// WebSocket으로 전송
_wsServer?.Broadcast(json);
Console.WriteLine($"[fWebView] BroadcastEvent: {eventType}");
}
catch (Exception ex)
{
Console.WriteLine($"BroadcastEvent failed: {ex.Message}");
}
}
}
}

View File

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

View File

@@ -32,8 +32,12 @@ namespace Project
//Pub.sm.setNewStep(eSMStep.XMOVE); //홈을 위해서 바로 이동 모션으로 가게한다
if (PUB.mot.HasHomeSetOff == true && DIO.GetIOInput(eDIName.PICKER_SAFE) == false)
{
//피커의 이동이 필요한 상황
this.BeginInvoke(new Action(() => { btManage.PerformClick(); }));
// 웹 UI에도 자동 관리창 열기 이벤트 전송
SendWebEvent("AUTO_OPEN_MANAGE", new { reason = "Picker needs to be moved to safe position" });
//피커의 이동이 필요한 상황
this.BeginInvoke(new Action(() => { btManage.PerformClick(); }));
}
else
{

View File

@@ -730,6 +730,9 @@
<EmbeddedResource Include="Dialog\fVAR.resx">
<DependentUpon>fVAR.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fWebView.resx">
<DependentUpon>fWebView.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Dialog\fZPLEditor.resx">
<DependentUpon>fZPLEditor.cs</DependentUpon>
</EmbeddedResource>

View File

@@ -6,6 +6,7 @@ using System.IO;
using System.Linq;
using System.Collections.Generic;
using AR;
using System.Threading.Tasks;
namespace Project.WebUI
{
@@ -1145,7 +1146,14 @@ namespace Project.WebUI
}
var cur = DIO.GetIOOutput(AR.eDOName.ROOMLIGHT);
DIO.SetRoomLight(!cur, true);
var success = DIO.SetRoomLight(!cur, true);
if (success == false)
{
var response = new { success = false, message = "Failed to control room light" };
return JsonConvert.SerializeObject(response);
}
PUB.log.Add($"User Request: Room Light {(!cur ? "ON" : "OFF")}", false);
var response2 = new { success = true, message = $"Light turned {(!cur ? "ON" : "OFF")}" };
return JsonConvert.SerializeObject(response2);
@@ -1173,7 +1181,7 @@ namespace Project.WebUI
var selectedPrinter = printer.ToLower() == "left" ? PUB.PrinterL : PUB.PrinterR;
// Create ZPL
string zpl = selectedPrinter.makeZPL_210908(new AR.Class.Reel
string zpl = selectedPrinter.makeZPL_210908(new Class.Reel
{
SID = sid,
venderLot = venderLot,
@@ -1290,10 +1298,11 @@ namespace Project.WebUI
return JsonConvert.SerializeObject(response);
}
// The manage dialog (fPickerMove) cannot be opened from web UI
// This would require implementing a separate picker management page
// Set flag to indicate picker move dialog is open
PUB.flag.set(eVarBool.FG_MOVE_PICKER, true, "PICKERMOVE_WEB");
PUB.log.Add("User Request: Manage (Web UI)", false);
var response2 = new { success = false, message = "Manage is not available in Web UI. Please use the main program." };
var response2 = new { success = true, message = "Manage dialog opened" };
return JsonConvert.SerializeObject(response2);
}
catch (Exception ex)
@@ -1304,6 +1313,29 @@ namespace Project.WebUI
}
}
public string CloseManage()
{
try
{
// Clear the flag
PUB.flag.set(eVarBool.FG_MOVE_PICKER, false, "PICKERMOVE_WEB");
// Check if auto-init is needed: home not set AND picker is in center position
bool shouldAutoInit = PUB.mot.HasHomeSetOff == true && DIO.GetIOInput(eDIName.PICKER_SAFE);
PUB.log.Add($"User Request: Close Manage (Web UI), shouldAutoInit={shouldAutoInit}", false);
var response = new { shouldAutoInit = shouldAutoInit };
return JsonConvert.SerializeObject(response);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to close manage: {ex.Message}");
var response = new { shouldAutoInit = false };
return JsonConvert.SerializeObject(response);
}
}
public string OpenManual()
{
try
@@ -1433,5 +1465,837 @@ namespace Project.WebUI
return JsonConvert.SerializeObject(response);
}
}
// ===== PICKER MOVE METHODS =====
private static bool _manPosL = false;
private static bool _manPosR = false;
public string GetPickerStatus()
{
try
{
// X축 피커 이동 가능 여부 체크
bool xEnabled = false;
if (PUB.mot.IsHomeSet((int)eAxis.PZ_PICK) == false)
{
if (PUB.mot.IsOrg((int)eAxis.PZ_PICK) || PUB.mot.IsLimitN((int)eAxis.PZ_PICK))
xEnabled = true;
}
else
{
var PosZ = MOT.GetPZPos(ePZLoc.READY);
var OffZ = MOT.getPositionOffset(PosZ);
xEnabled = OffZ < 1;
}
var doorsafef = DIO.isSaftyDoorF();
var managementEnabled = PUB.mot.HasHomeSetOff == false && doorsafef == true;
var status = new
{
xEnabled = xEnabled,
zEnabled = xEnabled, // Z jog uses same condition
pickerSafe = DIO.GetIOInput(eDIName.PICKER_SAFE),
managementEnabled = managementEnabled,
manPosL = _manPosL,
manPosR = _manPosR
};
return JsonConvert.SerializeObject(status);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get picker status: {ex.Message}");
return JsonConvert.SerializeObject(new { xEnabled = false, zEnabled = false, pickerSafe = false, managementEnabled = false, manPosL = false, manPosR = false });
}
}
private bool CheckPickerSafety(out string errorMessage)
{
errorMessage = null;
if (DIO.isSaftyDoorF() == false)
{
errorMessage = "Front door is open";
return false;
}
if (PUB.mot.HasHomeSetOff)
{
errorMessage = "Motion home operation is not completed";
return false;
}
return true;
}
private bool CheckZAxisReady(out string errorMessage)
{
errorMessage = null;
var z = MOT.GetPZPos(ePZLoc.READY);
var zpos = MOT.getPositionOffset(z);
if (zpos >= 0.5)
{
errorMessage = "Raise the Z axis and try again";
return false;
}
return true;
}
public string PickerMoveLeft()
{
try
{
if (!CheckPickerSafety(out string safetyError))
return JsonConvert.SerializeObject(new { success = false, message = safetyError });
if (!CheckZAxisReady(out string zError))
return JsonConvert.SerializeObject(new { success = false, message = zError });
var m1 = MOT.GetLMPos(eLMLoc.READY);
if (MOT.getPositionMatch(m1) == false)
return JsonConvert.SerializeObject(new { success = false, message = "Printer attachment is not in ready position.\nCannot move as collision may occur" });
var p1 = MOT.GetPXPos(ePXLoc.PICKOFFL);
MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);
return JsonConvert.SerializeObject(new { success = true, message = "Moving to left position" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerMoveLeftWait()
{
try
{
if (!CheckPickerSafety(out string safetyError))
return JsonConvert.SerializeObject(new { success = false, message = safetyError });
if (!CheckZAxisReady(out string zError))
return JsonConvert.SerializeObject(new { success = false, message = zError });
var m1 = MOT.GetLMPos(eLMLoc.READY);
if (MOT.getPositionMatch(m1) == false)
return JsonConvert.SerializeObject(new { success = false, message = "Printer attachment is not in ready position.\nCannot move as collision may occur" });
var p1 = MOT.GetPXPos(ePXLoc.READYL);
MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);
return JsonConvert.SerializeObject(new { success = true, message = "Moving to left wait position" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerMoveCenter()
{
try
{
if (!CheckPickerSafety(out string safetyError))
return JsonConvert.SerializeObject(new { success = false, message = safetyError });
if (!CheckZAxisReady(out string zError))
return JsonConvert.SerializeObject(new { success = false, message = zError });
var p1 = MOT.GetPXPos(ePXLoc.PICKON);
MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);
return JsonConvert.SerializeObject(new { success = true, message = "Moving to center position" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerMoveRightWait()
{
try
{
if (!CheckPickerSafety(out string safetyError))
return JsonConvert.SerializeObject(new { success = false, message = safetyError });
if (!CheckZAxisReady(out string zError))
return JsonConvert.SerializeObject(new { success = false, message = zError });
var m1 = MOT.GetRMPos(eRMLoc.READY);
if (MOT.getPositionMatch(m1) == false)
return JsonConvert.SerializeObject(new { success = false, message = "Printer attachment is not in ready position.\nCannot move as collision may occur" });
var p1 = MOT.GetPXPos(ePXLoc.READYR);
MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);
return JsonConvert.SerializeObject(new { success = true, message = "Moving to right wait position" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerMoveRight()
{
try
{
if (!CheckPickerSafety(out string safetyError))
return JsonConvert.SerializeObject(new { success = false, message = safetyError });
if (!CheckZAxisReady(out string zError))
return JsonConvert.SerializeObject(new { success = false, message = zError });
var m1 = MOT.GetRMPos(eRMLoc.READY);
if (MOT.getPositionMatch(m1) == false)
return JsonConvert.SerializeObject(new { success = false, message = "Printer attachment is not in ready position.\nCannot move as collision may occur" });
var p1 = MOT.GetPXPos(ePXLoc.PICKOFFR);
MOT.Move(eAxis.PX_PICK, p1.Position, 250, p1.Acc, false, false, false);
return JsonConvert.SerializeObject(new { success = true, message = "Moving to right position" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerJogStart(string direction)
{
try
{
switch (direction)
{
case "up":
PUB.mot.JOG((int)eAxis.PZ_PICK, arDev.MOT.MOTION_DIRECTION.Negative, AR.SETTING.Data.JOG_Speed, AR.SETTING.Data.JOG_Acc);
break;
case "down":
PUB.mot.JOG((int)eAxis.PZ_PICK, arDev.MOT.MOTION_DIRECTION.Positive, AR.SETTING.Data.JOG_Speed, AR.SETTING.Data.JOG_Acc);
break;
case "left":
PUB.mot.JOG((int)eAxis.PX_PICK, arDev.MOT.MOTION_DIRECTION.Negative, AR.SETTING.Data.JOG_Speed, AR.SETTING.Data.JOG_Acc);
break;
case "right":
PUB.mot.JOG((int)eAxis.PX_PICK, arDev.MOT.MOTION_DIRECTION.Positive, AR.SETTING.Data.JOG_Speed, AR.SETTING.Data.JOG_Acc);
break;
}
return JsonConvert.SerializeObject(new { success = true, message = $"Jog {direction} started" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerJogStop()
{
try
{
PUB.mot.MoveStop("pmove_web", (short)eAxis.PX_PICK);
PUB.mot.MoveStop("pmove_web", (short)eAxis.PZ_PICK);
return JsonConvert.SerializeObject(new { success = true, message = "Jog stopped" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerStop()
{
try
{
PUB.mot.MoveStop("pmove_web", (int)eAxis.PX_PICK);
return JsonConvert.SerializeObject(new { success = true, message = "Picker stopped" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string CancelVisionValidation(string side)
{
try
{
if (side == "left")
{
PUB.flag.set(eVarBool.FG_PRC_VISIONL, false, "CANCEL_WEB");
PUB.flag.set(eVarBool.FG_PORTL_ITEMON, false, "CANCEL_WEB");
PUB.log.Add($"LEFT-QR verification cancelled JGUID={PUB.Result.ItemDataL.guid}");
}
else
{
PUB.flag.set(eVarBool.FG_PRC_VISIONR, false, "CANCEL_WEB");
PUB.flag.set(eVarBool.FG_PORTR_ITEMON, false, "CANCEL_WEB");
PUB.log.Add($"RIGHT-QR verification cancelled JGUID={PUB.Result.ItemDataR.guid}");
}
return JsonConvert.SerializeObject(new { success = true, message = $"{side.ToUpper()}-QR verification cancelled" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerManagePosition(string side)
{
try
{
if (PUB.sm.Step != eSMStep.IDLE)
return JsonConvert.SerializeObject(new { success = false, message = "Available only in standby state" });
var Xpos = DIO.GetIOInput(eDIName.PICKER_SAFE);
if (Xpos == false)
return JsonConvert.SerializeObject(new { success = false, message = "Available only when picker is in center position" });
int vidx = side == "left" ? 0 : 2;
Task.Run(() =>
{
DateTime dt;
if (vidx == 0)
{
while (DIO.GetIOInput(eDIName.L_CYLUP) == false)
{
var dorlt = DIO.checkDigitalO(eDOName.L_CYLDN, new TimeSpan(1), false);
if (dorlt == eNormalResult.False)
System.Threading.Thread.Sleep(100);
else if (dorlt == eNormalResult.Error)
{
PUB.log.AddE("l_cylup check error");
return;
}
}
var zPos = MOT.GetLZPos(eLZLoc.READY).Clone();
zPos.Speed = 100;
MOT.Move(zPos);
dt = DateTime.Now;
while (MOT.getPositionMatch(zPos) == false)
{
if ((DateTime.Now - dt).TotalSeconds > 30) break;
System.Threading.Thread.Sleep(10);
}
var mPos = MOT.GetLMPos(eLMLoc.PRINTL07).Clone();
mPos.Speed = 100;
MOT.Move(mPos);
dt = DateTime.Now;
while (MOT.getPositionMatch(mPos) == false)
{
if ((DateTime.Now - dt).TotalSeconds > 30) break;
System.Threading.Thread.Sleep(10);
}
var zPos2 = MOT.GetLZPos(eLZLoc.PICKOFF);
var tPos = (zPos2.Position / 2f);
MOT.Move(eAxis.PL_UPDN, tPos, 100, zPos.Acc);
_manPosL = true;
}
else
{
while (DIO.GetIOInput(eDIName.R_CYLUP) == false)
{
var dorlt = DIO.checkDigitalO(eDOName.R_CYLDN, new TimeSpan(1), false);
if (dorlt == eNormalResult.False)
System.Threading.Thread.Sleep(100);
else if (dorlt == eNormalResult.Error)
{
PUB.log.AddE("r_cylup check error");
return;
}
}
var zPos = MOT.GetRZPos(eRZLoc.READY).Clone();
zPos.Speed = 100;
MOT.Move(zPos);
dt = DateTime.Now;
while (MOT.getPositionMatch(zPos) == false)
{
if ((DateTime.Now - dt).TotalSeconds > 30) break;
System.Threading.Thread.Sleep(10);
}
var mPos = MOT.GetRMPos(eRMLoc.PRINTL07).Clone();
mPos.Speed = 100;
MOT.Move(mPos);
dt = DateTime.Now;
while (MOT.getPositionMatch(mPos) == false)
{
if ((DateTime.Now - dt).TotalSeconds > 30) break;
System.Threading.Thread.Sleep(10);
}
var zPos2 = MOT.GetRZPos(eRZLoc.PICKOFF);
var tPos = (zPos2.Position / 2f);
MOT.Move(eAxis.PR_UPDN, tPos, 100, zPos.Acc);
_manPosR = true;
}
});
return JsonConvert.SerializeObject(new { success = true, message = $"Moving to {side} management position" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerManageReturn()
{
try
{
var Xpos = DIO.GetIOInput(eDIName.PICKER_SAFE);
if (Xpos == false)
return JsonConvert.SerializeObject(new { success = false, message = "Available only when picker is in center position" });
// Recover both positions
Task.Run(() => PosRecover(0));
Task.Run(() => PosRecover(2));
return JsonConvert.SerializeObject(new { success = true, message = "Returning to ready position" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
private void PosRecover(int vidx)
{
DateTime dt;
if (vidx == 0)
{
while (DIO.GetIOInput(eDIName.L_CYLUP) == false)
{
var dorlt = DIO.checkDigitalO(eDOName.L_CYLDN, new TimeSpan(1), false);
if (dorlt == eNormalResult.False)
System.Threading.Thread.Sleep(100);
else if (dorlt == eNormalResult.Error)
{
PUB.log.AddE("l_cylup check error");
return;
}
}
var zPos = MOT.GetLZPos(eLZLoc.READY).Clone();
zPos.Speed = 100;
MOT.Move(zPos);
dt = DateTime.Now;
while (MOT.getPositionMatch(zPos) == false)
{
if ((DateTime.Now - dt).TotalSeconds > 30) break;
System.Threading.Thread.Sleep(10);
}
var mPos = MOT.GetLMPos(eLMLoc.READY).Clone();
mPos.Speed = 100;
MOT.Move(mPos);
dt = DateTime.Now;
while (MOT.getPositionMatch(mPos) == false)
{
if ((DateTime.Now - dt).TotalSeconds > 30) break;
System.Threading.Thread.Sleep(10);
}
_manPosL = false;
}
else
{
while (DIO.GetIOInput(eDIName.R_CYLUP) == false)
{
var dorlt = DIO.checkDigitalO(eDOName.R_CYLDN, new TimeSpan(1), false);
if (dorlt == eNormalResult.False)
System.Threading.Thread.Sleep(100);
else if (dorlt == eNormalResult.Error)
{
PUB.log.AddE("R_cylup check error");
return;
}
}
var zPos = MOT.GetRZPos(eRZLoc.READY).Clone();
zPos.Speed = 100;
MOT.Move(zPos);
dt = DateTime.Now;
while (MOT.getPositionMatch(zPos) == false)
{
if ((DateTime.Now - dt).TotalSeconds > 30) break;
System.Threading.Thread.Sleep(10);
}
var mPos = MOT.GetRMPos(eRMLoc.READY).Clone();
mPos.Speed = 100;
MOT.Move(mPos);
dt = DateTime.Now;
while (MOT.getPositionMatch(mPos) == false)
{
if ((DateTime.Now - dt).TotalSeconds > 30) break;
System.Threading.Thread.Sleep(10);
}
_manPosR = false;
}
}
public string PickerZHome()
{
try
{
MOT.Home("Management_Web", eAxis.PZ_PICK, false);
return JsonConvert.SerializeObject(new { success = true, message = "Z-axis home search started" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerZZero()
{
try
{
if (PUB.mot.IsHomeSet((int)eAxis.PZ_PICK) == false)
return JsonConvert.SerializeObject(new { success = false, message = "Z home operation is not completed. Please perform HOME first" });
MOT.Move(eAxis.PZ_PICK, 0, 500, 1000, false, false, false);
return JsonConvert.SerializeObject(new { success = true, message = "Moving Z-axis to zero" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string PickerTestPrint(string side)
{
try
{
if (side == "left")
{
PUB.PrinterL.TestPrint(AR.SETTING.Data.DrawOutbox, "", "");
PUB.log.Add("Temporary print L:" + PUB.PrinterL.LastPrintZPL);
}
else
{
PUB.PrinterR.TestPrint(AR.SETTING.Data.DrawOutbox, "", "");
PUB.log.Add("Temporary print R:" + PUB.PrinterR.LastPrintZPL);
}
return JsonConvert.SerializeObject(new { success = true, message = $"{side.ToUpper()} test print completed" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
public string CanCloseManage()
{
try
{
if (_manPosL || _manPosR)
{
return JsonConvert.SerializeObject(new { canClose = false, message = "Printer motion is in management position.\nReturn to position and try again" });
}
return JsonConvert.SerializeObject(new { canClose = true, message = "" });
}
catch (Exception ex)
{
return JsonConvert.SerializeObject(new { canClose = false, message = ex.Message });
}
}
// ===== INTERLOCK METHODS =====
/// <summary>
/// 인터락 토글 (DIOMonitor.cs의 gvILXF_ItemClick과 동일)
/// </summary>
public string ToggleInterlock(int axisIndex, int lockIndex)
{
try
{
if (axisIndex < 0 || axisIndex >= PUB.iLock.Length)
{
return JsonConvert.SerializeObject(new { success = false, message = "Invalid axis index" });
}
var curValue = PUB.iLock[axisIndex].get(lockIndex);
PUB.iLock[axisIndex].set(lockIndex, !curValue, "IOMONITOR_WEB");
PUB.log.Add($"[Web] Interlock toggle: axis={axisIndex}, lock={lockIndex}, newVal={!curValue}");
return JsonConvert.SerializeObject(new { success = true, newState = !curValue });
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to toggle interlock: {ex.Message}");
return JsonConvert.SerializeObject(new { success = false, message = ex.Message });
}
}
// ===== HISTORY DATA =====
/// <summary>
/// 작업 이력 데이터 조회 (fHistory.cs의 refreshList와 동일)
/// </summary>
public string GetHistoryData(string startDate, string endDate, string search)
{
try
{
var results = new List<object>();
using (var ta = new DataSet1TableAdapters.K4EE_Component_Reel_ResultTableAdapter())
{
var ds = new DataSet1();
// 검색어 처리
var searchPattern = string.IsNullOrEmpty(search) ? "%" : "%" + search + "%";
// DB에서 조회
ta.FillBySearch(ds.K4EE_Component_Reel_Result, AR.SETTING.Data.McName, startDate, endDate, searchPattern);
foreach (DataSet1.K4EE_Component_Reel_ResultRow row in ds.K4EE_Component_Reel_Result.Rows)
{
results.Add(new
{
idx = row.idx,
jtype = row.IsJTYPENull() ? "" : row.JTYPE,
rid = row.IsRIDNull() ? "" : row.RID,
sid = row.IsSIDNull() ? "" : row.SID,
qty = row.IsQTYNull() ? 0 : row.QTY,
vname = row.IsVNAMENull() ? "" : row.VNAME,
vlot = row.IsVLOTNull() ? "" : row.VLOT,
loc = row.IsLOCNull() ? "" : row.LOC,
qr = row.IsQRNull() ? "" : row.QR,
stime = row.STIME.ToString("yyyy-MM-dd HH:mm:ss"),
etime = row.IsETIMENull() ? "" : row.ETIME.ToString("yyyy-MM-dd HH:mm:ss"),
prnattach = !row.IsPRNATTACHNull() && row.PRNATTACH,
prnvalid = !row.IsPRNVALIDNull() && row.PRNVALID,
rid0 = row.IsRID0Null() ? "" : row.RID0,
sid0 = row.IsSID0Null() ? "" : row.SID0,
qtymax = row.IsqtymaxNull() ? 0 : row.qtymax
});
}
}
return JsonConvert.SerializeObject(new {
success = true,
data = results,
mcName = AR.SETTING.Data.McName
});
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get history data: {ex.Message}");
return JsonConvert.SerializeObject(new { success = false, message = ex.Message, data = new List<object>() });
}
}
// ===== HARDWARE STATUS METHODS =====
/// <summary>
/// H/W 상태 조회 (_Interval_250ms.cs의 HWState 업데이트 로직과 동일)
/// </summary>
public string GetHWStatus()
{
try
{
var hwList = new List<object>();
// 1. KeyenceF (BCD Front) - 바코드 리더
if (PUB.keyenceF != null)
{
hwList.Add(new
{
name = "BCD-F",
title = PUB.keyenceF.IsConnect ? (PUB.keyenceF.IsTriggerOn ? "TRIG" : "ON") : "OFF",
status = PUB.keyenceF.IsConnect ? (PUB.keyenceF.IsTriggerOn ? 2 : 1) : 3
});
}
else
{
hwList.Add(new { name = "BCD-F", title = "SET", status = 0 });
}
// 2. KeyenceR (BCD Rear) - 바코드 리더
if (PUB.keyenceR != null)
{
hwList.Add(new
{
name = "BCD-R",
title = PUB.keyenceR.IsConnect ? (PUB.keyenceR.IsTriggerOn ? "TRIG" : "ON") : "OFF",
status = PUB.keyenceR.IsConnect ? (PUB.keyenceR.IsTriggerOn ? 2 : 1) : 3
});
}
else
{
hwList.Add(new { name = "BCD-R", title = "SET", status = 0 });
}
// 3. Vision WebSocket Left
if (PUB.wsL != null)
{
hwList.Add(new
{
name = "VIS-L",
title = PUB.wsL.Connected ? "ON" : "OFF",
status = PUB.wsL.Connected ? 1 : 3
});
}
else
{
hwList.Add(new { name = "VIS-L", title = "SET", status = 0 });
}
// 4. Vision WebSocket Right
if (PUB.wsR != null)
{
hwList.Add(new
{
name = "VIS-R",
title = PUB.wsR.Connected ? "ON" : "OFF",
status = PUB.wsR.Connected ? 1 : 3
});
}
else
{
hwList.Add(new { name = "VIS-R", title = "SET", status = 0 });
}
// 5. BarcodeFix (Fixed Barcode Reader)
hwList.Add(new
{
name = "FIX",
title = PUB.BarcodeFix.IsOpen() ? AR.SETTING.Data.Barcode_Port : "OFF",
status = PUB.BarcodeFix.IsOpen() ? 1 : 3
});
// 6. PrinterL
if (PUB.PrinterL != null)
{
hwList.Add(new
{
name = "PRT-L",
title = PUB.PrinterL.IsOpen ? AR.SETTING.Data.PrintL_Port : "OFF",
status = PUB.PrinterL.IsOpen ? 1 : 3
});
}
else
{
hwList.Add(new { name = "PRT-L", title = "SET", status = 0 });
}
// 7. PrinterR
if (PUB.PrinterR != null)
{
hwList.Add(new
{
name = "PRT-R",
title = PUB.PrinterR.IsOpen ? AR.SETTING.Data.PrintR_Port : "OFF",
status = PUB.PrinterR.IsOpen ? 1 : 3
});
}
else
{
hwList.Add(new { name = "PRT-R", title = "SET", status = 0 });
}
// 8. PLC
if (PUB.plc != null)
{
hwList.Add(new
{
name = "PLC",
title = PUB.plc.Init ? AR.SETTING.Data.swplc_name : "OFF",
status = PUB.plc.Init ? 1 : 3
});
}
else
{
hwList.Add(new { name = "PLC", title = "SET", status = 0 });
}
// 9. Motion
hwList.Add(new
{
name = "MOT",
title = PUB.mot?.IsInit == true ? "ON" : "OFF",
status = PUB.mot?.IsInit == true ? 1 : 3
});
return JsonConvert.SerializeObject(hwList);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get HW status: {ex.Message}");
return JsonConvert.SerializeObject(new List<object>());
}
}
/// <summary>
/// 인터락 목록 조회 (실시간 값 포함)
/// </summary>
public string GetInterlockList()
{
try
{
var interlocks = new List<object>();
for (int i = 0; i < PUB.iLock.Length; i++)
{
var axisName = ((AR.eAxis)i).ToString();
var nonAxis = false;
if (i >= 7)
{
axisName = PUB.iLock[i].Tag.ToString();
nonAxis = true;
}
string[] ilockNames;
if (i == 7) ilockNames = Enum.GetNames(typeof(eILockPRL));
else if (i == 8) ilockNames = Enum.GetNames(typeof(eILockPRR));
else if (i == 9) ilockNames = Enum.GetNames(typeof(eILockVS0));
else if (i == 10) ilockNames = Enum.GetNames(typeof(eILockVS1));
else if (i == 11) ilockNames = Enum.GetNames(typeof(eILockVS2));
else if (i == 12 || i == 13) ilockNames = Enum.GetNames(typeof(eILockCV));
else ilockNames = Enum.GetNames(typeof(eILock));
var lockValues = new List<object>();
for (int j = 0; j < ilockNames.Length && j < 64; j++)
{
bool state = PUB.iLock[i].get(j);
lockValues.Add(new
{
id = j,
name = ilockNames[j],
state = state
});
}
interlocks.Add(new
{
axisIndex = i,
axisName = axisName,
nonAxis = nonAxis,
locks = lockValues,
hexValue = PUB.iLock[i].Value().HexString()
});
}
return JsonConvert.SerializeObject(interlocks);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get interlock list: {ex.Message}");
return JsonConvert.SerializeObject(new List<object>());
}
}
}
}

View File

@@ -334,6 +334,13 @@ namespace Project.WebUI
var response = new { type = "MANAGE_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "CLOSE_MANAGE")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.CloseManage();
var response = new { type = "CLOSE_MANAGE_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "OPEN_MANUAL")
{
var bridge = new MachineBridge(_mainForm);
@@ -376,6 +383,151 @@ namespace Project.WebUI
var response = new { type = "FOLDER_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
// ===== PICKER MOVE HANDLERS =====
else if (type == "GET_PICKER_STATUS")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.GetPickerStatus();
var response = new { type = "PICKER_STATUS", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_MOVE_LEFT")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerMoveLeft();
var response = new { type = "PICKER_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_MOVE_LEFT_WAIT")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerMoveLeftWait();
var response = new { type = "PICKER_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_MOVE_CENTER")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerMoveCenter();
var response = new { type = "PICKER_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_MOVE_RIGHT_WAIT")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerMoveRightWait();
var response = new { type = "PICKER_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_MOVE_RIGHT")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerMoveRight();
var response = new { type = "PICKER_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_JOG_START")
{
var bridge = new MachineBridge(_mainForm);
string direction = json["direction"]?.ToString() ?? "";
string resultJson = bridge.PickerJogStart(direction);
var response = new { type = "PICKER_JOG_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_JOG_STOP")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerJogStop();
var response = new { type = "PICKER_JOG_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_STOP")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerStop();
var response = new { type = "PICKER_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "CANCEL_VISION_VALIDATION")
{
var bridge = new MachineBridge(_mainForm);
string side = json["side"]?.ToString() ?? "left";
string resultJson = bridge.CancelVisionValidation(side);
var response = new { type = "VISION_CANCEL_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_MANAGE_POSITION")
{
var bridge = new MachineBridge(_mainForm);
string side = json["side"]?.ToString() ?? "left";
string resultJson = bridge.PickerManagePosition(side);
var response = new { type = "PICKER_MANAGE_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_MANAGE_RETURN")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerManageReturn();
var response = new { type = "PICKER_MANAGE_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_Z_HOME")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerZHome();
var response = new { type = "PICKER_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_Z_ZERO")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.PickerZZero();
var response = new { type = "PICKER_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "PICKER_TEST_PRINT")
{
var bridge = new MachineBridge(_mainForm);
string side = json["side"]?.ToString() ?? "left";
string resultJson = bridge.PickerTestPrint(side);
var response = new { type = "PICKER_PRINT_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "CAN_CLOSE_MANAGE")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.CanCloseManage();
var response = new { type = "CAN_CLOSE_MANAGE_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
// ===== HISTORY DATA =====
else if (type == "GET_HISTORY_DATA")
{
var bridge = new MachineBridge(_mainForm);
string startDate = json["startDate"]?.ToString() ?? DateTime.Now.ToShortDateString();
string endDate = json["endDate"]?.ToString() ?? DateTime.Now.ToShortDateString();
string search = json["search"]?.ToString() ?? "";
string resultJson = bridge.GetHistoryData(startDate, endDate, search);
var response = new { type = "HISTORY_DATA_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
// ===== INTERLOCK HANDLERS =====
else if (type == "TOGGLE_INTERLOCK")
{
var bridge = new MachineBridge(_mainForm);
int axisIndex = json["axisIndex"]?.ToObject<int>() ?? 0;
int lockIndex = json["lockIndex"]?.ToObject<int>() ?? 0;
string resultJson = bridge.ToggleInterlock(axisIndex, lockIndex);
var response = new { type = "TOGGLE_INTERLOCK_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
else if (type == "GET_INTERLOCK_LIST")
{
var bridge = new MachineBridge(_mainForm);
string resultJson = bridge.GetInterlockList();
var response = new { type = "INTERLOCK_LIST_RESULT", data = Newtonsoft.Json.JsonConvert.DeserializeObject(resultJson) };
await Send(socket, Newtonsoft.Json.JsonConvert.SerializeObject(response));
}
}
catch (Exception ex)
{

View File

@@ -493,6 +493,18 @@ namespace Project
Dialog.fWebView fwebview = null;
// 웹 UI에 이벤트 전송
public void SendWebEvent(string eventType, object data = null)
{
if (fwebview != null)
{
this.BeginInvoke(new Action(() =>
{
fwebview.BroadcastEvent(eventType, data);
}));
}
}
private void Plc_ValueChanged(object sender, AR.MemoryMap.Core.monitorvalueargs e)
{
//