mcpField
I
Industrial-AIOps

Industrial-AIOps

本地服务
github.com/industrial-aiops数据与分析81 周前更新

关于 Industrial-AIOps

Industrial-AIOps 面向工厂团队和 AI Agent,是一款以读取为主的 OT 数据接入工具。它读取 15 种现场协议并跨协议关联信号,提供带证据引用的根因分析,同时对调用实施审计和变更管控。

主要功能

  1. 01读取 15 种现场协议
  2. 02跨协议根因分析
  3. 03告警、数据流与 OEE 分析
  4. 04审计、预算与风险分级
  5. 05以读取为主,写入受控
  6. 06无需大模型的确定性分析

适用场景

  1. 01调查生产线停机原因
  2. 02排查告警风暴和数据流故障
  3. 03根据采集历史测量 OEE
  4. 04解读旧式 PLC 程序

MCP 相关

MCP 服务向 AI 客户端提供 OT 数据访问和分析工具,并与 CLI 共用调用审计和风险管控机制。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"industrial-aiops-iaiops": {
"command": "uvx",
"args": [
"iaiops"
],
"env": {
"IAIOPS_MCP": "<IAIOPS_MCP>",
"IAIOPS_CONFIG": "<IAIOPS_CONFIG>",
"IAIOPS_MASTER_PASSWORD": "<IAIOPS_MASTER_PASSWORD>",
"IAIOPS_NO_EGRESS": "<IAIOPS_NO_EGRESS>"
}
}
}
}
启动命令uvx iaiops
需要配置的环境变量
IAIOPS_MCPTool-menu selection: comma-separated protocols or a named profile (e.g. 'fab', 'process', 'building', 'opcua,modbus'). No default since 0.10.0 — unset prints the selection menu and exits; set to a profile name (e.g. factory) or comma-list of protocols.
IAIOPS_CONFIGPath to the endpoints config YAML (default: ~/.iaiops/config.yaml).
IAIOPS_MASTER_PASSWORD密钥Master password unlocking the encrypted secret store (~/.iaiops/secrets.enc).
IAIOPS_NO_EGRESSSet to 1 to withhold every data-shipping tool (message bus, external historian, remote LLM) from list_tools() at registration time, for an airgap/sealed-box deployment. This is a data-exfiltration axis only, not read/write authorisation — whether a write is allowed is the caller's decision, and every call (read or write) is audited. Gates MCP tools only — not a firewall.

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
pypiiaiops0.32.0

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

Industrial-AIOps

English · 中文

询问 AI Agent 生产线为何停机,就能得到附有证据引用的答案。

面向工厂现场、厂商中立、只读优先的数据采集工具。它支持 15 种现场协议, 能关联跨协议读取的数据,并向你的 Agent 提供附有证据引用的判断,而不是猜测。每次调用都会经过审计,且任何读取都不会向外发送数据。

pip install "iaiops[opcua]"      # pick your protocol — or [all]
iaiops init                      # write ~/.iaiops/config.yaml
iaiops doctor                    # check the setup before you trust it

更倾向于使用容器?已发布的镜像经过 cosign 签名,并以 非 root 用户运行。它通过 stdio 使用 MCP, 因此请保持 stdin 打开,并挂载卷来存储审计数据:

cosign verify --key deploy/margo/cosign.pub ghcr.io/industrial-aiops/iaiops:0.32.0-factory
docker run -i --rm -v iaiops-state:/home/iaiops/.iaiops \
  ghcr.io/industrial-aiops/iaiops:0.32.0-factory

对于经过安全加固或隔离网部署(只读 rootfs、cap_drop: ALL、no-new-privileges、 可选的本机 LLM),请使用 deploy/margo/compose.yaml 和 deploy/airgap/。分析引擎不需要 GPU,也不需要模型 API——它是确定性的;LLM 为可选项,只负责为判断润色表述。

你将获得什么

读取OPC-UA(+ Historical Access、标签自动发现)· Modbus TCP/RTU · S7comm · Mitsubishi MC · Omron FINS · MTConnect · MQTT/Sparkplug B · EtherNet/IP · EtherCAT · PROFINET · SECS/GEM · HART-IP · BACnet/IP · IO-Link · 串行 ASCII 仪器(RS-232/485、串行设备服务器)——以及面向 BAS 监控系统(Metasys / Niagara)和 Ignition Gateway 的只读 REST 层
分析停机根因(旗舰级副驾驶工具)· 警报洪流(ISA-18.2)· 数据流中断· 数据可信度· OEE· 资产清单· 旧版 PLC 程序说明器(ST/AWL/L5X)
治理审计 · 预算 · 风险等级 · 撤销——通过同一引擎,在 MCP 和 CLI 的每次调用中执行
保持数据自主无遥测、无电话回传。六个工具按设计可能会将数据发送到本机之外(stream_publish、stream_publish_event、uns_publish、historian_push、mqtt_publish、rca_narrate)——设置 IAIOPS_NO_EGRESS=1 可禁用这六个工具,满足隔离网部署要求

此软件包包含十一个版本——十个面向各行业(fab · factory · process · building · water · warehouse · clinical · pharma · renewables · plcnext),每个版本都增加了专属的只读咨询检查; 另有一个跨协议版本:sensor(连接、解码并读取传感器;IAIOPS_MCP=sensor)。 变电站 / 公用事业遥控(IEC-104 · DNP3 · IEC-61850)作为独立项目发布: iaiops-energy。

最初五分钟

四条命令。只有一条会接触设备:doctor 会探测每个已配置的端点(iaiops doctor --skip-probe 不会联系任何对象)。

pip install "iaiops[modbus]"     # pick the protocol you actually have, or [all]
iaiops onboard status            # ← run this first. Contacts NOTHING.
iaiops doctor                    # config, secrets, reachability — and the version
iaiops readiness                 # every scenario, and what each gap needs

onboard status 会先回答你更关心的小问题:此站点目前处于哪条路径上——直接读取设备,还是订阅数据已流经的 UNS broker?目前进行到哪一步?推进到下一步需要运行哪条命令? 它每次都会根据你的存储和 config.yaml 推导结果。它唯一保留的是命名空间审计结论,而且该结论只适用于执行审计时使用的 broker 和主题筛选条件,并且始终显示其时间——因此,即使你手动编辑 config.yaml,答案仍然可靠。

readiness 会读取你的配置和本地存储,并回答一个问题:此站点今天可以运行哪些场景,每个缺口需要什么? 每个缺口都会附上补齐它的命令,并按解锁能力的多少排序。不需要 Agent、不需要云端、不需要账户,也不会发送任何数据。

接下来是关键步骤的顺序——先调查现状,再进行有限采样,最后才做解释:

会接触设备吗?
调查iaiops scan plan → iaiops scan run预览不会发送任何内容;运行时会逐项列出发送的每种数据包类型
配置iaiops onboard draft → 你将其合并到 config.yaml不会——它会读取已存储的扫描结果,不会写入任何内容
采集iaiops collect run line1 --duration 7d会——并报告它看到了什么,以及遗漏了什么
声明iaiops tags export → 由人员填写 role → iaiops tags apply --by <you>不会——role 列会有意留空
解释iaiops oee measure --since … --until … · iaiops investigate open · iaiops diag rca不会——所有分析都基于已采集的历史数据

大约两分钟即可查看完整流程如何在真实设备上运行,其中包括一次真实的运行中断。运行 ./demo/oee-line/run_demo.sh 即可——无需硬件或配置,也不会在临时目录之外写入任何内容。 demo/oee-line/ 说明了每一步的用途,以及这些数字能够和不能够说明什么。

为什么优先读取

OT 正是最需要严格约束 Agent 的领域。读取路径就是产品本身;少数写入路径对 OT 有危险,默认关闭,并受 MOC 规程约束——试运行、单次审批、撤销记录、哈希链审计。

证明分析无需模型

分析层无法访问大模型。这是一道防护措施,而非一句口号: tests/test_brain_is_llm_free.py 会扫描八个包——brain、discovery、runtime、 readiness、collect、knowledge、retain、connectors——检查是否存在任何可能访问大模型的导入, 扫描结果为空就是保证。模型仅在两个地方使用,而且都不是关键环节:rca_narrate 会改写已经计算并引用过的结论,Agent 前端则决定要调用哪个工具。移除这两者,得到的数字仍然完全相同。

这道防护是静态的——它只能证明不可能调用大模型。对验证团队来说,他们需要接受的说法必须经实际执行验证,因此我们会实际执行:

iaiops verify determinism --out determinism-record.json

固定在仓库中的数据集会依次经过可用性、生产计数、Six Big Losses、ISA-18.2 报警负载、控制图、保守基线和 RCA 副驾驶。每项结果都会经过规范编码并生成摘要;测试套件会在当前进程中运行两次,并在两个新启动的解释器中各运行一次,这两个解释器使用不同的 PYTHONHASHSEED 值——这是为了捕获集合或字典的迭代顺序影响结果,而单次运行永远无法发现这一点。整个过程中,套接字 API 都会抛出异常,因此任何试图访问设备或主机名的计算都会在此处失败,不会在恰好联网的机器上悄然成功。之后,测试会检查运行期间引入了哪些内容:对于已经加载的大模型库(MCP 服务会为选择启用的叙述工具加载 iaiops.core.llm),我们会记录而不判定——只有测试套件自身导入的内容才会导致失败。

记录会将 result(每次运行都相同——需要签署的部分)与 context(本次运行的时间和地点)分开。两次成功运行生成的记录并非逐字节相同,而且有人会对它们进行差异比较,因此我们会将两部分分别命名,而非混在一起。

这是让主张具备可用性的形式:不是“我们的模型很准确”——这在 GxP 场景中并非证据——而是一个可以写入 IQ/OQ 方案、执行并签署的测试用例:移除模型、阻断网络、重新运行标准数据集、比较哈希值。verify_determinism 是从 MCP 侧执行的同一项检查;iaiops verify suite 会列出检查覆盖的内容,但不会运行检查。

实际验证到什么程度

简而言之:已针对真实协议库、容器和进程内服务器完成验证—— 但尚未针对真实工业设备完成验证。 我们评估的是证据,而不是笼统地说“已测试”,因为真实容器往返通信与合成测试样例并不能证明同一件事。

阶段谁来判定我们的帧当前处于此阶段的协议
2a — 与第三方服务器进行真实线路通信他人编写的规范实现OPC-UA(Microsoft opc-plc;双向执行证书信任验证)· Modbus TCP(pymodbus)· Modbus RTU(通过 socat PTY 使用 pymodbus)· MQTT / Sparkplug B(真实 mosquitto 代理)· BACnet/IP(bacpypes3;同一子网中的两个 IP)· MTConnect(研究所自有的 cppagent)· SECS/GEM(secsgem 设备端)· EtherNet/IP(识别)
2b — 与我们根据规范编写的服务器进行真实线路通信第三方客户端解析我们的帧S7comm(pyS7)· Mitsubishi MC(pymcprotocol)· EtherNet/IP(标签层 — pycomm3,全部三条驱动路径)· PROFINET-DCP
2c — 使用真实线路,但两端都由我们编写没有独立判定方Omron FINS · IO-Link · HART-IP(编解码器本身处于阶段 1,针对 hart-protocol 验证)· 串口 ASCII(我们自有的称重模拟器)
仅模拟测试 — 已测试协议逻辑,未进行线路通信—EtherCAT · BAS(Metasys / Niagara)· Ignition 网关
3 — 真实物理设备 / 厂商设备设备本身所有协议均为零

每一行的详细信息,以及每种协议尚未覆盖的内容,请参阅 docs/VERIFICATION-RECORD.md。该文件是正式记录;此表是其摘要,如果两者不一致,测试就会失败。

每种协议的证据——包括每项测试未覆盖的内容——都记录在 docs/VERIFICATION-RECORD.md 中,每种协议各占一行,并注明支撑每项声明的测试。每个 待核实 项都受硬件条件限制,并非被遗忘——每项都列出了能够验证它的设备。

🧪 测试与共创 / Beta 测试与共创

我们正在寻找现场测试伙伴。 软件可验证的内容我们都已验证(真实的进程内服务器、真实协议库、Docker 容器 loopback)——剩下的 待核实 清单只有真实设备才能给出答案:物理 Modbus-RTU(RS-485)、EtherCAT 从站、HART 网关、在线 BACnet 楼宇设备、在线 Metasys/Niagara BAS 控制器、在线 Ignition 网关、国产 PLC(汇川/信捷)、真机 PLCnext、真实变电站 RTU/IED、欧姆龙 FINS 真机、IO-Link 主站。如果你是 OT 工程师、系统集成商或工厂团队,手头有任何这类设备:安装 iaiops,对你的设备运行 iaiops doctor,并把结果告诉我们。经你验证的设备会署名列入支持矩阵;现场反馈的问题会优先分诊;功能可通过 GitHub Issues/Discussions 直接共创。

我们正在寻找现场测试伙伴。 所有可通过软件验证的内容均已验证;剩下的诚实 待核实 清单只有真实设备才能给出答案——物理 Modbus-RTU(RS-485)、EtherCAT 从站、HART 网关、在线 BACnet HVAC、在线 Metasys/Niagara BAS 控制器、在线 Ignition 网关、国产 PLC(汇川/信捷)、真机 PLCnext、变电站 RTU/IED、欧姆龙 FINS 真机 PLC、IO-Link 主站。如果你是 OT 工程师、集成商或工厂团队,能够接触到这些设备中的任何一种:安装 iaiops,对你的设备运行 iaiops doctor,并告诉我们结果。经验证的设备报告会在支持矩阵中标注贡献者;现场报告的问题会得到快速分诊;功能将通过 GitHub Issues/Discussions 以开放方式共同设计。

👉 参与入口 | 从这里开始:提交 issue,标题中注明协议和设备型号,或发送邮件至 [email protected]。 两种方式都会联系到真人,报告也会根据当前版本得到回复。


汇总能力矩阵

