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:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user