Files
WebUITest-RealProjecT/Handler/Project/WebUI/MachineBridge.cs
arDTDev 8364f7478c feat: Migrate IO and Recipe systems to use real data sources
- Enhanced GetIOList() to read from DIO library (inputs, outputs, interlocks)
- Added interlock display to IOMonitorPage with 3-tab layout
- Migrated GetRecipeList() to use PUB.mdm.dataSet.OPModel table
- Migrated GetRecipe() to read from OPModel DataRow
- Migrated SaveRecipe() to save to OPModel table with type conversion
- Updated frontend to handle new structured IO format
- All methods now use real hardware/database instead of mock data

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 21:09:23 +09:00

678 lines
25 KiB
C#
Raw Blame History

using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Newtonsoft.Json;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using AR;
namespace Project.WebUI
{
// Important: Allows JavaScript to see this class
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class MachineBridge
{
// Reference to the main form to update logic
private Project.Dialog.fWebView _host;
// Data folder paths
private readonly string _dataFolder;
private readonly string _settingsPath;
private readonly string _recipeFolder;
public MachineBridge(Project.Dialog.fWebView host)
{
_host = host;
// Initialize data folder paths - use existing Data folder
_dataFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Data");
_settingsPath = Path.Combine(_dataFolder, "Setting.json");
_recipeFolder = Path.Combine(_dataFolder, "recipe");
// Ensure folders exist
EnsureDataFoldersExist();
}
private void EnsureDataFoldersExist()
{
try
{
if (!Directory.Exists(_dataFolder))
Directory.CreateDirectory(_dataFolder);
if (!Directory.Exists(_recipeFolder))
Directory.CreateDirectory(_recipeFolder);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to create data folders: {ex.Message}");
}
}
// --- Methods called from React ---
public void MoveAxis(string axis, double value)
{
Console.WriteLine($"[C#] Moving {axis} to {value}");
_host.SetTargetPosition(axis, value);
}
public void SetIO(int id, bool isInput, bool state)
{
Console.WriteLine($"[C#] Set IO {id} to {state}");
_host.SetOutput(id, state);
}
public void SystemControl(string command)
{
Console.WriteLine($"[C#] CMD: {command}");
_host.HandleCommand(command);
}
public string SelectRecipe(string recipeId)
{
Console.WriteLine($"[C#] Selecting Recipe: {recipeId}");
try
{
string recipePath = Path.Combine(_recipeFolder, $"{recipeId}.json");
if (!File.Exists(recipePath))
{
var response = new { success = false, message = "Recipe not found" };
return JsonConvert.SerializeObject(response);
}
string json = File.ReadAllText(recipePath);
var recipeData = JsonConvert.DeserializeObject<dynamic>(json);
_host.SetCurrentRecipe(recipeId);
Console.WriteLine($"[INFO] Recipe {recipeId} selected successfully");
var response2 = new { success = true, message = "Recipe selected successfully", recipeId = recipeId, recipe = recipeData };
return JsonConvert.SerializeObject(response2);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to select recipe: {ex.Message}");
var response = new { success = false, message = ex.Message };
return JsonConvert.SerializeObject(response);
}
}
/// <summary>
/// <20><><EFBFBD>̱׷<CCB1><D7B7>̼<EFBFBD> <20><><EFBFBD><EFBFBD>
/// </summary>
/// <returns></returns>
public string GetConfig()
{
try
{
// Use SETTING.Data (CommonSetting) from the project
if (AR.SETTING.Data == null)
{
Console.WriteLine($"[WARN] SETTING.Data is not initialized");
return "[]";
}
var settingsArray = new List<object>();
var properties = AR.SETTING.Data.GetType().GetProperties();
foreach (var prop in properties)
{
// Skip non-browsable properties
var browsable = prop.GetCustomAttributes(typeof(System.ComponentModel.BrowsableAttribute), false)
.FirstOrDefault() as System.ComponentModel.BrowsableAttribute;
if (browsable != null && !browsable.Browsable)
continue;
// Skip filename property
if (prop.Name == "filename")
continue;
// Get property info
string key = prop.Name;
object value = prop.GetValue(AR.SETTING.Data, null);
string type = "String";
string group = "General";
string description = key.Replace("_", " ");
// Get Category attribute
var categoryAttr = prop.GetCustomAttributes(typeof(System.ComponentModel.CategoryAttribute), false)
.FirstOrDefault() as System.ComponentModel.CategoryAttribute;
if (categoryAttr != null)
group = categoryAttr.Category;
// Get DisplayName attribute
var displayNameAttr = prop.GetCustomAttributes(typeof(System.ComponentModel.DisplayNameAttribute), false)
.FirstOrDefault() as System.ComponentModel.DisplayNameAttribute;
if (displayNameAttr != null)
description = displayNameAttr.DisplayName;
// Get Description attribute
var descAttr = prop.GetCustomAttributes(typeof(System.ComponentModel.DescriptionAttribute), false)
.FirstOrDefault() as System.ComponentModel.DescriptionAttribute;
if (descAttr != null)
description = descAttr.Description;
// Determine type based on property type
if (prop.PropertyType == typeof(bool))
{
type = "Boolean";
value = value?.ToString().ToLower() ?? "false";
}
else if (prop.PropertyType == typeof(int) || prop.PropertyType == typeof(long) ||
prop.PropertyType == typeof(double) || prop.PropertyType == typeof(float))
{
type = "Number";
value = value?.ToString() ?? "0";
}
else
{
value = value?.ToString() ?? "";
}
settingsArray.Add(new
{
Key = key,
Value = value,
Group = group,
Type = type,
Description = description
});
}
Console.WriteLine($"[INFO] Loaded {settingsArray.Count} settings from SETTING.Data");
return JsonConvert.SerializeObject(settingsArray);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to load settings: {ex.Message}");
return "[]";
}
}
/// <summary>
/// <20><><EFBFBD>̱׷<CCB1><D7B7>̼Ǽ<CCBC><C7BC><EFBFBD>
/// </summary>
/// <param name="configJson"></param>
public void SaveConfig(string configJson)
{
try
{
Console.WriteLine($"[Backend] SAVE CONFIG REQUEST RECEIVED");
// Parse array format from React
var settingsArray = JsonConvert.DeserializeObject<List<Dictionary<string, object>>>(configJson);
// Update SETTING.Data properties
var properties = AR.SETTING.Data.GetType().GetProperties();
foreach (var item in settingsArray)
{
string key = item["Key"].ToString();
string value = item["Value"].ToString();
string type = item["Type"].ToString();
var prop = properties.FirstOrDefault(p => p.Name == key);
if (prop == null || !prop.CanWrite)
continue;
// Convert value based on type
try
{
if (type == "Boolean")
{
prop.SetValue(AR.SETTING.Data, bool.Parse(value), null);
}
else if (type == "Number")
{
if (prop.PropertyType == typeof(int))
prop.SetValue(AR.SETTING.Data, int.Parse(value), null);
else if (prop.PropertyType == typeof(long))
prop.SetValue(AR.SETTING.Data, long.Parse(value), null);
else if (prop.PropertyType == typeof(double))
prop.SetValue(AR.SETTING.Data, double.Parse(value), null);
else if (prop.PropertyType == typeof(float))
prop.SetValue(AR.SETTING.Data, float.Parse(value), null);
}
else
{
prop.SetValue(AR.SETTING.Data, value, null);
}
}
catch (Exception ex)
{
Console.WriteLine($"[WARN] Failed to set property {key}: {ex.Message}");
}
}
// Save to file
AR.SETTING.Data.Save();
Console.WriteLine($"[INFO] Settings saved successfully");
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to save settings: {ex.Message}");
}
}
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <returns></returns>
public string GetIOList()
{
try
{
var result = new
{
inputs = GetDigitalInputs(),
outputs = GetDigitalOutputs(),
interlocks = GetInterlocks()
};
return JsonConvert.SerializeObject(result);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get IO list: {ex.Message}");
return JsonConvert.SerializeObject(new { inputs = new List<object>(), outputs = new List<object>(), interlocks = new List<object>() });
}
}
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <returns></returns>
private List<object> GetDigitalInputs()
{
var inputs = new List<object>();
try
{
var diNames = DIO.Pin.GetDIName;
var diPinNames = DIO.Pin.GetDIPinName;
int diCount = PUB.dio.GetDICount;
for (int i = 0; i < diCount; i++)
{
bool state = PUB.dio.GetDIValue(i);
string name = i < diNames.Length ? diNames[i] : $"DIN_{i:D2}";
string pinName = i < diPinNames.Length ? diPinNames[i] : $"X{i:D2}";
inputs.Add(new
{
id = i,
name = name,
pinName = pinName,
type = "input",
state = state
});
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get digital inputs: {ex.Message}");
}
return inputs;
}
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <returns></returns>
private List<object> GetDigitalOutputs()
{
var outputs = new List<object>();
try
{
var doNames = DIO.Pin.GetDOName;
var doPinNames = DIO.Pin.GetDOPinName;
int doCount = PUB.dio.GetDOCount;
for (int i = 0; i < doCount; i++)
{
bool state = PUB.dio.GetDOValue(i);
string name = i < doNames.Length ? doNames[i] : $"DOUT_{i:D2}";
string pinName = i < doPinNames.Length ? doPinNames[i] : $"Y{i:D2}";
outputs.Add(new
{
id = i,
name = name,
pinName = pinName,
type = "output",
state = state
});
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get digital outputs: {ex.Message}");
}
return outputs;
}
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <returns></returns>
private List<object> GetInterlocks()
{
var interlocks = new List<object>();
try
{
for (int i = 0; i < PUB.iLock.Length; i++)
{
var axisName = ((AR.eAxis)i).ToString();
var nonAxis = false;
// Check if this is a non-axis interlock (i >= 7)
if (i >= 7)
{
axisName = PUB.iLock[i].Tag.ToString();
nonAxis = true;
}
// Get enum names based on axis index
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));
// Get interlock values (up to 64 bits)
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()
});
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get interlocks: {ex.Message}");
}
return interlocks;
}
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <returns></returns>
public string GetRecipeList()
{
//PUB.mdm.dataSet.OPModel에 등록된 데이터가 모델 목록이다
try
{
var recipes = new List<object>();
if (PUB.mdm == null || PUB.mdm.dataSet == null || PUB.mdm.dataSet.OPModel == null)
{
Console.WriteLine($"[WARN] OPModel is not initialized");
return JsonConvert.SerializeObject(recipes);
}
// OPModel 테이블에서 Title이 비어있지 않은 항목만 가져오기
foreach (var row in PUB.mdm.dataSet.OPModel)
{
var modelRow = row as DataSet1.OPModelRow;
if (modelRow == null) continue;
// Title이 비어있으면 스킵
if (string.IsNullOrEmpty(modelRow.Title))
continue;
recipes.Add(new
{
id = modelRow.idx.ToString(),
name = modelRow.Title,
lastModified = DateTime.Now.ToString("yyyy-MM-dd")
});
}
Console.WriteLine($"[INFO] Loaded {recipes.Count} recipes from OPModel");
return JsonConvert.SerializeObject(recipes);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get recipe list: {ex.Message}");
return "[]";
}
}
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <param name="recipeTitle"></param>
/// <returns></returns>
public string GetRecipe(string recipeTitle)
{
try
{
var dr = PUB.mdm.GetDataV(recipeTitle);
if (dr == null)
{
var response = new { success = false, message = "Recipe not found" };
return JsonConvert.SerializeObject(response);
}
// DataRow의 모든 컬럼을 Dictionary로 변환
var recipeData = new Dictionary<string, object>();
foreach (System.Data.DataColumn col in dr.Table.Columns)
{
string colName = col.ColumnName;
object value = dr[colName];
// DBNull을 null로 변환
if (value == DBNull.Value)
value = null;
recipeData[colName] = value;
}
Console.WriteLine($"[INFO] Loaded recipe {recipeTitle}");
return JsonConvert.SerializeObject(recipeData);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get recipe {recipeTitle}: {ex.Message}");
var response = new { success = false, message = ex.Message };
return JsonConvert.SerializeObject(response);
}
}
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <param name="recipeTitle"></param>
/// <param name="recipeData"></param>
/// <returns></returns>
public string SaveRecipe(string recipeTitle, string recipeData)
{
try
{
var dr = PUB.mdm.GetDataV(recipeTitle);
if (dr == null)
{
var response = new { success = false, message = "Recipe not found" };
return JsonConvert.SerializeObject(response);
}
// JSON 데이터를 Dictionary로 변환
var recipeDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(recipeData);
// DataRow의 각 컬럼 업데이트
foreach (var kvp in recipeDict)
{
string colName = kvp.Key;
// 컬럼이 테이블에 존재하는지 확인
if (!dr.Table.Columns.Contains(colName))
continue;
// idx나 Midx 같은 자동 생성 컬럼은 스킵
if (colName == "idx" || colName == "Midx")
continue;
try
{
var col = dr.Table.Columns[colName];
object value = kvp.Value;
// null 처리
if (value == null)
{
dr[colName] = DBNull.Value;
}
else
{
// 타입 변환
if (col.DataType == typeof(bool))
dr[colName] = Convert.ToBoolean(value);
else if (col.DataType == typeof(int))
dr[colName] = Convert.ToInt32(value);
else if (col.DataType == typeof(double))
dr[colName] = Convert.ToDouble(value);
else if (col.DataType == typeof(float))
dr[colName] = Convert.ToSingle(value);
else
dr[colName] = value.ToString();
}
}
catch (Exception ex)
{
Console.WriteLine($"[WARN] Failed to set column {colName}: {ex.Message}");
}
}
// 변경사항 저장
PUB.mdm.dataSet.OPModel.AcceptChanges();
PUB.mdm.SaveModelV();
Console.WriteLine($"[INFO] Recipe {recipeTitle} saved successfully to OPModel");
var response2 = new { success = true, message = "Recipe saved successfully", recipeId = recipeTitle };
return JsonConvert.SerializeObject(response2);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to save recipe {recipeTitle}: {ex.Message}");
var response = new { success = false, message = ex.Message };
return JsonConvert.SerializeObject(response);
}
}
public string CopyRecipe(string recipeId, string newName)
{
Console.WriteLine($"[C#] Copying Recipe: {recipeId} as {newName}");
try
{
string sourcePath = Path.Combine(_recipeFolder, $"{recipeId}.json");
if (!File.Exists(sourcePath))
{
var response = new { success = false, message = "Source recipe not found" };
return JsonConvert.SerializeObject(response);
}
string newId = Guid.NewGuid().ToString().Substring(0, 8);
string destPath = Path.Combine(_recipeFolder, $"{newId}.json");
string json = File.ReadAllText(sourcePath);
var recipeData = JsonConvert.DeserializeObject<dynamic>(json);
recipeData.name = newName;
File.WriteAllText(destPath, JsonConvert.SerializeObject(recipeData, Formatting.Indented));
string timestamp = DateTime.Now.ToString("yyyy-MM-dd");
Console.WriteLine($"[INFO] Recipe copied from {recipeId} to {newId}");
var response2 = new {
success = true,
message = "Recipe copied successfully",
newRecipe = new {
id = newId,
name = newName,
lastModified = timestamp
}
};
return JsonConvert.SerializeObject(response2);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to copy recipe: {ex.Message}");
var response = new { success = false, message = ex.Message };
return JsonConvert.SerializeObject(response);
}
}
public string DeleteRecipe(string recipeId)
{
Console.WriteLine($"[C#] Deleting Recipe: {recipeId}");
try
{
if (recipeId == _host.GetCurrentRecipe())
{
var response1 = new { success = false, message = "Cannot delete currently selected recipe" };
return JsonConvert.SerializeObject(response1);
}
string recipePath = Path.Combine(_recipeFolder, $"{recipeId}.json");
if (!File.Exists(recipePath))
{
var response = new { success = false, message = "Recipe not found" };
return JsonConvert.SerializeObject(response);
}
File.Delete(recipePath);
Console.WriteLine($"[INFO] Recipe {recipeId} deleted successfully");
var response2 = new { success = true, message = "Recipe deleted successfully", recipeId = recipeId };
return JsonConvert.SerializeObject(response2);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to delete recipe: {ex.Message}");
var response = new { success = false, message = ex.Message };
return JsonConvert.SerializeObject(response);
}
}
}
}