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");
}
}
}