Files
hmitestWinform/backend/HMIWeb/MainForm.cs
LGram16 8dc6b0f921 Initial commit: Industrial HMI system with component architecture
- Implement WebView2-based HMI frontend with React + TypeScript + Vite
- Add C# .NET backend with WebSocket communication layer
- Separate UI components into modular structure:
  * RecipePanel: Recipe selection and management
  * IOPanel: I/O monitoring and control (32 inputs/outputs)
  * MotionPanel: Servo control for X/Y/Z axes
  * CameraPanel: Vision system feed with HUD overlay
  * SettingsModal: System configuration management
- Create reusable UI components (CyberPanel, TechButton, PanelHeader)
- Implement dual-mode communication (WebView2 native + WebSocket fallback)
- Add 3D visualization with Three.js/React Three Fiber
- Fix JSON parsing bug in configuration save handler
- Include comprehensive .gitignore for .NET and Node.js projects

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

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

159 lines
5.4 KiB
C#

using Microsoft.Web.WebView2.Core;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace HMIWeb
{
public partial class MainForm : Form
{
private Microsoft.Web.WebView2.WinForms.WebView2 webView;
private Timer plcTimer;
private WebSocketServer _wsServer;
// Machine State (Simulated PLC Memory)
private double currX = 0, currY = 0, currZ = 0;
private double targetX = 0, targetY = 0, targetZ = 0;
private bool[] inputs = new bool[32];
private bool[] outputs = new bool[32];
private string systemState = "IDLE";
public MainForm()
{
InitializeComponent();
InitializeWebView();
// Start WebSocket Server for HMR/Dev
try
{
_wsServer = new WebSocketServer("http://localhost:8081/", this);
}
catch (Exception ex)
{
MessageBox.Show("Failed to start WebSocket Server (Port 8081). Run as Admin or allow port.\n" + ex.Message);
}
// Set default inputs (Pressure OK, Estop OK)
inputs[4] = true;
inputs[6] = true;
}
private async void InitializeWebView()
{
// 1. Setup Virtual Host (http://hmi.local) pointing to frontend/dist folder
// Navigate up from bin/Debug/ to project root, then to frontend/dist
string projectRoot = Path.GetFullPath(Path.Combine(Application.StartupPath, @"..\..\..\.."));
string wwwroot = Path.Combine(projectRoot, @"frontend\dist");
this.Text = $"HMI Host - {wwwroot}";
webView = new Microsoft.Web.WebView2.WinForms.WebView2();
webView.Dock = DockStyle.Fill;
this.Controls.Add(webView);
await webView.EnsureCoreWebView2Async();
if (!Directory.Exists(wwwroot))
{
MessageBox.Show($"Could not find frontend build at:\n{wwwroot}\n\nPlease run 'npm run build' in the frontend folder.", "Frontend Not Found", MessageBoxButtons.OK, MessageBoxIcon.Warning);
// Fallback to local wwwroot if needed, or just allow it to fail gracefully
Directory.CreateDirectory(wwwroot);
}
webView.CoreWebView2.SetVirtualHostNameToFolderMapping(
"hmi.local",
wwwroot,
CoreWebView2HostResourceAccessKind.Allow
);
// 2. Inject Native Object
webView.CoreWebView2.AddHostObjectToScript("machine", new MachineBridge(this));
// 3. Load UI
//webView.Source = new Uri("http://hmi.local/index.html");
// Disable default browser keys (F5, F12 etc) if needed for kiosk mode
webView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
}
// --- Logic Loop ---
private void PlcTimer_Tick(object sender, EventArgs e)
{
// 1. Simulate Motion (Move Current towards Target)
currX = Lerp(currX, targetX, 0.1);
currY = Lerp(currY, targetY, 0.1);
currZ = Lerp(currZ, targetZ, 0.1);
// 2. Prepare Data Packet
var payload = new
{
type = "STATUS_UPDATE",
sysState = systemState,
position = new { x = currX, y = currY, z = currZ },
ioState = GetChangedIOs() // Function to return array of IO states
};
string json = JsonConvert.SerializeObject(payload);
// 3. Send to React via PostMessage (WebView2)
if (webView != null && webView.CoreWebView2 != null)
{
webView.CoreWebView2.PostWebMessageAsJson(json);
}
// 4. Broadcast to WebSocket (Dev/HMR)
_wsServer?.Broadcast(json);
}
private List<object> GetChangedIOs()
{
// Simply return list of all active IOs or just send all for simplicity
var list = new List<object>();
for (int i = 0; i < 32; i++)
{
list.Add(new { id = i, type = "input", state = inputs[i] });
list.Add(new { id = i, type = "output", state = outputs[i] });
}
return list;
}
private void MainForm_Load(object sender, EventArgs e)
{
// Setup Simulation Timer (50ms)
plcTimer = new Timer();
plcTimer.Interval = 50;
plcTimer.Tick += PlcTimer_Tick;
plcTimer.Start();
}
// --- Helper Methods called by Bridge ---
public void SetTargetPosition(string axis, double val)
{
if (axis == "X") targetX = val;
if (axis == "Y") targetY = val;
if (axis == "Z") targetZ = val;
}
public void SetOutput(int id, bool state)
{
if (id < 32) outputs[id] = state;
}
public void HandleCommand(string cmd)
{
systemState = (cmd == "START") ? "RUNNING" : (cmd == "STOP") ? "PAUSED" : "IDLE";
}
private double Lerp(double a, double b, double t) => a + (b - a) * t;
}
}