Files
WebUITest-RealProjecT/Handler/Project/WebUI/MachineBridge.cs
arDTDev c625947b2e feat: Add WebView2 backend integration for React UI
- Add WebView2 support to STDLabelAttach project
- Create MachineBridge.cs for JavaScript-C# communication
  - GetConfig/SaveConfig using SETTING.Data reflection
  - Recipe management (CRUD operations)
  - IO control interface
  - Motion control simulation
- Add WebSocketServer.cs for HMR development support
- Update fWebView.cs with PLC simulation and status updates
- Integrate with existing project settings (Data/Setting.json)
- Add virtual host mapping (http://hmi.local → FrontEnd/dist)

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-25 20:40:46 +09:00

475 lines
18 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}");
}
}
public string GetIOList()
{
var ioList = new List<object>();
// Outputs (0-31)
for (int i = 0; i < 32; i++)
{
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";
ioList.Add(new { id = i, name = name, type = "output", state = false });
}
// 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; }
ioList.Add(new { id = i, name = name, type = "input", state = initialState });
}
return JsonConvert.SerializeObject(ioList);
}
public string GetRecipeList()
{
try
{
var recipes = new List<object>();
if (!Directory.Exists(_recipeFolder))
{
Directory.CreateDirectory(_recipeFolder);
return JsonConvert.SerializeObject(recipes);
}
var jsonFiles = Directory.GetFiles(_recipeFolder, "*.json");
foreach (var filePath in jsonFiles)
{
try
{
string fileName = Path.GetFileNameWithoutExtension(filePath);
string json = File.ReadAllText(filePath);
var recipeData = JsonConvert.DeserializeObject<dynamic>(json);
var lastModified = File.GetLastWriteTime(filePath).ToString("yyyy-MM-dd");
recipes.Add(new
{
id = fileName,
name = recipeData?.name ?? fileName,
lastModified = lastModified
});
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to read recipe {filePath}: {ex.Message}");
}
}
Console.WriteLine($"[INFO] Loaded {recipes.Count} recipes from {_recipeFolder}");
return JsonConvert.SerializeObject(recipes);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get recipe list: {ex.Message}");
return "[]";
}
}
public string GetRecipe(string 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);
Console.WriteLine($"[INFO] Loaded recipe {recipeId}");
return json;
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to get recipe {recipeId}: {ex.Message}");
var response = new { success = false, message = ex.Message };
return JsonConvert.SerializeObject(response);
}
}
public string SaveRecipe(string recipeId, string recipeData)
{
try
{
string recipePath = Path.Combine(_recipeFolder, $"{recipeId}.json");
var recipe = JsonConvert.DeserializeObject(recipeData);
File.WriteAllText(recipePath, JsonConvert.SerializeObject(recipe, Formatting.Indented));
Console.WriteLine($"[INFO] Recipe {recipeId} saved successfully to {recipePath}");
var response = new { success = true, message = "Recipe saved successfully", recipeId = recipeId };
return JsonConvert.SerializeObject(response);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] Failed to save recipe {recipeId}: {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);
}
}
}
}