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>
This commit is contained in:
2025-11-25 21:09:23 +09:00
parent c625947b2e
commit 8364f7478c
5 changed files with 432 additions and 158 deletions

View File

@@ -104,7 +104,7 @@ namespace Project.WebUI
}
/// <summary>
/// <20><><EFBFBD>̱׷<CCB1><D7B7>̼<EFBFBD> <20><><EFBFBD><EFBFBD>
/// <20><><EFBFBD>̱׷<CCB1><D7B7>̼<EFBFBD> <20><><EFBFBD><EFBFBD>
/// </summary>
/// <returns></returns>
public string GetConfig()
@@ -196,7 +196,7 @@ namespace Project.WebUI
}
/// <summary>
/// <20><><EFBFBD>̱׷<CCB1><D7B7>̼Ǽ<CCBC><C7BC><EFBFBD>
/// <20><><EFBFBD>̱׷<CCB1><D7B7>̼Ǽ<CCBC><C7BC><EFBFBD>
/// </summary>
/// <param name="configJson"></param>
public void SaveConfig(string configJson)
@@ -261,79 +261,204 @@ namespace Project.WebUI
}
}
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <returns></returns>
public string GetIOList()
{
var ioList = new List<object>();
// Outputs (0-31)
for (int i = 0; i < 32; i++)
try
{
string name = $"DOUT_{i:D2}";
if (i == 0) name = "Tower Lamp Red";
if (i == 1) name = "Tower Lamp Yel";
if (i == 2) name = "Tower Lamp Grn";
var result = new
{
inputs = GetDigitalInputs(),
outputs = GetDigitalOutputs(),
interlocks = GetInterlocks()
};
ioList.Add(new { id = i, name = name, type = "output", state = false });
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}");
}
// Inputs (0-31)
for (int i = 0; i < 32; i++)
{
string name = $"DIN_{i:D2}";
bool initialState = false;
if (i == 0) name = "Front Door Sensor";
if (i == 1) name = "Right Door Sensor";
if (i == 2) name = "Left Door Sensor";
if (i == 3) name = "Back Door Sensor";
if (i == 4) { name = "Main Air Pressure"; initialState = true; }
if (i == 5) { name = "Vacuum Generator"; initialState = true; }
if (i == 6) { name = "Emergency Stop Loop"; initialState = true; }
return inputs;
}
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <returns></returns>
private List<object> GetDigitalOutputs()
{
var outputs = new List<object>();
ioList.Add(new { id = i, name = name, type = "input", state = initialState });
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 JsonConvert.SerializeObject(ioList);
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 (!Directory.Exists(_recipeFolder))
if (PUB.mdm == null || PUB.mdm.dataSet == null || PUB.mdm.dataSet.OPModel == null)
{
Directory.CreateDirectory(_recipeFolder);
Console.WriteLine($"[WARN] OPModel is not initialized");
return JsonConvert.SerializeObject(recipes);
}
var jsonFiles = Directory.GetFiles(_recipeFolder, "*.json");
foreach (var filePath in jsonFiles)
// OPModel 테이블에서 Title이 비어있지 않은 항목만 가져오기
foreach (var row in PUB.mdm.dataSet.OPModel)
{
try
{
string fileName = Path.GetFileNameWithoutExtension(filePath);
string json = File.ReadAllText(filePath);
var recipeData = JsonConvert.DeserializeObject<dynamic>(json);
var modelRow = row as DataSet1.OPModelRow;
if (modelRow == null) continue;
var lastModified = File.GetLastWriteTime(filePath).ToString("yyyy-MM-dd");
// Title이 비어있으면 스킵
if (string.IsNullOrEmpty(modelRow.Title))
continue;
recipes.Add(new
{
id = fileName,
name = recipeData?.name ?? fileName,
lastModified = lastModified
});
}
catch (Exception ex)
recipes.Add(new
{
Console.WriteLine($"[ERROR] Failed to read recipe {filePath}: {ex.Message}");
}
id = modelRow.idx.ToString(),
name = modelRow.Title,
lastModified = DateTime.Now.ToString("yyyy-MM-dd")
});
}
Console.WriteLine($"[INFO] Loaded {recipes.Count} recipes from {_recipeFolder}");
Console.WriteLine($"[INFO] Loaded {recipes.Count} recipes from OPModel");
return JsonConvert.SerializeObject(recipes);
}
catch (Exception ex)
@@ -343,47 +468,125 @@ namespace Project.WebUI
}
}
public string GetRecipe(string recipeId)
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <param name="recipeTitle"></param>
/// <returns></returns>
public string GetRecipe(string recipeTitle)
{
try
{
string recipePath = Path.Combine(_recipeFolder, $"{recipeId}.json");
var dr = PUB.mdm.GetDataV(recipeTitle);
if (!File.Exists(recipePath))
if (dr == null)
{
var response = new { success = false, message = "Recipe not found" };
return JsonConvert.SerializeObject(response);
}
string json = File.ReadAllText(recipePath);
Console.WriteLine($"[INFO] Loaded recipe {recipeId}");
return json;
// 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 {recipeId}: {ex.Message}");
Console.WriteLine($"[ERROR] Failed to get recipe {recipeTitle}: {ex.Message}");
var response = new { success = false, message = ex.Message };
return JsonConvert.SerializeObject(response);
}
}
public string SaveRecipe(string recipeId, string recipeData)
/// <summary>
/// 마이그레이션완료
/// </summary>
/// <param name="recipeTitle"></param>
/// <param name="recipeData"></param>
/// <returns></returns>
public string SaveRecipe(string recipeTitle, string recipeData)
{
try
{
string recipePath = Path.Combine(_recipeFolder, $"{recipeId}.json");
var dr = PUB.mdm.GetDataV(recipeTitle);
var recipe = JsonConvert.DeserializeObject(recipeData);
File.WriteAllText(recipePath, JsonConvert.SerializeObject(recipe, Formatting.Indented));
if (dr == null)
{
var response = new { success = false, message = "Recipe not found" };
return JsonConvert.SerializeObject(response);
}
Console.WriteLine($"[INFO] Recipe {recipeId} saved successfully to {recipePath}");
// JSON 데이터를 Dictionary로 변환
var recipeDict = JsonConvert.DeserializeObject<Dictionary<string, object>>(recipeData);
var response = new { success = true, message = "Recipe saved successfully", recipeId = recipeId };
return JsonConvert.SerializeObject(response);
// 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 {recipeId}: {ex.Message}");
Console.WriteLine($"[ERROR] Failed to save recipe {recipeTitle}: {ex.Message}");
var response = new { success = false, message = ex.Message };
return JsonConvert.SerializeObject(response);
}