协议工具操作R/W风险等级返回(关键字段)
OPC-UAopcua_server_info服务状态Rlowstate, product_name, namespaces
OPC-UAopcua_browse浏览节点树Rlow[{node_id, browse_name, depth}]
OPC-UAopcua_read_node读取单个节点Rlowvalue, datatype, source_timestamp, good
OPC-UAopcua_read_many批量读取Rlow[{node_id, value, ...}]
OPC-UAopcua_subscribe_sample有界采样Rlow{collected, samples[]}
OPC-UAopcua_read_alarms告警展示Rlow{active_alarms[], active_count}
OPC-UAopcua_read_history历史访问(HDA)Rlow{supported, count, values[]}
OPC-UAopcua_diagnose_connection连接问题分诊Rlow{verdict, checks[]}
OPC-UAopcua_discover_tags标签自动发现 → 语义资产模型Rlow{tag_count, assets[], naming_report}
OPC-UAopcua_health_summary阈值分类(原为 health_summary¹)Rlow{overall, counts, offenders[]}
OPC-UAopcua_anomaly_scan标准差异常值(原为 anomaly_scan¹)Rlow{mean, stddev, outliers[]}
Modbusmodbus_read_holdingFC03Rlow{raw_registers, decoded[]}
Modbusmodbus_read_inputFC04Rlow{raw_registers, decoded[]}
Modbusmodbus_read_coilsFC01Rlow{bits[]}
Modbusmodbus_read_discreteFC02Rlow{bits[]}
Modbusmodbus_detect_byte_order字节/字序自动检测Rlow{best_order, candidates[]}
Modbusmodbus_list_templates厂商寄存器模板Rlow{templates[]}
Modbusmodbus_apply_template使用模板解码数据块Rlow{values:{name: engineering_value}}
Modbusmodbus_health_summary阈值分类Rlow{overall, counts, offenders[]}
S7comms7_cpu_infoCPU 标识 + 运行/停止状态Rlow{cpu_status, cpu_info}
S7comms7_read_area读取 DB/M/I/QRlow{items:[{address, value}]}
S7comms7_read_db读取数据块Rlow{items:[{address, value}]}
S7comms7_read_many批量读取地址Rlow{items:[{address, value}]}
S7comms7_write_db写入数据块Whigh/MOC{before, written, _undo_id}
Mitsubishi MCmc_cpu_statusCPU 类型Rlow{cpu_type, cpu_code}
Mitsubishi MCmc_read_words字设备Rlow{words[]}
Mitsubishi MCmc_read_bits位设备Rlow{bits[]}
Mitsubishi MCmc_read_many随机读取Rlow{words[], dwords[]}
Mitsubishi MCmc_write_words写入字Whigh/MOC{before, written, _undo_id}
Omron FINSfins_cpu_info控制器数据读取(0501)Rlow{controller_model, controller_version}
Omron FINSfins_cpu_status控制器状态(0601)Rlow{run_mode, status}
Omron FINSfins_read_words存储区字读取(DM/CIO/W/H/A/EM)Rlow{words[]}
Omron FINSfins_read_bits存储区位读取Rlow{bits[]}
Omron FINSfins_read_many批量读取Rlow{items[]}
Omron FINSfins_write_words存储区写入Whigh/MOC{before, written, _undo_id}
MTConnectmtconnect_probe设备模型Rlow{devices:[{components:[{data_items}]}]}
MTConnectmtconnect_current最新值Rlow{observations[]}
MTConnectmtconnect_sample有界数据流Rlow{observations[]}
MTConnectmtconnect_assets资产Rlow{assets[]}
MTConnectmtconnect_oee_snapshotOEE 输入Rlow{availability, execution, verdict}
MQTT/Sparkplugmqtt_read_topic有界读取Rlow{messages:[{topic, payload}]}
MQTT/Sparkplugsparkplug_subscribe_sample有界 SpB 采样(完整解码)Rlow{samples:[{sparkplug, payload:{metrics[]}}], seq_gap_count}
MQTT/Sparkplugsparkplug_decode_payload解码原始 SpB 负载Rlow{metrics:[{name, alias, datatype, value, is_historical}]}
MQTT/Sparkplugsparkplug_node_list节点发现 + 状态Rlow{nodes:[{group_id, edge_node_id, online, devices}], primary_hosts[]}
MQTT/Sparkpluguns_browse浏览主题树Rlow{topics[], tree{}}
MQTT/Sparkpluguns_topic_auditUNS 命名 + 泛滥治理Rlow{verdict, sprawl_findings, findings{casing_collisions[], scattered_leaves[], …}}
MQTT/Sparkpluguns_schema_driftSparkplug 架构漂移(基线与当前状态对比)Rlow{verdict (none/additive/breaking), node_changes[]}
MQTT/Sparkpluguns_live_audit实时 UNS 审计(有界代理采样)Rlow{verdict, findings{}}
MQTT/Sparkplugsparkplug_live_schema实时 NBIRTH 架构快照Rlow{nodes[], metrics[]}
MQTT/Sparkpluguns_live_drift实时漂移与存储基线对比Rlow{verdict, node_changes[]}
MQTT/Sparkplugmqtt_publish发布/命令Whigh/MOC{published_bytes, applied}
EtherNet/IPeip_controller_infoLogix 控制器标识Rlow{controller:{vendor, product_name, revision, serial}}
EtherNet/IPeip_list_tags标签发现Rlow{tag_count, tags:[{name, data_type, structure}]}
EtherNet/IPeip_read_tag读取单个标签/数组Rlow{tag, value, type, good}
EtherNet/IPeip_read_many批量读取Rlow{items:[{tag, value, type}]}
EtherNet/IPeip_write_tag写入标签Whigh/MOC{before, written, _undo_id}
诊断diagnose_dataflow定位无数据问题Rlow{verdict, diagnosis, hops[]}
诊断alarm_bad_actorsISA-18.2 告警泛滥Rlow{flood_verdict, top_offenders[]}
诊断tag_health问题对象排名Rlow{overall, offenders[]}
诊断historian_health间隙/平线检测Rlow{verdict, gaps[]}
诊断subscription_health有序数据流丢失/重排/过载Rlow{verdict, missed_count, overloaded_channels[]}
诊断downtime_root_causeAI 停机根因分析副驾驶(附引用,仅供参考)Rlow{verdict, primary_cause, hypotheses:[{cause, confidence, evidence[]}]}
诊断downtime_root_cause_live自行收集实时证据的根因分析副驾驶Rlow{…downtime_root_cause…, collected_evidence}
诊断learn_cause_weights根据已标注事件学习各站点的 RCA 原因权重Rlow{cause_weights{}, rationale}
诊断data_quality_scorecard设备群数据可信度汇总Rlow{fleet_score, fleet_status, issue_breakdown, worst_tags[], endpoints[]}
诊断data_quality_fleet_rollup跨端点设备群视图Rlow{fleet_score, endpoints[]}
诊断heartbeat_health心跳/看门狗存活状态Rlow{alive, distinct_transitions, longest_stall_s, reason}
告警(ISA-18.2)alarm_flood_analysis泛滥事件/抖动/陈旧告警/摘要Rlow{episodes[], chattering[], stale[], summary{}}
告警(ISA-18.2)alarm_rationalization_worksheet可导出为 CSV 的合理化条目Rlow{rows[], csv_path?}
基线baseline_learn保守的变更日志基线(历史数据不足时拒绝处理)Rlow{band{p1,p99,median,mad} | insufficient_data}
基线baseline_check默认静默的违规检查Rlow{status, violations[] (附引用)}
基线baseline_record_change记录操作员变更(重新开始学习)Rlow{recorded, change_point}
基线baseline_status无基线/学习中/正常/违规Rlow{status, window}
历史数据库historian_query从 sqlite/TDengine/IoTDB 读取历史数据Rlow{rows[], truncated}
历史数据库historian_coverage每个标签的行数 + 首末时间戳Rlow{tags:[{tag, rows, first, last}]}
PLC 程序plc_program_outline导出 ST/AWL/L5X 程序的结构Rlow{blocks[], call_graph, timers[]}
PLC 程序plc_program_xref符号/地址交叉引用(附引用行)Rlow{sites:[{kind, source_file, line, quote}]}
PLC 程序plc_program_xref_config程序地址 ↔ 一个端点的已配置点位,仅精确地址匹配(绝不按名称匹配);可选实时读取匹配点位Rlow{comparable, matched, in_export_not_configured, configured_not_in_export, unmatchable}
PLC 程序plc_program_section一个命名代码块的源代码(≤200 行)Rlow{text, source_file}
导出export_data将本地存储导出为 CSV/SQLite/ParquetRlow{path, row_count, preview[]}
分析oee_computeOEE = A×P×QRlow{availability, performance, quality, oee, oee_pct}
分析downtime_events检测并分类停机Rlow{event_count, total_downtime_s, by_category, events[]}
分析oee_multidim按机器 × 零件 × 班次分析 OEERlow{matrix[], worst_performers[], mean_oee}
分析asset_inventory活跃指纹Rlow{assets:[{protocol, vendor, model, firmware, reachable}]}
分析cross_protocol_asset_model将发现的标签合并到一个资产模型中Rlow{assets[], tag_count}
分析adopt_alias_map / diff_alias_map标签别名映射采用/差异比较Rlow{aliases{}, changes[]}
分析monitor_changes有界的值变更监测Rlow{change_count, changes:[{value, previous, wall_clock}]}
EtherCATethercat_master_state主站/WKC + 从站数量Rlow{master_state, expected_working_counter, slaves_found, slaves_expected}
EtherCATethercat_slaves总线扫描Rlow{slave_count, slaves:[{index, name, vendor_id, product_code, state}]}
EtherCATethercat_slave_info从站详情Rlow{sync_managers[], fmmus[], object_dictionary[], input_bytes}
EtherCATethercat_read_sdoCoE SDO 上传Rlow{index, byte_length, hex, as_uint}
EtherCATethercat_read_pdo输入 PDO 快照Rlow{working_counter, input_hex, input_byte_length}
EtherCATethercat_write_sdoCoE SDO 下载Whigh/MOC{before, written, applied}
EtherCATethercat_set_stateAL 状态转换Whigh/MOC{before, requested, reached, applied}
PROFINETprofinet_discoverDCP IdentifyAll(整个网段)Rlow{station_count, stations:[{name_of_station, mac, ip, vendor_id, device_roles[]}]}
PROFINETprofinet_identify_station按站名识别Rlow{found, name_of_station, mac, ip, device_family}
PROFINETprofinet_station_params定向 DCP Get(按 MAC)Rlow{found, name_of_station, ip, netmask, gateway}
PROFINETprofinet_asset_inventoryDCP 资产登记Rlow{asset_count, io_controller_count, assets[]}
PROFINETprofinet_dcp_setDCP Set(站名/IP 套件)Whigh/MOC{before, applied, _undo_id}
SECS/GEMsecsgem_equipment_statusGEM 链路 + 标识(S1F1/F2)Rlow{communication_state, are_you_there}
SECS/GEMsecsgem_list_status_variablesSVID 名称列表(S1F11/F12)Rlow{count, status_variables[]}
SECS/GEMsecsgem_read_status_variablesSVID 值(S1F3/F4)Rlow{svids, values[]}
SECS/GEMsecsgem_list_equipment_constantsECID 名称列表(S2F29/F30)Rlow{count, equipment_constants[]}
SECS/GEMsecsgem_read_equipment_constantsECID 值(S2F13/F14)Rlow{ecids, values[]}
SECS/GEMsecsgem_list_alarms告警列表(S5F5/F6)Rlow{count, alarms[]}
SECS/GEMsecsgem_list_process_programsPPID 目录(S7F19/F20)Rlow{count, process_programs[]}
BACnet(楼宇)bacnet_discoverWho-Is 设备发现Rlow{device_count, devices:[{device_id, address}]}
BACnet(楼宇)bacnet_object_list设备的对象Rlow{object_count, objects:[{object_type, instance}]}
BACnet(楼宇)bacnet_read_property单个对象属性Rlow{object_type, instance, property, value}
BACnet(楼宇)bacnet_read_points所有当前值(HVAC 快照)Rlow{point_count, points:[{object_type, instance, present_value}]}
BACnet(楼宇)bacnet_cov_subscribe有界 COV 捕获(始终取消订阅)Rlow{notifications[], terminated_reason}
BACnet(楼宇)bacnet_read_trend_logTrendLog readRange(有界)Rlow{records:[{timestamp, value}]}
BACnet(楼宇)bacnet_write_property当前值写入(优先级)Whigh/MOC{before, written, _undo_id}
HART-IP(过程)hart_device_identity命令 0 标识Rlow{manufacturer, device_type, revision}
HART-IP(过程)hart_primary_variable命令 1 PVRlow{value, unit}
HART-IP(过程)hart_dynamic_variables命令 3 PV/SV/TV/QV + 回路电流Rlow{variables[], loop_current}
HART-IP(过程)hart_burst_sample有界突发变量采样Rlow{samples[]}
IO-Linkiolink_master_info主站标识Rlow{vendor, product, serial}
IO-Linkiolink_ports≤32 端口扫描(模式/状态/设备 ID)Rlow{ports[]}
IO-Linkiolink_device_info单端口设备标识Rlow{vendor_id, device_id, product_name}
IO-Linkiolink_read_pdin过程数据输入(原始十六进制 + 字节)Rlow{hex, bytes[]}
IO-Linkiolink_read_isduISDU 非周期参数读取Rlow{index, subindex, value}
IO-Linkiolink_scan主站 + 所有已连接设备Rlow{master{}, devices[]}
串行 ASCIIserial_sniff从串行仪器读取原始行(不发送任何内容)Rlow{endpoint, seconds, lines[], note}
传感器(IAIOPS_MCP=sensor)sensor_import_iodd根据 IODD 起草配置文件(待核实;返回文本,不写入任何内容;仅读取 Agent 读取根目录下的内容——参见 安全与治理)Rlow{draft, profile_id, device_name, notes[]}
传感器(IAIOPS_MCP=sensor)sensor_profile_check验证配置文件并列出其通道(仅读取 Agent 读取根目录下的内容)Rlow{id, protocol, verified, channels:[{channel, layout, unit, signal, band}]}
传感器(IAIOPS_MCP=sensor)sensor_read_channel读取单个点位一次;分别报告可访问性/可解码性Rlow{levels:{reachable, decodable}, value?, unit?, band?, verified?}
传感器(IAIOPS_MCP=sensor)sensor_profile_list列出随附的配置文件库(除非条目包含来源信息,否则均为 待核实)Rlow{profiles:[{id, protocol, verified, caveat}]}
传感器(IAIOPS_MCP=sensor)sensor_profile_show单个库内配置文件:摘要 + YAML 文本(不写入任何内容)Rlow{summary, yaml, next}
BAS(Metasys/Niagara)bas_point_list监控点目录Rlow{point_count, points:[{id, name, type}]}
BAS(Metasys/Niagara)bas_point_read读取单个监控点Rlow{point, value, unit, status}
BAS(Metasys/Niagara)bas_alarm_list控制器活动告警Rlow{alarm_count, alarms:[{id, priority, state}]}
BAS(Metasys/Niagara)bas_trend_read趋势/历史采样(有界)Rlow{records:[{timestamp, value}]}
BAS(Metasys/Niagara)bas_command监控命令(默认关闭;生命安全对象拒绝列表会在任何 I/O 前拒绝消防/烟雾/疏散/加压对象)Whigh/MOC{before, written, _undo_id}
Ignitionignition_gateway_statusGateway + 模块健康状态Rlow{state, version, modules:[{name, state}]}
Ignitionignition_tag_browse浏览标签树Rlow{tags[], tree{}}
Ignitionignition_tag_read当前标签值Rlow{values:[{path, value, quality, timestamp}]}
Ignitionignition_alarm_status活动告警Rlow{alarm_count, alarms:[{path, priority, state}]}
Ignitionignition_tag_history标签历史查询(有界)Rlow{rows:[{path, timestamp, value}]}
信创/合规compliance_mapping《工控网络安全防护指南》↔ iaiopsRlow{pillars[], status_summary, controls:[{pillar, status, gap}]}
信创/合规compliance_frameworks等保 2.0 + IEC 62443 FR1–6 + GxP 对照表;framework="nis2"|"cra" = NIS2 第 21 条/CRA 附件 I 逐项映射Rlow{crosswalk[]} | {items:[{ref, coverage, mechanisms, gap}]}
信创/合规compliance_dengbao_levels等保二级基线与三级增量Rlow{pillars:[{l2, l3_delta, status}]}
信创/合规compliance_report可交付的合规报告(md/html);framework="nis2"|"cra"|"eu" 用于英文 EU 自评估Rlow{markdown | out_path}
信创/合规compliance_evidence_bundle审计证据 zip(经哈希链验证)Rlow{bundle_path, manifest}
信创/历史数据库historian_push将遥测数据推送到 sqlite/TDengine/IoTDBR(→historian)low{sink, received, written, skipped_non_numeric}
自身protocols_supported能力映射Rlow{protocols[], diagnostics[], analytics[]}
(能源协议——IEC-104 / DNP3 / IEC-61850——已于 0.8.0 迁移至 iaiops-energy;其工具矩阵位于该仓库中。)

205 个受治理工具 = 188 个读取工具 + 4 个不会更改设备但会将数据发送到设备外的工具(stream_publish、stream_publish_event、uns_publish、rca_narrate)+ 10 个受 MOC 管控的设备写入工具 + historian_push(写入工具,写入对象是历史数据库而非设备:[WRITE][risk=low])+ 下方 2 个已弃用别名。设备写入工具为(s7_write_db、mc_write_words、fins_write_words、mqtt_publish、eip_write_tag、ethercat_write_sdo、ethercat_set_state、profinet_dcp_set、bacnet_write_property、bas_command)。读取侧现在还包括位于现场协议之上的两个供应商 REST 只读层——BAS 控制器层(Metasys/Niagara,楼宇版)和 Ignition Gateway MES/SCADA 层(工厂版)。¹ 2 个已弃用别名是两个已弃用的 brain 别名 health_summary / anomaly_scan,于 0.10.0 更名为 opcua_health_summary / opcua_anomaly_scan——这些已弃用别名仍已注册,将在未来版本中移除(目标版本:1.0.0)。各版本专属的只读工具仅会在对应版本下加载(参见下文的各版本工具模块),因此,纯协议版 / 单版本界面所包含的工具少于此处列出的总数。上表仅为示例,并未涵盖全部工具;运行 protocols_supported()(或 iaiops protocols)可查看实时列表。


