- 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>
1024 lines
41 KiB
TypeScript
1024 lines
41 KiB
TypeScript
|
|
// Check if running in WebView2
|
|
const isWebView = typeof window !== 'undefined' && !!window.chrome?.webview;
|
|
|
|
// 비동기 프록시 캐싱 (한 번만 초기화) - 매번 접근 시 오버헤드 제거
|
|
const machine = isWebView ? window.chrome!.webview!.hostObjects.machine : null;
|
|
|
|
type MessageCallback = (data: any) => void;
|
|
|
|
class CommunicationLayer {
|
|
private listeners: MessageCallback[] = [];
|
|
private ws: WebSocket | null = null;
|
|
private isConnected = false;
|
|
|
|
constructor() {
|
|
if (isWebView) {
|
|
console.log("[COMM] Running in WebView2 Mode");
|
|
this.isConnected = true; // WebView2 is always connected
|
|
window.chrome!.webview!.addEventListener('message', (event: any) => {
|
|
this.notifyListeners(event.data);
|
|
});
|
|
} else {
|
|
console.log("[COMM] Running in Browser Mode (WebSocket)");
|
|
this.connectWebSocket();
|
|
}
|
|
}
|
|
|
|
private connectWebSocket() {
|
|
this.ws = new WebSocket('ws://localhost:8081');
|
|
|
|
this.ws.onopen = () => {
|
|
console.log("[COMM] WebSocket Connected");
|
|
this.isConnected = true;
|
|
this.notifyListeners({ type: 'CONNECTION_STATE', connected: true });
|
|
};
|
|
|
|
this.ws.onmessage = (event) => {
|
|
try {
|
|
const data = JSON.parse(event.data);
|
|
this.notifyListeners(data);
|
|
} catch (e) {
|
|
console.error("[COMM] JSON Parse Error", e);
|
|
}
|
|
};
|
|
|
|
this.ws.onclose = () => {
|
|
console.log("[COMM] WebSocket Closed. Reconnecting...");
|
|
this.isConnected = false;
|
|
this.notifyListeners({ type: 'CONNECTION_STATE', connected: false });
|
|
setTimeout(() => this.connectWebSocket(), 2000);
|
|
};
|
|
|
|
this.ws.onerror = (err) => {
|
|
console.error("[COMM] WebSocket Error", err);
|
|
};
|
|
}
|
|
|
|
private notifyListeners(data: any) {
|
|
this.listeners.forEach(cb => cb(data));
|
|
}
|
|
|
|
public subscribe(callback: MessageCallback) {
|
|
this.listeners.push(callback);
|
|
return () => {
|
|
this.listeners = this.listeners.filter(cb => cb !== callback);
|
|
};
|
|
}
|
|
|
|
public getConnectionState(): boolean {
|
|
return this.isConnected;
|
|
}
|
|
|
|
// --- API Methods ---
|
|
|
|
public async getConfig(): Promise<string> {
|
|
if (isWebView && machine) {
|
|
return await machine.GetConfig();
|
|
} else {
|
|
// WebSocket Request/Response Pattern
|
|
return new Promise((resolve, reject) => {
|
|
// 1. Wait for Connection (max 2s)
|
|
if (!this.isConnected) {
|
|
// Simple wait logic (could be improved)
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject("WebSocket connection timeout");
|
|
}, 2000);
|
|
}
|
|
|
|
// 2. Send Request with Timeout (max 10s)
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject("Config fetch timeout");
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'CONFIG_DATA') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(JSON.stringify(data.data));
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'GET_CONFIG' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async getIOList(): Promise<string> {
|
|
if (isWebView && machine) {
|
|
return await machine.GetIOList();
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject("WebSocket connection timeout");
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject("IO fetch timeout");
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'IO_LIST_DATA') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(JSON.stringify(data.data));
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'GET_IO_LIST' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async getRecipeList(): Promise<string> {
|
|
if (isWebView && machine) {
|
|
return await machine.GetRecipeList();
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject("WebSocket connection timeout");
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject("Recipe fetch timeout");
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'RECIPE_LIST_DATA') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(JSON.stringify(data.data));
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'GET_RECIPE_LIST' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async saveConfig(configJson: string): Promise<void> {
|
|
if (isWebView && machine) {
|
|
await machine.SaveConfig(configJson);
|
|
} else {
|
|
this.ws?.send(JSON.stringify({ type: 'SAVE_CONFIG', data: JSON.parse(configJson) }));
|
|
}
|
|
}
|
|
|
|
public async sendControl(command: string) {
|
|
if (isWebView && machine) {
|
|
await machine.SystemControl(command);
|
|
} else {
|
|
this.ws?.send(JSON.stringify({ type: 'CONTROL', command }));
|
|
}
|
|
}
|
|
|
|
public async moveAxis(axis: string, value: number) {
|
|
if (isWebView && machine) {
|
|
await machine.MoveAxis(axis, value);
|
|
} else {
|
|
this.ws?.send(JSON.stringify({ type: 'MOVE', axis, value }));
|
|
}
|
|
}
|
|
|
|
public async setIO(id: number, state: boolean) {
|
|
if (isWebView && machine) {
|
|
await machine.SetIO(id, false, state);
|
|
} else {
|
|
this.ws?.send(JSON.stringify({ type: 'SET_IO', id, state }));
|
|
}
|
|
}
|
|
|
|
public async selectRecipe(recipeId: string): Promise<{ success: boolean; message: string; recipeId?: string }> {
|
|
if (isWebView && machine) {
|
|
const resultJson = await machine.SelectRecipe(recipeId);
|
|
return JSON.parse(resultJson);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" });
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Recipe selection timeout" });
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'RECIPE_SELECTED') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'SELECT_RECIPE', recipeId }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async copyRecipe(recipeId: string, newName: string): Promise<{ success: boolean; message: string; newRecipe?: any }> {
|
|
if (isWebView && machine) {
|
|
const resultJson = await machine.CopyRecipe(recipeId, newName);
|
|
return JSON.parse(resultJson);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" });
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Recipe copy timeout" });
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'RECIPE_COPIED') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'COPY_RECIPE', recipeId, newName }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async deleteRecipe(recipeId: string): Promise<{ success: boolean; message: string; recipeId?: string }> {
|
|
if (isWebView && machine) {
|
|
const resultJson = await machine.DeleteRecipe(recipeId);
|
|
return JSON.parse(resultJson);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" });
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Recipe delete timeout" });
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'RECIPE_DELETED') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'DELETE_RECIPE', recipeId }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async getRecipe(recipeTitle: string): Promise<string> {
|
|
if (isWebView && machine) {
|
|
return await machine.GetRecipe(recipeTitle);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject("WebSocket connection timeout");
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject("Recipe fetch timeout");
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'RECIPE_DATA') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(JSON.stringify(data.data));
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'GET_RECIPE', recipeTitle }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async saveRecipe(recipeTitle: string, recipeData: string): Promise<{ success: boolean; message: string; recipeId?: string }> {
|
|
if (isWebView && machine) {
|
|
const resultJson = await machine.SaveRecipe(recipeTitle, recipeData);
|
|
return JSON.parse(resultJson);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" });
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Recipe save timeout" });
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'RECIPE_SAVED') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'SAVE_RECIPE', recipeTitle, recipeData }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async initializeDevice(): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const resultJson = await machine.InitializeDevice();
|
|
return JSON.parse(resultJson);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" });
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Initialize device timeout" });
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'DEVICE_INITIALIZED') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'INITIALIZE_DEVICE' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async getInitializeStatus(): Promise<string> {
|
|
if (isWebView && machine) {
|
|
return await machine.GetInitializeStatus();
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject("WebSocket connection timeout");
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject("Initialize status fetch timeout");
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'INITIALIZE_STATUS_DATA') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(JSON.stringify(data.data));
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'GET_INITIALIZE_STATUS' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async getProcessedData(): Promise<string> {
|
|
if (isWebView && machine) {
|
|
return await machine.GetProcessedData();
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject("WebSocket connection timeout");
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject("Processed data fetch timeout");
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'PROCESSED_DATA') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(JSON.stringify(data.data));
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'GET_PROCESSED_DATA' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
// ===== VISION CONTROL METHODS =====
|
|
|
|
private async sendVisionCommand(command: string, responseType: string, methodName: string): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await (machine as any)[methodName]();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" });
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Vision command timeout" });
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === responseType) {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: command }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async cameraConnect(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendVisionCommand('CAMERA_CONNECT', 'CAMERA_RESULT', 'CameraConnect');
|
|
}
|
|
|
|
public async cameraDisconnect(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendVisionCommand('CAMERA_DISCONNECT', 'CAMERA_RESULT', 'CameraDisconnect');
|
|
}
|
|
|
|
public async cameraGetImage(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendVisionCommand('CAMERA_GET_IMAGE', 'CAMERA_RESULT', 'CameraGetImage');
|
|
}
|
|
|
|
public async cameraLiveView(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendVisionCommand('CAMERA_LIVE_VIEW', 'CAMERA_RESULT', 'CameraLiveView');
|
|
}
|
|
|
|
public async cameraReadTest(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendVisionCommand('CAMERA_READ_TEST', 'CAMERA_RESULT', 'CameraReadTest');
|
|
}
|
|
|
|
public async keyenceTriggerOn(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendVisionCommand('KEYENCE_TRIGGER_ON', 'KEYENCE_RESULT', 'KeyenceTriggerOn');
|
|
}
|
|
|
|
public async keyenceTriggerOff(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendVisionCommand('KEYENCE_TRIGGER_OFF', 'KEYENCE_RESULT', 'KeyenceTriggerOff');
|
|
}
|
|
|
|
public async keyenceGetImage(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendVisionCommand('KEYENCE_GET_IMAGE', 'KEYENCE_RESULT', 'KeyenceGetImage');
|
|
}
|
|
|
|
public async keyenceSaveImage(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendVisionCommand('KEYENCE_SAVE_IMAGE', 'KEYENCE_RESULT', 'KeyenceSaveImage');
|
|
}
|
|
|
|
// Light, Manual Print, Cancel Job commands
|
|
public async toggleLight(): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.ToggleLight();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject(new Error('Light toggle timeout'));
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'LIGHT_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'TOGGLE_LIGHT' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async executeManualPrint(printData: {
|
|
sid: string;
|
|
venderLot: string;
|
|
qty: string;
|
|
mfg: string;
|
|
rid: string;
|
|
spy: string;
|
|
partNo: string;
|
|
printer: string;
|
|
count: number;
|
|
}): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.ExecuteManualPrint(
|
|
printData.sid,
|
|
printData.venderLot,
|
|
printData.qty,
|
|
printData.mfg,
|
|
printData.rid,
|
|
printData.spy,
|
|
printData.partNo,
|
|
printData.printer,
|
|
printData.count
|
|
);
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject(new Error('Manual print timeout'));
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'MANUAL_PRINT_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({
|
|
type: 'EXECUTE_MANUAL_PRINT',
|
|
...printData
|
|
}));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async cancelJob(): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.CancelJob();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject(new Error('Cancel job timeout'));
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'CANCEL_JOB_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'CANCEL_JOB' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async openManage(): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.OpenManage();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject(new Error('Open manage timeout'));
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'MANAGE_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'OPEN_MANAGE' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async openManual(): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.OpenManual();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject(new Error('Open manual timeout'));
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'MANUAL_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'OPEN_MANUAL' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async openLogViewer(): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.OpenLogViewer();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject(new Error('Open log viewer timeout'));
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'LOG_VIEWER_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'OPEN_LOG_VIEWER' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
private async openFolder(command: string, methodName: string): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await (machine as any)[methodName]();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject(new Error('Open folder timeout'));
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'FOLDER_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: command }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async openProgramFolder(): Promise<{ success: boolean; message: string }> {
|
|
return this.openFolder('OPEN_PROGRAM_FOLDER', 'OpenProgramFolder');
|
|
}
|
|
|
|
public async openLogFolder(): Promise<{ success: boolean; message: string }> {
|
|
return this.openFolder('OPEN_LOG_FOLDER', 'OpenLogFolder');
|
|
}
|
|
|
|
public async openScreenshotFolder(): Promise<{ success: boolean; message: string }> {
|
|
return this.openFolder('OPEN_SCREENSHOT_FOLDER', 'OpenScreenshotFolder');
|
|
}
|
|
|
|
public async openSavedDataFolder(): Promise<{ success: boolean; message: string }> {
|
|
return this.openFolder('OPEN_SAVED_DATA_FOLDER', 'OpenSavedDataFolder');
|
|
}
|
|
|
|
// ===== PICKER MOVE METHODS =====
|
|
|
|
public async getPickerStatus(): Promise<void> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.GetPickerStatus();
|
|
this.notifyListeners({ type: 'PICKER_STATUS', data: JSON.parse(result) });
|
|
} else {
|
|
this.ws?.send(JSON.stringify({ type: 'GET_PICKER_STATUS' }));
|
|
}
|
|
}
|
|
|
|
private async sendPickerCommand(command: string, responseType: string, methodName: string): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await (machine as any)[methodName]();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
if (!this.isConnected) {
|
|
setTimeout(() => {
|
|
if (!this.isConnected) reject({ success: false, message: "WebSocket connection timeout" });
|
|
}, 2000);
|
|
}
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Picker command timeout" });
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === responseType) {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: command }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async pickerMoveLeft(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_MOVE_LEFT', 'PICKER_RESULT', 'PickerMoveLeft');
|
|
}
|
|
|
|
public async pickerMoveLeftWait(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_MOVE_LEFT_WAIT', 'PICKER_RESULT', 'PickerMoveLeftWait');
|
|
}
|
|
|
|
public async pickerMoveCenter(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_MOVE_CENTER', 'PICKER_RESULT', 'PickerMoveCenter');
|
|
}
|
|
|
|
public async pickerMoveRightWait(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_MOVE_RIGHT_WAIT', 'PICKER_RESULT', 'PickerMoveRightWait');
|
|
}
|
|
|
|
public async pickerMoveRight(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_MOVE_RIGHT', 'PICKER_RESULT', 'PickerMoveRight');
|
|
}
|
|
|
|
public async pickerJogStart(direction: 'up' | 'down' | 'left' | 'right'): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.PickerJogStart(direction);
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Jog command timeout" });
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'PICKER_JOG_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'PICKER_JOG_START', direction }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async pickerJogStop(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_JOG_STOP', 'PICKER_JOG_RESULT', 'PickerJogStop');
|
|
}
|
|
|
|
public async pickerStop(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_STOP', 'PICKER_RESULT', 'PickerStop');
|
|
}
|
|
|
|
public async cancelVisionValidation(side: 'left' | 'right'): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.CancelVisionValidation(side);
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Vision cancel timeout" });
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'VISION_CANCEL_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'CANCEL_VISION_VALIDATION', side }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async pickerManagePosition(side: 'left' | 'right'): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.PickerManagePosition(side);
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Manage position timeout" });
|
|
}, 30000); // Longer timeout for motion
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'PICKER_MANAGE_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'PICKER_MANAGE_POSITION', side }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async pickerManageReturn(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_MANAGE_RETURN', 'PICKER_MANAGE_RESULT', 'PickerManageReturn');
|
|
}
|
|
|
|
public async pickerZHome(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_Z_HOME', 'PICKER_RESULT', 'PickerZHome');
|
|
}
|
|
|
|
public async pickerZZero(): Promise<{ success: boolean; message: string }> {
|
|
return this.sendPickerCommand('PICKER_Z_ZERO', 'PICKER_RESULT', 'PickerZZero');
|
|
}
|
|
|
|
public async pickerTestPrint(side: 'left' | 'right'): Promise<{ success: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.PickerTestPrint(side);
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: "Test print timeout" });
|
|
}, 10000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'PICKER_PRINT_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'PICKER_TEST_PRINT', side }));
|
|
});
|
|
}
|
|
}
|
|
|
|
public async canCloseManage(): Promise<{ canClose: boolean; message: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.CanCloseManage();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve({ canClose: true, message: '' });
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'CAN_CLOSE_MANAGE_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'CAN_CLOSE_MANAGE' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
// Close manage dialog - backend handles flag clear and auto-init check
|
|
public async closeManage(): Promise<{ shouldAutoInit: boolean }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.CloseManage();
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve({ shouldAutoInit: false });
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'CLOSE_MANAGE_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'CLOSE_MANAGE' }));
|
|
});
|
|
}
|
|
}
|
|
|
|
// ===== HISTORY DATA =====
|
|
|
|
// Get history data from database (fHistory.cs의 refreshList와 동일)
|
|
public async getHistoryData(startDate: string, endDate: string, search: string): Promise<{ success: boolean; data: any[]; mcName?: string; message?: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.GetHistoryData(startDate, endDate, search);
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve, reject) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
reject({ success: false, message: 'History data fetch timeout', data: [] });
|
|
}, 30000); // 30 second timeout for potentially large data
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'HISTORY_DATA_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'GET_HISTORY_DATA', startDate, endDate, search }));
|
|
});
|
|
}
|
|
}
|
|
|
|
// ===== INTERLOCK METHODS =====
|
|
|
|
// Toggle interlock (DIOMonitor.cs의 gvILXF_ItemClick과 동일)
|
|
public async toggleInterlock(axisIndex: number, lockIndex: number): Promise<{ success: boolean; newState?: boolean; message?: string }> {
|
|
if (isWebView && machine) {
|
|
const result = await machine.ToggleInterlock(axisIndex, lockIndex);
|
|
return JSON.parse(result);
|
|
} else {
|
|
return new Promise((resolve) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve({ success: false, message: 'Timeout' });
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'TOGGLE_INTERLOCK_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(data.data);
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'TOGGLE_INTERLOCK', axisIndex, lockIndex }));
|
|
});
|
|
}
|
|
}
|
|
|
|
// Get interlock list
|
|
public async getInterlockList(): Promise<string> {
|
|
if (isWebView && machine) {
|
|
return await machine.GetInterlockList();
|
|
} else {
|
|
return new Promise((resolve) => {
|
|
const timeoutId = setTimeout(() => {
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve('[]');
|
|
}, 5000);
|
|
|
|
const handler = (data: any) => {
|
|
if (data.type === 'INTERLOCK_LIST_RESULT') {
|
|
clearTimeout(timeoutId);
|
|
this.listeners = this.listeners.filter(cb => cb !== handler);
|
|
resolve(JSON.stringify(data.data));
|
|
}
|
|
};
|
|
this.listeners.push(handler);
|
|
this.ws?.send(JSON.stringify({ type: 'GET_INTERLOCK_LIST' }));
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
export const comms = new CommunicationLayer();
|