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

33 KiB

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

{
  "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 设备状态

每台设备维护以下内部状态:

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 返回 {brokenHTTP 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输出示例

[
    {
        "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 品牌生成器接口

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后重启生效