# 发那科采集数据结构变更 - 系统调整方案 > 创建时间:2026-05-11 > 触发原因:发那科系统采集示例文件(根目录 `发那科系统采集示例.txt`)更新,实际FANUC采集数据结构与系统原有设计存在差异 > 涉及模块:CncSimulator模拟器、数据库设计、采集服务设计、后台管理功能 --- ## 一、实际FANUC数据结构(2026-05-11 采自生产环境) ### 1.1 顶层结构 ```json [ { "device": "fanake-1.2_1.2", // 设备编码,匹配 cnc_machine.device_code "desc": "西-1.2", // 设备描述 "tags": [ ... ] // 10个Tag }, ... ] ``` 共32台设备,每台10个Tag。其中25台在线、7台离线。 ### 1.2 Tag清单(10个) | # | Tag ID | desc(中文) | 类型 | 示例值 | 备注 | |---|--------|-------------|------|--------|------| | 1 | `_io_status` | 设备状态 | 数值 | 0.00000 / 1.00000 | 0=离线, 1=在线;**始终有效** | | 2 | `Tag5` | 执行的NC主程序名 | 字符串 | O1, O9001, 1370.NC, 7, 1027, O2, 037.NC | 可为空 | | 3 | `Tag6` | 执行的NC主程序号 | 字符串 | N0, N1, N9, N20 | | | 4 | `Tag7` | 当前加工程序内容 | 字符串 | G代码片段 | 可为空 | | 5 | `Tag8` | 当前加工零件数 | 数值 | 35.00000 | 当前程序的零件计数 | | 6 | `Tag9` | 运行状态 | 数值 | 0.00000 / 1.00000 / 3.00000 | 0=待机, 1=运行, 3=加工中 | | 7 | `Tag11` | 操作模式 | 数值 | 1 / 4 / 5 / 10 | 1=MEM, 4=?, 5=?, 10=JOG | | 8 | `Tag22` | 开机时间 | 数值 | 45129840.00000 | 秒,历史累计 | | 9 | `Tag23` | 运行时间 | 数值 | 12201.00000 | 秒,历史累计 | | 10 | `Tag1` | 加工零件总数 | 数值 | 45930.00000 | **历史终身累计,新增字段** | ### 1.3 数据有效性规则 #### 规则1:Quality字段 | quality值 | 含义 | 处理方式 | |-----------|------|---------| | `"0"` | 数据有效 | 正常解析 | | `"1"` | 数据无效(采集失败/设备离线) | 忽略该tag的value | #### 规则2:Time字段(采集失败标记) | time值 | 含义 | 处理方式 | |--------|------|---------| | `1970-01-01 08:00:00` | 采集失败 | 忽略该tag的value | | 其他正常时间 | 数据时间戳 | 正常使用 | #### 规则3:_io_status 特殊处理 `_io_status` tag **不受规则1和规则2约束**,始终读取value判断设备在线/离线: - 即使设备离线,`_io_status` 的 quality=0、time=真实时间、value=0 #### 规则4:Time与Value独立变化 每个tag的 `time` 和 `value` 更新频率不固定: - time变了、value没变 - value变了、time没变 - 都变了 - 都没变 **采集服务的变更检测必须对比value值,不能依赖time变化作为变更信号。** ### 1.4 离线设备特征 以 fanake-1.10 为例: ``` _io_status: quality=0, value=0.00000, time=2026-05-11 17:06:34 ← 始终有效 Tag5: quality=1, value="", time=1970-01-01 08:00:00 ← 无效 Tag6: quality=1, value="", time=1970-01-01 08:00:00 ← 无效 Tag7: quality=1, value="", time=1970-01-01 08:00:00 ← 无效 Tag8: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效 Tag9: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效 Tag11: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效 Tag22: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效 Tag23: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效 Tag1: quality=1, value=0.00000, time=1970-01-01 08:00:00 ← 无效 ``` --- ## 二、与现有设计的差异对比 ### 2.1 模拟器(FanucDataGenerator)差异 | 差异项 | 现有模拟器(18个Tag) | 实际FANUC(10个Tag) | 影响 | |--------|----------------------|---------------------|------| | Tag2 (当前轴数) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | Tag14 (主轴倍率) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | Tag17 (主轴设定速度) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | Tag18 (进给设定速度) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | Tag19 (主轴实际速度) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | Tag20 (进给实际转速) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | Tag21 (主轴负载) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | Tag24 (切削时间) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | Tag25 (循环时间) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | Tag26 (加工状态) | ✅ 有 | ❌ 不存在 | 模拟器多余 | | **Tag1 (加工零件总数)** | ❌ 缺少 | ✅ 有 | **模拟器缺少,需新增** | | Tag6 desc | "执行的NC主程序号" | "执行的NC主程序号" | 一致 | ### 2.2 数据库设计差异 | 差异项 | 现有设计 | 实际需求 | |--------|---------|---------| | 标准字段 total_part_count | ❌ 无 | ✅ 需要新增,对应Tag1 | | cnc_collect_record.total_part_count | ❌ 无 | ✅ 需要新增列 | | cnc_machine.last_total_part_count | ❌ 无 | ✅ 需要新增列 | ### 2.3 采集服务设计差异 | 差异项 | 现有设计假设 | 实际行为 | |--------|-------------|---------| | 数据有效性判断 | 未明确quality处理 | 需按quality+time双规则过滤 | | _io_status处理 | 未明确特殊性 | 始终有效,不受quality/time约束 | | 变更检测 | 未明确time/value独立性 | 必须对比value,不看time | | Tag1(总零件数) | 无此字段 | 需解析并存储 | ### 2.4 后台管理差异 | 差异项 | 现有设计 | 实际需求 | |--------|---------|---------| | 速度/倍率/负载字段 | 设计了展示 | FANUC设备这些字段为NULL,需处理空值 | | 加工零件总数 | 未设计展示 | 可选展示 | | 操作模式枚举 | 仅定义1=MEM, 10=JOG | 实际还有4、5值,需补充定义 | --- ## 三、调整方案 ### 3.1 CncSimulator 模拟器调整(优先级:高) > 目标:让模拟器生成的数据结构与实际FANUC完全一致 #### 3.1.1 DeviceState.cs 修改 ```diff public class DeviceState { // ===== 固定信息(来自配置) ===== public string DeviceCode { get; set; } public string Desc { get; set; } // ===== 动态状态 ===== public string CurrentScenario { get; set; } = "idle"; public bool IsOnline { get; set; } = true; public string ProgramName { get; set; } = "O0001"; public int PartCount { get; set; } = 0; + /// 历史终身累计零件总数(对应Tag1) + public decimal TotalPartCount { get; set; } = 0; public int DeviceStatus { get; set; } = 1; public int RunStatus { get; set; } = 0; public int OperateMode { get; set; } = 1; - public decimal SpindleSpeedSet { get; set; } = 450; - public decimal FeedSpeedSet { get; set; } = 60; - public decimal SpindleSpeedActual { get; set; } = 0; - public decimal FeedSpeedActual { get; set; } = 0; - public decimal SpindleLoad { get; set; } = 0; - public decimal SpindleOverride { get; set; } = 100; public decimal PowerOnTime { get; set; } = 0; public decimal RunTime { get; set; } = 0; - public decimal CuttingTime { get; set; } = 0; - public decimal CycleTime { get; set; } = 0; - public string MachiningStatus { get; set; } = ""; public string ProgramContent { get; set; } = ""; // ... 其余字段保留不变 } ``` **说明**:移除实际FANUC不存在的字段(SpindleSpeedSet、FeedSpeedSet、SpindleSpeedActual、FeedSpeedActual、SpindleLoad、SpindleOverride、CuttingTime、CycleTime、MachiningStatus),新增TotalPartCount。 #### 3.1.2 FanucDataGenerator.cs 修改 ```diff private JArray GenerateTags(DeviceState state) { var tags = new JArray(); DateTime baseTime = DateTime.Now; // 每个tag的时间基准上随机偏移 -5~0 秒 DateTime TagTime() { return baseTime.AddSeconds(-_rng.Next(0, 6)); } void AddNumericTag(string id, string desc, decimal value) { /* 不变 */ } void AddStringTag(string id, string desc, string value) { /* 不变 */ } - // 18个tag → 10个tag,与实际FANUC完全一致 AddNumericTag("_io_status", "设备状态", state.DeviceStatus); - AddNumericTag("Tag2", "当前轴数", 4); + AddNumericTag("Tag1", "加工零件总数", state.TotalPartCount); AddStringTag("Tag5", "执行的NC主程序名", state.ProgramName); AddStringTag("Tag6", "执行的NC主程序号", "N0"); AddStringTag("Tag7", "当前加工程序内容", /* ... */); AddNumericTag("Tag8", "当前加工零件数", state.PartCount); AddNumericTag("Tag9", "运行状态", state.RunStatus); AddNumericTag("Tag11", "操作模式", state.OperateMode); - AddNumericTag("Tag14", "当前主轴倍率", state.SpindleOverride); - AddNumericTag("Tag17", "主轴设定速度", state.SpindleSpeedSet); - AddNumericTag("Tag18", "进给设定速度", state.FeedSpeedSet); - AddNumericTag("Tag19", "主轴实际速度", state.SpindleSpeedActual); - AddNumericTag("Tag20", "进给实际转速", state.FeedSpeedActual); - AddNumericTag("Tag21", "主轴负载", state.SpindleLoad); AddNumericTag("Tag22", "开机时间", state.PowerOnTime); AddNumericTag("Tag23", "运行时间", state.RunTime); - AddNumericTag("Tag24", "切削时间", state.CuttingTime); - AddNumericTag("Tag25", "循环时间", state.CycleTime); - AddStringTag("Tag26", "加工状态", state.MachiningStatus); return tags; } ``` #### 3.1.3 DeviceSimulator.cs 修改 - 构造函数:新增 `TotalPartCount` 初始值配置(从simulator.json读取) - `machining` 场景:`TotalPartCount++` - `ApplyProgramChange()`:TotalPartCount 不清零(终身累计) - `ApplyManualReset()`:TotalPartCount 不清零 - `ApplyPowerOn()`:TotalPartCount 不清零 - 移除所有已删除字段(SpindleSpeed*、SpindleLoad、SpindleOverride、CuttingTime、CycleTime、MachiningStatus)的模拟逻辑 #### 3.1.4 simulator.json 修改 devices配置中新增 `initialTotalPartCount` 字段: ```json { "deviceCode": "CNC-A001", "desc": "西栋1号", "initialProgram": "O0001", "initialPartCount": 50, "initialTotalPartCount": 45000 } ``` #### 3.1.5 模拟器离线行为 当设备断电(power_off)时,需要生成与实际一致的离线数据: - `_io_status`:quality=0, value=0, time=当前时间 - 其他所有tag:quality=1, value="" 或 "0.00000", time="1970-01-01 08:00:00" --- ### 3.2 数据库设计调整(优先级:高) #### 3.2.1 cnc_brand_field_mapping 标准字段约定 在 `docs/01-数据库设计.md` 的标准字段约定表中新增: | standard_field | 含义 | data_type | |----------------|------|-----------| | **total_part_count** | **历史累计加工零件总数** | **number** | #### 3.2.2 cnc_collect_record 表 新增列: ```sql ALTER TABLE cnc_collect_record ADD COLUMN total_part_count DECIMAL(15,5) NULL COMMENT '历史累计加工零件总数' AFTER part_count; ``` 完整DDL变更位置:在 `part_count` 列之后增加 `total_part_count` 列。 #### 3.2.3 cnc_machine 表 新增列: ```sql ALTER TABLE cnc_machine ADD COLUMN last_total_part_count DECIMAL(15,5) NULL COMMENT '最新历史累计加工零件总数' AFTER last_part_count; ``` #### 3.2.4 FANUC品牌预置映射数据 在 `database/` 预置数据脚本中,FANUC品牌的 field_mapping 新增一行: | brand_id | standard_field | field_name | match_by | data_type | is_required | |----------|---------------|------------|----------|-----------|-------------| | (FANUC) | total_part_count | Tag1 | id | number | 0 | 同时,以下标准字段在实际FANUC中不存在,映射记录应标记 `is_required=0` 或不创建映射(由采集服务按实际数据决定): - spindle_speed_set → 无对应Tag - feed_speed_set → 无对应Tag - spindle_speed_actual → 无对应Tag - feed_speed_actual → 无对应Tag - spindle_load → 无对应Tag - spindle_override → 无对应Tag - cutting_time → 无对应Tag - cycle_time → 无对应Tag - machining_status → 无对应Tag --- ### 3.3 采集服务设计调整(优先级:高) #### 3.3.1 采集循环核心逻辑(伪代码) ``` 每次采集循环(按采集地址配置的间隔): 1. Ping目标地址 - Ping失败 → 更新 cnc_collect_address.fail_count++ - 连续5次失败 → 写告警 - 跳过本次采集 2. HTTP GET 拉取JSON数组 3. 原始JSON存日志库(cnc_raw_log,每次都存,不管有没有变化) 4. 逐设备处理JSON数组中的每个元素: 4.1 用 device 值匹配 cnc_machine.device_code - 未匹配到 → 记录告警"发现未知设备",跳过 4.2 解析 _io_status(特殊处理): → 直接读取 value,不管 quality 和 time → value=1 → 设备在线 → value=0 → 设备离线 4.3 解析其他tag(标准处理): for each tag in tags (排除 _io_status): if tag.quality != "0" → 跳过(数据无效) if tag.time 以 "1970-01-01" 开头 → 跳过(采集失败) 否则 → 按 brand_field_mapping 映射为标准字段,解析 value 4.4 写入结构化采集记录: → INSERT INTO cnc_collect_record (每台设备每次采集一条) → total_part_count 列写入 Tag1 的值(如果有效) → 速度/倍率/负载等字段写 NULL(FANUC无此数据) 4.5 业务变更检测(只对比 value,不看 time): a. NC程序名变更检测: if 新Tag5.value != 旧 cnc_machine.last_program_name: → 结账当前产量段 (close_reason='program_change') → 开新产量段 b. 零件数下降检测(手动清零): if 新Tag8.value < 旧 cnc_machine.last_part_count AND 新Tag5.value == 旧 cnc_machine.last_program_name: → 结账当前产量段 (close_reason='manual_reset') → 开新产量段,part_count 从新值开始 c. 运行状态变更: if 新Tag9.value != 旧 cnc_machine.last_run_status: → 仅更新实时状态,不触发产量段逻辑 d. 总零件数:仅记录,不触发任何业务逻辑 4.6 更新机床实时状态(cnc_machine 的 last_* 字段): → last_program_name = Tag5.value → last_part_count = Tag8.value → last_total_part_count = Tag1.value → last_run_status = Tag9.value → last_operate_mode = Tag11.value → last_device_status = _io_status.value → is_online = (_io_status.value == 1) → last_collect_time = 当前服务器时间 5. 更新采集地址状态: → last_collect_time = 当前时间 → last_collect_status = 'success' → fail_count = 0 ``` #### 3.3.2 数据有效性判断流程图 ``` 收到一个tag │ ├── tag.id == "_io_status" ? │ ├── YES → 始终有效,直接读 value │ └── NO → 进入标准判断 ↓ │ ├── tag.quality != "0" ? │ ├── YES → 无效,跳过 │ └── NO → 继续判断 ↓ │ └── tag.time 以 "1970-01-01" 开头 ? ├── YES → 无效,跳过 └── NO → 有效,解析 tag.value ``` --- ### 3.4 后台管理功能调整(优先级:中) #### 3.4.1 设备状态展示 `cnc_machine` 表已有 `last_device_status`、`last_run_status` 等字段,展示逻辑不变。新增: - 设备详情中可选展示"累计加工总数"(来自 `last_total_part_count`) #### 3.4.2 大屏看板空值处理 FANUC设备的以下字段恒为NULL: - 主轴设定速度、进给设定速度 - 主轴实际速度、进给实际速度 - 主轴负载、主轴倍率 - 切削时间、循环时间 - 加工状态 大屏如果展示这些指标: - 数值型 → 显示 `--` 或 `N/A` - 图表型 → 该FANUC设备的线不画 #### 3.4.3 品牌字段映射管理 映射配置页面需支持新增的 `total_part_count` 标准字段。FANUC品牌预置映射中需包含 `Tag1 → total_part_count`。 #### 3.4.4 前端Mock数据同步 前端Mock数据中与FANUC相关的设备状态Mock,应同步为10个Tag的字段集(移除速度/负载等,新增total_part_count)。 --- ### 3.5 设计文档更新(优先级:中) | 文档 | 更新内容 | |------|---------| | `docs/01-数据库设计.md` | 标准字段约定表增加 total_part_count;cnc_collect_record 和 cnc_machine 的DDL增加新列 | | `docs/00-需求与设计文档.md` | 4.2数据格式章节补充quality/1970-time/Tag1说明 | | `docs/03-API接口设计.md` | 如果有涉及采集记录字段的API响应格式,需同步增加 total_part_count | | `database/` | ALTER TABLE脚本 + FANUC预置映射数据新增 | --- ## 四、不需要调整的部分 | 项目 | 原因 | |------|------| | 顶层JSON结构 `[{device, desc, tags}]` | 实际数据与设计一致 | | device字段匹配机床逻辑 | 设计正确,device值匹配device_code | | 零件分段计数核心逻辑(基于Tag8) | 不受影响,仍基于NC程序名切换+清零 | | 产量分段记录表 cnc_production_segment | 结构不变,total_part_count不影响分段计算 | | 采集地址/品牌管理配置架构 | 不变 | | 双库分离设计(原始JSON存日志库) | 不变 | | 采集间隔配置(地址级统一间隔) | 不变 | | 日终汇总逻辑 | 不变 | --- ## 五、执行顺序 ``` 阶段1:模拟器适配(让测试数据与实际一致) ├── 1.1 DeviceState.cs 新增 TotalPartCount,移除多余字段 ├── 1.2 FanucDataGenerator.cs 改为10个Tag ├── 1.3 DeviceSimulator.cs 调整场景逻辑 ├── 1.4 simulator.json 新增 initialTotalPartCount └── 1.5 编译验证 阶段2:数据库变更(数据基础) ├── 2.1 cnc_collect_record 增加 total_part_count 列 ├── 2.2 cnc_machine 增加 last_total_part_count 列 ├── 2.3 标准字段约定表增加 total_part_count ├── 2.4 FANUC预置映射数据增加 Tag1 映射 └── 2.5 设计文档同步更新 阶段3:采集服务设计定稿(核心逻辑) ├── 3.1 编写采集服务数据解析逻辑(含quality/time过滤) ├── 3.2 编写变更检测逻辑(只看value不看time) └── 3.3 编写Tag1存储逻辑(total_part_count仅记录不触发业务) 阶段4:后台管理适配(展示层) ├── 4.1 大屏空值处理(FANUC设备速度/负载为NULL时显示N/A) ├── 4.2 前端Mock数据同步为10个Tag结构 └── 4.3 品牌映射管理支持total_part_count字段 ``` --- ## 六、补充说明 ### 6.1 操作模式枚举值 实际数据中出现了以下操作模式值,具体含义需与现场确认: | 值 | 已知含义 | 出现设备 | |----|---------|---------| | 1 | MEM(内存运行) | 加工中的设备 | | 4 | 待确认 | fanake-1.19(运行状态=1) | | 5 | 待确认 | fanake-1.24(运行状态=0) | | 10 | JOG(手动) | 待机中的设备 | ### 6.2 device字段格式 实际数据格式为 `fanake-1.2_1.2`(品牌-编号_编号),机床表的 `device_code` 需按此格式录入。 ### 6.3 数值精度 所有数值型Tag的value都带5位小数(如 `35.00000`),采集服务解析时需 `Convert.ToDecimal` 后存储到 `DECIMAL(15,5)` 列。 ### 6.4 time字段格式 统一格式 `yyyy-MM-dd HH:mm:ss`,需要按此格式解析。设备离线时为 `1970-01-01 08:00:00`(UTC+8时区下的epoch时间)。