You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
haoliang-net/docs/07-模拟采集服务设计.md

802 lines
33 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# CNC机床数据采集系统 - 模拟采集服务设计文档
> 最后更新2026-04-30
> 状态:设计中
---
## 一、概述
模拟采集服务CncSimulator是一个独立控制台应用用于模拟真实采集地址的HTTP JSON接口。开发采集服务时无需连接真实CNC设备通过本工具模拟各种采集场景进行联调测试。
### 1.1 定位
| 项目 | 说明 |
|------|------|
| 性质 | 开发测试工具,不投入生产 |
| 用户 | 开发人员 |
| 运行方式 | 控制台应用,双击即运行 |
| 生命周期 | 采集服务开发完成后仍可用于回归测试 |
### 1.2 核心功能
- 在本机启动HTTP服务模拟采集地址返回FANUC格式的JSON数据
- 浏览器管理界面,可操作启停、调整参数、触发特定事件
- 支持同时启动多个端口,模拟多个采集地址
- 按预设剧本自动循环各种真实场景
- 记录每次返回的完整数据日志,方便与采集服务核对
---
## 二、技术选型
| 项 | 选型 | 说明 |
|----|------|------|
| 项目类型 | .NET Framework 4.7.2 控制台应用 | 加入CncDataSystem.sln |
| HTTP服务 | System.Net.HttpListener | 一个端口同时提供数据API + 管理界面 |
| 管理界面 | 内嵌HTML + JavaScript | 无需前端构建,浏览器直接打开 |
| JSON | Newtonsoft.Json 12.0.3 | 与项目一致 |
| 日志 | log4net → 文件 | 每次返回完整JSON + 关键字段摘要 |
| 配置 | JSON文件 | simulator.json运行时可热修改 |
### 2.1 双角色HTTP服务
每个端口同时承担两个职责:
```
端口 9001:
GET / → 返回模拟JSON数组采集服务调用这个地址
GET /data → 同上(别名)
GET /admin → 管理界面HTML
GET /admin/api/status → JSON状态数据
POST /admin/api/start → 启动模拟
POST /admin/api/stop → 停止模拟
POST /admin/api/event → 触发手动事件
GET /admin/api/logs → 返回日志列表
```
采集服务配置采集地址时URL填 `http://localhost:9001/` 即可。
---
## 三、项目结构
### 3.1 文件清单
```
src/CncSimulator/
├── CncSimulator.csproj ← .NET Framework 4.7.2 控制台应用
├── Program.cs ← 主入口:读取配置→启动引擎→等待退出
├── Core/
│ ├── SimulatorEngine.cs ← 引擎管理多个SimulatorServer的生命周期
│ ├── SimulatorServer.cs ← 单地址服务HttpListener + 请求路由
│ └── LogRecorder.cs ← 返回日志记录器(内存环形缓冲 + 文件写入)
├── Device/
│ ├── DeviceSimulator.cs ← 单台设备状态机(维护当前状态、字段值)
│ ├── ScenarioPlayer.cs ← 剧本播放器(按时间线切换场景)
│ └── DeviceState.cs ← 设备状态数据结构
├── Generator/
│ ├── FanucDataGenerator.cs ← FANUC品牌JSON生成器
│ └── IBrandGenerator.cs ← 品牌生成器接口(预留扩展)
├── Admin/
│ ├── AdminHandler.cs ← 管理界面请求处理器
│ └── HtmlBuilder.cs ← 管理页面HTML生成
├── Config/
│ └── SimulatorConfig.cs ← 配置数据结构 + JSON序列化
├── simulator.json ← 默认配置文件
└── App.config ← log4net配置
```
### 3.2 项目引用
```
CncSimulator → Newtonsoft.JsonNuGet
log4netNuGet
System.Net.Http框架内置
不引用 CncModels / CncRepository / CncService / CncWebApi
模拟器完全独立,不依赖生产代码。
```
---
## 四、配置文件
### 4.1 simulator.json
```json
{
"gatewayPort": 9000,
"addresses": [
{
"name": "FANUC-1号模拟",
"port": 9001,
"brand": "fanuc",
"dataChangeInterval": 10,
"scenarioMode": "auto",
"devices": [
{
"deviceCode": "CNC-A001",
"desc": "西栋1号",
"initialProgram": "O0001",
"initialPartCount": 50
},
{
"deviceCode": "CNC-006",
"desc": "6号机床",
"initialProgram": "O0002",
"initialPartCount": 120
},
{
"deviceCode": "CNC-008",
"desc": "8号机床",
"initialProgram": "O0003",
"initialPartCount": 0
}
]
},
{
"name": "FANUC-2号模拟",
"port": 9002,
"brand": "fanuc",
"dataChangeInterval": 15,
"scenarioMode": "auto",
"devices": [
{
"deviceCode": "CNC-B002",
"desc": "B栋2号",
"initialProgram": "1566.NC",
"initialPartCount": 80
},
{
"deviceCode": "CNC-005",
"desc": "验证机床",
"initialProgram": "TEST001",
"initialPartCount": 10
}
]
}
]
}
```
### 4.2 配置字段说明
| 字段 | 类型 | 默认值 | 说明 |
|------|------|--------|------|
| gatewayPort | int | 9000 | 总管理页面端口 |
| addresses | array | - | 采集地址列表 |
| address.name | string | - | 显示名称 |
| address.port | int | - | 本地址的监听端口 |
| address.brand | string | fanuc | 品牌标识(预留扩展) |
| address.dataChangeInterval | int | 10 | 数据变化频率(秒) |
| address.scenarioMode | string | auto | auto=自动剧本 / manual=手动触发 |
| address.devices | array | - | 模拟设备列表 |
| device.deviceCode | string | - | 设备编码需与cnc_machine.device_code一致 |
| device.desc | string | - | 设备描述 |
| device.initialProgram | string | O0001 | 初始NC程序名 |
| device.initialPartCount | int | 0 | 初始零件数 |
---
## 五、设备状态机与场景
### 5.1 设备状态
每台设备维护以下内部状态:
```csharp
class DeviceState
{
string DeviceCode; // 设备编码(固定)
string Desc; // 设备描述(固定)
// 动态状态
string CurrentScenario; // 当前场景名
bool IsOnline; // 是否在线(断电=false
string ProgramName; // 当前NC程序名
int PartCount; // 当前零件数
int DeviceStatus; // _io_status: 0=离线, 1=在线
int RunStatus; // 运行状态: 0=待机, 1=运行, 3=加工中
int OperateMode; // 操作模式: 1=MEM, 10=JOG
decimal SpindleSpeedSet; // 主轴设定速度
decimal FeedSpeedSet; // 进给设定速度
decimal SpindleSpeedActual; // 主轴实际速度
decimal FeedSpeedActual; // 进给实际速度
decimal SpindleLoad; // 主轴负载
decimal SpindleOverride; // 主轴倍率
decimal PowerOnTime; // 开机累计时间(秒)
decimal RunTime; // 运行累计时间(秒)
decimal CuttingTime; // 切削累计时间(秒)
decimal CycleTime; // 循环时间(秒)
string MachiningStatus; // 加工状态: G01/G00/G28/等
string ProgramContent; // 加工程序内容片段
// 剧本控制
int ScenarioTick; // 当前场景已持续的tick数
int ScenarioDuration; // 当前场景总tick数
}
```
### 5.2 场景定义
| 场景名 | 标识 | 字段变化规则 | 典型持续 |
|--------|------|-------------|---------|
| 正常加工 | `machining` | part_count +1主轴有值run_status=3cutting_time递增machining_status=G01 | 20~40个tick |
| 加工同一零件 | `same_part` | 所有值不变只有time更新 | 5~10个tick |
| 待机空闲 | `idle` | part_count不变主轴实际=0run_status=0feed=0machining_status=G00 | 10~20个tick |
| 换零件 | `program_change` | program_name变为新值part_count清零cycle_time清零 | 1个tick瞬时 |
| 手动清零 | `manual_reset` | program_name不变part_count突然降为0 | 1个tick瞬时 |
| 断电 | `power_off` | 设备从返回数组中消失IsOnline=false | 5~15个tick |
| 恢复开机 | `power_on` | 设备重新出现_io_status从0→1part_count从0开始 | 1个tick |
| 暂停加工 | `pause` | run_status=1运行但非加工主轴实际=0feed=0 | 5~10个tick |
### 5.3 字段变化详细规则
#### 正常加工machining
```
每次tick:
part_count += 1
run_status = 3
device_status = 1
operate_mode = 1 // MEM模式
spindle_speed_actual = spindle_speed_set ± 随机波动10%
feed_speed_actual = feed_speed_set ± 随机波动5%
spindle_load = 随机 15~45%
spindle_override = 100
machining_status = 随机轮换 [G01, G01, G01, G02, G00] // 加工中以G01为主
cutting_time += dataChangeInterval
run_time += dataChangeInterval
power_on_time += dataChangeInterval
cycle_time += dataChangeInterval
program_content = 程序内容片段随program_name变化
```
#### 加工同一零件same_part
```
每次tick:
所有字段值不变包括part_count
只有每个tag的time更新为当前时间
代表:机床还在加工当前零件,上一次采集和这次采集之间零件没完成
```
#### 待机空闲idle
```
每次tick:
part_count 不变
run_status = 0 // 待机
operate_mode = 10 // JOG模式
spindle_speed_actual = 0
feed_speed_actual = 0
spindle_load = 0
feed_speed_set = 0
machining_status = ""
power_on_time += dataChangeInterval
run_time += dataChangeInterval // 运行时间照常增加
// cutting_time 不增加(没有切削)
```
#### 换零件program_change
```
瞬时触发1个tick:
旧的 program_name → 新的 program_name
新程序名从预设列表轮换: ["O0001", "O0002", "1566.NC", "PART-A", "TEST-03"]
part_count = 0
cycle_time = 0
run_status = 3 // 立即开始加工
machining_status = G01
spindle_speed_set = 随机 200~800 // 新程序新的速度
feed_speed_set = 随机 30~150
```
#### 手动清零manual_reset
```
瞬时触发1个tick:
program_name 不变
part_count 突然降为 0
run_status = 3
其他字段不变
之后进入 normal_machining 场景继续递增
```
#### 断电power_off
```
触发后:
该设备不参与JSON数组生成从返回数据中消失
所有内部状态冻结
采集服务视角调HTTP接口正常返回但少了这台设备的数据
```
#### 恢复开机power_on
```
瞬时触发1个tick:
设备重新出现
device_status (_io_status) = 0 → 1 // 开机瞬间可能先显示0
part_count = 0 // 重启后清零
cycle_time = 0
program_name = 上次断电前的程序名(设备重启不丢程序)
然后进入 idle 或 machining 场景
```
#### 暂停加工pause
```
每次tick:
part_count 不变
run_status = 1 // 运行但暂停
spindle_speed_actual = 0
feed_speed_actual = 0
spindle_load = 0
machining_status = ""
power_on_time += dataChangeInterval
run_time += dataChangeInterval
```
### 5.4 自动剧本
自动模式下,每台设备按以下顺序循环场景:
```
剧本循环(每个地址的设备剧本独立运行):
[正常加工] × 随机20~40个tick
[加工同一零件] × 随机5~10个tick
[正常加工] × 随机10~20个tick
[换零件] × 1个tick
[正常加工] × 随机15~30个tick
[暂停加工] × 随机5~10个tick
[正常加工] × 随机10~15个tick
[加工同一零件] × 随机3~5个tick
[手动清零] × 1个tick
[正常加工] × 随机20~30个tick
[待机空闲] × 随机10~20个tick
[正常加工] × 随机15~25个tick
[断电] × 随机5~15个tick
[恢复开机] × 1个tick
回到开头
每个tick间隔 = dataChangeInterval
每台设备的剧本随机偏移,避免所有设备同步变化
```
### 5.5 网络异常模拟
网络异常不是设备状态,而是**整个地址级别**的事件。通过管理界面手动触发。
| 异常类型 | 标识 | 模拟方式 | 持续 |
|---------|------|---------|------|
| 正常 | `normal` | 正常返回JSON | - |
| HTTP 500 | `http500` | 返回 HTTP 500 + 纯文本错误信息 | 手动恢复 |
| 连接超时 | `timeout` | 收到请求后延迟60秒才返回模拟网络慢 | 手动恢复 |
| 拒绝连接 | `refuse` | 停止HttpListener端口不通 | 手动恢复 |
| 空数组 | `empty` | 返回 `[]`正常HTTP 200 | 手动恢复 |
| 畸形JSON | `malformed` | 返回 `{broken`HTTP 200 | 手动恢复 |
---
## 六、FANUC数据模板
### 6.1 Tag结构
基于示例数据每台设备返回18个Tag
| Tag ID | desc | value格式 | 标准字段映射 |
|--------|------|----------|-------------|
| `_io_status` | 设备状态 | `"1.00000"` / `"0.00000"` | device_status |
| `Tag2` | 当前轴数 | `"4.00000"` | 非标准字段extra_data |
| `Tag5` | 执行的NC主程序名 | `"1566.NC"` / `"O1"` | program_name |
| `Tag6` | 执行的NC主程序号 | `"N0"` / `"N20"` | (非标准字段) |
| `Tag7` | 当前加工程序内容 | 多行文本 | (非标准字段) |
| `Tag8` | 当前加工零件数 | `"1219.00000"` | part_count |
| `Tag9` | 运行状态 | `"0.00000"` / `"3.00000"` | run_status |
| `Tag11` | 操作模式 | `"1.00000"` / `"10.00000"` | operate_mode |
| `Tag14` | 当前主轴倍率 | `"100.00000"` | spindle_override |
| `Tag17` | 主轴设定速度 | `"300.00000"` | spindle_speed_set |
| `Tag18` | 进给设定速度 | `"60.00000"` | feed_speed_set |
| `Tag19` | 主轴实际速度 | `"450.00000"` | spindle_speed_actual |
| `Tag20` | 进给实际转速 | `"60.00000"` | feed_speed_actual |
| `Tag21` | 主轴负载 | `"25.00000"` | spindle_load |
| `Tag22` | 开机时间 | `"23558160.00000"` | power_on_time |
| `Tag23` | 运行时间 | `"18224.00000"` | run_time |
| `Tag24` | 切削时间 | `"6848959.00000"` | cutting_time |
| `Tag25` | 循环时间 | `"699.00000"` | cycle_time |
| `Tag26` | 加工状态 | `"G01"` | machining_status |
### 6.2 时间处理
```
每个tag的time字段:
基准时间 = DateTime.Now
每个tag在基准时间上随机偏移 -5秒 ~ 0秒
格式: "yyyy-MM-dd HH:mm:ss"
示例:
Tag5 time = "2026-04-30 14:30:25" ← 偏移-5秒
Tag8 time = "2026-04-30 14:30:28" ← 偏移-2秒
Tag26 time = "2026-04-30 14:30:30" ← 无偏移
```
### 6.3 数值格式
```
数字类型Tag的value格式: "{值}.00000"
整数值: "1219.00000", "0.00000", "25.00000"
无小数的真实值加固定5位小数
字符串类型Tag的value格式: 原样
"1566.NC", "G01", "N20"
```
### 6.4 JSON输出示例
```json
[
{
"device": "CNC-A001",
"desc": "西栋1号",
"tags": [
{ "id": "_io_status", "desc": "设备状态", "quality": "0", "value": "1.00000", "time": "2026-04-30 14:30:30" },
{ "id": "Tag2", "desc": "当前轴数", "quality": "0", "value": "4.00000", "time": "2026-04-30 14:30:28" },
{ "id": "Tag5", "desc": "执行的NC主程序名", "quality": "0", "value": "O0001", "time": "2026-04-30 14:30:27" },
{ "id": "Tag6", "desc": "执行的NC主程序号", "quality": "0", "value": "N0", "time": "2026-04-30 14:30:27" },
{ "id": "Tag7", "desc": "当前加工程序内容", "quality": "0", "value": "<O0001>\nG40G49G80\n( SIMULATOR )", "time": "2026-04-30 14:30:27" },
{ "id": "Tag8", "desc": "当前加工零件数", "quality": "0", "value": "53.00000", "time": "2026-04-30 14:30:27" },
{ "id": "Tag9", "desc": "运行状态", "quality": "0", "value": "3.00000", "time": "2026-04-30 14:30:28" },
{ "id": "Tag11", "desc": "操作模式", "quality": "0", "value": "1.00000", "time": "2026-04-30 14:30:28" },
{ "id": "Tag14", "desc": "当前主轴倍率", "quality": "0", "value": "100.00000", "time": "2026-04-30 14:30:28" },
{ "id": "Tag17", "desc": "主轴设定速度", "quality": "0", "value": "450.00000", "time": "2026-04-30 14:30:28" },
{ "id": "Tag18", "desc": "进给设定速度", "quality": "0", "value": "60.00000", "time": "2026-04-30 14:30:28" },
{ "id": "Tag19", "desc": "主轴实际速度", "quality": "0", "value": "448.00000", "time": "2026-04-30 14:30:29" },
{ "id": "Tag20", "desc": "进给实际转速", "quality": "0", "value": "58.00000", "time": "2026-04-30 14:30:29" },
{ "id": "Tag21", "desc": "主轴负载", "quality": "0", "value": "32.00000", "time": "2026-04-30 14:30:29" },
{ "id": "Tag22", "desc": "开机时间", "quality": "0", "value": "23559020.00000", "time": "2026-04-30 14:30:29" },
{ "id": "Tag23", "desc": "运行时间", "quality": "0", "value": "18384.00000", "time": "2026-04-30 14:30:29" },
{ "id": "Tag24", "desc": "切削时间", "quality": "0", "value": "6849019.00000", "time": "2026-04-30 14:30:30" },
{ "id": "Tag25", "desc": "循环时间", "quality": "0", "value": "759.00000", "time": "2026-04-30 14:30:30" },
{ "id": "Tag26", "desc": "加工状态", "quality": "0", "value": "G01", "time": "2026-04-30 14:30:30" }
]
}
]
```
---
## 七、管理界面设计
### 7.1 总管理页面http://localhost:9000/admin
```
┌─────────────────────────────────────────────────────────────┐
│ CNC 模拟采集服务 [全部启动] [全部停止] │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌ 地址列表 ──────────────────────────────────────────────┐ │
│ │ │ │
│ │ 名称 端口 状态 设备数 数据频率 操作 │ │
│ │ FANUC-1号模拟 9001 ●运行中 3台 10秒 [管理] │ │
│ │ FANUC-2号模拟 9002 ○已停止 2台 15秒 [管理] │ │
│ │ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌ 控制台日志 ────────────────────────────────────────────┐ │
│ │ 14:30:30 [9001] 返回3台设备, 总耗时5ms │ │
│ │ 14:30:28 [9002] 已停止 │ │
│ │ 14:30:20 [9001] 返回3台设备, 总耗时4ms │ │
│ │ ... │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 7.2 单地址管理页面http://localhost:9001/admin
```
┌─────────────────────────────────────────────────────────────┐
│ FANUC-1号模拟 (端口 9001) [启动] [停止] │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌ 全局设置 ──────────────────────────────────────────────┐ │
│ │ 数据变化频率: [====10====] 秒 │ │
│ │ 剧本模式: (●) 自动循环 ( ) 手动触发 │ │
│ │ 网络异常模拟: [正常 ▼] │ │
│ │ 正常 / HTTP 500 / 连接超时 / 拒绝连接 │ │
│ │ / 返回空数组 / 畸形JSON │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌ 设备状态卡片 ──────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌ CNC-A001 西栋1号 ──────────┐ │ │
│ │ │ 场景: 正常加工 │ [换零件] [手动清零] │ │
│ │ │ 程序: O0001 │ [断电] [暂停] │ │
│ │ │ 零件数: 53 │ [恢复] │ │
│ │ │ 运行状态: 3 (加工中) │ │ │
│ │ │ 主轴: 448/450 负载32% │ │ │
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ ┌ CNC-006 6号机床 ────────────┐ │ │
│ │ │ 场景: 待机空闲 │ [换零件] [手动清零] │ │
│ │ │ 程序: O0002 │ [断电] [暂停] │ │
│ │ │ 零件数: 120 │ [恢复] │ │
│ │ │ 运行状态: 0 (待机) │ │ │
│ │ │ 主轴: 0/0 负载0% │ │ │
│ │ └────────────────────────────┘ │ │
│ │ │ │
│ │ ┌ CNC-008 8号机床 ────────────┐ │ │
│ │ │ 场景: 断电 (已断电3个tick) │ [恢复] │ │
│ │ │ (设备离线,不返回数据) │ │ │
│ │ └────────────────────────────┘ │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌ 当前返回JSON预览 ─────────────────────────────────────┐ │
│ │ [ │ │
│ │ { "device": "CNC-A001", "desc": "西栋1号", ... }, │ │
│ │ { "device": "CNC-006", "desc": "6号机床", ... } │ │
│ │ ] │ │
│ │ ↑ 每次 tick 自动刷新 │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌ 返回数据日志最近100条──────────────────────────────┐ │
│ │ # 时间 设备数 关键变化 │ │
│ │ 100 14:30:30 2 CNC-A001 零件53, 程序O0001 │ │
│ │ 99 14:30:20 2 CNC-A001 零件52, 程序O0001 │ │
│ │ 98 14:30:10 2 CNC-008 断电消失 │ │
│ │ 97 14:30:10 3 CNC-A001 零件51, 程序O0001 │ │
│ │ ... │ │
│ │ [点击展开完整JSON] │ │
│ └────────────────────────────────────────────────────────┘ │
│ │
│ ┌ 统计 ─────────────────────────────────────────────────┐ │
│ │ 总请求次数: 256 │ │
│ │ 成功次数: 253 失败次数: 3 (网络异常模拟) │ │
│ │ 平均响应时间: 4ms │ │
│ │ 本次启动时长: 42分30秒 │ │
│ └────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
### 7.3 管理界面技术实现
```
HTML生成方式
服务端拼接HTML字符串HtmlBuilder.cs
不使用任何前端框架
CSS内联JavaScript内联
页面通过 setInterval 每2秒 AJAX 刷新状态
AJAX接口:
GET /admin/api/status → 所有设备当前状态
GET /admin/api/logs?page=1 → 日志列表
POST /admin/api/start → 启动
POST /admin/api/stop → 停止
POST /admin/api/interval?value=15 → 修改数据变化频率
POST /admin/api/event → 触发手动事件
body: { deviceId: "CNC-A001", eventType: "program_change" }
POST /admin/api/network → 设置网络异常类型
body: { type: "http500" }
POST /admin/api/mode → 切换剧本模式
body: { mode: "manual" }
```
---
## 八、日志记录
### 8.1 文件日志
```
logs/simulator-2026-04-30.log
[14:30:30 INFO ] [9001] 返回请求: 3台设备, 耗时4ms
[14:30:30 DEBUG] [9001] 完整JSON: [{"device":"CNC-A001","desc":"西栋1号","tags":[...]}, ...]
[14:30:30 INFO ] [9001] 关键数据: CNC-A001(P=53,Prog=O0001,Run=3) CNC-006(P=120,Prog=O0002,Run=0)
[14:30:20 INFO ] [9001] 返回请求: 3台设备, 耗时5ms
[14:30:20 INFO ] [9001] 关键数据: CNC-A001(P=52,Prog=O0001,Run=3) CNC-006(P=120,Prog=O0002,Run=0)
```
### 8.2 日志级别
| 级别 | 内容 |
|------|------|
| INFO | 每次请求摘要(设备数、耗时) |
| DEBUG | 完整JSON响应默认关闭调试时开启 |
| WARN | 场景切换事件(换零件、断电、恢复等) |
| ERROR | 网络异常模拟触发 |
### 8.3 关键数据摘要格式
```
设备编码(P=零件数,Prog=程序名,Run=运行状态,Status=设备状态)
示例:
CNC-A001(P=53,Prog=O0001,Run=3,St=1) CNC-006(P=120,Prog=O0002,Run=0,St=1)
```
### 8.4 内存环形缓冲
管理界面的日志列表不查文件,从内存环形缓冲读取:
```
RingBuffer<LogEntry>
容量: 200条
每条记录: { timestamp, addressPort, deviceCount, keyData, fullJson, duration }
溢出时覆盖最旧的记录
```
---
## 九、数据流时序
### 9.1 正常流程
```
采集服务 CncSimulator(:9001)
│ │
│ GET / │
│ ───────────────────────────→ │
│ │ ← ScenarioPlayer tick按interval
│ │ ← 每台DeviceSimulator生成当前状态
│ │ ← FanucDataGenerator组装JSON
│ │ ← LogRecorder记录日志
│ HTTP 200 + JSON Array │
│ ←─────────────────────────── │
│ │
等30秒 collect_interval
│ │ ← ScenarioPlayer tick
│ │ ← 数据变化part_count+1等
│ GET / │
│ ───────────────────────────→ │
│ HTTP 200 + 新JSON │
│ ←─────────────────────────── │
```
### 9.2 断电场景
```
Tick N-1: 返回 [CNC-A001, CNC-006, CNC-008] ← 3台设备正常
Tick N: ScenarioPlayer触发 CNC-008 断电
返回 [CNC-A001, CNC-006] ← CNC-008消失
日志: [WARN] CNC-008 断电,从返回数据中移除
采集服务视角:
第N-1次采集: 3台设备CNC-008 last_program_name = O0003
第N次采集: 2台设备CNC-008 不在列表中
→ CNC-008 不更新 last_collect_time状态停留在上次
```
### 9.3 换零件场景
```
Tick N-1: CNC-A001 part_count=52, program_name=O0001
Tick N: ScenarioPlayer触发 CNC-A001 换零件
CNC-A001: program_name=O0002, part_count=0
返回数据中 CNC-A001 的 Tag5="O0002", Tag8="0.00000"
日志: [WARN] CNC-A001 换零件: O0001 → O0002, part_count 52 → 0
采集服务视角:
→ 检测到 program_name 变化
→ 结账旧段(O0001, qty=52)
→ 开新段(O0002, start=0)
```
---
## 十、扩展其他品牌
### 10.1 品牌生成器接口
```csharp
interface IBrandGenerator
{
/// <summary>品牌标识(配置文件用)</summary>
string BrandKey { get; }
/// <summary>根据设备状态生成一个设备的JSON对象</summary>
/// <param name="state">设备当前状态</param>
/// <returns>Newtonsoft.Json.Linq.JObject一个设备对象含device/desc/tags</returns>
JObject GenerateDevice(DeviceState state);
}
```
### 10.2 新增品牌步骤
```
1. 创建 {BrandName}DataGenerator.cs实现 IBrandGenerator
2. 在 SimulatorEngine 中注册新品牌生成器
3. simulator.json 中 brand 字段填新品牌标识
4. 设备状态机共用只是输出JSON格式不同
```
### 10.3 预留扩展
| 品牌 | JSON结构差异 | Tag体系 |
|------|-------------|---------|
| FANUC | device/desc/tags 数组 | Tag5=程序名, Tag8=零件数, ... |
| Siemens | 待确认 | 可能是不同的tag命名 |
| Mitsubishi | 待确认 | 可能是不同的tag命名 |
| 兄弟 | 待确认 | 可能是不同的tag命名 |
不同品牌的差异仅在 `IBrandGenerator.GenerateDevice()` 中处理,状态机、剧本、管理界面完全复用。
---
## 十一、启动与运行
### 11.1 控制台输出
```
CNC 模拟采集服务 v1.0
================================================
加载配置: simulator.json
- 地址1: FANUC-1号模拟 (:9001) 3台设备
- 地址2: FANUC-2号模拟 (:9002) 2台设备
启动服务...
[✓] 总管理页面: http://localhost:9000/admin
[✓] FANUC-1号模拟: http://localhost:9001/ (管理: http://localhost:9001/admin)
[✓] FANUC-2号模拟: http://localhost:9002/ (管理: http://localhost:9002/admin)
按任意键退出...
```
### 11.2 运行时控制台
```
14:30:30 [9001] GET / → 3台设备, 4ms
14:30:30 [9001] 场景: CNC-A001 正常加工(P=53) CNC-006 待机(P=120) CNC-008 断电
14:30:35 [9001] GET / → 3台设备, 3ms
14:30:40 [9001] GET / → 2台设备, 4ms ← CNC-008 断电消失
14:30:45 [9002] GET / → 2台设备, 5ms
...
```
### 11.3 退出
```
按任意键后:
停止所有HttpListener...
保存日志...
已退出。
```
---
## 十二、开发顺序
| 步骤 | 内容 | 可验证结果 |
|------|------|-----------|
| 1 | 项目骨架 + HttpListener + 返回固定JSON | 浏览器访问localhost:9001看到固定JSON |
| 2 | DeviceSimulator 状态机 + ScenarioPlayer | 每次请求返回不同数据part_count递增 |
| 3 | FanucDataGenerator 完整Tag生成 | 返回18个Tag格式与示例一致 |
| 4 | 所有场景实现(断电/换零件/手动清零等) | 剧本自动循环 |
| 5 | 管理界面HTML + AJAX | 浏览器操作启停、查看日志 |
| 6 | 多端口支持 + 总管理页面 | 同时模拟2个地址 |
| 7 | 网络异常模拟 | 手动触发HTTP 500/超时等 |
| 8 | 日志记录(文件+内存缓冲) | 日志文件可查、管理界面可看 |
| 9 | 配置文件读写 | 修改simulator.json后重启生效 |