Skip to main content
Glama
industrial-aiops

OT-AIops

Industrial-AIOps

English · 中文

Governed, vendor-neutral industrial data tap + intelligent troubleshooting for AI agents — read-first tools across 14 field protocols in this package (17 line-wide with the energy edition): OPC-UA (incl. Historical Access + tag auto-discovery), Modbus-TCP/RTU (byte-order auto-detect + vendor templates), S7comm, Mitsubishi MC, Omron FINS (stdlib-only client), MTConnect, MQTT/Sparkplug B (full decode), EtherNet/IP (Rockwell/Allen-Bradley Logix), EtherCAT (pysoem/SOEM), PROFINET (DCP), SECS/GEM (HSMS fab), HART-IP (process instrumentation), BACnet/IP (building), and IO-Link (master JSON integration), plus two vendor-REST read-only layers above the field bus — a BAS supervisory-controller layer (Johnson Controls Metasys / Tridium Niagara, sitting above the BACnet field connector, building edition) and an Ignition Gateway MES/SCADA read layer (Inductive Automation Ignition's HTTP/Gateway web API — module health, tag browse/read, alarms, tag-history — factory edition) — plus an AI downtime root-cause copilot (with a downtime_triage composer), conservative baseline learning, ISA-18.2 alarm-flood analysis, historian READ integration (RCA pre-incident evidence), a legacy PLC program explainer (ST/AWL/L5X), nine per-industry editions (fab / factory / process / building / water / warehouse / clinical / renewables / plcnext) each carrying its own read-only advisory checks (SPC, PID control-loop, AHU economizer, line bottleneck, medical-gas, disinfection-CT …), open-format export (iaiops export CSV/SQLite/Parquet) with a Prometheus/Grafana bridge, data-quality watchdog, UNS governance, OEE/downtime, asset-inventory, and 信创 (TDengine/IoTDB historian sinks + 防护指南/等保2.0/IEC 62443 compliance mapping, report & evidence bundle). The energy edition (变电/电力: IEC-104 / DNP3 / IEC-61850) ships separately as iaiops-energy.

Industrial-AIOps is the OT member of the industrial-aiops org. It is a factory-level, vendor-neutral, governed data tap that lets an AI agent safely read industrial control systems across many field protocols, plus a cross-protocol intelligence layer that localizes "no data" breaks, analyzes alarm floods (ISA-18.2), scores data trustworthiness, ranks unhealthy tags, computes OEE / categorizes downtime, builds an active asset register, auto-discovers OPC-UA tags into a semantic asset model, and — the flagship — runs an AI downtime root-cause copilot that correlates the evidence into an evidence-cited, advisory verdict. Read-first by design; the few write/command paths are OT-dangerous and gated by MOC discipline. Every tool runs through a vendored governance harness (audit / budget / risk-tier / undo).

v0.14.0 — validation status (honest). Pure analysis + the OPC-UA path are tested against a real in-process asyncua server. The 信创 bindings were run against real libraries + containers: IoTDB + TDengine (live container write→read round-trip) are verified; the HART command codec is verified vs hart-protocol. Phoenix Contact PLCnext vPLC (virtualized PLC) is route-verified over its OPC-UA server (an in-process asyncua server reproducing the Arp.Plc.Eclr GDS address space) and a Modbus-TCP process-data block (tests/test_plcnext_route.py); live PLCnext hardware reads stay 待核实. Modbus-RTU (live serial) is verified (2026-07-02): the read ops round-trip over a real serial link built from a socat PTY pair + a pymodbus RTU server (tests/test_modbus_rtu_live.py), exercising the actual RTU framing — though not yet validated against a specific physical RS-485 device. The BACnet/IP read path is verified (2026-07-02): a genuine Who-Is discover + present-value read round-trip against a real bacpypes3 virtual BACnet/IP device on a two-IP subnet in a Linux container (tests/test_bacnet_live.py), through the actual async BAC0 (2024+) stack. The new Omron FINS connector is verified against an in-repo mock FINS UDP/TCP responder (tests/test_fins.py); the new IO-Link connector against an in-process mock master in both JSON dialects (tests/test_iolink.py). The two new vendor-REST read layers are mock-verified: the BAS controller layer against an in-repo mock supervisory controller in both vendor dialects (Metasys OpenBlue REST + Niagara oBIX/REST), and the Ignition Gateway read layer against an in-repo mock Gateway in both flavors (webdev / gateway) — live Metasys/Niagara controllers (incl. native oBIX-XML encoding) and live Ignition gateways (exact API version/paths) stay 待核实. Still 待核实 (preview, not hardware-verified): live Omron PLCs (incl. banked-EM access), live IO-Link master datapoint paths, BACnet write/COV/trend on live HVAC, HART-IP wire transport (live gateway), EtherCAT (no software simulator — Linux + root + a real bus only), physical Modbus-RTU RS-485 devices, live PLCnext. Mocked clients cover S7/MC/EtherNet-IP/SECS-GEM; MTConnect uses static XML fixtures; Sparkplug uses synthetic protobuf payloads. (The energy edition's IEC-104 / DNP3 / IEC-61850 validation lives in the iaiops-energy repo.) See Safety.

Why

OT is exactly where you want an agent on a tight leash: read first, never blind-write. Industrial-AIOps is the safe, neutral read wedge — one package, one MCP server, many protocols — with governance and an intelligence layer that turns raw reads into actionable diagnoses.

Related MCP server: OPC UA MCP Server

🧪 测试与共创 / Beta testing & co-creation

我们在找现场测试伙伴。 软件里能验证的我们都验证了(真实 in-process 服务器、真实协议库、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 直接共创。

We're looking for field-testing partners. Everything software-verifiable has been verified; what's left on the honest 待核实 list only real equipment can answer — physical Modbus-RTU (RS-485), EtherCAT slaves, HART gateways, live BACnet HVAC, live Metasys/Niagara BAS controllers, live Ignition gateway, domestic PLCs (Inovance/Xinje), live PLCnext, substation RTUs/IEDs, live Omron FINS PLCs, IO-Link masters. If you're an OT engineer, integrator, or factory team with access to any of these: install iaiops, run iaiops doctor against your gear, and tell us what happened. Verified-equipment reports get credited in the support matrix, field-reported issues get fast triage, and features are co-designed in the open via GitHub Issues/Discussions.

👉 参与入口 | Start here: #28 — 招募现场测试伙伴 | Call for field-testing partners (v0.10.0) (pinned)


Consolidated capability matrix

Protocol

Tool

Operation

R/W

risk_tier

Returns (key fields)

OPC-UA

opcua_server_info

server status

R

low

state, product_name, namespaces

OPC-UA

opcua_browse

browse node tree

R

low

[{node_id, browse_name, depth}]

OPC-UA

opcua_read_node

read one node

R

low

value, datatype, source_timestamp, good

OPC-UA

opcua_read_many

batch read

R

low

[{node_id, value, ...}]

OPC-UA

opcua_subscribe_sample

bounded sample

R

low

{collected, samples[]}

OPC-UA

opcua_read_alarms

alarm surfacing

R

low

{active_alarms[], active_count}

OPC-UA

opcua_read_history

Historical Access (HDA)

R

low

{supported, count, values[]}

OPC-UA

opcua_diagnose_connection

connection triage

R

low

{verdict, checks[]}

OPC-UA

opcua_discover_tags

tag auto-discovery → semantic asset model

R

low

{tag_count, assets[], naming_report}

OPC-UA

opcua_health_summary

threshold classify (was health_summary¹)

R

low

{overall, counts, offenders[]}

OPC-UA

opcua_anomaly_scan

stddev outliers (was anomaly_scan¹)

R

low

{mean, stddev, outliers[]}

Modbus

modbus_read_holding

FC03

R

low

{raw_registers, decoded[]}

Modbus

modbus_read_input

FC04

R

low

{raw_registers, decoded[]}

Modbus

modbus_read_coils

FC01

R

low

{bits[]}

Modbus

modbus_read_discrete

FC02

R

low

{bits[]}

Modbus

modbus_detect_byte_order

byte/word-order auto-detect

R

low

{best_order, candidates[]}

Modbus

modbus_list_templates

vendor register templates

R

low

{templates[]}

Modbus

modbus_apply_template

decode block via template

R

low

{values:{name: engineering_value}}

Modbus

modbus_health_summary

threshold classify

R

low

{overall, counts, offenders[]}

S7comm

s7_cpu_info

CPU id + run/stop

R

low

{cpu_status, cpu_info}

S7comm

s7_read_area

read DB/M/I/Q

R

low

{items:[{address, value}]}

S7comm

s7_read_db

read data block

R

low

{items:[{address, value}]}

S7comm

s7_read_many

batch addresses

R

low

{items:[{address, value}]}

S7comm

s7_write_db

write data block

W

high/MOC

{before, written, _undo_id}

Mitsubishi MC

mc_cpu_status

CPU type

R

low

{cpu_type, cpu_code}

Mitsubishi MC

mc_read_words

word devices

R

low

{words[]}

Mitsubishi MC

mc_read_bits

bit devices

R

low

{bits[]}

Mitsubishi MC

mc_read_many

random read

R

low

{words[], dwords[]}

Mitsubishi MC

mc_write_words

write words

W

high/MOC

{before, written, _undo_id}

Omron FINS

fins_cpu_info

controller data read (0501)

R

low

{controller_model, controller_version}

Omron FINS

fins_cpu_status

controller status (0601)

R

low

{run_mode, status}

Omron FINS

fins_read_words

memory-area word read (DM/CIO/W/H/A/EM)

R

low

{words[]}

Omron FINS

fins_read_bits

memory-area bit read

R

low

{bits[]}

Omron FINS

fins_read_many

batch reads

R

low

{items[]}

Omron FINS

fins_write_words

memory-area write

W

high/MOC

{before, written, _undo_id}

MTConnect

mtconnect_probe

device model

R

low

{devices:[{components:[{data_items}]}]}

MTConnect

mtconnect_current

latest values

R

low

{observations[]}

MTConnect

mtconnect_sample

bounded stream

R

low

{observations[]}

MTConnect

mtconnect_assets

assets

R

low

{assets[]}

MTConnect

mtconnect_oee_snapshot

OEE inputs

R

low

{availability, execution, verdict}

MQTT/Sparkplug

mqtt_read_topic

bounded read

R

low

{messages:[{topic, payload}]}

MQTT/Sparkplug

sparkplug_subscribe_sample

bounded SpB sample (full decode)

R

low

{samples:[{sparkplug, payload:{metrics[]}}], seq_gap_count}

MQTT/Sparkplug

sparkplug_decode_payload

decode raw SpB payload

R

low

{metrics:[{name, alias, datatype, value, is_historical}]}

MQTT/Sparkplug

sparkplug_node_list

node discovery + state

R

low

{nodes:[{group_id, edge_node_id, online, devices}], primary_hosts[]}

MQTT/Sparkplug

uns_browse

topic-tree browse

R

low

{topics[], tree{}}

MQTT/Sparkplug

uns_topic_audit

UNS naming + sprawl governance

R

low

{verdict, sprawl_findings, findings{casing_collisions[], scattered_leaves[], …}}

MQTT/Sparkplug

uns_schema_drift

Sparkplug schema-drift (baseline vs current)

R

low

{verdict (none/additive/breaking), node_changes[]}

MQTT/Sparkplug

uns_live_audit

live UNS audit (bounded broker sample)

R

low

{verdict, findings{}}

MQTT/Sparkplug

sparkplug_live_schema

live NBIRTH schema snapshot

R

low

{nodes[], metrics[]}

MQTT/Sparkplug

uns_live_drift

live drift vs stored baseline

R

low

{verdict, node_changes[]}

MQTT/Sparkplug

mqtt_publish

publish/command

W

high/MOC

{published_bytes, applied}

EtherNet/IP

eip_controller_info

Logix controller id

R

low

{controller:{vendor, product_name, revision, serial}}

EtherNet/IP

eip_list_tags

tag discovery

R

low

{tag_count, tags:[{name, data_type, structure}]}

EtherNet/IP

eip_read_tag

read one tag/array

R

low

{tag, value, type, good}

EtherNet/IP

eip_read_many

batch read

R

low

{items:[{tag, value, type}]}

EtherNet/IP

eip_write_tag

write tag

W

high/MOC

{before, written, _undo_id}

Diagnostics

diagnose_dataflow

localize no-data

R

low

{verdict, diagnosis, hops[]}

Diagnostics

alarm_bad_actors

ISA-18.2 flood

R

low

{flood_verdict, top_offenders[]}

Diagnostics

tag_health

offender ranking

R

low

{overall, offenders[]}

Diagnostics

historian_health

gap/flatline

R

low

{verdict, gaps[]}

Diagnostics

subscription_health

sequenced-feed loss/reorder/overload

R

low

{verdict, missed_count, overloaded_channels[]}

Diagnostics

downtime_root_cause

AI downtime RCA copilot (cited, advisory)

R

low

{verdict, primary_cause, hypotheses:[{cause, confidence, evidence[]}]}

Diagnostics

downtime_root_cause_live

RCA copilot that gathers its own live evidence

R

low

{…downtime_root_cause…, collected_evidence}

Diagnostics

learn_cause_weights

learn per-site RCA cause weights from labeled incidents

R

low

{cause_weights{}, rationale}

Diagnostics

data_quality_scorecard

fleet data-trust rollup

R

low

{fleet_score, fleet_status, issue_breakdown, worst_tags[], endpoints[]}

Diagnostics

data_quality_fleet_rollup

cross-endpoint fleet view

R

low

{fleet_score, endpoints[]}

Diagnostics

heartbeat_health

heartbeat/watchdog liveness

R

low

{alive, distinct_transitions, longest_stall_s, reason}

Alarm (ISA-18.2)

alarm_flood_analysis

flood episodes / chattering / stale / summary

R

low

{episodes[], chattering[], stale[], summary{}}

Alarm (ISA-18.2)

alarm_rationalization_worksheet

CSV-exportable rationalization rows

R

low

{rows[], csv_path?}

Baseline

baseline_learn

conservative change-log baseline (refuses thin history)

R

low

{band{p1,p99,median,mad} | insufficient_data}

Baseline

baseline_check

silent-by-default violation check

R

low

{status, violations[] (cited)}

Baseline

baseline_record_change

record operator change (restarts learning)

R

low

{recorded, change_point}

Baseline

baseline_status

no_baseline / learning / ok / violation

R

low

{status, window}

Historian

historian_query

read history back out of sqlite/TDengine/IoTDB

R

low

{rows[], truncated}

Historian

historian_coverage

per-tag row counts + first/last ts

R

low

{tags:[{tag, rows, first, last}]}

PLC program

plc_program_outline

structure of exported ST/AWL/L5X program

R

low

{blocks[], call_graph, timers[]}

PLC program

plc_program_xref

symbol/address cross-reference (cited lines)

R

low

{sites:[{kind, source_file, line, quote}]}

PLC program

plc_program_section

one named block's source (≤200 lines)

R

low

{text, source_file}

Export

export_data

export local store → CSV/SQLite/Parquet

R

low

{path, row_count, preview[]}

Analytics

oee_compute

OEE = A×P×Q

R

low

{availability, performance, quality, oee, oee_pct}

Analytics

downtime_events

stoppage detect + categorize

R

low

{event_count, total_downtime_s, by_category, events[]}

Analytics

oee_multidim

OEE machine×part×shift

R

low

{matrix[], worst_performers[], mean_oee}

Analytics

asset_inventory

active fingerprint

R

low

{assets:[{protocol, vendor, model, firmware, reachable}]}

Analytics

cross_protocol_asset_model

merge discovered tags into one asset model

R

low

{assets[], tag_count}

Analytics

adopt_alias_map / diff_alias_map

tag alias-map adopt/diff

R

low

{aliases{}, changes[]}

Analytics

monitor_changes

bounded change-of-value

R

low

{change_count, changes:[{value, previous, wall_clock}]}

EtherCAT

ethercat_master_state

master/WKC + slave count

R

low

{master_state, expected_working_counter, slaves_found, slaves_expected}

EtherCAT

ethercat_slaves

bus scan

R

low

{slave_count, slaves:[{index, name, vendor_id, product_code, state}]}

EtherCAT

ethercat_slave_info

slave detail

R

low

{sync_managers[], fmmus[], object_dictionary[], input_bytes}

EtherCAT

ethercat_read_sdo

CoE SDO upload

R

low

{index, byte_length, hex, as_uint}

EtherCAT

ethercat_read_pdo

input PDO snapshot

R

low

{working_counter, input_hex, input_byte_length}

EtherCAT

ethercat_write_sdo

CoE SDO download

W

high/MOC

{before, written, applied}

EtherCAT

ethercat_set_state

AL-state transition

W

high/MOC

{before, requested, reached, applied}

PROFINET

profinet_discover

DCP IdentifyAll (segment-wide)

R

low

{station_count, stations:[{name_of_station, mac, ip, vendor_id, device_roles[]}]}

PROFINET

profinet_identify_station

identify by name-of-station

R

low

{found, name_of_station, mac, ip, device_family}

PROFINET

profinet_station_params

targeted DCP Get (by MAC)

R

low

{found, name_of_station, ip, netmask, gateway}

PROFINET

profinet_asset_inventory

DCP asset register

R

low

{asset_count, io_controller_count, assets[]}

PROFINET

profinet_dcp_set

DCP Set (station name / IP suite)

W

high/MOC

{before, applied, _undo_id}

SECS/GEM

secsgem_equipment_status

GEM link + identity (S1F1/F2)

R

low

{communication_state, are_you_there}

SECS/GEM

secsgem_list_status_variables

SVID namelist (S1F11/F12)

R

low

{count, status_variables[]}

SECS/GEM

secsgem_read_status_variables

SVID values (S1F3/F4)

R

low

{svids, values[]}

SECS/GEM

secsgem_list_equipment_constants

ECID namelist (S2F29/F30)

R

low

{count, equipment_constants[]}

SECS/GEM

secsgem_read_equipment_constants

ECID values (S2F13/F14)

R

low

{ecids, values[]}

SECS/GEM

secsgem_list_alarms

alarm list (S5F5/F6)

R

low

{count, alarms[]}

SECS/GEM

secsgem_list_process_programs

PPID directory (S7F19/F20)

R

low

{count, process_programs[]}

BACnet (building)

bacnet_discover

Who-Is device discovery

R

low

{device_count, devices:[{device_id, address}]}

BACnet (building)

bacnet_object_list

a device's objects

R

low

{object_count, objects:[{object_type, instance}]}

BACnet (building)

bacnet_read_property

one object property

R

low

{object_type, instance, property, value}

BACnet (building)

bacnet_read_points

all present-values (HVAC snapshot)

R

low

{point_count, points:[{object_type, instance, present_value}]}

BACnet (building)

bacnet_cov_subscribe

bounded COV capture (always unsubscribes)

R

low

{notifications[], terminated_reason}

BACnet (building)

bacnet_read_trend_log

TrendLog readRange (bounded)

R

low

{records:[{timestamp, value}]}

BACnet (building)

bacnet_write_property

present-value write (priority)

W

high/MOC

{before, written, _undo_id}

HART-IP (process)

hart_device_identity

cmd 0 identity

R

low

{manufacturer, device_type, revision}

HART-IP (process)

hart_primary_variable

cmd 1 PV

R

low

{value, unit}

HART-IP (process)

hart_dynamic_variables

cmd 3 PV/SV/TV/QV + loop current

R

low

{variables[], loop_current}

HART-IP (process)

hart_burst_sample

bounded burst-variable sampling

R

low

{samples[]}

IO-Link

iolink_master_info

master identity

R

low

{vendor, product, serial}

IO-Link

iolink_ports

≤32-port sweep (mode/status/device id)

R

low

{ports[]}

IO-Link

iolink_device_info

per-port device identity

R

low

{vendor_id, device_id, product_name}

IO-Link

iolink_read_pdin

process-data-in (raw hex + bytes)

R

low

{hex, bytes[]}

IO-Link

iolink_read_isdu

ISDU acyclic parameter read

R

low

{index, subindex, value}

IO-Link

iolink_scan

master + all connected devices

R

low

{master{}, devices[]}

BAS (Metasys/Niagara)

bas_point_list

supervisory point directory

R

low

{point_count, points:[{id, name, type}]}

BAS (Metasys/Niagara)

bas_point_read

read one supervisory point

R

low

{point, value, unit, status}

BAS (Metasys/Niagara)

bas_alarm_list

active controller alarms

R

low

{alarm_count, alarms:[{id, priority, state}]}

BAS (Metasys/Niagara)

bas_trend_read

trend/history samples (bounded)

R

low

{records:[{timestamp, value}]}

BAS (Metasys/Niagara)

bas_command

supervisory command (default-OFF; life-safety object denylist refuses fire/smoke/egress/pressurization before any I/O)

W

high/MOC

{before, written, _undo_id}

Ignition

ignition_gateway_status

Gateway + module health

R

low

{state, version, modules:[{name, state}]}

Ignition

ignition_tag_browse

tag-tree browse

R

low

{tags[], tree{}}

Ignition

ignition_tag_read

current tag values

R

low

{values:[{path, value, quality, timestamp}]}

Ignition

ignition_alarm_status

active alarms

R

low

{alarm_count, alarms:[{path, priority, state}]}

Ignition

ignition_tag_history

tag-history query (bounded)

R

low

{rows:[{path, timestamp, value}]}

信创 / compliance

compliance_mapping

《工控网络安全防护指南》↔ iaiops

R

low

{pillars[], status_summary, controls:[{pillar, status, gap}]}

信创 / compliance

compliance_frameworks

等保 2.0 + IEC 62443 FR1–6 crosswalk

R

low

{controls:[{crosswalk}]}

信创 / compliance

compliance_dengbao_levels

等保 二级 baseline vs 三级 增量

R

low

{pillars:[{l2, l3_delta, status}]}

信创 / compliance

compliance_report

deliverable compliance report (md/html)

R

low

{markdown | out_path}

信创 / compliance

compliance_evidence_bundle

audit-evidence zip (hash-chain verified)

R

low

{bundle_path, manifest}

信创 / historian

historian_push

push telemetry to sqlite/TDengine/IoTDB

R(→historian)

low

{sink, received, written, skipped_non_numeric}

Self

protocols_supported

capability map

R

low

{protocols[], diagnostics[], analytics[]}

(The energy protocols — IEC-104 / DNP3 / IEC-61850 — moved to iaiops-energy in 0.8.0; their tool matrix lives in that repo.)

166 governed tools = 156 read + 10 MOC-gated writes (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). The read side now includes two vendor-REST read-only layers above the field protocols — a BAS controller layer (Metasys/Niagara, building edition) and an Ignition Gateway MES/SCADA layer (factory edition). ¹ The 156 reads include the two deprecated brain aliases health_summary / anomaly_scan, renamed to opcua_health_summary / opcua_anomaly_scan in 0.10.0 — the deprecated aliases are still registered and will be removed in a future release (target: 1.0.0). Read-only per-edition tools load ONLY under their edition (see per-edition tool modules below), so a bare protocol / single-edition surface is smaller than this line-wide total. The table above is representative, not exhaustive; run protocols_supported() (or iaiops protocols) for the live map.


Per-protocol reference

OPC-UA

  • Versions/variants: binary opc.tcp:// via asyncua (sync facade). Security: anonymous + username/password, plus application-certificate message security (Sign / SignAndEncrypt) — set client_cert + client_key (+ optional server_cert) and the client opens a signed/encrypted secure channel (no cert ⇒ the anonymous / username path is unchanged). Validated end-to-end against an in-process asyncua server (tests/test_opcua_security.py) for Basic256Sha256 in both Sign and SignAndEncrypt modes: server_cert pinning and client-side server-cert auto-discovery are exercised, and the test asserts the negotiated policy URI + message-security mode on the live encrypted channel (plus a negative test that anonymous is refused by a secure-only server).

  • Connection params: endpoint_url, username (password encrypted), security_mode, security_policy; for cert security client_cert / client_key / optional server_cert (PEM or DER paths; aliases certfile / keyfile).

  • Alarms & Conditions: opcua_alarm_events — bounded event subscription + ConditionRefresh, events carry the server's own timestamps (verified against an in-process asyncua server; third-party A&C servers 待核实). Untimed fallback: opcua_read_alarms browses alarm-like boolean nodes.

  • Not supported / planned (待核实): cert-security interop with third-party / vendor servers (KEPServerEX / Prosys / Siemens / real PLCs), the other policies (Aes128Sha256RsaOaep / Aes256Sha256RsaPss / Basic128Rsa15 / Basic256), strict server-side certificate-trust enforcement, and cert-based user identity (X509 identity token, distinct from channel security).

Modbus-TCP / Modbus-RTU

  • Versions/variants: Modbus-TCP and Modbus-RTU (serial RS-485/232) via pymodbus (+ pyserial). Read function codes FC01 (coils), FC02 (discrete), FC03 (holding), FC04 (input). Write FCs (FC05/06/15/16) = not implemented (read-only).

  • Connection params: TCP — host, port (502), unit_id. RTU — transport: rtu, serial_port (e.g. /dev/ttyUSB0), baudrate, unit_id. Registers are untyped 16-bit words → decode hint (uint16/int16/uint32/int32/float32/raw); modbus_detect_byte_order auto-detects the byte/word order (AB/BA · ABCD/DCBA/BADC/CDAB) from hint values — pure logic, no extra device load.

  • Vendor register templates (modbus_list_templates / modbus_apply_template): named register maps decoding a block into engineering values — energy meters (Eastron SDM630, Schneider PM5xxx, Carlo Gavazzi EM24), PV inverters (Huawei SUN2000, Growatt), Phoenix PLCnext process data, and water-industry templates (E+H Promag, Hach SC controller, generic dosing pump). Each template carries an explicit 待核实 caveat — no invented "verified" addresses.

  • Coverage: many domestic 国产 PLCs (汇川 Inovance / 信捷 Xinje / 和利时 Hollysys / 台达 Delta) and any Modbus vendor. RTU framing is live-verified over a real serial link (socat PTY + pymodbus RTU server); specific physical RS-485 devices stay 待核实.

S7comm (Siemens + 仿西门子 国产)

  • Versions/variants: pyS7 (pure-Python, ISO-on-TCP / RFC1006 — no native libsnap7). S7-300/400/1200/1500 and compatible clones. Memory areas DB / M (merker) / I / Q. No protocol auth (CPU gates via "Permit access with PUT/GET").

  • Connection params: host, port (102), rack, slot (0/1 for 1200/1500; 0/2 common for 300/400).

  • Write: s7_write_db = high risk_tier, MOC, dry-run default, captures BEFORE value + undo.

  • Not supported / planned: optimized/symbolic DB access on 1500 with "optimized block access" can require absolute-addressing config on the CPU.

Mitsubishi MC

  • Versions/variants: pymcprotocolMC 3E frame (binary) only. 1E / 4E frames = not supported. PLC types Q / L / QnA / iQ-R / iQ-L. Devices: D/W/R (word), M/X/Y/B (bit).

  • Connection params: host, port (5007 default; set to the module's open MC port), plctype.

  • Write: mc_write_words = high/MOC/dry-run default, captures BEFORE + undo.

Omron FINS (CS/CJ/CP/NX-via-FINS)

  • Versions/variants: in-repo, stdlib-only FINS client (no third-party dependency — the iaiops[fins] extra pins nothing): 10-byte FINS header framing, FINS/UDP (default port 9600) and FINS/TCP (node-address handshake per Omron W342), SID matching, bounded response parsing, end-code table per W227/W342. Commands: 0101 memory-area read (words/bits over DM/CIO/W/H/A/EM), 0102 write, 0501 controller data read, 0601 controller status.

  • Connection params: host, port (9600), transport (udp default / tcp), FINS network/node/unit addressing.

  • Write: fins_write_words = high/MOC/dry-run default, captures BEFORE + undo; CLI double-confirm on --apply.

  • Validation: verified against an in-repo mock FINS UDP/TCP responder (tests/test_fins.py); live Omron PLC behaviour and banked-EM access stay 待核实.

  • Versions/variants: sensor-level visibility via the IO-Link master's HTTP/JSON interface (IO-Link consortium "JSON Integration"), both dialects selectable per endpoint via flavor:iotcore (ifm IoT-Core POST envelope, default) and rest (plain-REST GET, Balluff/Turck-style). Reads: master identity, bounded ≤32-port sweep, per-port device identity, process-data-in (raw hex + bytes), ISDU acyclic parameter read. NO write tools. Bounded/size-capped HTTP (256 KiB response cap), schema-checked JSON with teaching errors. Reuses the MTConnect HTTP pin (iaiops[iolink]requests).

  • Connection params: master host/URL, flavor, timeout_s. protocol: iolink.

  • Validation: in-process mock master in both flavors (tests/test_iolink.py); live master datapoint paths stay 待核实.

HART-IP (process instrumentation — read-only)

  • Versions/variants: HART universal commands over HART-IP UDP (default, port 5094) or TCP (transport: tcp, length-delimited framing) via an in-tree transport; the HART command codec is verified vs hart-protocol. Tools: hart_device_identity (cmd 0), hart_primary_variable (cmd 1), hart_dynamic_variables (cmd 3, PV/SV/TV/QV + loop current), hart_burst_sample (bounded sampling of burst-published variables). No write / device-specific commands exposed (OT-dangerous on live instruments).

  • Connection params: host (HART-IP server/gateway), port (5094), transport (udp default / tcp).

  • Validation: TCP transport loopback-verified (in-process HART-IP server, real long-frame ACK through the real codec path); live gateway behaviour and a true unsolicited burst subscription stay 待核实.

MTConnect (ALL CNC machine tools)

  • Versions/variants: agent REST + XML (requests + xml.etree), namespace-agnostic (parses MTConnect 1.x Devices/Streams/Assets schemas). Endpoints: /probe, /current, /sample, /assets. Read-only by specification. XML parsing is hardened (DTD/entity declarations rejected — XXE/billion-laughs defense).

  • Connection params: agent_url (e.g. http://host:5000).

  • Not supported / planned: MTConnect streaming (long-poll interval=); only bounded count= samples.

MQTT / Sparkplug B / UNS

  • Versions/variants: paho-mqttMQTT 3.1.1 & 5. Sparkplug B topic convention spBv1.0/{group}/{type}/{edge}/[device] (NBIRTH/DBIRTH/NDATA/DDATA/NDEATH/DDEATH/STATE). TLS + username/password supported.

  • Full Sparkplug B decode (no optional extra): payloads are protobuf-decoded with a vendored, byte-for-byte copy of the official Eclipse Tahu sparkplug_b.proto generated module (depends only on protobuf). Per metric you get name, alias (resolved to its name via the BIRTH model), datatype (Int8…Int64/UInt…/Float/Double/Boolean/String/DateTime/Text/UUID/DataSet/Bytes/File/Template/PropertySet…), value, timestamp, and the is_historical / is_null flags. A birth/death + seq model tracks node/device online state (NBIRTH/DBIRTH ↔ NDEATH/DDEATH), builds the alias→name map from BIRTH, applies NDATA/DDATA by alias, and flags seq gaps / out-of-order. Primary-host awareness: STATE/<host_id> topics surface in sparkplug_node_list. sparkplug_decode_payload decodes a single raw payload (base64/hex) offline.

  • Connection params: host/broker, port (1883 / 8883 TLS), topic, use_tls, username (password encrypted).

  • Command: mqtt_publish = high/MOC/dry-run default; a published command has no automatic inverse.

EtherNet/IP (Rockwell / Allen-Bradley)

  • Supported: ControlLogix / CompactLogix (and GuardLogix) via CIP / EtherNet-IP using pycomm3 (pure-Python — no native deps). Tag-based, symbolic access: read/write tags by name (Conveyor.Speed, Array[3], Program:Main.X) and discover the controller's tag list at runtime (eip_list_tags, the headline feature). eip_controller_info reads the controller identity.

  • Connection params: host, slot (0 for CompactLogix; the CPU slot for a ControlLogix chassis), port (44818). protocol: ethernetip (alias eip).

  • Write: eip_write_tag = high risk_tier, MOC, dry-run default, captures BEFORE value + undo.

  • Not supported / planned: PLC-5 / SLC-500 (PCCC) and Micro800 are not supported = roadmap (Logix tag model only).

EtherCAT (pysoem / SOEM fieldbus master)

  • Supported: a real EtherCAT master via pysoem (the Python binding for the SOEM C stack). CoE SDO read (ethercat_read_sdo, acyclic mailbox upload) + SDO write (ethercat_write_sdo, download), input PDO read (ethercat_read_pdo, one bounded cyclic snapshot), bus scan / slave enumeration (ethercat_slaves, ethercat_slave_info — identity, SM/FMMU mapping, object-dictionary summary), master/working-counter state (ethercat_master_state), and AL-state transitions INIT↔PREOP↔SAFEOP↔OP (ethercat_set_state).

  • HARD REQUIREMENTS (no way around them): Linux, root or CAP_NET_RAW, a dedicated NIC cabled to the bus, and real EtherCAT slave hardware. pysoem is an OPTIONAL extra: pip install iaiops[ethercat] — the base package installs and imports without it, and every EtherCAT tool then degrades to a teaching error (never crashes, never imports pysoem at module load).

  • NOT supported: no software simulator exists (unlike OPC-UA / Modbus) — EtherCAT is hardware-only and not testable in mock-only CI; macOS is unsupported. EoE / FoE / SoE mailbox protocols and full PDO-mapping decode/expansion = roadmap.

  • Connection params: nic (the dedicated interface name, e.g. eth1; alias interface), optional expected_slaves (a sanity check vs the bus scan). protocol: ethercat.

  • Operations matrix:

    Tool

    Op

    R/W

    risk

    Capture/notes

    ethercat_master_state

    master + WKC state, slave count

    R

    low

    expected vs found

    ethercat_slaves

    bus scan / enumerate

    R

    low

    index/vendor/product/rev/addr/AL-state

    ethercat_slave_info

    one-slave detail

    R

    low

    SM/FMMU + OD summary

    ethercat_read_sdo

    CoE SDO upload

    R

    low

    hex + uint interpretation

    ethercat_read_pdo

    input PDO snapshot

    R

    low

    single cycle, never loops

    ethercat_write_sdo

    CoE SDO download

    W

    high/MOC

    before-value (SDO read-back) + undo

    ethercat_set_state

    AL-state transition

    W

    high/MOC

    before-state + undo; can start/stop motion

  • Write/state safety: ethercat_write_sdo (hex little-endian bytes) and ethercat_set_state are high risk_tier, MOC, dry-run by default, capture the BEFORE value/state for undo, and need a CLI double-confirm. Changing EtherCAT state can START or STOP machine motion — treat with extreme care. 未经授权勿对生产控制系统写入.

PROFINET (DCP discovery / identify + gated DCP Set)

  • Supported: layer-2 PROFINET-DCP via pnio-dcpprofinet_discover (DCP IdentifyAll: one broadcast surfaces every station on the segment — name-of-station, MAC, IP, vendor/device id, role — closer to passive discovery than a per-device fingerprint), profinet_identify_station (by name-of-station), profinet_station_params (targeted DCP Get by MAC → name + IP suite), and profinet_asset_inventory (a register with IO-controller vs IO-device role decoding).

  • Write: profinet_dcp_set re-addresses one station (name-of-station and/or IP suite, by MAC) — high risk_tier, MOC, dry-run default, captures the BEFORE addressing + undo descriptor. Re-addressing a live station can disrupt its IO connection.

  • Scope (deliberate): no RT cyclic process data (that needs an IO-controller/IO-device stack and hard real-time — out of scope and unsafe to tap); the blink / factory-reset DCP services stay unexposed.

  • HARD REQUIREMENTS: raw-socket access (root / admin / CAP_NET_RAW) on the NIC on the PROFINET subnet. pnio-dcp is an OPTIONAL extra: pip install iaiops[profinet] — the base package installs/imports without it, and every tool then degrades to a teaching error.

  • Connection params: hostTHIS machine's IP on the PROFINET subnet (the DCP broadcast goes out on it). protocol: profinet.

  • Preview caveat: validated against a mocked pnio-dcp DCPnot verified against live PROFINET devices yet.

Energy edition (electrical substation / utility telecontrol) → iaiops-energy

The energy vertical — IEC 60870-5-104 / DNP3 / IEC 61850 MMS read-only monitoring for substation RTUs/IEDs — moved to its own package in 0.8.0: iaiops-energy (pip install iaiops-energy), built on iaiops.core (shared governance / brain / runtime). Its protocol reference, support matrix, and validation status live in that repo.

Building edition (facility / HVAC / 厂务)

The building vertical adds BACnet/IP (ASHRAE 135) — the dominant building-automation protocol for HVAC, lighting, metering, and facility plant. Install with pip install iaiops[building] and expose with IAIOPS_MCP=building (bundle: bacnet + modbus + opcua + iolink).

  • BACnet/IP (BAC0 over bacpypes3): bacnet_discover (Who-Is device discovery), bacnet_object_list (a device's objects), bacnet_read_property (one object property), bacnet_read_points (present-value of all analog/binary/multistate points — the HVAC snapshot), bacnet_cov_subscribe (bounded change-of-value capture — capped by count AND wall-clock, always unsubscribes), bacnet_read_trend_log (TrendLog buffered records via one bounded readRange). Config: host = THIS machine's BACnet/IP interface (ip or ip/mask) / port (47808).

  • Write: bacnet_write_property (present-value at a BACnet priority 1..16, or relinquish) = high risk_tier, MOC, dry-run default, BEFORE-value read-back + undo. Overriding a live building-control point can move real HVAC/plant.

  • Validation: the read path is verified against a real bacpypes3 virtual BACnet/IP device through the actual async BAC0 stack (tests/test_bacnet_live.py); COV / trend-log / writes on live HVAC gear stay 待核实.

Water treatment edition (水处理)

IAIOPS_MCP=water (or iaiops-mcp-water, pip install iaiops[water]) exposes modbus + opcua + hart + the brain — the protocol set waterworks / wastewater plants actually run. Adds water-domain tag semantics (溶解氧 DO / ORP / 余氯 chlorine / 氨氮 ammonia / TSS/MLSS / 跨膜压差 TMP / UV / 加药 dosing / 曝气 aeration) and water-industry Modbus templates (E+H Promag, Hach SC controller, generic dosing pump — all with explicit 待核实 caveats).

Warehouse / intralogistics edition (仓储 / 物料搬运)

IAIOPS_MCP=warehouse (or iaiops-mcp-warehouse, pip install iaiops[warehouse]) exposes eip + profinet + modbus + opcua + sparkplug + the brain — conveyor & sorter drives over EtherNet/IP (Rockwell) and Profinet (Siemens), VFD / energy meters over Modbus (conveyor_vfd / agv_battery templates), WMS/WCS gateways over OPC-UA, and AMR/IoT telemetry over MQTT-Sparkplug. Edition tools (read-only, advisory): line_bottleneck (Theory-of-Constraints throughput bottleneck across stations) + sortation_health. PdM (pdm_forecast), downtime_triage and OEE are reused as-is.

Clinical-facility edition (医疗设施)

IAIOPS_MCP=clinical (or iaiops-mcp-clinical, pip install iaiops[clinical]) exposes bacnet + modbus + opcua + the brain — hospital facilities as a distinct patient-safety vertical over the building brain. Edition tools (read-only, advisory): isolation_room_check (负压/正压 isolation-room pressurization), medical_gas_check (medical-gas alarm-panel safety), or_environment_check (OR temperature / humidity / pressure envelope). BACnet BMS + Modbus gas-alarm panels + OPC-UA plant SCADA.

Renewables edition (光伏 / 风电)

IAIOPS_MCP=renewables (or iaiops-mcp-renewables, pip install iaiops[renewables]) exposes modbus + opcua + sparkplug + the brain — PV inverters (SUN2000 / Growatt templates) + wind-turbine controllers over Modbus, OPC-UA plant SCADA, and MQTT-Sparkplug telemetry. Edition tool (read-only, advisory): pv_performance (PV string performance vs expectation). Device-level monitoring + PdM via baseline / RCA.

PLCnext packaging edition (Phoenix Contact vPLC)

IAIOPS_MCP=plcnext (or iaiops-mcp-plcnext, pip install iaiops[plcnext]) exposes opcua + modbus + the brain — the Phoenix Contact PLCnext virtualized PLC reached over its built-in OPC-UA server (opc.tcp 4840, Arp.Plc.Eclr address space) + Modbus-TCP process-data server; no new connector. Route-verified in-process; live PLCnext hardware reads stay 待核实 (see validation status above).

信创 / China entry (offline · 国产 TSDB · compliance)

For 自主可控 / 信创 deployments — see docs/CHINA.md for the full guide.

  • Air-gapped install: pure-Python core + per-protocol optional extras → install from a local wheelhouse with pip install --no-index --find-links ./wheelhouse "iaiops[...]"; secrets stay local (encrypted store), no cloud KMS.

  • National TSDB historian sink (historian_push, CLI iaiops historian push): write collected telemetry to TDengine (iaiops[tdengine]) or Apache IoTDB (iaiops[iotdb]) — domestic, controllable; we don't build our own store or bind InfluxDB. Data egress to the operator's own historian, not a control write.

  • Compliance mapping (compliance_mapping, CLI iaiops compliance): an honest 《工控系统网络安全防护指南》 ↔ iaiops self-assessment across 分区隔离 / 可审计 / 双向认证 / 最小权限 / 数据保护 / 自主可控, with per-control status (addressed / partial / 待核实) and the named gap.

  • 国产 PLC: 汇川 / 台达 / 信捷 over the existing Modbus-TCP / S7 connectors.

  • ⚠️ 待核实: 国产 OS (麒麟/统信) · 芯 (鲲鹏/海光) · PLC validation and the TSDB write paths are documented but not yet hardware-verified — see the validation matrix in docs/CHINA.md.

OEE / downtime analytics (cross-protocol, read-only)

  • oee_computeOEE = Availability × Performance × Quality from production inputs (planned time, run time, ideal cycle, total/good counts). Each factor is reported raw + clamped to [0,1]; a capped performance >1.0 flags an optimistic ideal cycle.

  • downtime_events — auto-detects running→stopped transitions in a {timestamp, state} series and produces stoppage events with durations, categorized (changeover / material / mechanical / quality / break / unknown, by keyword heuristics or a {state: category} override).

  • oee_multidim — aggregates OEE across machine × part × shift (or any dimensions) from labelled records → the matrix + worst performers.

  • Operate over provided/collected inputs (fully testable without a plant). mtconnect_oee_snapshot surfaces the live MTConnect availability/execution inputs that feed these.

Active asset inventory / fingerprint (read-only)

  • asset_inventory — for each configured (or named) endpoint, actively connects with our own protocol client and reads its identity call (S7 s7_cpu_info, EtherNet/IP eip_controller_info, OPC-UA server build info, Modbus Device Identification FC43/0x2B, Mitsubishi CPU type, MTConnect device model), aggregating vendor / model / firmware / serial / reachable / last_seen into an asset register.

  • Honest scope (IEC 62443-flavored): this is ACTIVE fingerprinting via our client connections, NOT passive SPAN/tap discovery — it only finds devices we are configured to reach and adds light load to each. Passive, traffic-mirroring discovery is roadmap.

OPC-UA Historical Access (HDA)

  • opcua_read_history — reads stored historical values for a node over a [start,end] ISO-8601 window via the server's HistoryRead service (asyncua read_raw_history), bounded by max_points (≤2000). Returns {supported:false, note} gracefully when the server does not historize the node (no crash). Read-only.

Change-of-value (CoV) monitor

  • monitor_changes — bounded deadband report: polls a point and returns only the value CHANGES (with timestamps), not every sample. Works over OPC-UA / Modbus / S7 / Mitsubishi MC / EtherNet-IP. Never an infinite loop — hard-capped by both duration_s (≤120) and max_changes (≤500). Read-only.

Cross-protocol brain — 0.9/0.10 additions (all read-only)

  • Conservative baseline learning (baseline_learn/check/record_change/status, CLI iaiops baseline …) — a change-log baseline, explicitly NOT black-box anomaly detection: robust p1/p99 + median/MAD band over the local history, refuses thin history (<100 samples or <24h) with an explicit insufficient_data verdict, restarts at recorded operator changes, and is **silent by default** — a violation needs >3×MAD beyond the band AND ≥3 consecutive samples, and every violation cites its baseline window and offending samples.

  • Historian READ integration (historian_query / historian_coverage, CLI iaiops historian query|coverage) — query history back out of the sqlite/TDengine/IoTDB sinks; an optional per-site historian: config block lets the RCA copilot pull the 2h pre-incident window as one more cited evidence class (strictly additive — without the config, RCA output is byte-identical, test-proven).

  • Legacy PLC program explainer (plc_program_outline/xref/section, CLI iaiops program …) — structural extraction over exported program files (Siemens SCL/ST .scl/.st, AWL/STL .awl, Rockwell Studio 5000 .L5X — never a live PLC upload); every element carries source_file + line (rung for L5X) so the explaining agent must cite real locations. XXE-hardened, ≤5 MB, extension allowlist.

  • ISA-18.2 alarm flood deep-dive (alarm_flood_analysis / alarm_rationalization_worksheet, CLI iaiops diag alarm-flood|alarm-worksheet) — flood episodes (≥10 alarms/10 min), chattering, stale/standing (>24h), percent-time-in-flood vs target, and a CSV-exportable rationalization worksheet; over injected events or a live OPC-UA active-condition scan.

  • Open-format export + metrics bridgeiaiops export csv|sqlite|parquet (from the local SQLite sink; Parquet via iaiops[export]) / MCP export_data; iaiops metrics serve --port 9184 exposes Prometheus /metrics (latest tag values + counters, binds 127.0.0.1 by default) — Grafana recipe in docs/GRAFANA.md.

  • Compliance deliverablesiaiops compliance report (等保 2.0 L2/L3 status + IEC 62443 FR1–6 crosswalk + honest gap list, md/html) and iaiops compliance evidence (audit-evidence zip with hash-chain verification + manifest); MCP compliance_report / compliance_evidence_bundle. Onboarding aids, 非认证.

Cross-protocol brain & editions — 0.11/0.12 additions (all read-only)

  • Downtime triage copilot (downtime_triage) — composes alarm cascade + RCA verdict + PdM precursors into one triage and cross-checks whether the first-out alarm agrees with the diagnosed cause; advisory, cite-first (builds on the earlier alarm_cascade first-out reconstruction and pdm_forecast time-to-threshold early-warning).

  • Legacy-PLC maintainability (plc_program_visibility) — a risk/maintainability read over an exported ST/AWL/L5X program (size, block count, xref density, undocumented sections), never a live upload — pairs with the plc_program_outline/xref/section explainer.

  • Per-edition tool modules (EDITION_MODULES in mcp_server/profiles.py) — a named edition can carry its own @mcp.tool group that loads only when that edition is selected — never for a bare protocol key and never in the always-on brain, so edition-specific tools stay off other surfaces and don't inflate the base. Every edition tool is read-only, cite-first, advisory:

    • warehouseline_bottleneck (Theory-of-Constraints throughput bottleneck) + sortation_health

    • clinicalisolation_room_check (负压隔离病房 pressurization) + medical_gas_check + or_environment_check

    • buildingeconomizer_check (AHU economizer FDD) + zone_comfort

    • processcontrol_loop_health (PID oscillation/offset/saturation) + heat_exchanger_fouling

    • fabspc_check (SPC control-chart rules) + defect_pareto

    • factorychangeover_analysis (SMED)

    • waterdisinfection_ct + water_quality_compliance

    • renewablespv_performance (PV string performance)

  • Agent skills — the repo ships a router skill (skills/iaiops) plus nine per-edition skills (iaiops-fab / iaiops-factory / iaiops-process / iaiops-building / iaiops-water / iaiops-warehouse / iaiops-clinical / iaiops-renewables / iaiops-plcnext) that route an agent to the right MCP server and document the tool surface.

Deployment & ecosystem fit (edge-native / Margo)

iaiops is designed to ride on a hardened, centrally-managed edge host as a portable, governed edge application — not to own the host or the fleet manager. It maps naturally onto the Margo edge-interoperability roles: the host/device is the immutable edge OS, a compliant orchestrator places workloads by desired-state, and iaiops is the OT-domain application — read-first tap + cross-protocol RCA, exposed as governed MCP tools, with an optional on-box LLM brain for a fully air-gapped diagnostic path (data never leaves the plant).

Honest status: iaiops is a natural Margo edge application but is NOT Margo-compliant yet — a container image + application description + a published conformance-toolkit result are roadmap (see docs/MARGO-ALIGNMENT.md and docs/ROADMAP.md). No material claims Margo-compliant until that test result exists.

A container + application-description skeleton lives in deploy/margo/ (hardened Dockerfile · compose · 待核实-marked app descriptor); per-host distribution overlays that reuse it live under deploy/ (one folder per candidate edge host).


Install

Protocol client libraries are optional extras — install only the 1–2 protocols a site actually runs (every protocol library is imported lazily; the base package installs and imports without any of them, and a call to a not-installed protocol returns a teaching error pointing at the right extra):

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

Protocol extras: opcua · modbus · s7 · mc · fins (stdlib — pins nothing) · eip · mtconnect · sparkplug · secsgem · ethercat · profinet · bacnet · hart · iolink · bas (BAS supervisory REST — reuses the mtconnect HTTP pin) · ignition (Ignition Gateway read layer — reuses the mtconnect HTTP pin) · plus tdengine · iotdb · influxdb (historian sinks) · nats (stream egress) · ollama (on-box LLM narration) · export (Parquet) · all (every pip-installable connector).

Adapter belt (docs/ADAPTERS.md): iaiops is a small neutral core (ingress → normalize/govern/RCA → egress) with pluggable, lazily-imported adapters — bind no store/bus/host/model, install only what a site runs. The RCA core is deterministic + cited, not a black box (docs/RCA.md); footprint is small by design (docs/FOOTPRINT.md).

Edition bundles (match the same-named IAIOPS_MCP profiles — install the protocols a vertical runs): fab (secsgem + opcua + s7 + modbus) · factory (the discrete-manufacturing set: 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 inverters (SUN2000/Growatt) + wind turbines + plant SCADA; device-level monitoring + PdM via baseline/RCA) · plcnext (opcua + modbus). The grid/substation energy bundle (IEC-104/DNP3/61850) ships in iaiops-energy.

Master password

Secrets (per-endpoint passwords, MQTT credentials) are never stored in plaintext — they live in ~/.iaiops/secrets.enc (Fernet + scrypt). Export IAIOPS_MASTER_PASSWORD so the MCP server/CLI can unlock non-interactively:

export IAIOPS_MASTER_PASSWORD='…'

Example ~/.iaiops/config.yaml (one block per protocol)

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

iaiops init walkthrough (per protocol)

$ 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 prompts add TLS/topic/username; MTConnect prompts for agent_url; EtherCAT prompts for the nic + expected_slaves and warns about the Linux/root/NIC/optional-extra requirement; OPC-UA/MQTT prompt for a hidden password stored encrypted.)

Test against a simulator (per protocol)

  • OPC-UA — an asyncua demo server (the test suite runs a real in-process one).

  • Modbus — ModbusPal or a pymodbus server simulator.

  • S7 — a pyS7/snap7 S7 server sim (Snap7 server) on :102.

  • MTConnect — the public MTConnect demo agent, or a local agent.

  • MQTT — a local mosquitto broker (+ a Sparkplug edge for SpB topics).

  • Mitsubishi MC — GX Simulator / an MC 3E server sim.

  • EtherNet/IP — a pycomm3-compatible CIP/Logix simulator (or a spare CompactLogix).

  • Omron FINS — the in-repo mock FINS UDP/TCP responder (tests/test_fins.py) or a spare CP/CJ PLC.

  • IO-Link — the in-process mock master (tests/test_iolink.py, both JSON dialects) or any ifm/Balluff/Turck master on the bench.

  • EtherCATno simulator exists (hard-real-time, raw-Ethernet). Validate only on Linux, as root / with CAP_NET_RAW, on a dedicated NIC wired to real slaves (e.g. a Beckhoff EK1100 coupler + EL terminals). iaiops doctor reports a clear "needs Linux/root/NIC/pysoem" status off the bus rather than failing.


Usage

CLI (read)

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 = A×P×Q
iaiops analytics asset -e press1 -e cell5           # active asset register

CLI (write — dry-run by default, double-confirm on --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 tool calls (JSON args → sample structured return)

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 (dry-run):

{ "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 (dry-run):

{ "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 upload):

{ "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 (dry-run; can start/stop motion):

{ "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 (full SpB metric decode):

{ "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 (active fingerprint):

{ "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"} ] }

Diagnostics (multi-dimensional JSON for an agent to visualize)

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 copilot (flagship)

downtime_root_cause correlates whatever evidence you can hand over — alarm events, tag samples, a diagnose_dataflow verdict, a machine-state series — around an incident window and returns an evidence-cited, advisory verdict. Read-first: it proposes a human-approved, MOC-gated, undoable action and executes nothing. Anti-hallucination by design — it cites only signals actually present in the input, weights them by temporal proximity to onset (a cause precedes its effect), and downgrades to insufficient_evidence (with a recommended_next_data list) rather than guessing when evidence is thin.

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 …" }

The same copilot is on the CLI: iaiops diag rca --input bundle.json where the bundle is {window, alarms?, tags?, dataflow?, state_series?}.

Let it gather its own evidence. downtime_root_cause_live (CLI iaiops diag rca-live) takes just an endpoint + window + the refs to look at, then pulls the evidence itself — a cross-protocol diagnose_dataflow probe, a short sampled series per ref (so flatline / bad-quality / anomaly surface via tag_health), and active OPC-UA conditions — before running the same advisory, read-only copilot. The gathered bundle is echoed back under collected_evidence (no hidden inputs):

iaiops diag rca-live -e line1 --start 2026-06-28T10:00:00Z \
  --asset line1 --ref "ns=2;i=5" --ref "ns=2;i=6"

Data-quality watchdog & UNS governance (read-only intelligence)

Two more pure-analysis layers — fully testable without live gear, and they feed the RCA copilot.

  • data_quality_scorecard (CLI iaiops diag dataquality) — a fleet data-TRUST rollup: scores each tag 0-100 on whether its data can be believed — staleness, dead heartbeat (first-class), bad-quality, flatline, gaps, anomaly — then rolls up per endpoint and across the fleet with an issue breakdown and ranked worst offenders. Distinct from process health: it asks "can I trust this number," not "is this number alarming." heartbeat_health (CLI iaiops diag heartbeat) is the standalone watchdog-liveness check (a flatlined heartbeat = dead upstream even when comms look fine).

  • uns_topic_audit (CLI iaiops mqtt uns-audit) — governs a UNS topic tree: naming conformance (allowed roots / min depth) + topic sprawl (casing collisions of the same logical name, leaf metrics scattered under many parents, depth outliers, duplicates) → a clean/minor/sprawling verdict. uns_schema_drift (CLI iaiops mqtt uns-drift) — compares two Sparkplug NBIRTH-style snapshots and classifies the change none / additive / breaking (a metric removed or its datatype changed). Positions the UNS as a governable neutral data source, not just a broker.

MCP server

IAIOPS_MCP=opcua iaiops-mcp   # stdio transport (`iaiops mcp` is equivalent)

Menu — expose only the protocols a site runs. A fab usually runs 1–2 protocols; exposing all 14 floods the model with tools it can't use. Set IAIOPS_MCP to a comma-list of protocols and/or a named profile. There is no default (since 0.10.0): a bare iaiops-mcp prints the selection menu (profiles, protocol keys, tool counts) to stderr and exits 2 instead of silently exposing 100+ tools. The cross-protocol brain (OEE / downtime / diagnostics / asset / analysis) is included by default with every selection.

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)

Named entry-point sugar. For the common single-protocol / single-edition case there is a pre-scoped console script per protocol and per named profile — no env var to set. Each is a thin shim over the same server:

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)

Multi-process sites — 1 brain MCP + N protocol MCPs. Running several protocol servers side by side (e.g. iaiops-mcp-opcua + iaiops-mcp-modbus) would duplicate the ~30 brain tools in every server. Instead run one dedicated iaiops-mcp-brain and set IAIOPS_MCP_NO_BRAIN=1 on the protocol servers to strip the brain from them — the protocols_supported discovery tool stays exposed everywhere:

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

Named profiles: all · brain · fab · factory · process · building · plcnext · water · renewables · warehouse · clinical. In an MCP client (e.g. Claude Desktop) set IAIOPS_MCP per server entry — or point the entry straight at the matching iaiops-mcp-<name> script — one entry per site/line, each a lean single- or dual-protocol server.


Safety & governance

  • Read-first. 156 of 166 tools are read-only. The 10 write/command tools (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) are OT-dangerous: governed at high risk_tier, off by default (dry-run), require a double-confirm in the CLI, and a recorded approver (one-shot iaiops approve tokens; with no risk_tiers configured, high/critical operations default to the dual tier) — MOC discipline. 9 of the 10 capture the BEFORE value/state and register an undo descriptor; the exceptions are mqtt_publish (a fire-and-forget MQTT/Sparkplug command has no automatic inverse) and ethercat_set_state (a state transition has no clean inverse) — both have no automatic undo. ethercat_set_state can START or STOP machine motion. 未经授权勿对生产控制系统写入.

  • Do not point this at a production control system without authorization. OT networks are safety-critical; even reads add load. Test against a simulator first.

  • All endpoint-returned text is sanitized (prompt-injection defense); secrets are never returned by any tool; MTConnect XML is parsed with DTD/entity declarations refused.

  • Every tool runs through the vendored governance harness: SQLite audit (~/.iaiops/audit.db, SHA-256 hash-chained rows + iaiops audit verify; audit fails closed for high/critical writes), token/call budget + runaway breaker, risk-tier gate (policy engine fails closed on a broken rules.yaml), undo recording. The MCP server refuses to start if any registered tool lacks the governance marker.

Roadmap

  • EtherNet/IP PLC-5 / SLC-500 (PCCC) and Micro800 support (Logix tags are done in 0.2.0).

  • Passive asset discovery (SPAN/tap, no connections) alongside today's active fingerprint.

  • EtherCAT EoE / FoE / SoE mailbox protocols and full PDO-mapping decode (CoE SDO/PDO read+write and AL-state landed in 0.3.0 via the optional pysoem extra).

  • OPC-UA certificate security (A&C event subscriptions landed via opcua_alarm_events).

  • MTConnect streaming long-poll; Sparkplug B DataSet/Template deep expansion.

Missing a protocol, device, or feature? 缺功能提 issue/PR 欢迎留言 — open a GitHub issue or PR.

License

MIT © wei

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
1dRelease cycle
20Releases (12mo)
Commit activity

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/industrial-aiops/industrial-aiops'

If you have feedback or need assistance with the MCP directory API, please join our Discord server