各协议参考

OPC-UA
  • 版本/变体:通过 asyncua 使用二进制 opc.tcp://(同步封装)。安全性:匿名 + 用户名/密码,以及应用程序证书消息安全(Sign / SignAndEncrypt)——设置 client_cert + client_key(+ 可选的 server_cert)后,客户端会打开已签名 / 加密的安全通道(不提供证书时,匿名 / 用户名路径保持不变)。已通过进程内 asyncua 服务端到端验证(tests/test_opcua_security.py),适用于 Basic256Sha256 的 Sign 和 SignAndEncrypt 两种模式:已测试 server_cert 固定以及客户端自动发现服务器证书,并且测试会断言实时加密通道上协商的策略 URI + 消息安全模式(另有一项负向测试,验证安全模式专用服务会拒绝匿名访问)。
  • 连接参数:endpoint_url、username(密码会加密)、security_mode、security_policy;证书安全所需的 client_cert / client_key / 可选的 server_cert(PEM 或 DER 路径;别名为 certfile / keyfile)。
  • 警报与条件:opcua_alarm_events——有界事件订阅 + ConditionRefresh,事件携带服务器自身的时间戳(已通过进程内 asyncua 服务验证;第三方 A&C 服务 待核实)。无时间戳回退方式:opcua_read_alarms 浏览类似警报的布尔节点。
  • 不支持 / 计划中(待核实):与第三方 / 供应商服务(KEPServerEX / Prosys / Siemens / 真实 PLC)的证书安全互操作、其他策略(Aes128Sha256RsaOaep / Aes256Sha256RsaPss / Basic128Rsa15 / Basic256)、严格的服务器端证书信任强制,以及基于证书的用户身份验证(X509 身份令牌,与通道安全不同)。
Modbus-TCP / Modbus-RTU
  • 版本/变体:通过 pymodbus(以及 pyserial)支持 Modbus-TCP 和 Modbus-RTU(串行 RS-485/232)。读取功能码 FC01(线圈)、FC02(离散输入)、FC03(保持寄存器)、FC04(输入寄存器)。写入功能码(FC05/06/15/16)= 尚未实现(只读)。
  • 连接参数:TCP — host、port(502)、unit_id。RTU — transport: rtu、serial_port(例如 /dev/ttyUSB0)、baudrate、unit_id。寄存器是无类型的 16 位字 → decode 提示(uint16/int16/uint32/int32/float32/raw);modbus_detect_byte_order 会根据提示值自动检测字节/字顺序(AB/BA · ABCD/DCBA/BADC/CDAB)——纯逻辑,不会增加设备负载。
  • 用于采集的类型化点位(HLD §16):采集/监控引用可以指定其表、类型、字顺序和缩放比例 — ir:0:float32:CDAB、hr:12:int16*0.1(语法 <hr|ir>:<addr>[:<type>[:<order>]][*<scale>];32 位类型必须指定字顺序)。在端点上设置 sensor_profile: <file> 后,ch:<name> 会读取该配置文件中的通道(哨兵值 → 缺口,强制执行声明的范围)。裸地址保持原有含义(一个保持寄存器,uint16),而 readiness 会将其报告为无类型。在 DI 模块上读取用于切换的传感器(NPN / PNP / 干接点)时,使用 di:<addr>:<high|low>(FC02)或 co:<addr>:<high|low>(FC01):必须指定有效电平(常闭开关在动作时读取为 0),值为 1 表示已动作;如果将切换点位声明为生产计数器,readiness 会报告 count_source 缺口(轮询会漏掉短脉冲)——仅检查 Modbus 引用;尚未检查 PLC 位。随附的配置文件库(iaiops sensor list | show,MCP sensor_profile_list | show)包含适用于常见电能表和光伏逆变器的社区寄存器映射,每项均源自同一设备的 Modbus 模板,并附带注意事项——每项均标记为 待核实。复制一份(show <id> --out <file>),将读数与设备显示值核对,然后绑定该副本。
  • 厂商寄存器模板(modbus_list_templates / modbus_apply_template):命名寄存器映射,可将一个数据块解码为工程值 — 电能表(Eastron SDM630、Schneider PM5xxx、Carlo Gavazzi EM24)、光伏逆变器(Huawei SUN2000、Growatt)、Phoenix PLCnext 过程数据,以及水务行业模板(E+H Promag、Hach SC 控制器、通用加药泵)。每个模板都带有明确的 待核实 注意事项——不会编造“已验证”的地址。
  • 覆盖范围:许多国产 PLC(汇川 Inovance / 信捷 Xinje / 和利时 Hollysys / 台达 Delta)以及任何 Modbus 厂商。RTU 帧封装已通过真实串行链路(socat PTY + pymodbus RTU 服务端)进行实时验证;具体的物理 RS-485 设备仍标记为 待核实。
S7comm(Siemens + 仿西门子国产设备)
  • 版本/变体:pyS7(纯 Python,ISO-on-TCP / RFC1006 — 不使用原生 libsnap7)。S7-300/400/1200/1500 及兼容克隆设备。存储区 DB / M(merker)/ I / Q。协议无身份验证(CPU 通过“Permit access with PUT/GET”进行限制)。
  • 连接参数:host、port(102)、rack、slot(1200/1500 为 0/1;300/400 通常为 0/2)。
  • 写入:s7_write_db = high risk_tier、MOC、默认 dry-run,会捕获操作前的值并提供撤销操作。
  • 不支持 / 计划支持:在启用了“optimized block access”的 1500 上,优化/符号化 DB 访问可能需要在 CPU 上配置绝对寻址。
Mitsubishi MC
  • 版本/变体:pymcprotocol — 仅支持 MC 3E 帧(二进制)。不支持 1E / 4E 帧。 PLC 类型 Q / L / QnA / iQ-R / iQ-L。设备:D/W/R(字)、M/X/Y/B(位)。
  • 连接参数:host、port(默认 5007;设置为模块的开放 MC 端口)、plctype。
  • 写入:mc_write_words = high/MOC/默认 dry-run,会捕获操作前的值并提供撤销操作。
Omron FINS(CS/CJ/CP/NX-via-FINS)
  • 版本/变体:仓库内置、仅使用标准库的 FINS 客户端(无第三方依赖——iaiops[fins] extra 不固定任何依赖):10 字节 FINS 标头帧封装、FINS/UDP(默认端口 9600)和 FINS/TCP(按 Omron W342 进行节点地址握手)、SID 匹配、受限的响应解析,以及符合 W227/W342 的结束代码表。命令:0101 存储区读取(读取 DM/CIO/W/H/A/EM 中的字/位)、0102 写入、0501 控制器数据读取、0601 控制器状态。
  • 连接参数:host、port(9600)、transport(默认 udp/tcp)、FINS 网络/节点/单元寻址。
  • 写入:fins_write_words 默认采用 高危操作/MOC/dry-run 模式,采集写入前数据并支持撤销;CLI 在 --apply 时要求二次确认。
  • 验证:已通过仓库内的 FINS UDP/TCP 模拟响应器验证(tests/test_fins.py);真实 Omron PLC 行为及分组 EM 访问仍为 待核实。
IO-Link(主站 JSON 集成——只读)
  • 版本/变体:通过 IO-Link 主站的 HTTP/JSON 接口(IO-Link 联盟的 “JSON Integration”)查看传感器级数据;可为每个端点选择两种方言:iotcore(ifm IoT-Core POST 信封格式,默认)和 rest(纯 REST GET,Balluff/Turck 风格)。读取:主站身份信息、有界的 ≤32 个端口扫描、每个端口的设备身份信息、过程数据输入(原始十六进制 + 字节)、ISDU 非周期参数读取。**无写入工具。**HTTP 请求有界且限制大小(响应上限为 256 KiB),JSON 经过架构校验并提供易于理解的错误信息。复用 MTConnect HTTP 依赖(iaiops[iolink] → requests)。
  • 连接参数:主站 host/URL、flavor、timeout_s。protocol: iolink。
  • 采集(HLD §16):port_profiles: {<port>: <profile.yaml>} 为每个端口绑定传感器配置文件;随后即可采集 port<N>/<channel> 引用(collect、monitor)。没有配置文件的端口不会解码——将拒绝处理,绝不猜测。如果配置文件包含 match: {vendor_id, device_id},则会在解码前检查端口上的设备(按主站和端口缓存 5 秒,任何读取失败都会清除缓存)。如果配置文件包含 pd_bits,则会拒绝长度不符的过程数据映像。iaiops sensor import-iodd <iodd.xml> 根据供应商的 IODD 草拟配置文件(verified: 待核实,不映射单位代码,并列出无法表示的项目);iaiops sensor check 可校验配置文件。
  • 验证:使用两种方言的进程内模拟主站(tests/test_iolink.py);使用手写的 IODD 格式样例验证配置文件解码(tests/fixtures/iodd/)。真实主站的数据点路径、身份编码及供应商 IODD 仍为 待核实。
串行 ASCII 仪器(秤、分析仪、条码读取器、旧式仪表——只读)
  • 版本/变体:通过 RS-232 / RS-485 / RS-422 / USB 虚拟 COM(pyserial>=3.5、额外依赖 iaiops[serial],包含在 iaiops[sensor] 中)或通过串行设备服务器的原始 TCP 套接字,每次读数发送一行文本的仪器。解码方式由传感器配置文件指定(protocol: serial):framing(终止符、编码、最大行长度)、mode: listen | poll(以及轮询时可发送的唯一一条 query),以及每个通道的正则表达式(必须匹配整行,并包含 value 分组)、状态码(ok / 指定的无效状态 → 缺失值),还有固定单位和/或从行中读取的单位。
  • 安全规则:端口以独占方式打开——如果端口被其他程序占用,则拒绝连接,绝不抢占(使用建议锁;未加锁便打开端口的程序无法检测,按操作系统而定,待核实)。轮询要求端点设置 bus: dedicated(shared = 已有主站的 RS-485 总线:仅监听);去皮 / 归零 / 校准不属于读数,不能放入 query。监听模式必须设置 stale_after_s(在此时间内没有收到行 = 缺失值,绝不返回最后一次读数)。采集要求每个通道使用固定单位。
  • 工具:serial_sniff / iaiops serial sniff(原始行,不发送任何内容);通过 sensor_read_channel / iaiops sensor read 读取解码后的数据;通过 iaiops collect run 进行采集。
  • 验证:通过真实的 pyserial 路径测试一对 pty,通过本地 TCP 设备服务器路径测试,并在实验室局域网中使用我们自己的称重模拟器进行演练(第 2c 级——不使用第三方仪器)。不附带任何串行配置文件——需为每台仪器自行编写配置文件(iaiops serial sniff 会显示原始行);在对照真实设备验证之前,其行格式仍为 待核实。
HART-IP(过程仪表——只读)
  • 版本/变体:通过 HART-IP UDP(默认,端口 5094)或 TCP(transport: tcp,长度定界帧格式),使用树内传输实现 HART 通用命令;HART 命令编解码器已通过 hart-protocol 验证。工具:hart_device_identity(命令 0)、hart_primary_variable(命令 1)、hart_dynamic_variables(命令 3,PV/SV/TV/QV + 回路电流)、hart_burst_sample(对突发发布的变量进行有界采样)。不公开写入命令或设备专用命令(在运行中的仪器上存在 OT 风险)。
  • 连接参数:host(HART-IP 服务器/网关)、port(5094)、transport(默认 udp / tcp)。
  • 验证:TCP 传输已通过环回验证(进程内 HART-IP 服务器通过真实编解码器路径返回真实长帧 ACK);真实网关行为和真正的非请求突发订阅仍为 待核实。
