using System; using System.Collections.Generic; using System.IO; using System.Net; using System.Text; using System.Timers; using CncSimulator.Admin; using CncSimulator.Config; using CncSimulator.Device; using CncSimulator.Generator; using Newtonsoft.Json; using Newtonsoft.Json.Linq; namespace CncSimulator.Core { /// /// 单个模拟地址的HTTP服务。 /// 一个端口同时提供:数据API + 管理界面。 /// public class SimulatorServer { private readonly AddressConfig _config; private readonly List _devices; private readonly List _players; private readonly IBrandGenerator _generator; private readonly LogRecorder _logRecorder; private readonly AdminHandler _adminHandler; private HttpListener _listener; private Timer _tickTimer; private bool _isRunning; private string _networkError = "normal"; private DateTime _startTime; private long _requestCount; private long _successCount; private long _failCount; private bool _stopped; /// 地址名称 public string Name => _config.Name; /// 端口 public int Port => _config.Port; /// 是否运行中 public bool IsRunning => _isRunning; /// 总请求次数 public long RequestCount => _requestCount; /// 设备总数 public int TotalDeviceCount => _devices.Count; /// 在线设备数 public int OnlineDeviceCount { get { int count = 0; foreach (var d in _devices) if (d.State.IsOnline) count++; return count; } } /// 启动时间 public DateTime StartTime => _startTime; /// 配置引用 public AddressConfig Config => _config; /// 设备列表引用 public List Devices => _devices; /// 日志记录器引用 public LogRecorder LogRecorder => _logRecorder; /// 获取本次启动后所有设备的总加工零件数 public int GetTotalParts() { int total = 0; foreach (var d in _devices) total += d.State.TotalPartsSinceStart; return total; } /// 获取按设备+NC程序名统计的零件数 public JObject GetPartsByDeviceAndProgram() { var result = new JObject(); foreach (var dev in _devices) { var s = dev.State; var programs = new JObject(); foreach (var kvp in s.PartsByProgram) { programs[kvp.Key] = kvp.Value; } result[s.DeviceCode] = new JObject { ["desc"] = s.Desc, ["totalParts"] = s.TotalPartsSinceStart, ["currentProgram"] = s.ProgramName, ["currentPartCount"] = s.PartCount, ["programs"] = programs }; } return result; } /// 添加一台模拟设备 public void AddDevice(string deviceCode, string desc) { var devCfg = new Config.DeviceConfig { DeviceCode = deviceCode, Desc = desc, InitialProgram = "O0001", InitialPartCount = 0 }; var sim = new DeviceSimulator(devCfg, _config.DataChangeInterval); _devices.Add(sim); var player = new ScenarioPlayer( sim, _config.ScenarioMode == "auto", Environment.TickCount + _devices.Count * 1000 + deviceCode.GetHashCode() ); _players.Add(player); } /// 移除一台模拟设备 public bool RemoveDevice(string deviceCode) { for (int i = 0; i < _devices.Count; i++) { if (_devices[i].State.DeviceCode == deviceCode) { _devices.RemoveAt(i); _players.RemoveAt(i); return true; } } return false; } public SimulatorServer(AddressConfig config) { _config = config; _devices = new List(); _players = new List(); _logRecorder = new LogRecorder(200); _generator = new FanucDataGenerator(); _adminHandler = new AdminHandler(); // 初始化设备和剧本播放器 for (int i = 0; i < config.Devices.Count; i++) { var devCfg = config.Devices[i]; var sim = new DeviceSimulator(devCfg, config.DataChangeInterval); _devices.Add(sim); var player = new ScenarioPlayer( sim, config.ScenarioMode == "auto", Environment.TickCount + i * 1000 + devCfg.DeviceCode.GetHashCode() ); _players.Add(player); } } /// 启动数据模拟定时器(HttpListener由构造函数或Shutdown后由外部重建) public void Start() { if (_isRunning) return; _startTime = DateTime.Now; _isRunning = true; _stopped = false; // 启动tick定时器 _tickTimer = new Timer(_config.DataChangeInterval * 1000); _tickTimer.Elapsed += OnTick; _tickTimer.Start(); // 如果HttpListener未运行,则启动 if (_listener == null || !_listener.IsListening) { _listener = new HttpListener(); _listener.Prefixes.Add($"http://+:{_config.Port}/"); _listener.Start(); _listener.BeginGetContext(OnRequest, null); } Console.WriteLine($" [✓] {_config.Name}: http://localhost:{_config.Port}/ (管理: http://localhost:{_config.Port}/admin)"); } /// 停止模拟(停止Timer,HttpListener继续运行以接受管理请求) public void Stop() { _isRunning = false; _tickTimer?.Stop(); _tickTimer?.Dispose(); _tickTimer = null; } /// 完全关闭(包括HttpListener) public void Shutdown() { Stop(); _stopped = true; try { _listener?.Stop(); } catch { } } /// 手动触发设备事件 public void TriggerDeviceEvent(string deviceCode, string eventType) { for (int i = 0; i < _devices.Count; i++) { if (_devices[i].State.DeviceCode == deviceCode) { _players[i].TriggerEvent(eventType); return; } } } /// 设置网络异常类型 public void SetNetworkError(string type) { _networkError = type; if (type == "refuse") { // 停止HttpListener模拟拒绝连接 try { _listener?.Stop(); } catch { } } else if (type == "normal") { // 恢复HttpListener if (_listener != null && !_stopped) { try { if (!_listener.IsListening) { _listener.Start(); _listener.BeginGetContext(OnRequest, null); } } catch { } } } } /// 修改数据变化频率 public void SetInterval(int seconds) { _config.DataChangeInterval = seconds; if (_tickTimer != null) { _tickTimer.Interval = seconds * 1000; } } /// 切换剧本模式 public void SetMode(string mode) { _config.ScenarioMode = mode; foreach (var player in _players) { player.SetMode(mode); } } /// 获取所有设备状态(用于管理API) public JArray GetDeviceStatusArray() { var arr = new JArray(); foreach (var dev in _devices) { var s = dev.State; arr.Add(new JObject { ["deviceCode"] = s.DeviceCode, ["desc"] = s.Desc, ["scenario"] = s.CurrentScenario, ["isOnline"] = s.IsOnline, ["programName"] = s.ProgramName, ["partCount"] = s.PartCount, ["totalPartCount"] = s.TotalPartCount, ["runStatus"] = s.RunStatus, ["operateMode"] = s.OperateMode, ["scenarioTick"] = s.ScenarioTick, ["scenarioDuration"] = s.ScenarioDuration }); } return arr; } // ===== 私有方法 ===== /// 定时器回调:推进每台设备的剧本和状态 private void OnTick(object sender, ElapsedEventArgs e) { if (!_isRunning) return; foreach (var player in _players) { player.Tick(); // 每台设备tick后更新状态 } foreach (var dev in _devices) { dev.Tick(); } } /// 生成当前JSON响应(包括在线和离线设备) private string GenerateCurrentJson() { var devices = new JArray(); foreach (var dev in _devices) { if (dev.State.IsOnline) { devices.Add(_generator.GenerateDevice(dev.State)); } else { // 离线设备:生成带有quality=1和1970时间的tag devices.Add(GenerateOfflineDevice(dev.State)); } } return devices.ToString(Formatting.None); } /// 生成离线设备的JSON(_io_status有效,其余tag quality=1) private JObject GenerateOfflineDevice(DeviceState state) { var tags = new JArray(); DateTime now = DateTime.Now; // _io_status 始终有效 tags.Add(new JObject { ["id"] = "_io_status", ["desc"] = "设备状态", ["quality"] = "0", ["value"] = "0.00000", ["time"] = now.ToString("yyyy-MM-dd HH:mm:ss") }); // 其余tag全部标记为无效(quality=1, time=1970) string epoch = "1970-01-01 08:00:00"; var offlineTags = new[] { new { id = "Tag1", desc = "加工零件总数", value = "0.00000" }, new { id = "Tag5", desc = "执行的NC主程序名", value = "" }, new { id = "Tag6", desc = "执行的NC主程序号", value = "" }, new { id = "Tag7", desc = "当前加工程序内容", value = "" }, new { id = "Tag8", desc = "当前加工零件数", value = "0.00000" }, new { id = "Tag9", desc = "运行状态", value = "0.00000" }, new { id = "Tag22", desc = "开机时间", value = "0.00000" }, new { id = "Tag23", desc = "运行时间", value = "0.00000" }, new { id = "Tag24", desc = "切削时间", value = "0.00000" }, new { id = "Tag25", desc = "循环时间", value = "0.00000" }, new { id = "Tag26", desc = "加工状态", value = "" } }; foreach (var t in offlineTags) { tags.Add(new JObject { ["id"] = t.id, ["desc"] = t.desc, ["quality"] = "1", ["value"] = t.value, ["time"] = epoch }); } return new JObject { ["device"] = state.DeviceCode, ["desc"] = state.Desc, ["tags"] = tags }; } /// 生成关键数据摘要 private string GenerateKeyData() { var parts = new List(); foreach (var dev in _devices) { var s = dev.State; if (s.IsOnline) { parts.Add($"{s.DeviceCode}(P={s.PartCount},Prog={s.ProgramName},Run={s.RunStatus})"); } else { parts.Add($"{s.DeviceCode}(OFFLINE)"); } } return string.Join(" ", parts); } /// HttpListener请求回调 private void OnRequest(IAsyncResult ar) { HttpListenerContext ctx; try { if (_listener == null || !_listener.IsListening) return; ctx = _listener.EndGetContext(ar); } catch { return; } // 继续接收下一个请求 try { if (_listener.IsListening) _listener.BeginGetContext(OnRequest, null); } catch { } ProcessRequest(ctx); } /// 处理单个HTTP请求 private void ProcessRequest(HttpListenerContext ctx) { string path = ctx.Request.Url.AbsolutePath.TrimEnd('/'); string method = ctx.Request.HttpMethod; try { // ===== 管理页面路由 ===== if (path == "/admin") { ServeAdminPage(ctx); return; } // ===== 管理API路由 ===== if (path.StartsWith("/admin/api/")) { HandleAdminApi(ctx, path, method); return; } // ===== 数据接口 ===== if (path == "" || path == "/data") { ServeData(ctx); return; } // 404 SendJsonResponse(ctx, 404, new JObject { ["error"] = "Not Found" }.ToString()); } catch (Exception ex) { try { SendJsonResponse(ctx, 500, new JObject { ["error"] = ex.Message }.ToString()); } catch { } } } /// 提供数据接口 private void ServeData(HttpListenerContext ctx) { _requestCount++; // 网络异常模拟 switch (_networkError) { case "http500": _failCount++; _logRecorder.RecordError("http500", "模拟HTTP 500错误", OnlineDeviceCount); SendTextResponse(ctx, 500, "Internal Server Error (模拟)"); return; case "timeout": _failCount++; _logRecorder.RecordError("timeout", "模拟超时响应(60秒延迟)", OnlineDeviceCount); System.Threading.Thread.Sleep(60000); SendTextResponse(ctx, 200, "delayed response"); return; case "empty": _successCount++; _logRecorder.RecordError("empty", "模拟空数据返回([])", OnlineDeviceCount); SendTextResponse(ctx, 200, "[]", "application/json"); return; case "malformed": _successCount++; _logRecorder.RecordError("malformed", "模拟畸形JSON返回({broken)", OnlineDeviceCount); SendTextResponse(ctx, 200, "{broken", "application/json"); return; } // 正常生成数据 var sw = System.Diagnostics.Stopwatch.StartNew(); string json = GenerateCurrentJson(); sw.Stop(); string keyData = GenerateKeyData(); _logRecorder.Record(_config.Port, OnlineDeviceCount, keyData, json, sw.ElapsedMilliseconds); _successCount++; SendTextResponse(ctx, 200, json, "application/json"); Console.WriteLine($"{DateTime.Now:HH:mm:ss} [{_config.Port}] GET / → {OnlineDeviceCount}台设备, {sw.ElapsedMilliseconds}ms"); } /// 提供管理页面 private void ServeAdminPage(HttpListenerContext ctx) { string html = _adminHandler.BuildSingleAddressPage(this); SendTextResponse(ctx, 200, html, "text/html; charset=utf-8"); } /// 处理管理API private void HandleAdminApi(HttpListenerContext ctx, string path, string method) { switch (path) { case "/admin/api/status": var status = new JObject { ["name"] = _config.Name, ["port"] = _config.Port, ["isRunning"] = _isRunning, ["requestCount"] = _requestCount, ["successCount"] = _successCount, ["failCount"] = _failCount, ["totalDevices"] = _devices.Count, ["onlineDevices"] = OnlineDeviceCount, ["dataChangeInterval"] = _config.DataChangeInterval, ["scenarioMode"] = _config.ScenarioMode, ["networkError"] = _networkError, ["startTime"] = _startTime.ToString("yyyy-MM-dd HH:mm:ss"), ["uptime"] = (DateTime.Now - _startTime).ToString(@"hh\:mm\:ss"), ["devices"] = GetDeviceStatusArray() }; SendTextResponse(ctx, 200, status.ToString(), "application/json"); break; case "/admin/api/start": Start(); SendTextResponse(ctx, 200, "{\"ok\":true}", "application/json"); break; case "/admin/api/stop": Stop(); SendTextResponse(ctx, 200, "{\"ok\":true}", "application/json"); break; case "/admin/api/event": string eventBody = ReadRequestBody(ctx); var eventObj = JObject.Parse(eventBody); TriggerDeviceEvent( eventObj["deviceId"]?.ToString(), eventObj["eventType"]?.ToString() ); SendTextResponse(ctx, 200, "{\"ok\":true}", "application/json"); break; case "/admin/api/interval": string intervalBody = ReadRequestBody(ctx); var intervalObj = JObject.Parse(intervalBody); SetInterval(intervalObj["value"]?.Value() ?? 10); SendTextResponse(ctx, 200, "{\"ok\":true}", "application/json"); break; case "/admin/api/network": string netBody = ReadRequestBody(ctx); var netObj = JObject.Parse(netBody); SetNetworkError(netObj["type"]?.ToString() ?? "normal"); SendTextResponse(ctx, 200, "{\"ok\":true}", "application/json"); break; case "/admin/api/mode": string modeBody = ReadRequestBody(ctx); var modeObj = JObject.Parse(modeBody); SetMode(modeObj["mode"]?.ToString() ?? "auto"); SendTextResponse(ctx, 200, "{\"ok\":true}", "application/json"); break; case "/admin/api/logs": var logs = _logRecorder.GetRecentLogs(100); var logsArr = new JArray(); for (int i = 0; i < logs.Count; i++) { var l = logs[i]; logsArr.Add(new JObject { ["index"] = logs.Count - i, ["timestamp"] = l.Timestamp.ToString("HH:mm:ss"), ["deviceCount"] = l.DeviceCount, ["keyData"] = l.KeyData, ["duration"] = l.Duration, ["fullJson"] = l.FullJson }); } SendTextResponse(ctx, 200, logsArr.ToString(), "application/json"); break; case "/admin/api/stats": var stats = new JObject { ["totalDevices"] = _devices.Count, ["onlineDevices"] = OnlineDeviceCount, ["totalParts"] = GetTotalParts(), ["partsByDevice"] = GetPartsByDeviceAndProgram() }; SendTextResponse(ctx, 200, stats.ToString(), "application/json"); break; case "/admin/api/add-device": string addBody = ReadRequestBody(ctx); var addObj = JObject.Parse(addBody); AddDevice( addObj["deviceCode"]?.ToString(), addObj["desc"]?.ToString() ); SendTextResponse(ctx, 200, "{\"ok\":true}", "application/json"); break; case "/admin/api/remove-device": string remBody = ReadRequestBody(ctx); var remObj = JObject.Parse(remBody); bool removed = RemoveDevice(remObj["deviceCode"]?.ToString()); SendTextResponse(ctx, 200, removed ? "{\"ok\":true}" : "{\"ok\":false}", "application/json"); break; case "/admin/api/event-history": HandleEventHistoryApi(ctx); break; case "/admin/api/full-summary": HandleFullSummaryApi(ctx); break; case "/admin/api/error-log": HandleErrorLogApi(ctx); break; default: SendJsonResponse(ctx, 404, "{\"error\":\"Unknown API\"}"); break; } } // ===== 新增API处理方法 ===== /// 事件历史API:返回所有设备的事件变更记录 private void HandleEventHistoryApi(HttpListenerContext ctx) { var allEvents = new JArray(); foreach (var dev in _devices) { foreach (var evt in dev.State.EventHistory) { allEvents.Add(new JObject { ["timestamp"] = evt.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"), ["deviceCode"] = evt.DeviceCode, ["eventType"] = evt.EventType, ["oldProgram"] = evt.OldProgram, ["newProgram"] = evt.NewProgram, ["partCountBefore"] = evt.PartCountBefore, ["partCountAfter"] = evt.PartCountAfter, ["detail"] = evt.Detail }); } } SendTextResponse(ctx, 200, allEvents.ToString(), "application/json"); } /// 完整汇总导出API:用于测试结束后数据对比 private void HandleFullSummaryApi(HttpListenerContext ctx) { var devices = new JArray(); foreach (var dev in _devices) { var s = dev.State; var programs = new JObject(); foreach (var kvp in s.PartsByProgram) { programs[kvp.Key] = kvp.Value; } devices.Add(new JObject { ["deviceCode"] = s.DeviceCode, ["desc"] = s.Desc, ["isOnline"] = s.IsOnline, ["currentProgram"] = s.ProgramName, ["currentPartCount"] = s.PartCount, ["totalParts"] = s.TotalPartsSinceStart, ["programs"] = programs, ["eventCount"] = s.EventHistory.Count, ["lastEvent"] = s.EventHistory.Count > 0 ? new JObject { ["type"] = s.EventHistory[s.EventHistory.Count - 1].EventType, ["time"] = s.EventHistory[s.EventHistory.Count - 1].Timestamp.ToString("yyyy-MM-dd HH:mm:ss") } : null }); } var summary = new JObject { ["exportTime"] = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), ["addressName"] = _config.Name, ["port"] = _config.Port, ["startTime"] = _startTime.ToString("yyyy-MM-dd HH:mm:ss"), ["uptime"] = (DateTime.Now - _startTime).ToString(@"hh\:mm\:ss"), ["totalDevices"] = _devices.Count, ["onlineDevices"] = OnlineDeviceCount, ["totalParts"] = GetTotalParts(), ["totalRequests"] = _requestCount, ["successRequests"] = _successCount, ["failRequests"] = _failCount, ["errorCount"] = _logRecorder.GetErrorCount(), ["devices"] = devices }; SendTextResponse(ctx, 200, summary.ToString(), "application/json"); } /// 异常日志API:返回所有异常记录 private void HandleErrorLogApi(HttpListenerContext ctx) { var errors = _logRecorder.GetErrors(); var arr = new JArray(); foreach (var err in errors) { arr.Add(new JObject { ["timestamp"] = err.Timestamp.ToString("yyyy-MM-dd HH:mm:ss"), ["errorType"] = err.ErrorType, ["description"] = err.Description, ["affectedDevices"] = err.AffectedDevices }); } SendTextResponse(ctx, 200, arr.ToString(), "application/json"); } // ===== HTTP辅助方法 ===== private string ReadRequestBody(HttpListenerContext ctx) { using (var reader = new StreamReader(ctx.Request.InputStream, Encoding.UTF8)) { return reader.ReadToEnd(); } } private void SendTextResponse(HttpListenerContext ctx, int statusCode, string body, string contentType = "text/plain") { try { ctx.Response.StatusCode = statusCode; ctx.Response.ContentType = contentType; byte[] bytes = Encoding.UTF8.GetBytes(body); ctx.Response.ContentLength64 = bytes.Length; ctx.Response.OutputStream.Write(bytes, 0, bytes.Length); ctx.Response.OutputStream.Close(); } catch { } } private void SendJsonResponse(HttpListenerContext ctx, int statusCode, string json) { SendTextResponse(ctx, statusCode, json, "application/json"); } } }