iaiops 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- iaiops-0.4.0/.gitignore +9 -0
- iaiops-0.4.0/CHANGELOG.md +37 -0
- iaiops-0.4.0/LICENSE +21 -0
- iaiops-0.4.0/PKG-INFO +528 -0
- iaiops-0.4.0/README.md +463 -0
- iaiops-0.4.0/docs/ROADMAP.md +52 -0
- iaiops-0.4.0/iaiops/__init__.py +12 -0
- iaiops-0.4.0/iaiops/cli/__init__.py +5 -0
- iaiops-0.4.0/iaiops/cli/_common.py +56 -0
- iaiops-0.4.0/iaiops/cli/_root.py +78 -0
- iaiops-0.4.0/iaiops/cli/analytics.py +75 -0
- iaiops-0.4.0/iaiops/cli/diagnostics.py +67 -0
- iaiops-0.4.0/iaiops/cli/doctor.py +21 -0
- iaiops-0.4.0/iaiops/cli/eip.py +74 -0
- iaiops-0.4.0/iaiops/cli/ethercat.py +125 -0
- iaiops-0.4.0/iaiops/cli/init.py +177 -0
- iaiops-0.4.0/iaiops/cli/mc.py +72 -0
- iaiops-0.4.0/iaiops/cli/modbus.py +82 -0
- iaiops-0.4.0/iaiops/cli/mqtt.py +78 -0
- iaiops-0.4.0/iaiops/cli/mtconnect.py +56 -0
- iaiops-0.4.0/iaiops/cli/opcua.py +131 -0
- iaiops-0.4.0/iaiops/cli/s7.py +75 -0
- iaiops-0.4.0/iaiops/cli/secret.py +102 -0
- iaiops-0.4.0/iaiops/connectors/__init__.py +4 -0
- iaiops-0.4.0/iaiops/connectors/eip/__init__.py +1 -0
- iaiops-0.4.0/iaiops/connectors/eip/ops.py +182 -0
- iaiops-0.4.0/iaiops/connectors/ethercat/__init__.py +1 -0
- iaiops-0.4.0/iaiops/connectors/ethercat/ops.py +422 -0
- iaiops-0.4.0/iaiops/connectors/mc/__init__.py +1 -0
- iaiops-0.4.0/iaiops/connectors/mc/ops.py +138 -0
- iaiops-0.4.0/iaiops/connectors/modbus/__init__.py +1 -0
- iaiops-0.4.0/iaiops/connectors/modbus/ops.py +202 -0
- iaiops-0.4.0/iaiops/connectors/mtconnect/__init__.py +1 -0
- iaiops-0.4.0/iaiops/connectors/mtconnect/ops.py +245 -0
- iaiops-0.4.0/iaiops/connectors/opcua/__init__.py +1 -0
- iaiops-0.4.0/iaiops/connectors/opcua/diagnostics.py +152 -0
- iaiops-0.4.0/iaiops/connectors/opcua/ops.py +338 -0
- iaiops-0.4.0/iaiops/connectors/s7/__init__.py +1 -0
- iaiops-0.4.0/iaiops/connectors/s7/ops.py +199 -0
- iaiops-0.4.0/iaiops/connectors/secsgem/__init__.py +1 -0
- iaiops-0.4.0/iaiops/connectors/secsgem/ops.py +115 -0
- iaiops-0.4.0/iaiops/connectors/sparkplug/__init__.py +1 -0
- iaiops-0.4.0/iaiops/connectors/sparkplug/ops.py +539 -0
- iaiops-0.4.0/iaiops/connectors/sparkplug/sparkplug_b_pb2.py +56 -0
- iaiops-0.4.0/iaiops/core/__init__.py +6 -0
- iaiops-0.4.0/iaiops/core/brain/__init__.py +1 -0
- iaiops-0.4.0/iaiops/core/brain/_shared.py +34 -0
- iaiops-0.4.0/iaiops/core/brain/analysis.py +167 -0
- iaiops-0.4.0/iaiops/core/brain/asset_inventory.py +174 -0
- iaiops-0.4.0/iaiops/core/brain/diagnostics.py +694 -0
- iaiops-0.4.0/iaiops/core/brain/monitor.py +122 -0
- iaiops-0.4.0/iaiops/core/brain/oee.py +269 -0
- iaiops-0.4.0/iaiops/core/brain/overview.py +148 -0
- iaiops-0.4.0/iaiops/core/governance/__init__.py +40 -0
- iaiops-0.4.0/iaiops/core/governance/audit.py +377 -0
- iaiops-0.4.0/iaiops/core/governance/budget.py +225 -0
- iaiops-0.4.0/iaiops/core/governance/decorators.py +474 -0
- iaiops-0.4.0/iaiops/core/governance/paths.py +45 -0
- iaiops-0.4.0/iaiops/core/governance/patterns.py +378 -0
- iaiops-0.4.0/iaiops/core/governance/policy.py +411 -0
- iaiops-0.4.0/iaiops/core/governance/sanitize.py +39 -0
- iaiops-0.4.0/iaiops/core/governance/undo.py +218 -0
- iaiops-0.4.0/iaiops/core/runtime/__init__.py +1 -0
- iaiops-0.4.0/iaiops/core/runtime/config.py +364 -0
- iaiops-0.4.0/iaiops/core/runtime/connection.py +719 -0
- iaiops-0.4.0/iaiops/core/runtime/secretstore.py +311 -0
- iaiops-0.4.0/iaiops/doctor.py +231 -0
- iaiops-0.4.0/mcp_server/__init__.py +1 -0
- iaiops-0.4.0/mcp_server/_shared.py +121 -0
- iaiops-0.4.0/mcp_server/profiles.py +93 -0
- iaiops-0.4.0/mcp_server/server.py +56 -0
- iaiops-0.4.0/mcp_server/tools/__init__.py +1 -0
- iaiops-0.4.0/mcp_server/tools/analysis_tools.py +53 -0
- iaiops-0.4.0/mcp_server/tools/asset_tools.py +42 -0
- iaiops-0.4.0/mcp_server/tools/diagnostics_tools.py +169 -0
- iaiops-0.4.0/mcp_server/tools/eip_tools.py +133 -0
- iaiops-0.4.0/mcp_server/tools/ethercat_tools.py +229 -0
- iaiops-0.4.0/mcp_server/tools/mc_tools.py +135 -0
- iaiops-0.4.0/mcp_server/tools/modbus_tools.py +92 -0
- iaiops-0.4.0/mcp_server/tools/monitor_tools.py +48 -0
- iaiops-0.4.0/mcp_server/tools/mtconnect_tools.py +103 -0
- iaiops-0.4.0/mcp_server/tools/oee_tools.py +88 -0
- iaiops-0.4.0/mcp_server/tools/opcua_tools.py +161 -0
- iaiops-0.4.0/mcp_server/tools/overview_tools.py +27 -0
- iaiops-0.4.0/mcp_server/tools/s7_tools.py +154 -0
- iaiops-0.4.0/mcp_server/tools/secsgem_tools.py +100 -0
- iaiops-0.4.0/mcp_server/tools/sparkplug_tools.py +182 -0
- iaiops-0.4.0/pyproject.toml +111 -0
- iaiops-0.4.0/server.json +21 -0
- iaiops-0.4.0/skills/iaiops/SKILL.md +160 -0
- iaiops-0.4.0/tests/test_asset.py +73 -0
- iaiops-0.4.0/tests/test_diagnostics.py +160 -0
- iaiops-0.4.0/tests/test_doctor.py +70 -0
- iaiops-0.4.0/tests/test_eip.py +128 -0
- iaiops-0.4.0/tests/test_ethercat.py +272 -0
- iaiops-0.4.0/tests/test_mc.py +106 -0
- iaiops-0.4.0/tests/test_modbus.py +135 -0
- iaiops-0.4.0/tests/test_mtconnect.py +152 -0
- iaiops-0.4.0/tests/test_oee.py +76 -0
- iaiops-0.4.0/tests/test_opcua_diagnostics.py +103 -0
- iaiops-0.4.0/tests/test_opcua_server.py +189 -0
- iaiops-0.4.0/tests/test_profiles.py +76 -0
- iaiops-0.4.0/tests/test_s7.py +105 -0
- iaiops-0.4.0/tests/test_secretstore.py +110 -0
- iaiops-0.4.0/tests/test_secsgem.py +121 -0
- iaiops-0.4.0/tests/test_smoke.py +431 -0
- iaiops-0.4.0/tests/test_sparkplug.py +216 -0
- iaiops-0.4.0/tests/test_subscription_health.py +84 -0
- iaiops-0.4.0/uv.lock +1382 -0
iaiops-0.4.0/.gitignore
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0 — Industrial-AIOps
|
|
4
|
+
|
|
5
|
+
First release under the standalone **`industrial-aiops`** org (split out of the
|
|
6
|
+
`AIops-tools` IT line). Same governance harness, read-first stance, and preview /
|
|
7
|
+
mock-or-sim validation caveat — now a monorepo with a shared core, per-protocol
|
|
8
|
+
connectors, a menu-configurable MCP, and a semiconductor/display fab connector.
|
|
9
|
+
|
|
10
|
+
### Breaking
|
|
11
|
+
- **Renamed `ot-aiops` → `iaiops`**: package `ot_aiops`→`iaiops`, CLI/MCP
|
|
12
|
+
`ot-aiops`→`iaiops`, env `OT_AIOPS_*`→`IAIOPS_*`, home `~/.ot-aiops`→`~/.iaiops`.
|
|
13
|
+
Legacy env vars and the legacy home directory are honored as a fallback so
|
|
14
|
+
existing installs keep unlocking secrets / reading audit.
|
|
15
|
+
- **Protocol client libraries are now optional extras** — the base package installs
|
|
16
|
+
and imports without them; install only what a site runs:
|
|
17
|
+
`pip install "iaiops[opcua,modbus]"` (or `iaiops[all]`). A call to a
|
|
18
|
+
not-installed protocol returns a teaching error pointing at the right extra.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- **Shared core** — `iaiops/core/{governance,runtime,brain}`; connectors import it.
|
|
22
|
+
- **`IAIOPS_MCP` menu** — expose only the protocols a site runs (named profiles
|
|
23
|
+
`all` / `fab` / `factory` / `process`, or a comma list). `fab` profile = 29 tools
|
|
24
|
+
vs 66 for `all`.
|
|
25
|
+
- **SECS/GEM connector** — host-side reads for semiconductor/display fab equipment
|
|
26
|
+
over HSMS (SEMI E5/E30/E37) via the `secsgem` extra: equipment status, SVID/ECID
|
|
27
|
+
namelists + values, alarms, process programs (7 tools).
|
|
28
|
+
- **OPC-UA connection self-diagnosis** (`opcua_diagnose_connection`) — classifies a
|
|
29
|
+
failed connect (certificate / security policy / auth / firewall / dns / port /
|
|
30
|
+
config) with the fix; wired into `iaiops doctor`.
|
|
31
|
+
- **`subscription_health`** — sequenced-feed loss/reorder/overload (OPC-UA monitored
|
|
32
|
+
items or Sparkplug B): sequence gaps, republish-rejection rate, overloaded channels.
|
|
33
|
+
- **Per-industry edition bundles** — `iaiops[fab]` / `iaiops[factory]` / `iaiops[process]`.
|
|
34
|
+
|
|
35
|
+
### Notes
|
|
36
|
+
- 66 tools across 9 protocols. Still **preview** — validated against simulators /
|
|
37
|
+
mocks, not live equipment.
|
iaiops-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wei <zhouwei008@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
iaiops-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: iaiops
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Governed, vendor-neutral OT data tap + intelligent troubleshooting for AI agents (OPC-UA / Modbus / S7comm / Mitsubishi MC / MTConnect / MQTT-Sparkplug / EtherNet-IP / EtherCAT) with a built-in governance harness (audit, budget, risk tiers, MOC) plus OEE, downtime, and asset-inventory analytics
|
|
5
|
+
Author-email: wei <zhouwei008@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: ai-ops,allen-bradley,ethercat,ethernet-ip,industrial,mcp,mitsubishi,modbus,mqtt,mtconnect,oee,opc-ua,ot,plc,pysoem,rockwell,s7comm,scada,siemens,soem,sparkplug,uns
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: cryptography>=42.0
|
|
11
|
+
Requires-Dist: mcp[cli]<2.0,>=1.10
|
|
12
|
+
Requires-Dist: python-dotenv<2.0,>=1.0
|
|
13
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
14
|
+
Requires-Dist: rich<16.0,>=13.0
|
|
15
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
16
|
+
Provides-Extra: all
|
|
17
|
+
Requires-Dist: asyncua<2,>=1.0; extra == 'all'
|
|
18
|
+
Requires-Dist: paho-mqtt<3,>=2.0; extra == 'all'
|
|
19
|
+
Requires-Dist: protobuf<8,>=4.25; extra == 'all'
|
|
20
|
+
Requires-Dist: pycomm3<2,>=1.2; extra == 'all'
|
|
21
|
+
Requires-Dist: pymcprotocol<1,>=0.3; extra == 'all'
|
|
22
|
+
Requires-Dist: pymodbus<4,>=3.5; extra == 'all'
|
|
23
|
+
Requires-Dist: pys7<3,>=2.8; extra == 'all'
|
|
24
|
+
Requires-Dist: pysoem<2,>=1.1; extra == 'all'
|
|
25
|
+
Requires-Dist: requests<3,>=2.31; extra == 'all'
|
|
26
|
+
Requires-Dist: secsgem<1,>=0.3; extra == 'all'
|
|
27
|
+
Provides-Extra: eip
|
|
28
|
+
Requires-Dist: pycomm3<2,>=1.2; extra == 'eip'
|
|
29
|
+
Provides-Extra: ethercat
|
|
30
|
+
Requires-Dist: pysoem<2,>=1.1; extra == 'ethercat'
|
|
31
|
+
Provides-Extra: fab
|
|
32
|
+
Requires-Dist: asyncua<2,>=1.0; extra == 'fab'
|
|
33
|
+
Requires-Dist: pymodbus<4,>=3.5; extra == 'fab'
|
|
34
|
+
Requires-Dist: pys7<3,>=2.8; extra == 'fab'
|
|
35
|
+
Requires-Dist: secsgem<1,>=0.3; extra == 'fab'
|
|
36
|
+
Provides-Extra: factory
|
|
37
|
+
Requires-Dist: asyncua<2,>=1.0; extra == 'factory'
|
|
38
|
+
Requires-Dist: paho-mqtt<3,>=2.0; extra == 'factory'
|
|
39
|
+
Requires-Dist: protobuf<8,>=4.25; extra == 'factory'
|
|
40
|
+
Requires-Dist: pycomm3<2,>=1.2; extra == 'factory'
|
|
41
|
+
Requires-Dist: pymcprotocol<1,>=0.3; extra == 'factory'
|
|
42
|
+
Requires-Dist: pymodbus<4,>=3.5; extra == 'factory'
|
|
43
|
+
Requires-Dist: pys7<3,>=2.8; extra == 'factory'
|
|
44
|
+
Requires-Dist: pysoem<2,>=1.1; extra == 'factory'
|
|
45
|
+
Requires-Dist: requests<3,>=2.31; extra == 'factory'
|
|
46
|
+
Provides-Extra: mc
|
|
47
|
+
Requires-Dist: pymcprotocol<1,>=0.3; extra == 'mc'
|
|
48
|
+
Provides-Extra: modbus
|
|
49
|
+
Requires-Dist: pymodbus<4,>=3.5; extra == 'modbus'
|
|
50
|
+
Provides-Extra: mtconnect
|
|
51
|
+
Requires-Dist: requests<3,>=2.31; extra == 'mtconnect'
|
|
52
|
+
Provides-Extra: opcua
|
|
53
|
+
Requires-Dist: asyncua<2,>=1.0; extra == 'opcua'
|
|
54
|
+
Provides-Extra: process
|
|
55
|
+
Requires-Dist: asyncua<2,>=1.0; extra == 'process'
|
|
56
|
+
Requires-Dist: pymodbus<4,>=3.5; extra == 'process'
|
|
57
|
+
Provides-Extra: s7
|
|
58
|
+
Requires-Dist: pys7<3,>=2.8; extra == 's7'
|
|
59
|
+
Provides-Extra: secsgem
|
|
60
|
+
Requires-Dist: secsgem<1,>=0.3; extra == 'secsgem'
|
|
61
|
+
Provides-Extra: sparkplug
|
|
62
|
+
Requires-Dist: paho-mqtt<3,>=2.0; extra == 'sparkplug'
|
|
63
|
+
Requires-Dist: protobuf<8,>=4.25; extra == 'sparkplug'
|
|
64
|
+
Description-Content-Type: text/markdown
|
|
65
|
+
|
|
66
|
+
<!-- mcp-name: io.github.industrial-aiops/iaiops -->
|
|
67
|
+
|
|
68
|
+
# Industrial-AIOps
|
|
69
|
+
|
|
70
|
+
**Governed, vendor-neutral industrial data tap + intelligent troubleshooting for AI agents — across OPC-UA (incl. Historical Access), Modbus-TCP, S7comm, Mitsubishi MC, MTConnect, MQTT/Sparkplug B (full decode), EtherNet/IP (Rockwell/Allen-Bradley Logix), EtherCAT (pysoem/SOEM fieldbus master), and SECS/GEM (HSMS fab equipment) — plus OEE/downtime, active asset-inventory, and change-of-value analytics.**
|
|
71
|
+
|
|
72
|
+
Industrial-AIOps is the OT/industrial member of [Industrial-AIOps](https://github.com/industrial-aiops). 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), ranks unhealthy tags, computes OEE / categorizes downtime, and builds an active asset register. 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).
|
|
73
|
+
|
|
74
|
+
> ⚠️ **Preview / v0.4.0** — validated against an **in-process OPC-UA simulator (incl. HDA), mocked Modbus/S7/Mitsubishi/EtherNet-IP(pycomm3)/EtherCAT(pysoem)/SECS-GEM(secsgem) clients, static MTConnect XML fixtures, and synthetic MQTT/Sparkplug B protobuf payloads**. **NOT tested against live PLCs / SCADA / brokers / Logix controllers / EtherCAT slaves.** EtherCAT is hard-real-time and has **no software simulator** (Linux + root + a real bus only), so it is **entirely unverified against hardware**. See *Safety*.
|
|
75
|
+
|
|
76
|
+
## Why
|
|
77
|
+
|
|
78
|
+
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.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Consolidated capability matrix
|
|
83
|
+
|
|
84
|
+
| Protocol | Tool | Operation | R/W | risk_tier | Returns (key fields) |
|
|
85
|
+
|----------|------|-----------|:---:|:---------:|----------------------|
|
|
86
|
+
| OPC-UA | `opcua_server_info` | server status | R | low | state, product_name, namespaces |
|
|
87
|
+
| OPC-UA | `opcua_browse` | browse node tree | R | low | [{node_id, browse_name, depth}] |
|
|
88
|
+
| OPC-UA | `opcua_read_node` | read one node | R | low | value, datatype, source_timestamp, good |
|
|
89
|
+
| OPC-UA | `opcua_read_many` | batch read | R | low | [{node_id, value, ...}] |
|
|
90
|
+
| OPC-UA | `opcua_subscribe_sample` | bounded sample | R | low | {collected, samples[]} |
|
|
91
|
+
| OPC-UA | `opcua_read_alarms` | alarm surfacing | R | low | {active_alarms[], active_count} |
|
|
92
|
+
| OPC-UA | `opcua_read_history` | Historical Access (HDA) | R | low | {supported, count, values[]} |
|
|
93
|
+
| OPC-UA | `health_summary` | threshold classify | R | low | {overall, counts, offenders[]} |
|
|
94
|
+
| OPC-UA | `anomaly_scan` | stddev outliers | R | low | {mean, stddev, outliers[]} |
|
|
95
|
+
| Modbus | `modbus_read_holding` | FC03 | R | low | {raw_registers, decoded[]} |
|
|
96
|
+
| Modbus | `modbus_read_input` | FC04 | R | low | {raw_registers, decoded[]} |
|
|
97
|
+
| Modbus | `modbus_read_coils` | FC01 | R | low | {bits[]} |
|
|
98
|
+
| Modbus | `modbus_read_discrete` | FC02 | R | low | {bits[]} |
|
|
99
|
+
| Modbus | `modbus_health_summary` | threshold classify | R | low | {overall, counts, offenders[]} |
|
|
100
|
+
| S7comm | `s7_cpu_info` | CPU id + run/stop | R | low | {cpu_status, cpu_info} |
|
|
101
|
+
| S7comm | `s7_read_area` | read DB/M/I/Q | R | low | {items:[{address, value}]} |
|
|
102
|
+
| S7comm | `s7_read_db` | read data block | R | low | {items:[{address, value}]} |
|
|
103
|
+
| S7comm | `s7_read_many` | batch addresses | R | low | {items:[{address, value}]} |
|
|
104
|
+
| S7comm | `s7_write_db` | write data block | **W** | **high/MOC** | {before, written, _undo_id} |
|
|
105
|
+
| Mitsubishi MC | `mc_cpu_status` | CPU type | R | low | {cpu_type, cpu_code} |
|
|
106
|
+
| Mitsubishi MC | `mc_read_words` | word devices | R | low | {words[]} |
|
|
107
|
+
| Mitsubishi MC | `mc_read_bits` | bit devices | R | low | {bits[]} |
|
|
108
|
+
| Mitsubishi MC | `mc_read_many` | random read | R | low | {words[], dwords[]} |
|
|
109
|
+
| Mitsubishi MC | `mc_write_words` | write words | **W** | **high/MOC** | {before, written, _undo_id} |
|
|
110
|
+
| MTConnect | `mtconnect_probe` | device model | R | low | {devices:[{components:[{data_items}]}]} |
|
|
111
|
+
| MTConnect | `mtconnect_current` | latest values | R | low | {observations[]} |
|
|
112
|
+
| MTConnect | `mtconnect_sample` | bounded stream | R | low | {observations[]} |
|
|
113
|
+
| MTConnect | `mtconnect_assets` | assets | R | low | {assets[]} |
|
|
114
|
+
| MTConnect | `mtconnect_oee_snapshot` | OEE inputs | R | low | {availability, execution, verdict} |
|
|
115
|
+
| MQTT/Sparkplug | `mqtt_read_topic` | bounded read | R | low | {messages:[{topic, payload}]} |
|
|
116
|
+
| MQTT/Sparkplug | `sparkplug_subscribe_sample` | bounded SpB sample (full decode) | R | low | {samples:[{sparkplug, payload:{metrics[]}}], seq_gap_count} |
|
|
117
|
+
| MQTT/Sparkplug | `sparkplug_decode_payload` | decode raw SpB payload | R | low | {metrics:[{name, alias, datatype, value, is_historical}]} |
|
|
118
|
+
| MQTT/Sparkplug | `sparkplug_node_list` | node discovery + state | R | low | {nodes:[{group_id, edge_node_id, online, devices}], primary_hosts[]} |
|
|
119
|
+
| MQTT/Sparkplug | `uns_browse` | topic-tree browse | R | low | {topics[], tree{}} |
|
|
120
|
+
| MQTT/Sparkplug | `mqtt_publish` | publish/command | **W** | **high/MOC** | {published_bytes, applied} |
|
|
121
|
+
| EtherNet/IP | `eip_controller_info` | Logix controller id | R | low | {controller:{vendor, product_name, revision, serial}} |
|
|
122
|
+
| EtherNet/IP | `eip_list_tags` | tag discovery | R | low | {tag_count, tags:[{name, data_type, structure}]} |
|
|
123
|
+
| EtherNet/IP | `eip_read_tag` | read one tag/array | R | low | {tag, value, type, good} |
|
|
124
|
+
| EtherNet/IP | `eip_read_many` | batch read | R | low | {items:[{tag, value, type}]} |
|
|
125
|
+
| EtherNet/IP | `eip_write_tag` | write tag | **W** | **high/MOC** | {before, written, _undo_id} |
|
|
126
|
+
| Diagnostics | `diagnose_dataflow` | localize no-data | R | low | {verdict, diagnosis, hops[]} |
|
|
127
|
+
| Diagnostics | `alarm_bad_actors` | ISA-18.2 flood | R | low | {flood_verdict, top_offenders[]} |
|
|
128
|
+
| Diagnostics | `tag_health` | offender ranking | R | low | {overall, offenders[]} |
|
|
129
|
+
| Diagnostics | `historian_health` | gap/flatline | R | low | {verdict, gaps[]} |
|
|
130
|
+
| Analytics | `oee_compute` | OEE = A×P×Q | R | low | {availability, performance, quality, oee, oee_pct} |
|
|
131
|
+
| Analytics | `downtime_events` | stoppage detect + categorize | R | low | {event_count, total_downtime_s, by_category, events[]} |
|
|
132
|
+
| Analytics | `oee_multidim` | OEE machine×part×shift | R | low | {matrix[], worst_performers[], mean_oee} |
|
|
133
|
+
| Analytics | `asset_inventory` | active fingerprint | R | low | {assets:[{protocol, vendor, model, firmware, reachable}]} |
|
|
134
|
+
| Analytics | `monitor_changes` | bounded change-of-value | R | low | {change_count, changes:[{value, previous, wall_clock}]} |
|
|
135
|
+
| EtherCAT | `ethercat_master_state` | master/WKC + slave count | R | low | {master_state, expected_working_counter, slaves_found, slaves_expected} |
|
|
136
|
+
| EtherCAT | `ethercat_slaves` | bus scan | R | low | {slave_count, slaves:[{index, name, vendor_id, product_code, state}]} |
|
|
137
|
+
| EtherCAT | `ethercat_slave_info` | slave detail | R | low | {sync_managers[], fmmus[], object_dictionary[], input_bytes} |
|
|
138
|
+
| EtherCAT | `ethercat_read_sdo` | CoE SDO upload | R | low | {index, byte_length, hex, as_uint} |
|
|
139
|
+
| EtherCAT | `ethercat_read_pdo` | input PDO snapshot | R | low | {working_counter, input_hex, input_byte_length} |
|
|
140
|
+
| EtherCAT | `ethercat_write_sdo` | CoE SDO download | **W** | **high/MOC** | {before, written, applied} |
|
|
141
|
+
| EtherCAT | `ethercat_set_state` | AL-state transition | **W** | **high/MOC** | {before, requested, reached, applied} |
|
|
142
|
+
| SECS/GEM | `secsgem_equipment_status` | GEM link + identity (S1F1/F2) | R | low | {communication_state, are_you_there} |
|
|
143
|
+
| SECS/GEM | `secsgem_list_status_variables` | SVID namelist (S1F11/F12) | R | low | {count, status_variables[]} |
|
|
144
|
+
| SECS/GEM | `secsgem_read_status_variables` | SVID values (S1F3/F4) | R | low | {svids, values[]} |
|
|
145
|
+
| SECS/GEM | `secsgem_list_equipment_constants` | ECID namelist (S2F29/F30) | R | low | {count, equipment_constants[]} |
|
|
146
|
+
| SECS/GEM | `secsgem_read_equipment_constants` | ECID values (S2F13/F14) | R | low | {ecids, values[]} |
|
|
147
|
+
| SECS/GEM | `secsgem_list_alarms` | alarm list (S5F5/F6) | R | low | {count, alarms[]} |
|
|
148
|
+
| SECS/GEM | `secsgem_list_process_programs` | PPID directory (S7F19/F20) | R | low | {count, process_programs[]} |
|
|
149
|
+
| Self | `protocols_supported` | capability map | R | low | {protocols[], diagnostics[], analytics[]} |
|
|
150
|
+
|
|
151
|
+
**66 tools** = 60 read + 6 write (MOC). The 60 reads = 49 protocol-read · 5 diagnostics · 5 analytics · 1 self. Run `protocols_supported()` (or `iaiops protocols`) for the live map.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Per-protocol reference
|
|
156
|
+
|
|
157
|
+
### OPC-UA
|
|
158
|
+
- **Versions/variants**: binary `opc.tcp://` via `asyncua` (sync facade). Security: **anonymous + username/password**. Certificate message security (Sign / SignAndEncrypt) = **roadmap, not validated**.
|
|
159
|
+
- **Connection params**: `endpoint_url`, `username` (password encrypted), `security_mode`, `security_policy`.
|
|
160
|
+
- **Not supported / planned**: cert security; real Alarms & Conditions event subscriptions (alarms are surfaced best-effort by browsing alarm-like boolean nodes).
|
|
161
|
+
|
|
162
|
+
### Modbus-TCP
|
|
163
|
+
- **Versions/variants**: Modbus-TCP via `pymodbus`. Read function codes **FC01 (coils), FC02 (discrete), FC03 (holding), FC04 (input)**. Write FCs (**FC05/06/15/16**) = **not implemented** (read-only preview).
|
|
164
|
+
- **Connection params**: `host`, `port` (502), `unit_id`. Registers are untyped 16-bit words → `decode` hint (uint16/int16/uint32/int32/float32/raw); **big-endian** word order.
|
|
165
|
+
- **Coverage**: many domestic 国产 PLCs (汇川 Inovance / 信捷 Xinje / 和利时 Hollysys / 台达 Delta) and any Modbus-TCP vendor.
|
|
166
|
+
|
|
167
|
+
### S7comm (Siemens + 仿西门子 国产)
|
|
168
|
+
- **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").
|
|
169
|
+
- **Connection params**: `host`, `port` (102), `rack`, `slot` (0/1 for 1200/1500; 0/2 common for 300/400).
|
|
170
|
+
- **Write**: `s7_write_db` = **high risk_tier, MOC, dry-run default**, captures BEFORE value + undo.
|
|
171
|
+
- **Not supported / planned**: optimized/symbolic DB access on 1500 with "optimized block access" can require absolute-addressing config on the CPU.
|
|
172
|
+
|
|
173
|
+
### Mitsubishi MC
|
|
174
|
+
- **Versions/variants**: `pymcprotocol` — **MC 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).
|
|
175
|
+
- **Connection params**: `host`, `port` (5007 default; set to the module's open MC port), `plctype`.
|
|
176
|
+
- **Write**: `mc_write_words` = **high/MOC/dry-run default**, captures BEFORE + undo.
|
|
177
|
+
|
|
178
|
+
### MTConnect (ALL CNC machine tools)
|
|
179
|
+
- **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).
|
|
180
|
+
- **Connection params**: `agent_url` (e.g. `http://host:5000`).
|
|
181
|
+
- **Not supported / planned**: MTConnect streaming (long-poll `interval=`); only bounded `count=` samples.
|
|
182
|
+
|
|
183
|
+
### MQTT / Sparkplug B / UNS
|
|
184
|
+
- **Versions/variants**: `paho-mqtt` — **MQTT 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.
|
|
185
|
+
- **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.
|
|
186
|
+
- **Connection params**: `host`/`broker`, `port` (1883 / 8883 TLS), `topic`, `use_tls`, `username` (password encrypted).
|
|
187
|
+
- **Command**: `mqtt_publish` = **high/MOC/dry-run default**; a published command has **no automatic inverse**.
|
|
188
|
+
|
|
189
|
+
### EtherNet/IP (Rockwell / Allen-Bradley)
|
|
190
|
+
- **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.
|
|
191
|
+
- **Connection params**: `host`, `slot` (0 for CompactLogix; the CPU slot for a ControlLogix chassis), `port` (44818). `protocol: ethernetip` (alias `eip`).
|
|
192
|
+
- **Write**: `eip_write_tag` = **high risk_tier, MOC, dry-run default**, captures BEFORE value + undo.
|
|
193
|
+
- **Not supported / planned**: **PLC-5 / SLC-500 (PCCC)** and **Micro800** are **not supported = roadmap** (Logix tag model only).
|
|
194
|
+
|
|
195
|
+
### EtherCAT (pysoem / SOEM fieldbus master)
|
|
196
|
+
- **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`).
|
|
197
|
+
- **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).
|
|
198
|
+
- **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**.
|
|
199
|
+
- **Connection params**: `nic` (the dedicated interface name, e.g. `eth1`; alias `interface`), optional `expected_slaves` (a sanity check vs the bus scan). `protocol: ethercat`.
|
|
200
|
+
- **Operations matrix**:
|
|
201
|
+
|
|
202
|
+
| Tool | Op | R/W | risk | Capture/notes |
|
|
203
|
+
|------|----|:---:|:----:|---------------|
|
|
204
|
+
| `ethercat_master_state` | master + WKC state, slave count | R | low | expected vs found |
|
|
205
|
+
| `ethercat_slaves` | bus scan / enumerate | R | low | index/vendor/product/rev/addr/AL-state |
|
|
206
|
+
| `ethercat_slave_info` | one-slave detail | R | low | SM/FMMU + OD summary |
|
|
207
|
+
| `ethercat_read_sdo` | CoE SDO upload | R | low | hex + uint interpretation |
|
|
208
|
+
| `ethercat_read_pdo` | input PDO snapshot | R | low | single cycle, never loops |
|
|
209
|
+
| `ethercat_write_sdo` | CoE SDO download | **W** | **high/MOC** | before-value (SDO read-back) + undo |
|
|
210
|
+
| `ethercat_set_state` | AL-state transition | **W** | **high/MOC** | before-state + undo; **can start/stop motion** |
|
|
211
|
+
|
|
212
|
+
- **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. 未经授权勿对生产控制系统写入.
|
|
213
|
+
|
|
214
|
+
### OEE / downtime analytics (cross-protocol, read-only)
|
|
215
|
+
- `oee_compute` — **OEE = 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.
|
|
216
|
+
- `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).
|
|
217
|
+
- `oee_multidim` — aggregates OEE across **machine × part × shift** (or any dimensions) from labelled records → the matrix + worst performers.
|
|
218
|
+
- Operate over **provided/collected inputs** (fully testable without a plant). `mtconnect_oee_snapshot` surfaces the live MTConnect availability/execution inputs that feed these.
|
|
219
|
+
|
|
220
|
+
### Active asset inventory / fingerprint (read-only)
|
|
221
|
+
- `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.
|
|
222
|
+
- **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.**
|
|
223
|
+
|
|
224
|
+
### OPC-UA Historical Access (HDA)
|
|
225
|
+
- `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.
|
|
226
|
+
|
|
227
|
+
### Change-of-value (CoV) monitor
|
|
228
|
+
- `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.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Install
|
|
233
|
+
|
|
234
|
+
Protocol client libraries are **optional extras** — install only the 1–2 protocols a site
|
|
235
|
+
actually runs (every protocol library is imported lazily; the base package installs and
|
|
236
|
+
imports without any of them, and a call to a not-installed protocol returns a teaching
|
|
237
|
+
error pointing at the right extra):
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
uv tool install "iaiops[opcua,modbus]" # just the protocols you need
|
|
241
|
+
# or one per site: pip install "iaiops[s7]" · everything: pip install "iaiops[all]"
|
|
242
|
+
# or a per-industry edition bundle: pip install "iaiops[fab]"
|
|
243
|
+
|
|
244
|
+
iaiops init # interactive: add endpoints, store passwords encrypted
|
|
245
|
+
iaiops doctor # config + per-protocol connectivity probe (point at simulators)
|
|
246
|
+
iaiops protocols # the capability map
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Protocol extras: `opcua` · `modbus` · `s7` · `mc` · `eip` · `mtconnect` · `sparkplug` · `secsgem` · `ethercat` · `all`.
|
|
250
|
+
|
|
251
|
+
**Edition bundles** (match the same-named `IAIOPS_MCP` profiles — install the protocols a vertical runs):
|
|
252
|
+
`fab` (secsgem + opcua + s7 + modbus) · `factory` (the discrete-manufacturing set — all protocols except SECS/GEM) · `process` (opcua + modbus). Energy/building bundles arrive with their signature protocols (IEC-104/DNP3/61850, BACnet).
|
|
253
|
+
|
|
254
|
+
### Master password
|
|
255
|
+
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:
|
|
256
|
+
```bash
|
|
257
|
+
export IAIOPS_MASTER_PASSWORD='…'
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Example `~/.iaiops/config.yaml` (one block per protocol)
|
|
261
|
+
```yaml
|
|
262
|
+
endpoints:
|
|
263
|
+
- name: line1
|
|
264
|
+
protocol: opcua
|
|
265
|
+
endpoint_url: opc.tcp://plc.lan:4840
|
|
266
|
+
# username: operator # password stored encrypted via init/secret set
|
|
267
|
+
tags:
|
|
268
|
+
- { ref: "ns=2;i=5", label: temp, warn_high: 70, alarm_high: 90 }
|
|
269
|
+
- name: plc2
|
|
270
|
+
protocol: modbus
|
|
271
|
+
host: 10.0.0.5
|
|
272
|
+
port: 502
|
|
273
|
+
unit_id: 1
|
|
274
|
+
- name: press1
|
|
275
|
+
protocol: s7
|
|
276
|
+
host: 10.0.0.6
|
|
277
|
+
rack: 0
|
|
278
|
+
slot: 1 # S7-1200/1500
|
|
279
|
+
- name: cell3
|
|
280
|
+
protocol: mc
|
|
281
|
+
host: 10.0.0.7
|
|
282
|
+
port: 5007
|
|
283
|
+
plctype: iQ-R
|
|
284
|
+
- name: vmc1
|
|
285
|
+
protocol: mtconnect
|
|
286
|
+
agent_url: http://10.0.0.8:5000
|
|
287
|
+
- name: uns
|
|
288
|
+
protocol: mqtt
|
|
289
|
+
host: broker.lan
|
|
290
|
+
use_tls: true # → port 8883
|
|
291
|
+
topic: spBv1.0/#
|
|
292
|
+
# username: edge1 # password stored encrypted
|
|
293
|
+
- name: cell5
|
|
294
|
+
protocol: ethernetip # alias: eip
|
|
295
|
+
host: 10.0.0.9
|
|
296
|
+
slot: 0 # 0 for CompactLogix; CPU slot for ControlLogix
|
|
297
|
+
- name: bus1
|
|
298
|
+
protocol: ethercat # Linux + root/CAP_NET_RAW + pip install iaiops[ethercat]
|
|
299
|
+
nic: eth1 # dedicated NIC cabled to the EtherCAT bus
|
|
300
|
+
expected_slaves: 8 # optional sanity check vs the bus scan
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### `iaiops init` walkthrough (per protocol)
|
|
304
|
+
```
|
|
305
|
+
$ iaiops init
|
|
306
|
+
Step 1 — master password: ********
|
|
307
|
+
Step 2 — add an endpoint
|
|
308
|
+
Endpoint name (e.g. line1): press1
|
|
309
|
+
Protocol ('opcua','modbus','s7','mc','mtconnect','mqtt') [opcua]: s7
|
|
310
|
+
S7 PLC host (IP/FQDN): 10.0.0.6
|
|
311
|
+
Port [102]: 102
|
|
312
|
+
Rack (0 for S7-1200/1500) [0]: 0
|
|
313
|
+
Slot (1 for S7-1200/1500, 2 for S7-300/400) [1]: 1
|
|
314
|
+
✓ Saved endpoint 'press1'.
|
|
315
|
+
```
|
|
316
|
+
(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.)
|
|
317
|
+
|
|
318
|
+
### Test against a simulator (per protocol)
|
|
319
|
+
- **OPC-UA** — an `asyncua` demo server (the test suite runs a real in-process one).
|
|
320
|
+
- **Modbus** — ModbusPal or a `pymodbus` server simulator.
|
|
321
|
+
- **S7** — a pyS7/snap7 S7 server sim (Snap7 server) on `:102`.
|
|
322
|
+
- **MTConnect** — the public MTConnect demo agent, or a local agent.
|
|
323
|
+
- **MQTT** — a local `mosquitto` broker (+ a Sparkplug edge for SpB topics).
|
|
324
|
+
- **Mitsubishi MC** — GX Simulator / an MC 3E server sim.
|
|
325
|
+
- **EtherNet/IP** — a pycomm3-compatible CIP/Logix simulator (or a spare CompactLogix).
|
|
326
|
+
- **EtherCAT** — **no 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.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Usage
|
|
331
|
+
|
|
332
|
+
### CLI (read)
|
|
333
|
+
```bash
|
|
334
|
+
iaiops opcua read "ns=2;i=5" -e line1
|
|
335
|
+
iaiops modbus holding 0 -e plc2 --count 4 --decode float32
|
|
336
|
+
iaiops s7 read-db 1 REAL 4 -e press1 --count 2
|
|
337
|
+
iaiops mc words D100 -e cell3 --count 8
|
|
338
|
+
iaiops mtconnect oee -e vmc1
|
|
339
|
+
iaiops mqtt nodes -e uns --timeout-s 15
|
|
340
|
+
iaiops eip tags -e cell5 # Logix tag discovery
|
|
341
|
+
iaiops eip read "Conveyor.Speed" -e cell5
|
|
342
|
+
iaiops ethercat slaves -e bus1 # EtherCAT bus scan (Linux+root)
|
|
343
|
+
iaiops ethercat read-sdo 0 4120 --subindex 1 -e bus1 # CoE SDO 0x1018:1
|
|
344
|
+
iaiops opcua history "ns=2;i=5" -e line1 --start 2026-06-28T08:00:00Z # HDA
|
|
345
|
+
iaiops opcua monitor "ns=2;i=5" -e line1 --duration-s 20 --deadband 0.5 # CoV
|
|
346
|
+
iaiops diag dataflow -e line1 --ref "ns=2;i=5" --freshness-s 30
|
|
347
|
+
iaiops analytics oee 28800 25200 2.0 12000 11800 # OEE = A×P×Q
|
|
348
|
+
iaiops analytics asset -e press1 -e cell5 # active asset register
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### CLI (write — dry-run by default, double-confirm on `--apply`)
|
|
352
|
+
```bash
|
|
353
|
+
iaiops s7 write-db 1 INT 0 42 -e press1 # dry-run preview
|
|
354
|
+
iaiops s7 write-db 1 INT 0 42 -e press1 --apply # double-confirm prompt
|
|
355
|
+
iaiops mqtt publish factory/line1/cmd '{"setpoint":50}' -e uns --apply
|
|
356
|
+
iaiops eip write-tag Setpoint 42 -e cell5 --apply # Logix tag write (double-confirm)
|
|
357
|
+
iaiops ethercat write-sdo 0 24698 e8030000 -e bus1 --apply # CoE SDO 0x607A download
|
|
358
|
+
iaiops ethercat set-state PREOP --slave 0 -e bus1 --apply # AL-state (can stop motion!)
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### MCP tool calls (JSON args → sample structured return)
|
|
362
|
+
|
|
363
|
+
`s7_read_db`:
|
|
364
|
+
```json
|
|
365
|
+
{ "db": 1, "dtype": "REAL", "start": 4, "endpoint": "press1", "count": 2 }
|
|
366
|
+
```
|
|
367
|
+
```json
|
|
368
|
+
{ "endpoint": "press1", "area": "DB", "db": 1, "dtype": "REAL", "start": 4,
|
|
369
|
+
"count": 2, "items": [ {"address": "DB1,REAL4", "value": 20.5},
|
|
370
|
+
{"address": "DB1,REAL8", "value": 4.2} ] }
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
`s7_write_db` (dry-run):
|
|
374
|
+
```json
|
|
375
|
+
{ "db": 1, "dtype": "INT", "start": 0, "value": 42, "endpoint": "press1" }
|
|
376
|
+
```
|
|
377
|
+
```json
|
|
378
|
+
{ "address": "DB1,INT0", "dry_run": true, "before": 7, "would_write": 42,
|
|
379
|
+
"note": "Dry run — nothing written. Re-run with dry_run=false AND a recorded approver…" }
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`mtconnect_oee_snapshot`:
|
|
383
|
+
```json
|
|
384
|
+
{ "availability": "AVAILABLE", "execution": "ACTIVE", "controller_mode": "AUTOMATIC",
|
|
385
|
+
"program": "O1234", "available": true, "running": true, "verdict": "running" }
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
`eip_read_tag`:
|
|
389
|
+
```json
|
|
390
|
+
{ "tag": "Conveyor.Speed", "endpoint": "cell5" }
|
|
391
|
+
```
|
|
392
|
+
```json
|
|
393
|
+
{ "endpoint": "cell5", "tag": "Conveyor.Speed", "value": 1500.0, "type": "REAL",
|
|
394
|
+
"error": "", "good": true }
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`eip_write_tag` (dry-run):
|
|
398
|
+
```json
|
|
399
|
+
{ "tag": "Setpoint", "value": 42, "endpoint": "cell5" }
|
|
400
|
+
```
|
|
401
|
+
```json
|
|
402
|
+
{ "endpoint": "cell5", "tag": "Setpoint", "dry_run": true, "before": 7,
|
|
403
|
+
"would_write": 42, "note": "Dry run — nothing written. Re-run with dry_run=false AND a recorded approver…" }
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
`ethercat_read_sdo` (CoE SDO upload):
|
|
407
|
+
```json
|
|
408
|
+
{ "slave": 0, "index": 4120, "subindex": 1, "endpoint": "bus1" }
|
|
409
|
+
```
|
|
410
|
+
```json
|
|
411
|
+
{ "endpoint": "bus1", "slave": 0, "index": "0x1018", "subindex": 1,
|
|
412
|
+
"byte_length": 4, "hex": "9a020000", "as_uint": 666 }
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
`ethercat_set_state` (dry-run; can start/stop motion):
|
|
416
|
+
```json
|
|
417
|
+
{ "state": "OP", "slave": 0, "endpoint": "bus1" }
|
|
418
|
+
```
|
|
419
|
+
```json
|
|
420
|
+
{ "endpoint": "bus1", "scope": "slave[0]", "dry_run": true, "before": "SAFEOP",
|
|
421
|
+
"would_request": "OP", "note": "Dry run — no state change. … Changing EtherCAT state can start/stop machine motion." }
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
`sparkplug_decode_payload` (full SpB metric decode):
|
|
425
|
+
```json
|
|
426
|
+
{ "payload": "CAESBwoDYWJjEAE=", "encoding": "base64" }
|
|
427
|
+
```
|
|
428
|
+
```json
|
|
429
|
+
{ "encoding": "sparkplug_b", "seq": 0, "metric_count": 2, "historical_count": 0,
|
|
430
|
+
"metrics": [ {"name": "Temperature", "alias": 1, "datatype": "Double", "value": 21.5,
|
|
431
|
+
"is_historical": false, "is_null": false} ] }
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
`oee_compute`:
|
|
435
|
+
```json
|
|
436
|
+
{ "planned_time_s": 28800, "run_time_s": 25200, "ideal_cycle_time_s": 2.0,
|
|
437
|
+
"total_count": 12000, "good_count": 11800 }
|
|
438
|
+
```
|
|
439
|
+
```json
|
|
440
|
+
{ "availability": {"raw": 0.875, "value": 0.875, "capped": false},
|
|
441
|
+
"performance": {"value": 0.952381}, "quality": {"value": 0.983333},
|
|
442
|
+
"oee": 0.819444, "oee_pct": 81.94 }
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
`asset_inventory` (active fingerprint):
|
|
446
|
+
```json
|
|
447
|
+
{ "endpoints": ["press1", "cell5"] }
|
|
448
|
+
```
|
|
449
|
+
```json
|
|
450
|
+
{ "asset_count": 2, "reachable_count": 2, "method": "active_fingerprint",
|
|
451
|
+
"assets": [ {"endpoint": "press1", "protocol": "s7", "vendor": "Siemens/compatible",
|
|
452
|
+
"model": "CPU 1511-1 PN", "firmware": "2.8", "reachable": true,
|
|
453
|
+
"last_seen": "2026-06-28T10:00:00+00:00"} ] }
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Diagnostics (multi-dimensional JSON for an agent to visualize)
|
|
457
|
+
|
|
458
|
+
`diagnose_dataflow(endpoint="line1", ref="ns=2;i=5", freshness_threshold_s=30)`:
|
|
459
|
+
```json
|
|
460
|
+
{ "verdict": "comms_ok_value_stale",
|
|
461
|
+
"diagnosis": "Connected with good status, but the value is STALE (age 412s > 30s) — the source/field upstream has stopped updating this point.",
|
|
462
|
+
"recommended_action": "Trace upstream: the device serves the last value fine, so suspect the source/scanner/field signal that should refresh it.",
|
|
463
|
+
"hops": [ {"hop":"connect","protocol":"opcua","ok":true,"detail":"OPC-UA state=0"},
|
|
464
|
+
{"hop":"read_tag","ref":"ns=2;i=5","ok":true,"detail":"5.0"},
|
|
465
|
+
{"hop":"freshness","evaluated":true,"stale":true,"age_seconds":412.0} ] }
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
`alarm_bad_actors(events=[…])`:
|
|
469
|
+
```json
|
|
470
|
+
{ "event_count": 55, "window_minutes": 0.82, "alarms_per_hour": 4024.4,
|
|
471
|
+
"isa_18_2": {"ok_max":6,"manageable_max":12,"flood_min":30},
|
|
472
|
+
"flood_verdict": "flood",
|
|
473
|
+
"priority_distribution": {"high":50,"low":5},
|
|
474
|
+
"pareto_sources_for_80pct": ["FIC101"],
|
|
475
|
+
"top_offenders": [ {"source":"FIC101","count":50,"share_pct":90.9,"chattering":true,"standing":false} ],
|
|
476
|
+
"chattering": ["FIC101"], "standing": [] }
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
`tag_health(tags=[…])`:
|
|
480
|
+
```json
|
|
481
|
+
{ "evaluated": 4, "overall": "alarm", "offender_count": 3,
|
|
482
|
+
"offenders": [ {"ref":"hot","latest":99,"flags":["out_of_range_alarm"],"severity":3},
|
|
483
|
+
{"ref":"flat","latest":5,"flags":["flatline"],"severity":2},
|
|
484
|
+
{"ref":"bad","latest":null,"flags":["bad_quality"],"severity":3} ] }
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### MCP server
|
|
488
|
+
```bash
|
|
489
|
+
iaiops mcp # stdio transport; or the `iaiops-mcp` entry point
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
**Menu — expose only the protocols a site runs.** A fab usually runs 1–2 protocols;
|
|
493
|
+
exposing all 8 floods the model with tools it can't use. Set `IAIOPS_MCP` to a
|
|
494
|
+
comma-list of protocols and/or a named profile (default `all`). The cross-protocol
|
|
495
|
+
brain (OEE / downtime / diagnostics / asset / analysis) is **always** exposed.
|
|
496
|
+
|
|
497
|
+
```bash
|
|
498
|
+
IAIOPS_MCP=opcua,modbus iaiops-mcp # 26 tools instead of 66
|
|
499
|
+
IAIOPS_MCP=fab iaiops-mcp # named profile (opcua+s7+modbus)
|
|
500
|
+
IAIOPS_MCP=opcua iaiops-mcp # effectively a single-protocol MCP
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Named profiles: `all` · `fab` · `factory` · `process`. In an MCP client (e.g. Claude
|
|
504
|
+
Desktop) set `IAIOPS_MCP` per server entry — one entry per site/line, each a lean
|
|
505
|
+
single- or dual-protocol server.
|
|
506
|
+
|
|
507
|
+
---
|
|
508
|
+
|
|
509
|
+
## Safety & governance
|
|
510
|
+
|
|
511
|
+
- **Read-first.** 60 of 66 tools are read-only. The 6 write/command tools (`s7_write_db`, `mc_write_words`, `mqtt_publish`, `eip_write_tag`, `ethercat_write_sdo`, `ethercat_set_state`) are **OT-dangerous**: governed at **high risk_tier**, **off by default (dry-run)**, capture the **BEFORE value/state for undo**, require a **double-confirm in the CLI**, and (via policy) a recorded approver — **MOC discipline**. **`ethercat_set_state` can START or STOP machine motion.** 未经授权勿对生产控制系统写入.
|
|
512
|
+
- **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.
|
|
513
|
+
- 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.
|
|
514
|
+
- Every tool runs through the vendored governance harness: SQLite **audit** (`~/.iaiops/audit.db`), token/call **budget** + runaway breaker, **risk-tier** gate, **undo** recording.
|
|
515
|
+
|
|
516
|
+
## Roadmap
|
|
517
|
+
|
|
518
|
+
- EtherNet/IP **PLC-5 / SLC-500 (PCCC)** and **Micro800** support (Logix tags are done in 0.2.0).
|
|
519
|
+
- **Passive** asset discovery (SPAN/tap, no connections) alongside today's active fingerprint.
|
|
520
|
+
- 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).
|
|
521
|
+
- OPC-UA certificate security + real Alarms & Conditions subscriptions.
|
|
522
|
+
- MTConnect streaming long-poll; Sparkplug B DataSet/Template deep expansion.
|
|
523
|
+
|
|
524
|
+
**Missing a protocol, device, or feature? 缺功能提 issue/PR 欢迎留言** — open a [GitHub issue or PR](https://github.com/industrial-aiops/industrial-aiops/issues).
|
|
525
|
+
|
|
526
|
+
## License
|
|
527
|
+
|
|
528
|
+
MIT © wei
|