MTConnect(所有 CNC 机床)
  • 版本/变体:Agent REST + XML(requests + xml.etree),与命名空间无关(解析 MTConnect 1.x Devices/Streams/Assets 架构)。端点:/probe、/current、/sample、/assets。按规范只读。 XML 解析经过强化(拒绝 DTD/实体声明——防御 XXE/十亿笑声攻击)。
  • 连接参数:agent_url(例如 http://host:5000)。
  • 不支持 / 计划实现:MTConnect 流式传输(长轮询 interval=);目前仅支持有界的 count= 采样。
MQTT / Sparkplug B / UNS
  • 版本/变体:paho-mqtt — MQTT 3.1.1 和 5。Sparkplug B 主题约定 spBv1.0/{group}/{type}/{edge}/[device](NBIRTH/DBIRTH/NDATA/DDATA/NDEATH/DDEATH/STATE)。支持 TLS 和用户名/密码。
  • 完整的 Sparkplug B 解码(无需可选附加组件):使用逐字节 vendored 的官方 Eclipse Tahu sparkplug_b.proto 生成模块(仅依赖 protobuf)对有效负载进行 protobuf 解码。每个指标都包含名称、别名(通过 BIRTH 模型解析为名称)、数据类型(Int8…Int64/UInt…/Float/Double/Boolean/String/DateTime/Text/UUID/DataSet/Bytes/File/Template/PropertySet…)、值、时间戳以及 is_historical / is_null 标志。birth/death + seq 模型跟踪节点/设备的在线状态(NBIRTH/DBIRTH ↔ NDEATH/DDEATH),根据 BIRTH 构建别名→名称映射,按别名应用 NDATA/DDATA,并标记 seq 间隔/乱序。具备主机感知能力:STATE/<host_id> 主题会显示在 sparkplug_node_list 中。sparkplug_decode_payload 可离线解码单个原始有效负载(base64/hex)。
  • 数据源,而不只是总线(新):MQTT/Sparkplug 可用于采集 — iaiops collect run 会在整个运行期间保持一个订阅打开,并从其最新值缓存中采样,因此无需触碰 PLC,即可接入数据已统一到 UNS 中的工厂。拒绝读取本身就是一项功能。 MQTT 使用推送模式,而显而易见的缓存会在发布者停止后永远返回最后一个值 — 于是生产线停止后,各个订阅者看到的可用率都会是 100%。因此,只有当某个点至少被读取过一次(从未发布不等于零)、其数据新鲜度高于 stale_after_s,并且 — 对于 Sparkplug — 其节点尚未发送 NDEATH/DDEATH 时,该点才算一次读数。其他情况都会引发 OTNoReadingError,采集器会将其记录为间隙(采集期间不可见),而绝不会记为停机。
  • 采集时必须设置 stale_after_s,且故意不提供默认值:停止更新的值与恒定值在传输线上完全相同,因此只有现场知道某个点的发布频率。在这里猜测会让每项可用率数据都建立在猜测之上。这与 run_state 标签上的 running_when 遵循同样的原则。
  • 两种保证并不相同,且端点会获知其具备哪种保证:Sparkplug 同时提供数据过时检测和死亡检测(代理会发布节点的 Last Will);纯 MQTT 完全没有死亡信号,因此只能依靠数据过时检测,无法区分主题静默是因为发布者已死亡还是暂时没有消息。
  • 引用:纯 MQTT 引用就是主题本身;Sparkplug 引用为 group/edge[/device]:metric — 与 mqtt live-schema 报告的节点 ID 完全一致,因此其输出可直接用作配置引用,无需转换。有效负载可解码为裸标量,或 {"value": …} / {"v": …} JSON 封装;对于未知封装,不会擅自挑选其中唯一的数字。
  • 连接参数:host/broker、port(1883 / 8883 TLS)、topic、use_tls、username(密码加密)、stale_after_s(采集必填)。
  • 验证:通过完整的 paho 循环,在真实的 eclipse-mosquitto:2 代理上进行了端到端验证(tests/test_uns_tap_live.py,选择启用)——发布 → 读取 → 发布者停止 → 运行记录间隙,而非重复使用缓存值。还在三台独立主机(代理、边缘节点、读取器)组成的真实网络上,针对符合规范的 Sparkplug 节点进行了测试 — 仅携带别名的 NDATA、定期重新 BIRTH,以及发布者在另一台机器上被 kill -9 终止后由代理发出的真实 Last Will(在 stale_after_s 特意设为 60 的情况下,约 2 秒后拒绝读取,因此只有死亡信号可能导致这一结果)。待核实:边缘节点仍是我们自行实现的,并非商用 EoN(Ignition / Litmus / HighByte),代理也不是商用代理(HiveMQ / EMQX)。
  • 命令:mqtt_publish = 高风险/MOC/默认 dry-run。临时发布没有自动逆操作(消息一旦送达便无法收回);保留发布会覆盖代理中的持久状态,因此会先捕获此前保留的有效负载并记录逆操作。
EtherNet/IP (Rockwell / Allen-Bradley)
  • 支持:通过 CIP / EtherNet-IP 和 pycomm3(纯 Python,无原生依赖)连接 ControlLogix / CompactLogix(以及 GuardLogix)。支持基于标签的符号访问:按名称读取/写入标签(Conveyor.Speed、Array[3]、Program:Main.X),并可在运行时发现控制器的标签列表(eip_list_tags,核心功能)。eip_controller_info 可读取控制器身份信息。
  • 连接参数:host、slot(CompactLogix 为 0;ControlLogix 机架中的 CPU 插槽)、port(44818)。protocol: ethernetip(别名 eip)。
  • 写入:eip_write_tag = 高 risk_tier、MOC、默认 dry-run,会捕获写入前的值并提供撤销操作。
  • 也支持(仅模拟驱动——真实硬件 待核实):通过 PCCC 数据表寻址连接 PLC-5 / SLC-500 / MicroLogix,以及 Micro800。
EtherCAT(pysoem / SOEM 现场总线主站)
  • 支持:通过 pysoem(SOEM C 协议栈的 Python 绑定)连接真实 EtherCAT 主站。支持 CoE SDO 读取(ethercat_read_sdo,非周期邮箱上传)和 SDO 写入(ethercat_write_sdo,下载)、输入 PDO 读取(ethercat_read_pdo,一次有界的周期快照)、总线扫描/从站枚举(ethercat_slaves、ethercat_slave_info——身份、SM/FMMU 映射、对象字典摘要)、主站/工作计数器状态(ethercat_master_state),以及 AL 状态转换 INIT↔PREOP↔SAFEOP↔OP(ethercat_set_state)。

  • 硬性要求(无法绕过):Linux、root 或 CAP_NET_RAW、一块连接到总线的专用网卡,以及真实 EtherCAT 从站硬件。pysoem 是可选附加项:pip install iaiops[ethercat]——基础软件包无需安装它即可安装和导入;若未安装,所有 EtherCAT 工具都会降级为教学用错误(不会崩溃,也不会在模块加载时导入 pysoem)。

  • 不支持:没有软件模拟器(与 OPC-UA / Modbus 不同)——EtherCAT 仅支持硬件,且无法在仅使用模拟环境的 CI 中测试;不支持 macOS。EoE / FoE / SoE 邮箱协议以及完整 PDO 映射解码/展开 = 规划中。

  • 连接参数:nic(专用接口名称,例如 eth1;别名 interface),可选的 expected_slaves(用于与总线扫描结果进行合理性检查)。protocol: ethercat。

  • 操作矩阵:

    工具操作读/写风险捕获/备注
    ethercat_master_state主站 + WKC 状态、从站数量R低预期数量与实际发现数量
    ethercat_slaves总线扫描/枚举R低索引/厂商/产品/修订版/地址/AL 状态
    ethercat_slave_info单个从站详情R低SM/FMMU + OD 摘要
    ethercat_read_sdoCoE SDO 上传R低十六进制 + uint 解释
    ethercat_read_pdo输入 PDO 快照R低单次周期,不会循环
    ethercat_write_sdoCoE SDO 下载W高/MOC写入前的值(SDO 读回)+ 撤销操作
    ethercat_set_stateAL 状态转换W高/MOC写入前的状态 + 撤销操作;可能启动/停止运动
  • 写入/状态安全性:ethercat_write_sdo(十六进制小端字节)和 ethercat_set_state 均为高 risk_tier、MOC、默认 dry-run,会捕获写入前的值/状态以便撤销,并需要通过 CLI 二次确认。更改 EtherCAT 状态可能会启动或停止机器运动——请务必格外谨慎。未经授权勿对生产控制系统写入。

PROFINET(DCP 发现 / 识别 + 受限的 DCP Set)
  • 支持:通过 pnio-dcp 支持二层 PROFINET-DCP — profinet_discover(DCP IdentifyAll:一次广播即可发现网段上的所有站点——站名、MAC、IP、厂商/设备 ID、角色——更接近被动发现,而非逐台设备指纹识别)、profinet_identify_station(按站名识别)、profinet_station_params(按 MAC 定向执行 DCP Get → 获取名称 + IP 套件),以及 profinet_asset_inventory(资产登记表,包含 IO 控制器与 IO 设备角色解码)。
  • 写入:profinet_dcp_set 可为一个站点重新寻址(按 MAC 设置站名和/或 IP 套件)——高 risk_tier、MOC、默认 dry-run,会捕获修改前的寻址信息 + 撤销描述符。为运行中的站点重新寻址可能会中断其 IO 连接。
  • 范围(有意限定):不包含 RT 循环过程数据(这需要 IO 控制器/IO 设备协议栈和硬实时支持——不在范围内,且不安全);不公开 闪烁 / 恢复出厂设置 DCP 服务。
  • 硬性要求:必须在 PROFINET 子网上的 NIC 上具备 原始套接字访问权限(root / admin / CAP_NET_RAW)。pnio-dcp 是可选附加组件:pip install iaiops[profinet] — 基础包无需它即可安装/导入,缺少它时,每个工具都会降级为教学错误。
  • 连接参数:host — 本机在 PROFINET 子网上的 IP(DCP 广播会通过该 IP 发出)。protocol: profinet。
  • 预览版注意事项:已通过模拟的 pnio-dcp DCP 验证——尚未在真实 PROFINET 设备上验证。
能源版(电气变电站 / 公用事业遥测)→ iaiops-energy

能源垂直领域——面向变电站 RTU/IED 的 IEC 60870-5-104 / DNP3 / IEC 61850 MMS 只读监控——已于 0.8.0 版本移至独立软件包:iaiops-energy(pip install iaiops-energy),基于 iaiops.core(共享治理 / 大脑 / 运行时)构建。该软件包的协议参考、支持矩阵和验证状态均见其代码库。

楼宇版(设施 / HVAC / 厂务)

楼宇垂直领域新增 BACnet/IP(ASHRAE 135)——这是用于 HVAC、照明、计量和设施设备的主流楼宇自动化协议。使用 pip install iaiops[building] 安装,并通过 IAIOPS_MCP=building 启用(套件:bacnet + modbus + opcua + iolink)。

  • BACnet/IP(通过 bacpypes3 使用 BAC0):bacnet_discover(Who-Is 设备发现)、bacnet_object_list(设备对象列表)、bacnet_read_property(读取单个对象属性)、bacnet_read_points(读取所有模拟量 / 二进制 / 多状态点的 present-value——HVAC 快照)、bacnet_cov_subscribe(有界变化值捕获——按数量和墙钟时间限制,始终取消订阅)、bacnet_read_trend_log(通过一次有界 readRange 读取 TrendLog 缓冲记录)。配置:host = 本机的 BACnet/IP 接口(ip 或 ip/mask)/ port(47808)。
  • 写入:bacnet_write_property(以 BACnet 优先级 1..16 写入 present-value,或放弃控制权)= 高 risk_tier、MOC、默认 dry-run,写入前读取值 + 撤销。覆盖运行中的楼宇控制点可能会影响实际 HVAC / 设备运行。
  • 验证:已通过实际异步 BAC0 协议栈,针对真实的 bacpypes3 虚拟 BACnet/IP 设备验证读取路径(tests/test_bacnet_live.py);在实际 HVAC 设备上的 COV / 趋势日志 / 写入仍为 待核实。
水处理版(水处理)

IAIOPS_MCP=water(或 iaiops-mcp-water、pip install iaiops[water])提供 modbus + opcua + hart + 大脑——这是水厂 / 污水处理厂实际使用的协议套件。新增水领域标签语义(溶解氧 DO / ORP / 余氯 chlorine / 氨氮 ammonia / TSS/MLSS / 跨膜压差 TMP / UV / 加药 dosing / 曝气 aeration),以及水行业 Modbus 模板(E+H Promag、Hach SC 控制器、通用加药泵——均带有明确的 待核实 注意事项)。

仓储 / 物料搬运版(仓储 / 物料搬运)

IAIOPS_MCP=warehouse(或 iaiops-mcp-warehouse、pip install iaiops[warehouse])提供 eip + profinet + modbus + opcua + sparkplug 以及核心能力——通过 EtherNet/IP(Rockwell)和 Profinet(Siemens)连接输送机与分拣机驱动器,通过 Modbus 连接 VFD / 电能表(conveyor_vfd / agv_battery 模板),通过 OPC-UA 连接 WMS/WCS 网关,并通过 MQTT-Sparkplug 接收 AMR/IoT 遥测数据。该版本的工具(只读、提供建议):line_bottleneck(基于约束理论分析各工位的吞吐量瓶颈)和 sortation_health。PdM(pdm_forecast)、downtime_triage 和 OEE 原样复用。

临床设施版(医疗设施)

IAIOPS_MCP=clinical(或 iaiops-mcp-clinical、pip install iaiops[clinical])提供 bacnet + modbus + opcua 以及核心能力——将医院设施作为独立的患者安全垂直领域纳入楼宇核心系统。该版本的工具(只读、提供建议):isolation_room_check(负压 / 正压隔离病房压力检查)、medical_gas_check(医用气体报警面板安全检查)、or_environment_check(手术室温度 / 湿度 / 压力范围检查)。通过 BACnet 连接 BMS,通过 Modbus 连接气体报警面板,并通过 OPC-UA 连接工厂 SCADA。

制药生产版(制药)

IAIOPS_MCP=pharma(或 iaiops-mcp-pharma、pip install iaiops[pharma])提供 bacnet + modbus + hart + opcua 以及核心能力。没有新增协议——这正是重点:没有任何现场协议是制药行业专用的。洁净室使用 BACnet,纯化水系统使用 Modbus 和 HART,灌装和冻干使用 S7,DCS 和生物反应器使用 OPC-UA,而这些功能本就已具备。制药行业需要的是语义:水务版的指标面向市政供水(DO、ORP、氯、浊度),临床版评估单个房间的压力,而 Annex 1 检查的是压差梯级。

该版本的工具(只读、提供建议):cleanroom_pressure_cascade(EU GMP Annex 1,逐门检查——相邻关系必须明确声明,绝不从房间列表推断)、cleanroom_particle_check、pharma_water_check(USP <645> 阶段 1 程序:采用未进行温度补偿的读数,将测得的温度向下取整到表列温度档,并将超过阶段 1 限值报告为继续进入阶段 2,而非判定为失败)。

不附带任何药典限值表。 粒子限值、阶段 1 电导率表和 TOC 限值均取决于站点在其药典版本下的合格规格。仓库中无人能够核实的转录内容,最终可能会决定某批次的环境是否合格——最危险的错误是给出过于宽松的限值,因为这会让结果显示为“符合规格”。限值由调用方传入,并在结果中注明来源;任何未声明的项目都会被列出,并报告为 no_limit / not_graded,绝不算作通过。该版本的技能中列出了已知缺口:没有 PI 历史数据连接器,S7 未经硬件验证,以及 LIMS / QMS 有意不纳入范围——它们使用 REST 和数据库,而非现场协议。

可再生能源版(光伏 / 风电)

IAIOPS_MCP=renewables(或 iaiops-mcp-renewables、pip install iaiops[renewables])提供 modbus + opcua + sparkplug 以及核心能力——通过 Modbus 连接光伏逆变器(SUN2000 / Growatt 模板)和风机控制器,通过 OPC-UA 连接电厂 SCADA,并接收 MQTT-Sparkplug 遥测数据。该版本的工具(只读、提供建议):pv_performance(对比光伏组串实际表现与预期表现)。通过基线 / RCA 实现设备级监控和 PdM。

半小时电力计量(供应商 HH 文件——建筑与可再生能源版本)

读取并解读供应商的半小时(HH)电力文件——英国所有非住宅电表都会生成这些数据,用于完成全市场半小时结算。不会连接任何设备;输入是文件。 iaiops meter check FILE 会说明文件包含什么内容(宽表布局:日期 + 46–50 个半小时列;长表布局:时间戳 + kWh),以及哪些数据被拒绝;iaiops meter profile FILE [--hours 'mon-fri 08:00-18:00'] [--holiday YYYY-MM-DD] [--tariff tariff.yaml] [--report out.html] 会报告夜间基础负荷、非工作时段用电与申报工作时间的对比、峰值需求、完整日期的工作日与关闭日平均值、负荷率、星期 × 时间热力图;若提供费率表,还会报告各时段的用电成本,以及将用电从最贵时段转移出去的成本上限。MCP(建筑与可再生能源版本):meter_hh_check、meter_load_profile(使用相同引擎;HTML 报告仅限 CLI)。演示:demo/metering/。

  • 拒绝执行的操作:空白单元格表示缺失值,绝不视为零;总量仅根据实测数据计算,不会跨缺失区间向上估算;日期仅接受英国日优先格式(拒绝 01/13/2026,不会尝试按其他格式重新读取);如果文件没有实际/估算标志,则读取类型报告为未知,而不是“实际”;高于基础负荷的非工作时段用电会标记为一项迹象,而非节省量。
  • 时钟日:根据英国时钟(GMT/BST)的规则进行计算——无需 tzdata,因此能正确读取有 46 或 50 个时段的时钟变更日。
  • 未内置的内容:DNO DUoS 红/琥珀/绿时段(因地区而异——请在费率表中传入;demo/metering/tariff_example.yaml 仅供参考)、VAT、CCL、kVA 容量和无功电费(每项成本中都会列为不包含)。验证:仅使用合成文件和单元测试套件——尚未使用任何真实供应商文件进行测试(待核实)。
PLCnext 打包版本(Phoenix Contact vPLC)

IAIOPS_MCP=plcnext(或 iaiops-mcp-plcnext、pip install iaiops[plcnext])会提供 opcua + modbus + 核心能力——可通过内置 OPC-UA 服务端(opc.tcp 4840,Arp.Plc.Eclr 地址空间)和 Modbus-TCP 过程数据服务端访问 Phoenix Contact PLCnext 虚拟化 PLC;不会新增连接器。已在进程内验证路由;读取真实 PLCnext 硬件仍为 待核实(参见 实际验证情况)。

信创 / 中国版(离线 · 国产 TSDB · 合规)

适用于自主可控 / 信创部署——完整指南请参见 docs/CHINA.md。

  • 隔离网络安装:纯 Python 核心 + 按协议划分的可选扩展 → 使用 pip install --no-index --find-links ./wheelhouse "iaiops[...]" 从本地 wheelhouse 安装;密钥保留在本地(加密存储),不使用云 KMS。
  • 国产 TSDB 历史数据接收端(historian_push,CLI iaiops historian push):将采集的遥测数据写入 TDengine(iaiops[tdengine])或 Apache IoTDB(iaiops[iotdb])——国产、可控;我们不构建自有存储,也不绑定 InfluxDB。数据导出至运营方自己的历史数据库,不执行控制写入。
  • 合规映射(compliance_mapping,CLI iaiops compliance):依据《工控系统网络安全防护指南》对 iaiops 进行如实的自评,覆盖分区隔离 / 可审计 / 双向认证 / 最小权限 / 数据保护 / 自主可控,并针对每项控制措施标注状态(已满足 / 部分满足 / 待核实)及具体差距。
  • 国产 PLC:通过现有 Modbus-TCP / S7 连接器连接汇川 / 台达 / 信捷设备。
  • ⚠️ 待核实:国产操作系统(麒麟/统信)· 芯片(鲲鹏/海光)· PLC 验证及 TSDB 写入路径均已有文档说明,但尚未在硬件上验证——请参见 docs/CHINA.md 中的验证矩阵。
OEE / 停机分析(跨协议,只读)
  • oee_compute — 根据生产输入(计划时间、运行时间、理想周期、总数 / 合格数)计算 OEE = Availability × Performance × Quality。每个因子均报告 原始值 + 限制在 [0,1] 范围内的值;capped 性能值 >1.0 表示理想周期可能过于乐观。
  • downtime_events — 在 {timestamp, state} 序列中自动检测 运行→停止转换,并生成包含持续时间的停机事件;通过关键词启发式规则进行分类(换型 / 物料 / 机械 / 质量 / 休息 / 未知),也可使用 {state: category} 覆盖分类。
  • oee_multidim — 根据带标签的记录,按 机器 × 零件 × 班次(或任意维度)聚合 OEE → 矩阵 + 表现最差项。
  • 对提供 / 收集的输入进行运算(无需工厂即可完整测试)。mtconnect_oee_snapshot 会展示为这些运算提供输入的实时 MTConnect 可用性 / 执行数据。
活跃资产清单 / 指纹识别(只读)
  • asset_inventory — 对每个已配置(或已命名)的端点,使用我们自己的协议客户端主动连接并读取其标识信息调用(S7 s7_cpu_info、EtherNet/IP eip_controller_info、OPC-UA 服务器构建信息、Modbus 设备标识 FC43/0x2B、Mitsubishi CPU 类型、MTConnect 设备模型),并将供应商 / 型号 / 固件 / 序列号 / 可连接状态 / last_seen汇总到资产登记表中。
  • 真实的适用范围(带有 IEC 62443 色彩):这是通过我们的客户端连接进行的主动指纹识别,不是被动的 SPAN / TAP 发现——它只能找到我们配置为可访问的设备,并会对每台设备产生轻微负载。被动流量镜像发现功能已列入路线图。
OPC-UA 历史访问(HDA)
  • opcua_read_history — 通过服务器的 HistoryRead 服务(asyncua read_raw_history),读取节点在 [start,end] ISO-8601 时间窗口内存储的历史值,并受 max_points(≤2000)限制。当服务器未记录该节点的历史数据时,会正常返回 {supported:false, note}(不会崩溃)。只读。
数值变化(CoV)监控
  • monitor_changes — 有界死区报告:轮询某个点,仅返回发生变化的值(含时间戳),而非每个采样值。适用于 OPC-UA / Modbus / S7 / Mitsubishi MC / EtherNet-IP。不会无限循环——由 duration_s(≤120)和 max_changes(≤500)共同设置硬性上限。只读。
跨协议大脑 — 0.9/0.10 新增功能(全部只读)
  • 保守的基线学习(baseline_learn/check/record_change/status,CLI iaiops baseline …)——一种变更日志基线,明确不是黑盒异常检测:基于本地历史数据计算稳健的 p1/p99 + 中位数/MAD 区间;历史数据不足(少于 100 个样本或少于 24 小时)时会拒绝分析,并明确给出 insufficient_data 判定;在记录的操作员变更处重新开始;默认静默运行——只有当数据超出区间且偏离超过 3×MAD,并连续至少出现 3 个样本时才判定为违规,且每次违规都会标明其基线时间窗口和异常样本。
  • Historian 读取集成(historian_query / historian_coverage,CLI iaiops historian query|coverage)——从 sqlite/TDengine/IoTDB 存储中查询历史数据;可选的站点级 historian: 配置块可让 RCA 副驾驶将事件发生前 2 小时的时间窗口作为另一类带引用的证据(严格仅增加功能——未配置时,RCA 输出与原来逐字节相同,且已通过测试验证)。
  • 旧版 PLC 程序说明工具(plc_program_outline/xref/section,CLI iaiops program …)——对已导出的程序文件进行结构提取(Siemens SCL/ST .scl/.st、AWL/STL .awl、Rockwell Studio 5000 .L5X——绝不上传运行中的 PLC);每个元素都包含 source_file 和行号(L5X 中为梯级),确保说明 Agent 必须引用真实位置。已加固以防范 XXE,文件大小 ≤5 MB,并设有扩展名允许列表。
  • ISA-18.2 报警洪泛深度分析(alarm_flood_analysis / alarm_rationalization_worksheet,CLI iaiops diag alarm-flood|alarm-worksheet)——分析洪泛事件(10 分钟内 ≥10 个报警)、抖动报警、陈旧/持续报警(>24 小时)、洪泛时间占比与目标值,并生成可导出为 CSV 的合理化工作表;可分析注入的事件,也可扫描实时 OPC-UA 活动条件。
  • 开放格式导出 + 指标桥接——iaiops export csv|sqlite|parquet(从本地 SQLite 存储导出;Parquet 需安装 iaiops[export])/ MCP export_data;iaiops metrics serve --port 9184 提供 Prometheus /metrics(最新标签值 + 计数器,默认绑定到 127.0.0.1)——Grafana 配置示例见 docs/GRAFANA.md。
  • 合规交付材料——iaiops compliance report(等保 2.0 L2/L3 状态 + IEC 62443 FR1–6 对照表 + 如实列出的差距,md/html)和 iaiops compliance evidence(含哈希链验证 + 清单的审计证据 zip);MCP compliance_report / compliance_evidence_bundle。用于入门引导,非认证。
  • NIS2 / CRA 自评估——iaiops compliance --framework nis2|cra 和 iaiops compliance report --framework nis2|cra|eu(MCP:compliance_frameworks / compliance_report 上的 framework 参数;无新增工具)。逐项评估 NIS2 Art. 21(2)(a)–(j) 和 CRA Annex I Part I(1)、I(2)(a)–(m)、II(1)–(8):product_evidence(代码锚定的机制可体现措施的核心要求——绝不声称“已满足该措施”,且每一行都会说明尚缺内容)、partial_evidence(有所涉及,但缺少核心要求)、gap 或 organisational(产品未提供相关证据)。NIS2 约束实体,CRA 约束制造商;这是产品自评估,不是认证,也不是法律意见。目前仍存在以下情况:没有 MFA 或按用户区分的身份验证(HTTP 传输使用一个共享的 bearer token,且自身不提供 TLS);仅可选择不记录成功的读取操作(写入、拒绝和失败始终会记录);审计数据库和采集的历史数据静态存储时未加密(iaiops reset 会将其删除,但不会进行安全擦除);没有渗透测试或模糊测试;SBOM、PyPI 证明和安全公告流程均已就绪,但尚未通过发布或报告实际验证。漏洞处理方式见 SECURITY.md。
跨协议核心与版本 — 0.11/0.12 新增内容(全部为只读)
  • 停机分诊副驾驶(downtime_triage)— 将报警级联 + RCA 判定 + PdM 前兆整合为一次分诊,并交叉核查首发报警是否与诊断原因一致;仅供参考,优先引用依据(基于早期的 alarm_cascade 首发报警重建和 pdm_forecast 达到阈值的剩余时间预警)。
  • 旧式 PLC 可维护性(plc_program_visibility)— 对导出的 ST/AWL/L5X 程序进行风险/可维护性分析(大小、块数量、交叉引用密度、未记录的区段),绝不上传运行中的程序 — 与 plc_program_outline/xref/section 说明工具配套。
  • 程序 ↔ 现场点位,仅精确地址(plc_program_xref_config,iaiops program xref-config <file> --endpoint <name> [--live])— 查明哪个程序地址对应哪个已配置点位:匹配 / 存在于导出文件但未配置 / 已配置但未在此导出文件中命名。仅当两侧在同一地址空间中使用相同地址时才进行关联(Siemens AWL/.scl × s7,L5X × Logix ethernetip);绝不按名称匹配 — 看起来像符号的标签并不构成匹配。任何其他组合都会返回不可比较的原因,不提供列表。--live 会以只读方式读取匹配的点位;读取失败属于数据缺口,绝不作为有效值。iaiops program doc <file> [--endpoint <name>] --out x.html 会将同一导出文件转换为一份自包含、确定性的 HTML 文档(块、变量、I/O 列表、交叉引用、调用图、变更历史、现场交叉引用)— 不使用 LLM,不会将任何内容写回程序。
  • 各版本专属工具模块(mcp_server/profiles.py 中的 EDITION_MODULES)— 命名版本可以包含自己的 @mcp.tool 组,且仅在选择该版本时加载 — 不会为裸协议键加载,也不会加载到常驻核心中,因此版本专属工具不会出现在其他界面,也不会增加基础部分的负担。每个版本的工具均为只读、优先引用依据、仅供参考:
    • warehouse — line_bottleneck(约束理论产能瓶颈)+ sortation_health
    • clinical — isolation_room_check(负压隔离病房加压情况)+ medical_gas_check + or_environment_check
    • building — economizer_check(AHU 经济器 FDD)+ zone_comfort
    • process — control_loop_health(PID 振荡/偏移/饱和)+ heat_exchanger_fouling
    • fab — spc_check(SPC 控制图规则)+ defect_pareto
    • factory — changeover_analysis(SMED)
    • water — disinfection_ct + water_quality_compliance
    • renewables — pv_performance(PV 组串性能)
    • sensor(IAIOPS_MCP=sensor / iaiops-mcp-sensor)— sensor_import_iodd + sensor_profile_check + sensor_read_channel + sensor_profile_list + sensor_profile_show(连接、解码并读取传感器;绑定配置文件仍需由人员编辑 config.yaml)
  • Agent 技能 — 此仓库提供一个路由技能(skills/iaiops)和十一个各版本技能(iaiops-fab / iaiops-factory / iaiops-process / iaiops-building / iaiops-water / iaiops-warehouse / iaiops-clinical / iaiops-pharma / iaiops-renewables / iaiops-plcnext / iaiops-sensor),用于将 Agent 路由到合适的 MCP 服务并说明工具接口。
部署与生态适配(边缘原生 / Margo)

iaiops 旨在作为可移植、受治理的边缘应用,部署在经过强化、由中央管理的边缘主机上——而非接管主机或设备群管理器。它与 Margo 的边缘互操作角色自然契合:主机/设备是不可变的边缘 OS,合规编排器根据期望状态部署工作负载,而 iaiops 是 OT 领域应用——提供只读接入 + 跨协议 RCA,并以受治理的 MCP 工具形式开放;还可选配本机 LLM 大脑,实现完全隔离的诊断路径(数据绝不离开工厂)。

如实说明状态:iaiops 是天然适配 Margo 的边缘应用,但尚未符合 Margo 规范——容器镜像 + 应用描述 + 已发布的符合性工具包测试结果仍在路线图中 ⏳(参见 docs/MARGO-ALIGNMENT.md 和 docs/ROADMAP.md)。在取得该测试结果之前,不会作出任何 符合 Margo 规范 的实质性声明。

容器 + 应用描述骨架位于 deploy/margo/(强化版 Dockerfile · compose · 标注 待核实 的应用描述符);复用该骨架、针对各主机的分发覆盖配置位于 deploy/ 下(每个候选边缘主机对应一个文件夹)。


安装

协议客户端库是可选额外依赖——只安装站点实际运行的 1–2 种协议(所有协议库均按需导入;基础包无需安装任何协议库即可安装和导入,而调用未安装的协议时会返回说明性错误,指出应安装的对应额外依赖):

uv tool install "iaiops[opcua,modbus]"   # just the protocols you need
# or one per site:  pip install "iaiops[s7]"   ·   everything:  pip install "iaiops[all]"
# or a per-industry edition bundle:        pip install "iaiops[fab]"

iaiops init                 # interactive: add endpoints, store passwords encrypted
iaiops doctor               # config + per-protocol connectivity probe (point at simulators)
iaiops protocols            # the capability map

协议额外依赖:opcua · modbus · s7 · mc · fins(标准库——不固定任何依赖)· eip · mtconnect · sparkplug · secsgem · ethercat · profinet · bacnet · hart · iolink · bas(BAS 监控 REST——复用 mtconnect 的 HTTP 依赖版本)· ignition(Ignition Gateway 只读层——复用 mtconnect 的 HTTP 依赖版本)· 以及 tdengine · iotdb · influxdb(历史数据接收端)· nats(流出站)· ollama(本机 LLM 叙述)· export(Parquet)· all(所有可通过 pip 安装的连接器)。

适配器带(docs/ADAPTERS.md):iaiops 是一个小型中立核心(ingress → normalize/govern/RCA → egress),配有可插拔、按需导入的适配器——不绑定任何存储、总线、主机或模型,只安装站点实际运行所需的组件。RCA 核心是确定性的,并提供引用依据,不是黑箱(docs/RCA.md);其占用空间经过有意控制(docs/FOOTPRINT.md)。

版本组合(与同名的 IAIOPS_MCP 配置匹配——安装对应行业所运行的协议): fab(secsgem + opcua + s7 + modbus)· factory(离散制造套件:opcua + modbus + s7 + mc + fins + eip + mtconnect + sparkplug + ethercat + profinet + iolink + ignition)· process(opcua + modbus + hart)· building(bacnet + modbus + opcua + iolink + bas)· water(modbus + opcua + hart)· warehouse(仓储/物料搬运:eip + profinet + modbus + opcua + sparkplug)· clinical(医疗设施:bacnet + modbus + opcua)· renewables(光伏/风电:modbus + opcua + sparkplug — PV 逆变器(SUN2000/Growatt)+ 风力涡轮机 + 电站 SCADA;通过基线/RCA 实现设备级监控 + PdM)· pharma(制药:bacnet + modbus + hart + opcua)· plcnext(opcua + modbus)· sensor(跨协议:modbus + iolink + sparkplug + 串口——连接并解码传感器)。电网/变电站能源套件(IEC-104/DNP3/61850)随 iaiops-energy 一同发布。

主密码

密钥(每个端点的密码、MQTT 凭据)绝不会以明文存储——它们保存在 ~/.iaiops/secrets.enc 中(Fernet + scrypt)。导出 IAIOPS_MASTER_PASSWORD,以便 MCP 服务/CLI 以非交互方式解锁:

export IAIOPS_MASTER_PASSWORD='…'
示例 ~/.iaiops/config.yaml(每个协议对应一个区块)
endpoints:
  - name: line1
    protocol: opcua
    endpoint_url: opc.tcp://plc.lan:4840
    # username: operator           # password stored encrypted via init/secret set
    tags:
      - { ref: "ns=2;i=5", label: temp, warn_high: 70, alarm_high: 90 }
  - name: plc2
    protocol: modbus
    host: 10.0.0.5
    port: 502
    unit_id: 1
  - name: press1
    protocol: s7
    host: 10.0.0.6
    rack: 0
    slot: 1                        # S7-1200/1500
  - name: cell3
    protocol: mc
    host: 10.0.0.7
    port: 5007
    plctype: iQ-R
  - name: meter1
    protocol: modbus                 # Modbus-RTU (serial): set transport + serial_port
    transport: rtu
    serial_port: /dev/ttyUSB0
    baudrate: 9600
    unit_id: 1
  - name: omron1
    protocol: fins                   # Omron FINS (UDP default; transport: tcp for FINS/TCP)
    host: 10.0.0.11
    port: 9600
  - name: xmtr1
    protocol: hart                   # HART-IP gateway (read-only; udp default / transport: tcp)
    host: 10.0.0.20
  - name: iolm1
    protocol: iolink                 # IO-Link master JSON integration (read-only)
    host: 10.0.0.21
    flavor: iotcore                  # ifm IoT-Core (default) | rest (Balluff/Turck-style)
  - name: vmc1
    protocol: mtconnect
    agent_url: http://10.0.0.8:5000
  - name: uns
    protocol: mqtt
    host: broker.lan
    use_tls: true                  # → port 8883
    topic: spBv1.0/#
    # username: edge1              # password stored encrypted
  - name: cell5
    protocol: ethernetip           # alias: eip
    host: 10.0.0.9
    slot: 0                        # 0 for CompactLogix; CPU slot for ControlLogix
  - name: bus1
    protocol: ethercat             # Linux + root/CAP_NET_RAW + pip install iaiops[ethercat]
    nic: eth1                      # dedicated NIC cabled to the EtherCAT bus
    expected_slaves: 8             # optional sanity check vs the bus scan

此文件无法识别的键会被拒绝,而非忽略——无论它出现在端点、标签、historian: 还是 retention: 中。丢弃一个键与它从未写入过无法区分:对现场而言,rolle: good_count 声明了一个生产计数器,而 iaiops readiness 随后报告 OEE 映射未满足,却完全没有指出拼写错误。错误信息会列出键名、可接受的键集合以及最接近的匹配项。

iaiops init 操作演示(每个协议)
$ iaiops init
Step 1 — master password: ********
Step 2 — add an endpoint
  Endpoint name (e.g. line1): press1
  Protocol ('opcua','modbus','s7','mc','mtconnect','mqtt') [opcua]: s7
  S7 PLC host (IP/FQDN): 10.0.0.6
  Port [102]: 102
  Rack (0 for S7-1200/1500) [0]: 0
  Slot (1 for S7-1200/1500, 2 for S7-300/400) [1]: 1
✓ Saved endpoint 'press1'.

(MQTT 提示会添加 TLS、主题和用户名;MTConnect 会提示输入 agent_url;EtherCAT 会提示输入 nic 和 expected_slaves,并警告需要 Linux/root/NIC/可选额外组件;OPC-UA/MQTT 会提示输入隐藏密码,并以加密形式存储。)

使用模拟器进行测试(每个协议)
  • OPC-UA — asyncua 演示服务器(测试套件会运行一个真正的进程内服务器)。
  • Modbus — ModbusPal 或 pymodbus 服务器模拟器。
  • S7 — 在 :102 上运行 pyS7/snap7 S7 服务器模拟器(Snap7 服务器)。
  • MTConnect — 公共 MTConnect 演示 Agent,或本地 Agent。
  • MQTT — 本地 mosquitto broker(+ 用于 SpB 主题的 Sparkplug 边缘节点)。
  • Mitsubishi MC — GX Simulator / MC 3E 服务器模拟器。
  • EtherNet/IP — 兼容 pycomm3 的 CIP/Logix 模拟器(或备用 CompactLogix)。
  • Omron FINS — 仓库内的模拟 FINS UDP/TCP 响应器(tests/test_fins.py),或备用 CP/CJ PLC。
  • IO-Link — 进程内模拟主站(tests/test_iolink.py,支持两种 JSON 方言),或实验台上的任意 ifm/Balluff/Turck 主站。
  • EtherCAT — 不存在模拟器(硬实时、原始以太网)。仅在 Linux 上以 root / 具备 CAP_NET_RAW 权限,使用连接到真实从站的专用 NIC 进行验证(例如 Beckhoff EK1100 耦合器 + EL 端子)。离线时,iaiops doctor 会报告清晰的“需要 Linux/root/NIC/pysoem”状态,而不会失败。

用法

现场勘查 — 查找未提供设备清单的网络上有哪些设备

其他每条命令都需要已配置的端点。scan 用来回答最先要解决的问题。它没有全端口模式,不使用原始套接字,不发送半开 SYN,也不具备任何写入路径;它可能触及的范围仅限固定的工业端口允许列表,而且扫描速度受调用者无法提高的上限约束。

iaiops scan profiles                                   # what each posture does
iaiops scan plan --targets 10.0.0.0/24                 # sends NOTHING — the artifact you get signed
iaiops scan run  --targets 10.0.0.0/24 --site "Line 1" \
                 --approved-by "J. Controls" --ticket CHG-91 \
                 --report survey.html                  # scan → store → one HTML file
iaiops scan list                                       # stored surveys
iaiops scan report --out survey.html                   # re-render the latest

scan plan 不会向网络发送任何内容。它会列出所有可能触及的主机和端口、所有将发送的数据包类别、最坏情况下的耗时,以及此工具绝不会执行的操作清单。因此,你可以在获得扫描许可前先对网络运行该命令,并将输出交给授予许可的人。scan run 会显示相同的预览,并在发送任何内容前询问一次(使用 --yes 可跳过)。

扫描模式从 passive(读取本地 ARP 缓存,完全不发送任何内容)到 legacy-safe(仅检查可达性,每次处理一台主机,每秒连接五次——适用于连格式正确的识别请求都可能造成风险的 1990 年代控制器)。standard 和 deep 在没有记录的批准签字时会拒绝运行。

HTML 报告是独立的:不依赖任何外部字体、脚本、样式或图像,打开时也不会发起网络请求。报告的第一部分列出扫描触及的内容——按类别统计发送次数,包括失败的请求——随后列出此工具绝不会执行的操作。设备表格位于其后。

从调查到测得 OEE — 现场实际走过的路径

scan 回答的是那里有什么。以下命令回答的是我能用它做什么,以及数值是多少。每一步都是一条真实命令;这里没有任何路线图项目。

iaiops readiness                                    # contacts NOTHING

此安装今天能运行什么,以及对于每件无法运行的事,缺少的具体输入是什么——按补充后能解锁多少功能排序。它不会接触任何设备,因此你可以针对尚未获准探测的现场运行它,而这样的现场最需要这个答案。它会报告缺口;绝不会填补缺口(§9.4/D16——猜测生产计数器会得出看似合理的 OEE,这比报错更糟)。

iaiops collect plan line1 --duration 7d --interval-ms 1000   # contacts NOTHING
iaiops collect run  line1 --duration 7d --interval-ms 1000
iaiops collect run  line1 --duration 7d --resume             # after a closed lid
iaiops store status                                          # what the store holds
iaiops store prune --sealed-before 2026-08-01 --apply        # refuses without a seal

一次有期限的评估运行——最长 14 天,且操作员必须指定结束时间。这里刻意不提供永远运行模式:在 OT 网络上常驻运行的进程需要变更管理,而笔记本电脑运行一周则不需要(D21)。每次运行都会记录它无法观测到的时间段,因此绝不会把缺口悄然解读为停机。

要从中得出 OEE,必须声明三个标签——哪个值代表运行、哪个寄存器统计零件数量,以及(对于质量)哪个寄存器统计合格品数量:

endpoints:
  - name: line1
    ideal_cycle_time_s: 0.1
    tags:
      - {ref: "0",  role: run_state,   running_when: [2]}   # 2 = running
      - {ref: "10", role: total_count}
      - {ref: "11", role: good_count}

running_when 必须由人声明,绝不推断。对于大多数 PLC 会提供的 0=stopped 1=idle 2=running 3=fault 状态字,“任何非零值都表示运行”会把四种状态中的三种计为生产状态。

iaiops oee measure line1 --reported 97               # against the figure the site keeps
iaiops oee measure line1 --since 2026-03-02T06:00:00Z --until 2026-03-09T06:00:00Z
iaiops oee measure line1 --report oee.html --lang zh --site "一号厂区"

**使用 --since / --until 一起限定测量范围。**如果不指定,统计周期就是该端点在存储中的全部数据——单次评估运行时没问题,但只要有两次运行就不对了:三月运行和八月运行之间的空闲周会变成一个巨大的盲区,导致两次运行都无法测量。

整个时间窗口都会计入统计。其中从未采样的部分——首次采样之前和末次采样之后——与中间的缺口一样,都会算作盲区,因此,把问题缩小到恰好有数据的那几分钟并不能提高覆盖率。询问一个你只观测了两小时的班次,得到的答案会是覆盖率 25% 并拒绝计算,而不是“所见数据的覆盖率为 100%”。

可观测时间内的可用率会排除盲区,而不会将其计为停机;此外还包括性能、质量和六大损失。只有在输入已声明时才会报告相应因素——明确指出缺少什么的部分 OEE,胜过包含猜测的完整 OEE。

--report 会生成一个自包含的 HTML 文件——不依赖任何来源的字体、脚本、样式或图片,打开时也不会发出网络请求,因此可在隔离网络的笔记本电脑上使用,也适合以附件形式转发。报告的第一部分会说明测量能够观测到什么(覆盖率、盲区时长、采样间隔),然后才展示数值;报告还会列出销售演示文稿通常会省略的一项内容:得出每个数值需要声明什么,以及仍缺少什么。

iaiops case open line1 --min-stop-s 300              # one case per long stoppage
iaiops case list                                     # each carries what someone DID after it
iaiops case causes                                   # the vocabulary a confirmation may use
iaiops case confirm <id> --cause material_starvation --by wei
iaiops case agreement                                # >90% agreement is a WARNING, not a score
iaiops diag learn-weights --site default             # learn a per-site cause profile
iaiops diag rca --input bundle.json --from-case <id> # a person's answer reaches the verdict

这个标签是已在进行的工作的副产品:审计跟踪早已记录有人在线路停止四分钟后执行了一次写入,因此案例中会显示这一点。确认操作只能从固定词表中选择,不能自由填写文本;驳回也同样是一个标签。答案是否算作独立,取决于工具是否曾建议过该答案——回答者不能自行声称其独立。

调查本身——八个步骤,以及每个步骤需要什么

readiness 会回答此站点可以运行哪些场景。这里回答的是再深入一层的问题:如果明天发生故障,我们实际上能调查到哪一步?

iaiops investigate plan                              # contacts NOTHING

八个证据步骤——定义事件、收集证据、规范化并检查、压缩并排序、关联时间线、检验假设、对照已知机制、得出结论并结束。对于无法推进的步骤,它会说明这是因为你尚未提供所需内容(并给出相应命令),还是因为此产品根本无法表达。这两种情况会把人引向完全不同的方向。

iaiops investigate open line1 --start <iso> --end <iso> --asset "Line 1"
iaiops investigate show <id>                         # the state it was left in
iaiops investigate list

针对一个真实的过往时间窗口,执行相同的八个步骤并保存结果,以便之后重新读取和继续推进。不会联系任何设备——该时间窗口已经结束,其中的证据就是当时收集到的内容。

以下三种方式中的任何一种都会生成可转发的版本——一个可在工厂办公室的隔离网络笔记本电脑上打开的独立 HTML 文件:

iaiops investigate plan --report readiness.html --lang zh
iaiops investigate open line1 --start <iso> --end <iso> --report incident.html

与 oee measure --report 不同,后者会拒绝为被拒绝的测量写入文件;此处则会特意为受阻的调查生成文件。OEE 报告是一个数字,因此只要文件存在,就意味着测量已完成。而这份报告的内容是调查推进到了哪一步,以及每个步骤还需要什么——因此,受阻的情况恰恰最值得交接;对于尚未配置任何仪表的站点,这就是全部交付内容。它不会让受阻的调查看起来已经完成:标题始终显示推进进度(2 / 8),而不是结论;任何步骤的具体说明都不会显示在标题上方。

两种路径:读取设备,或订阅 UNS 代理

从哪里开始,取决于一个文件不一定能替你回答的问题:此站点的数据是否已经通过 MQTT/UNS 代理流转?

iaiops onboard status                  # derives the journey from config.yaml — or asks
iaiops onboard status --track devices  # no UNS yet: survey, then read PLCs directly
iaiops onboard status --track uns      # already on a broker: connect, audit, subscribe
  • devices——调查 → 端点 → 每个端点的点位列表 → 每个点位的含义 → 收集 → 提问。这也是正在构建 UNS 的站点所走的路径:在发布点位之前先确认其含义,因为一次发布、多方订阅会让一个错误映射同时变成每个使用方看到的同一个错误数字。
  • uns——连接代理(必须设置 stale_after_s)→ 审核命名空间 → 选择点位 → 确认其含义 → 收集 → 提问。不扫描:扫描永远无法识别 MQTT,因此让 UNS 站点执行扫描只会走进死胡同。命名空间审核会把此路径分成 B1(审核通过)和 B2(需要进行治理——修正命名;对于 Sparkplug 命名空间,则使用 iaiops mqtt uns-live-drift 监控架构基线),此时治理工作本身就是重点,而不是后续步骤。普通 MQTT 没有漂移监控:两条架构命令都会读取 Sparkplug BIRTH 消息。

路径由 config.yaml 决定:仅配置代理端点 → uns,仅配置设备端点 → devices。如果没有配置任何端点,或两类端点都有,唯一的步骤就是提出这个问题并列出两种答案,不会替你选择。B1/B2 只能根据已保存的 iaiops mqtt uns-live-audit 审核结果判定——onboard 不会联系任何对象——因此,在审核结果尚不存在时,系统会说明分支未知。只有同时满足以下条件,站点才会被判定为 B1:针对命名标准进行审核(--root、--min-segments),未因达到消息上限而提前结束,监听时间至少覆盖一个发布间隔(stale_after_s),并且使用的代理和主题筛选器与端点当前使用的一致;不满足任何一项都会让分支保持未定,并说明原因。只有 sprawling 结果才会判定为 B2:minor 结果只是少量启发式发现——Sparkplug STATE 主题就会触发这些发现——因此会将其报告为待审查的发现,而不是治理结论。如果采集到的审核中没有发现任何主题,则不会保存该审核:这表示什么都没有审核,而“零项发现”不能被误读为命名空间干净。在 UNS 路径中,点位仍需手动填入 tags:;目前还不能将代理列表转换为草稿。

从扫描到配置

路径上的所有环节都已存在;只是还没有把它们串起来。一个站点可以扫描出 40 台设备,然后再手动逐一重新输入,而任何地方都没有说明接下来该运行 6 条命令中的哪一条。

iaiops onboard status                 # where am I, and what is the ONE next command
iaiops onboard draft                  # the newest scan → config.yaml endpoints
iaiops onboard draft --out draft.yaml # ...to a file you can review and merge

draft 不会向 config.yaml 写入任何内容——你需要像使用 tags apply 一样将其合并。它的输出经过刻意限制:

  • 只有已确认的协议才会成为端点。 502 端口开放只表示有服务在监听,并不代表它是 Modbus 设备;如果配置这么写,就会被当真。这些主机会列为已跳过,并注明原因。
  • 每个值都会指出支持它的观测结果——其中关键之处在于扫描得到的那些信息,否则你只能自己去查找:CPU 实际响应的 S7 插槽、MELSEC CPU 自身的 plctype,以及 OPC-UA 服务器是否公布了不安全的端点,还是需要凭据。
  • 扫描无法确定的字段会以注释形式输出,并说明它在等待什么——绝不会省略。省略会让协议默认值悄然生效,导致 Modbus 网关以单元 1 为地址读取,并为错误的设备显示一个看似可靠的数值。
  • tags: 会留空。 扫描可以发现设备,但无法确定其数据的含义。接下来要做的就是下面这一步。
  • 缺失不代表证据。 草稿会说明其结构上无法包含哪些内容——BACnet/FINS/HART 使用 UDP,且从未进行扫描;MQTT 和监控层也不会被识别,这是有意为之。

每次都会根据存储数据和 config.yaml 推导 status。没有引导状态文件,因此即使手动编辑配置或还原备份,也能得到真实的结果,而非依赖记忆;即便操作顺序有误,真正完成的步骤仍会显示为已完成。

对于点表步骤,它会指定适用于你所用协议的命令:opcua browse、eip tags、mtconnect probe、iolink ports、mqtt browse、bacnet objects、ethercat slaves、hart dynamic 或 modbus templates。如果确实无从查询——S7 CPU 不会通过网络公开符号表,MELSEC 和 Omron 的内存中也没有符号表——它会用该协议自身的术语说明这一点,并告诉你地址究竟从何而来,而不是用一句话笼统概括 OPC-UA 之外的所有情况。

确认点位列表的含义

这是该产品唯一拒绝推断的内容,而直到现在,提供它的唯一方式就是手动编辑配置文件中的 role:——这正是点位达到 100 行时行不通的做法。

iaiops tags export sheet.csv          # every monitored tag; `role` column EMPTY
# a person fills in role + running_when
iaiops tags apply sheet.csv --by wei  # prints the exact config.yaml edit

即使旁边的标签名为 GoodPartsCounter,role 列也会显示为空。名称不等于声明;很多工厂都有名称相同、但计数其他内容的标签,而错误的生产计数器会得出看似合理的 OEE——这比报错更糟(D16)。

apply 会生成补丁,而不会写入补丁。config.yaml 仍是唯一事实来源:oee measure 从配置中的标签对象读取角色,因此,另设一个存储区会导致 readiness 认为映射已满足,而 oee measure 仍无法运行。此处会拒绝没有 running_when 的 run_state,原因与 MonitorTag 拒绝它相同——“任何非零值”都会把空闲和故障计为生产。

也可以在页面中逐项勾选,而不是使用电子表格:

iaiops tags page confirm.html --lang zh

HLD §13.9 的 App 前端,以文件而非托管应用的形式交付。在 OT 设备内部运行 localhost 服务,就必须回答它绑定哪个地址以及谁来进行身份验证;而且,由于这里的每项声明都需要 --by,没有身份信息的页面就无法记录是谁确认了标签,而这正是此步骤要捕获的唯一信息。因此,页面负责收集,作者则在 apply 时提供。

页面不会重新实现任何拒绝规则。run_state 需要 running_when、引用的标签必须受监控、同一角色不能被认领两次——用 JavaScript 重现其中任何一条,都会让它们偏离真正控制配置的规则;如果页面说“看起来没问题”,而 apply 却拒绝,那还不如没有页面。页面附带脚本(它是一个表单),但不会发出网络请求:拔掉网线也能使用。

这里有意不提供 MCP 工具。 让 Agent 填写角色列,恰恰就是 D16 要禁止的猜测行为。

有两个步骤需要由人来声明一些内容:

iaiops relations declare press oven --by wei         # which asset feeds which
iaiops relations downstream press                    # nearest first

根因分析的第二个维度。仅凭时间信息,上游停机就会产生一连串置信度相同的下游假原因——在产线上,无论原因是什么,下游同时发生的情况都必然出现。正因如此,这一信息必须声明而非推断(D25)。没有它,时间线仍可运行;但会退化为单个资产的分析,并明确说明这一点。

iaiops knowledge mount mechanisms.yaml --by wei      # what is known about this equipment
iaiops knowledge check sensor_fault --protocol modbus

一个依据 ISO 14224 构建的故障机理库:失效模式(你观察到的情况)、机理(要去检查什么)和原因(要修复什么)各自独立,因为它们回答的是不同的问题。库中的条目关联到七种分类原因;它们绝不会新增原因——代码数量超过约 40 个后,两名操作员就不再会选择同一个代码。

它可以排除,但绝不会确认。如果某种机理不适用于这台设备,就可以排除该候选项;这是排序器做不到的有力判断:

✗ sensor_fault — excluded
  every mounted mechanism for 'sensor_fault' applies only to hart, opcua — not to modbus

如果库中从未收录某个原因,就会报告未知,而绝不会说“没有异议”。对某件事一无所知的知识库,并没有为它放行。

程序变更基线 — 已批准的逻辑是否发生了变化?

控制程序是受控文档,通常只有在有人记得时,才会发现其中发生了未记录的变更。记录你认定为已批准的版本,然后检查后续导出,看看是否有变化:

iaiops program snapshot ~/exports/Line3.scl --name Line3 --label "approved v3.2 / MOC-118"
iaiops program drift    ~/exports/Line3_today.scl --name Line3
# Over MCP the agent reads ~/exports only once the operator lists it:
#   IAIOPS_AGENT_READ_DIRS=~/exports

快照会保存文件的 SHA-256,以及按块计算的结构指纹——名称/类型/语言、声明的变量、调用、分支条件、定时器——并有意排除行号、注释和块顺序,因此在文件顶部添加一条注释,不会导致整个程序都被报告为已更改。磁盘上存储的是块名称、哈希和计数;绝不会存储声明、源代码行或注释,因此基线存储不会成为程序的第二份副本。

三种判定,每个词都至关重要:

判定含义
identicalSHA-256 相同。只有这种情况才配得上这个判定。
logic_changed提取出的结构存在差异——按块报告,并指出 variables/calls/branches/timers_counters 中哪些发生了变化。
changed_outside_extracted_structure字节不同,且所有块指纹都匹配。

第三种判定才是坦诚的说法。它通常表示注释或格式发生了变化——但这些解析器提取的是结构,并不解析语法,因此这里看起来相同的内容,可能实际上在它们未建模的结构中发生了变化。将它称为“仅文档发生变化”,是对证据做出过于乐观的解读,因此不会这样称呼:判定旁会报告行数和注释数,并仍然提示需要检查。漂移报告是阅读差异的理由,绝不是放行依据。

iaiops program history 会列出受跟踪的内容;iaiops program compare <name> <before> <after> 会比较两个已存储的快照。删除历史记录需使用 iaiops program forget,且只能通过 CLI 操作——不应让 Agent 只需一次调用,就能删除变更控制证据。系统不会自动清理任何记录。标识依据是名称(而不是路径),因为每次有人打开工程站时,导出目录都会改变,而程序不会改变;如果未指定 --name,则会使用文件名主体,并在输出中说明。整个过程中都不会接触设备——读取的是由人员导出的文件。

CLI(读取)
iaiops opcua read "ns=2;i=5" -e line1
iaiops modbus holding 0 -e plc2 --count 4 --decode float32
iaiops s7 read-db 1 REAL 4 -e press1 --count 2
iaiops mc words D100 -e cell3 --count 8
iaiops fins words 100 --area DM -e omron1 --count 8   # Omron FINS memory-area read
iaiops hart pv -e xmtr1                            # HART primary variable
iaiops iolink scan -e iolm1                        # IO-Link master + connected devices
iaiops mtconnect oee -e vmc1
iaiops mqtt nodes -e uns --timeout-s 15
iaiops eip tags -e cell5                           # Logix tag discovery
iaiops eip read "Conveyor.Speed" -e cell5
iaiops ethercat slaves -e bus1                     # EtherCAT bus scan (Linux+root)
iaiops ethercat read-sdo 0 4120 --subindex 1 -e bus1   # CoE SDO 0x1018:1
iaiops opcua history "ns=2;i=5" -e line1 --start 2026-06-28T08:00:00Z   # HDA
iaiops opcua monitor "ns=2;i=5" -e line1 --duration-s 20 --deadband 0.5 # CoV
iaiops diag dataflow -e line1 --ref "ns=2;i=5" --freshness-s 30
iaiops analytics oee 28800 25200 2.0 12000 11800   # OEE from five numbers you already have
                                                   # (for a measured one, see `oee measure` above)
iaiops analytics asset -e press1 -e cell5           # active asset register
CLI(写入——默认试运行,使用 --apply 时需二次确认)
iaiops s7 write-db 1 INT 0 42 -e press1            # dry-run preview
iaiops s7 write-db 1 INT 0 42 -e press1 --apply    # double-confirm prompt
iaiops mqtt publish factory/line1/cmd '{"setpoint":50}' -e uns --apply
iaiops eip write-tag Setpoint 42 -e cell5 --apply  # Logix tag write (double-confirm)
iaiops fins write-words 100 42 --area DM -e omron1 --apply  # Omron FINS write (double-confirm)
iaiops ethercat write-sdo 0 24698 e8030000 -e bus1 --apply   # CoE SDO 0x607A download
iaiops ethercat set-state PREOP --slave 0 -e bus1 --apply     # AL-state (can stop motion!)
MCP 工具调用(JSON 参数 → 结构化返回示例)

s7_read_db:

{ "db": 1, "dtype": "REAL", "start": 4, "endpoint": "press1", "count": 2 }
{ "endpoint": "press1", "area": "DB", "db": 1, "dtype": "REAL", "start": 4,
  "count": 2, "items": [ {"address": "DB1,REAL4", "value": 20.5},
                         {"address": "DB1,REAL8", "value": 4.2} ] }

s7_write_db(试运行):

{ "db": 1, "dtype": "INT", "start": 0, "value": 42, "endpoint": "press1" }
{ "address": "DB1,INT0", "dry_run": true, "before": 7, "would_write": 42,
  "note": "Dry run — nothing written. Re-run with dry_run=false AND a recorded approver…" }

mtconnect_oee_snapshot:

{ "availability": "AVAILABLE", "execution": "ACTIVE", "controller_mode": "AUTOMATIC",
  "program": "O1234", "available": true, "running": true, "verdict": "running" }

eip_read_tag:

{ "tag": "Conveyor.Speed", "endpoint": "cell5" }
{ "endpoint": "cell5", "tag": "Conveyor.Speed", "value": 1500.0, "type": "REAL",
  "error": "", "good": true }

eip_write_tag(试运行):

{ "tag": "Setpoint", "value": 42, "endpoint": "cell5" }
{ "endpoint": "cell5", "tag": "Setpoint", "dry_run": true, "before": 7,
  "would_write": 42, "note": "Dry run — nothing written. Re-run with dry_run=false AND a recorded approver…" }

ethercat_read_sdo(CoE SDO 上传):

{ "slave": 0, "index": 4120, "subindex": 1, "endpoint": "bus1" }
{ "endpoint": "bus1", "slave": 0, "index": "0x1018", "subindex": 1,
  "byte_length": 4, "hex": "9a020000", "as_uint": 666 }

ethercat_set_state(试运行;可能启动/停止运动):

{ "state": "OP", "slave": 0, "endpoint": "bus1" }
{ "endpoint": "bus1", "scope": "slave[0]", "dry_run": true, "before": "SAFEOP",
  "would_request": "OP", "note": "Dry run — no state change. … Changing EtherCAT state can start/stop machine motion." }

sparkplug_decode_payload(完整的 SpB 指标解码):

{ "payload": "CAESBwoDYWJjEAE=", "encoding": "base64" }
{ "encoding": "sparkplug_b", "seq": 0, "metric_count": 2, "historical_count": 0,
  "metrics": [ {"name": "Temperature", "alias": 1, "datatype": "Double", "value": 21.5,
                "is_historical": false, "is_null": false} ] }

oee_compute:

{ "planned_time_s": 28800, "run_time_s": 25200, "ideal_cycle_time_s": 2.0,
  "total_count": 12000, "good_count": 11800 }
{ "availability": {"raw": 0.875, "value": 0.875, "capped": false},
  "performance": {"value": 0.952381}, "quality": {"value": 0.983333},
  "oee": 0.819444, "oee_pct": 81.94 }

asset_inventory(活动指纹):

{ "endpoints": ["press1", "cell5"] }
{ "asset_count": 2, "reachable_count": 2, "method": "active_fingerprint",
  "assets": [ {"endpoint": "press1", "protocol": "s7", "vendor": "Siemens/compatible",
               "model": "CPU 1511-1 PN", "firmware": "2.8", "reachable": true,
               "last_seen": "2026-06-28T10:00:00+00:00"} ] }
诊断(供 Agent 可视化的多维 JSON)

diagnose_dataflow(endpoint="line1", ref="ns=2;i=5", freshness_threshold_s=30):

{ "verdict": "comms_ok_value_stale",
  "diagnosis": "Connected with good status, but the value is STALE (age 412s > 30s) — the source/field upstream has stopped updating this point.",
  "recommended_action": "Trace upstream: the device serves the last value fine, so suspect the source/scanner/field signal that should refresh it.",
  "hops": [ {"hop":"connect","protocol":"opcua","ok":true,"detail":"OPC-UA state=0"},
            {"hop":"read_tag","ref":"ns=2;i=5","ok":true,"detail":"5.0"},
            {"hop":"freshness","evaluated":true,"stale":true,"age_seconds":412.0} ] }

alarm_bad_actors(events=[…]):

{ "event_count": 55, "window_minutes": 0.82, "alarms_per_hour": 4024.4,
  "isa_18_2": {"ok_max":6,"manageable_max":12,"flood_min":30},
  "flood_verdict": "flood",
  "priority_distribution": {"high":50,"low":5},
  "pareto_sources_for_80pct": ["FIC101"],
  "top_offenders": [ {"source":"FIC101","count":50,"share_pct":90.9,"chattering":true,"standing":false} ],
  "chattering": ["FIC101"], "standing": [] }

tag_health(tags=[…]):

{ "evaluated": 4, "overall": "alarm", "offender_count": 3,
  "offenders": [ {"ref":"hot","latest":99,"flags":["out_of_range_alarm"],"severity":3},
                 {"ref":"flat","latest":5,"flags":["flatline"],"severity":2},
                 {"ref":"bad","latest":null,"flags":["bad_quality"],"severity":3} ] }
AI 停机根因副驾驶(旗舰功能)

downtime_root_cause 会围绕事件时间窗口关联你提供的所有证据——报警 事件、标签样本、diagnose_dataflow 判定、机器状态序列——并返回附有证据引用的建议性判定。 先读取:它会提出一项经人工批准、由 MOC 把关且可撤销的操作,但不会执行 任何操作。其设计可防止幻觉——只引用输入中实际存在的信号,根据它们与起始时刻的时间接近程度加权(原因先于 结果),证据不足时则降级为 insufficient_evidence(并附上 recommended_next_data 列表),而非猜测。

downtime_root_cause(window={"start":"2026-06-28T10:00:00Z","asset":"line1"}, alarms=[{"source":"M1_DRIVE","timestamp":"2026-06-28T09:59:52Z","message":"motor overload trip"}], tags=[{"ref":"DRV1.Torque","samples":[10,11,99,99],"alarm_high":80}], dataflow={"verdict":"healthy"}):

{ "window": {"start":"2026-06-28 10:00:00+00:00","asset":"line1","duration_s":300.0},
  "verdict": "root_cause_identified",
  "primary_cause": {
    "cause": "mechanical_fault", "confidence": 0.722, "confidence_band": "high",
    "evidence": [
      {"signal":"alarm","ref":"M1_DRIVE","at":"2026-06-28 09:59:52+00:00","lead_time_s":8.0,
       "detail":"motor overload trip","weight":0.4959},
      {"signal":"tag","ref":"DRV1.Torque","detail":"flags=out_of_range_alarm severity=3","weight":0.45} ],
    "recommended_action": "Dispatch maintenance to inspect the faulting unit; if a latch/interlock is set, the reversible step is to clear the fault and reset the latch (MOC-approved, undo captures the prior latch state)." },
  "evidence_summary": {"alarms_supplied":1,"tags_supplied":1,"dataflow_verdict":"healthy","total_evidence_items":2},
  "anti_hallucination": "Advisory only — nothing is executed. Every cited signal is present in the supplied evidence …" }

命令行中也可使用同一副驾驶:iaiops diag rca --input bundle.json,其中 bundle 为 {window, alarms?, tags?, dataflow?, state_series?}。

让它自行收集证据。 downtime_root_cause_live(命令行 iaiops diag rca-live)只需提供端点、时间窗口和要检查的 refs,随后便会自行拉取证据——跨协议 diagnose_dataflow 探测、每个 ref 的短时采样序列(以便通过 tag_health 发现平线、质量不佳或异常),以及活动的 OPC-UA 条件——然后运行同一只读建议型副驾驶。 收集到的 bundle 会在 collected_evidence 下回传(没有隐藏输入):

iaiops diag rca-live -e line1 --start 2026-06-28T10:00:00Z \
  --asset line1 --ref "ns=2;i=5" --ref "ns=2;i=6"
数据质量看门狗与 UNS 治理(只读智能分析)

另外还有两个纯分析层——无需连接实时设备即可全面测试,并可为 RCA 副驾驶提供数据。

  • data_quality_scorecard(命令行 iaiops diag dataquality)——全厂范围的数据可信度汇总:为每个标签评分 0–100,衡量其数据是否可信——检查陈旧、心跳失效(一级检查项)、质量不佳、平线、间隙和异常——然后按端点和全厂汇总,并提供问题明细及最严重问题的排名。它与流程健康状况不同:它关注的是“我能否信任这个数值”,而不是“这个数值是否报警”。heartbeat_health(命令行 iaiops diag heartbeat)是独立的看门狗运行状态检查(心跳平线意味着上游失效,即使通信看起来正常也一样)。
  • uns_topic_audit(命令行 iaiops mqtt uns-audit)——治理 UNS 主题树:检查命名规范(允许的根节点/最小深度)和主题膨胀(同一逻辑名称的大小写冲突、叶子指标散落在多个父节点下、深度异常、重复项)→ 给出 clean/minor/sprawling 判定。uns_schema_drift(命令行 iaiops mqtt uns-drift)——比较两个 Sparkplug NBIRTH 风格的快照,并将变更归类为 none/additive/breaking(指标被移除或其数据类型发生变化)。它将 UNS 定位为可治理的中立数据源,而不仅仅是一个代理。
MCP 服务
IAIOPS_MCP=opcua iaiops-mcp   # stdio transport (`iaiops mcp` is equivalent)

菜单——仅公开现场使用的协议。 工厂通常只运行 1–2 种协议; 公开全部 14 种协议会让模型面对它无法使用的工具。将 IAIOPS_MCP 设置为以逗号分隔的协议列表和/或命名配置。没有默认配置(自 0.10.0 起):直接运行 iaiops-mcp 会向 stderr 打印选择菜单(配置、协议键、 工具数量),并以状态码 2 退出,而不是默默公开 100 多个工具。每种选择默认都会包含跨协议 核心功能(OEE/停机/诊断/资产/分析)。

IAIOPS_MCP=menu         iaiops-mcp   # print the menu (selections + tool counts)
IAIOPS_MCP=opcua,modbus iaiops-mcp   # two protocols + brain
IAIOPS_MCP=fab          iaiops-mcp   # named profile (secsgem+opcua+s7+modbus)
IAIOPS_MCP=opcua        iaiops-mcp   # effectively a single-protocol MCP
IAIOPS_MCP=all          iaiops-mcp   # everything — explicit opt-in only
                                     # (logs a tool-flood warning above 100 tools)

命名入口快捷方式。 对于常见的单协议/单版本场景,每种协议和每个命名配置都有预先限定范围的控制台脚本——无需设置环境变量。每个脚本都是同一服务的轻量封装:

iaiops-mcp-opcua     # == IAIOPS_MCP=opcua    iaiops-mcp
iaiops-mcp-modbus    # == IAIOPS_MCP=modbus   iaiops-mcp
iaiops-mcp-fab       # == IAIOPS_MCP=fab      iaiops-mcp  (per-edition)
iaiops-mcp-building  # == IAIOPS_MCP=building iaiops-mcp
iaiops-mcp-brain     # == IAIOPS_MCP=brain    iaiops-mcp  (brain only, 0 protocols)

多进程站点 — 1 个脑 MCP + N 个协议 MCP。 同时运行多个协议服务(例如 iaiops-mcp-opcua + iaiops-mcp-modbus)会导致每个服务都重复包含约 30 个脑工具。为此,请运行一个专用的 iaiops-mcp-brain,并在协议服务上设置 IAIOPS_MCP_NO_BRAIN=1,以从这些服务中移除脑工具 — protocols_supported 发现工具仍会在所有地方开放:

iaiops-mcp-brain                          # the one cross-protocol brain server
IAIOPS_MCP_NO_BRAIN=1 iaiops-mcp-opcua    # lean protocol server, no brain
IAIOPS_MCP_NO_BRAIN=1 iaiops-mcp-modbus

写入授权不是 tap 的职责。 iaiops 不会通过服务开关屏蔽写入工具。是否允许写入由调用方决定 — 由 Agent 判断或通过账户/权限管理来决定 — 而 tap 的职责是确保写入准确、高效且无法绕过审计。每次调用,无论读取还是写入,也无论使用哪种前端(MCP 工具或 iaiops CLI),都会经过 @governed_tool,并在 ~/.iaiops/audit.db 中留下一条记录(除非操作员设置 IAIOPS_AUDIT_OPT_OUT=reads,此时仅跳过成功的声明式读取)。写入还会被标记为 HIGH risk_tier,并受 MOC 门控(试运行 + 二次确认 + 撤销信息捕获 + 记录批准人)。protocols_supported 会报告这一规则,让模型获知这些规则,而不是自行推断。

从 0.20.3 起,这项承诺由针对真实工具接口的契约测试来保障,而非合成替身:十个高风险写入中的每一个都会经过端到端测试,并且必须在没有批准人的情况下被拒绝,且绝不触达连接器 — “它抛出了异常”只能证明发生了异常,不能证明没有触达设备。这些测试揭示了两个问题:一次失败的调用被审计为 ok(工具返回规范的 {error, hint} 信封而非抛出异常,因此治理封装器将其视为普通返回),这也导致模式熔断器将每次失败都视为“成功”;此外,运行失控防护无法识别调用方不断重试被拒绝的请求,导致 500 次相同的拒绝写入突破了 10 次的上限。两个问题均已修复并通过测试锁定。

密封站点 — 让数据传输工具彻底消失。 IAIOPS_NO_EGRESS=1 会在注册时移除所有用于将本地或工厂数据传输到调用方指定目标的工具,因此弱模型或遭提示词注入的模型无法调用自己看不到的工具:

IAIOPS_NO_EGRESS=1 iaiops-mcp-factory   # 134 tools -> 129; 5 withheld

屏蔽的工具:stream_publish、stream_publish_event(NATS 消息总线)、uns_publish(MQTT 代理/Unified Namespace)、historian_push(外部 TSDB)、mqtt_publish(代理)、rca_narrate(将 RCA 判定 — 包括工厂标签、数值和引文 — POST 到调用方提供的模型 base_url)。这是一个数据外泄/气隙维度,而非读写授权:historian_push 的 risk_level="low" — 它不会改变工厂状态 — 但它会将遥测数据传出设备,因此此开关会屏蔽它。它仅对 MCP 工具生效,并非防火墙(读取操作仍会与工厂设备建立套接字连接)。protocols_supported 会分别报告每项规则。

明确说明范围 — 这不是防火墙:

  • 它只限制 MCP 工具。iaiops audit forward(SIEM)是 CLI 路径,任何注册表限制都无法触及;如果必须封闭这台设备,请在主机端阻止它。
  • 读取操作仍会建立出站套接字。iaiops 是网络探针——要读取任何数据,它都必须与 PLC、代理和历史数据库通信。该限制会移除那些目的是向外发送数据的工具,而不是禁用网络本身。
  • 它不会检查参数。工具要么整体可用,要么整体不可用;调用时不会检查任何内容(这正是本设计所拒绝的调用时拒绝机制)。因此,即使某个工具的默认目标是 localhost,只要它允许调用方提供目标地址,也会被隐藏——参数由模型选择。
  • 写入本地文件不属于出站传输:export_data 和 compliance_evidence_bundle 仍会公开。字节不会离开这台设备;之后如何将其取出是主机层面的事项。
  • 两个开关都只适用于 iaiops-mcp 服务(包括其各协议/各版本的入口适配器)。iaiops-energy-mcp 是独立软件包中的独立服务,目前尚不遵循这两个开关——它会镜像基础版的 brain/compliance 工具,因此在 iaiops-energy 0.1.6 及更早版本中,即使设置了 IAIOPS_NO_EGRESS=1,historian_push、rca_narrate、stream_publish 和 stream_publish_event 仍会公开。已在 iaiops-energy 0.1.7 中修复;该版本出于这一确切原因,将 iaiops>=0.17 固定为依赖版本。特此明确说明,因为以为开关已启用,远比明知开关不存在更糟。

命名配置:all · brain · fab · factory · process · building · plcnext · water · renewables · warehouse · clinical · pharma · sensor。在 MCP 客户端(例如 Claude Desktop)中,为每个服务条目设置 IAIOPS_MCP——或者直接将条目指向对应的 iaiops-mcp-<name> 脚本——每个站点/产线配置一个条目,每个条目都是精简的单协议或双协议服务。


安全与治理

  • 经过身份验证的套接字。 使用 HTTP/SSE(IAIOPS_MCP_TRANSPORT=streamable-http|sse)时,一旦设置了 IAIOPS_MCP_TOKEN(或 IAIOPS_MCP_TOKEN_FILE),每个请求都必须携带 Authorization: Bearer <token>;若绑定地址不是回环地址且未设置令牌,服务将拒绝启动。因此,随附的容器(Margo、airgap、Siemens IE、IGEL)都需要从密钥存储注入令牌;IAIOPS_ALLOWLIST_IPS 是可选的第二层防护,默认关闭(如果容器端口映射后只允许回环地址,切勿设置此项:调用方到达时的地址是桥接网关地址)。所有用户共享一个令牌,不代表用户身份;自身不提供 TLS。
  • 先读后写。 205 个工具中有 190 个是只读的(其中 2 个是已弃用的别名);另外 4 个不会更改设备,但会将数据发送到设备外(stream_publish、stream_publish_event、uns_publish、rca_narrate);historian_push 则写入历史数据库,而不是设备。10 个写入/命令工具(s7_write_db、mc_write_words、fins_write_words、mqtt_publish、eip_write_tag、ethercat_write_sdo、ethercat_set_state、profinet_dcp_set、bacnet_write_property、bas_command)对 OT 有危险:受 high risk_tier 管控,默认关闭(dry-run),需要在 CLI 中双重确认,并且必须记录审批人(一次性 iaiops approve 令牌;若未配置 risk_tiers,high/critical 操作默认使用 dual 层级)——遵守 MOC 流程。这十个工具都声明了撤销操作(自 0.20.3 起没有例外);写入成功后会捕获写入前的值/状态,并注册一个逆向操作描述符。若没有可用的撤销操作,系统会如实报告 "none"——无法撤回 retain=False 的临时 mqtt_publish 消息,而 ethercat_set_state 的 +ERR/NONE/BOOT 也无法可靠地重新请求为 AL-state。过度承诺的撤销操作比没有更糟,因为有人会将其重放到正在运行的设备上。ethercat_set_state 可以启动或停止机器运动。 未经授权勿对生产控制系统写入.
  • 读写授权由调用方决定,而不是 tap。 iaiops 不会通过隐藏工具来编码“此服务不得写入”——这应由 Agent 的判断或账户/权限管理决定。tap 的保障是两个前端都执行不可绕过的审计:每次调用,无论读写,无论通过 MCP 工具还是iaiops CLI,都会经过 @governed_tool,并在 ~/.iaiops/audit.db 中留下一条记录。IAIOPS_AUDIT_OPT_OUT=reads 是唯一例外,由操作员设置:它会停止记录成功的声明式读取;写入、预览、外发、拒绝、失败以及每条 CLI 命令仍会记录,且退出记录时会留下一条标记记录。在 iaiops-mcp 和 iaiops CLI 中,记录会注明调用所经的前端(origin:mcp 或 cli;空值表示未知,绝不可解读为人类或 Agent——适用于此字段出现前写入的记录,以及未声明此字段的服务),MCP 客户端自行报告的名称、每个会话的 ID,以及对于带有预览标志的工具,该调用是 preview 还是 apply。此外,写入还受高 risk_tier 管控、受 MOC 限制,并会捕获撤销信息(见上文)。如果审计数据库无法写入,高/critical 调用将默认拒绝执行。
  • 通过注册机制强制执行无外发模式。 对于隔离网/密封设备部署,IAIOPS_NO_EGRESS=1 会在注册时撤销 6 个会将数据发送到设备外的工具(stream_publish、stream_publish_event、uns_publish、historian_push、mqtt_publish、rca_narrate),并在无法确认时默认拒绝。此项管控的是数据外泄,而非授权——historian_push 风险较低(不会更改任何内容),但会将遥测数据推送到外部 TSDB,因此此开关会撤销该工具。纳入范围的工具根据 @governed_tool(egress=True) 元数据确定,并由 CI 中的 AST 扫描保护,因此后续添加的外发工具不会悄悄绕过此门禁。
  • Agent 只能读取操作员指定位置的文件。 14 个会读取 Agent 指定的主机文件的 MCP 工具——sensor_import_iodd、sensor_profile_check、7 个 plc_program_* 文件工具、device_advisory_check、site_readiness / onboarding_status / onboarding_config_draft 的 db= 覆盖项,以及 sink="sqlite" 时的 historian_push——会解析路径(~、.. 和所有符号链接);除非路径位于 $IAIOPS_HOME、配置目录或 IAIOPS_AGENT_READ_DIRS 列出的目录下(以 os.pathsep 分隔的绝对路径,例如 ~/exports:~/advisories;在服务的环境变量或 ~/.iaiops/.env 中设置——没有任何工具会写入其中),否则拒绝读取。相对路径、/、包含主目录的路径、归其他用户所有的路径或组用户/其他用户可写的路径条目会被忽略。拒绝信息不会透露文件的任何信息,甚至不会透露文件是否存在。CLI 可以读取操作员输入的任何路径。
  • Agent 只能将文件写入自己的导出目录。 会写入 Agent 指定文件的 MCP 工具——export_data、compliance_report、compliance_evidence_bundle、alarm_rationalization_worksheet——会以相同方式解析路径;除非路径位于 $IAIOPS_HOME/exports 或 IAIOPS_AGENT_WRITE_DIRS 列出的目录下(遵循相同规则;若条目与 $IAIOPS_HOME 或配置目录重叠,则会被忽略,因此审计、撤销和样例存储永远无法触及),否则拒绝写入。historian_push(sink="sqlite") 还会拒绝访问审计/撤销存储,以及包含 samples 以外表的任何现有数据库。在 0.31.0 之前,这些写入工具只检查 ..:export_data(fmt="sqlite", out_path="~/.iaiops/audit.db") 会替换审计数据库。
  • iaiops 只写入数值,从不写入 PLC 逻辑。 没有任何工具会下载、上传、刷写或编辑 PLC 程序——plc_program_* 工具读取的是已导出的文件。唯一会更改状态的工具是 10 个受 MOC 管控的数值写入工具(设定点、标签、寄存器、属性和状态更改),默认采用 dry-run、需要审批人批准,并会捕获写入前的值以便撤销。如果这一点不再成立,tests/test_no_plc_logic_writes.py 就会失败。
  • safety: sis——绝不写入安全仪表系统。 将端点标记为 safety: sis 后,所有写入工具都会拒绝对其执行操作,包括预览,无论已有何种审批(规则 safety_sis_no_writes,并作为拒绝事件记录);读取仍然允许。iaiops 不是安全系统,也不是 IEC 61508 / 61511 工具——SIS 的更改应遵循工厂自身的功能安全管理变更流程。
  • 未经授权,切勿将此工具指向生产控制系统。 OT 网络对安全至关重要;即使读取也会增加负载。请先在模拟器上测试。
  • 所有端点返回的文本都会经过清理(提示词注入防护);任何工具都不会返回密钥;解析 MTConnect XML 时会拒绝 DTD/实体声明。
  • 每个工具都会经过随附的治理框架:SQLite 审计(~/.iaiops/audit.db,采用 SHA-256 哈希链记录,并支持 iaiops audit verify;高/critical 写入的审计失败时默认拒绝)、令牌/调用预算和失控断路器、风险层级门禁(若 rules.yaml 损坏,策略引擎会默认拒绝),以及撤销记录。如果任何已注册工具缺少治理标记,MCP 服务将拒绝启动。

路线图

这里曾列出的四项功能实际上都已交付,其中两项就在本 README 上方三个章节中标记为已验证。把已完成的工作列为未来工作,与声称尚未实现的工作已完成是同一种问题,因此这些条目已移除: EtherNet/IP PCCC(PLC-5/SLC-500)和 Micro800、被动资产发现 (iaiops scan --posture passive,仅使用 ARP 缓存,不会发出任何流量)、 OPC-UA 证书安全,以及 MTConnect 流式长轮询。

真正尚待完成的工作:

  • EtherCAT EoE / FoE / SoE 邮箱协议,以及完整的 PDO 映射解码(CoE SDO/PDO 读写和 AL-state 已通过可选的 pysoem extra 在 0.3.0 中交付)。
  • Sparkplug B Template 深度展开(DataSet 已在 0.17.0 中交付)。
  • 情境化基线——目前每个标签只有一个正常范围,但 OT 的正常范围会随班次、产品和启动阶段而变化。按分组学习;如果样本不足,就拒绝该分组,而不是回退到全局范围,否则会把“从未见过这种工况”伪装成“这种工况属于正常”。
  • 进一步支持关系感知的根因分析——iaiops relations declare 已能为 RCA 提供声明的产线顺序,作为第二个维度;剩下的工作是让它发挥更多作用,超出时间线范围(例如默认用它进行排序)。
  • 通过 SPAN/网络分流器进行被动发现——基于 ARP 缓存的模式已实现;读取镜像端口则尚未实现。

缺少协议、设备或功能?欢迎提交 issue/PR,提出功能需求并留言——请在 GitHub issue 或 PR 中提出。

许可证

MIT © wei

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。