ot-aiops 0.1.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.
- ot_aiops-0.1.0/.gitignore +9 -0
- ot_aiops-0.1.0/LICENSE +21 -0
- ot_aiops-0.1.0/PKG-INFO +306 -0
- ot_aiops-0.1.0/README.md +283 -0
- ot_aiops-0.1.0/mcp_server/__init__.py +1 -0
- ot_aiops-0.1.0/mcp_server/_shared.py +115 -0
- ot_aiops-0.1.0/mcp_server/server.py +42 -0
- ot_aiops-0.1.0/mcp_server/tools/__init__.py +1 -0
- ot_aiops-0.1.0/mcp_server/tools/analysis_tools.py +53 -0
- ot_aiops-0.1.0/mcp_server/tools/diagnostics_tools.py +126 -0
- ot_aiops-0.1.0/mcp_server/tools/ethercat_tools.py +23 -0
- ot_aiops-0.1.0/mcp_server/tools/ethernetip_tools.py +26 -0
- ot_aiops-0.1.0/mcp_server/tools/mc_tools.py +135 -0
- ot_aiops-0.1.0/mcp_server/tools/modbus_tools.py +92 -0
- ot_aiops-0.1.0/mcp_server/tools/mtconnect_tools.py +103 -0
- ot_aiops-0.1.0/mcp_server/tools/opcua_tools.py +108 -0
- ot_aiops-0.1.0/mcp_server/tools/overview_tools.py +25 -0
- ot_aiops-0.1.0/mcp_server/tools/s7_tools.py +154 -0
- ot_aiops-0.1.0/mcp_server/tools/sparkplug_tools.py +144 -0
- ot_aiops-0.1.0/ot_aiops/__init__.py +10 -0
- ot_aiops-0.1.0/ot_aiops/cli/__init__.py +5 -0
- ot_aiops-0.1.0/ot_aiops/cli/_common.py +56 -0
- ot_aiops-0.1.0/ot_aiops/cli/_root.py +71 -0
- ot_aiops-0.1.0/ot_aiops/cli/diagnostics.py +67 -0
- ot_aiops-0.1.0/ot_aiops/cli/doctor.py +21 -0
- ot_aiops-0.1.0/ot_aiops/cli/init.py +155 -0
- ot_aiops-0.1.0/ot_aiops/cli/mc.py +72 -0
- ot_aiops-0.1.0/ot_aiops/cli/modbus.py +82 -0
- ot_aiops-0.1.0/ot_aiops/cli/mqtt.py +78 -0
- ot_aiops-0.1.0/ot_aiops/cli/mtconnect.py +56 -0
- ot_aiops-0.1.0/ot_aiops/cli/opcua.py +102 -0
- ot_aiops-0.1.0/ot_aiops/cli/s7.py +75 -0
- ot_aiops-0.1.0/ot_aiops/cli/secret.py +102 -0
- ot_aiops-0.1.0/ot_aiops/config.py +343 -0
- ot_aiops-0.1.0/ot_aiops/connection.py +449 -0
- ot_aiops-0.1.0/ot_aiops/doctor.py +139 -0
- ot_aiops-0.1.0/ot_aiops/governance/__init__.py +40 -0
- ot_aiops-0.1.0/ot_aiops/governance/audit.py +377 -0
- ot_aiops-0.1.0/ot_aiops/governance/budget.py +225 -0
- ot_aiops-0.1.0/ot_aiops/governance/decorators.py +474 -0
- ot_aiops-0.1.0/ot_aiops/governance/paths.py +23 -0
- ot_aiops-0.1.0/ot_aiops/governance/patterns.py +378 -0
- ot_aiops-0.1.0/ot_aiops/governance/policy.py +411 -0
- ot_aiops-0.1.0/ot_aiops/governance/sanitize.py +39 -0
- ot_aiops-0.1.0/ot_aiops/governance/undo.py +218 -0
- ot_aiops-0.1.0/ot_aiops/ops/__init__.py +5 -0
- ot_aiops-0.1.0/ot_aiops/ops/_shared.py +34 -0
- ot_aiops-0.1.0/ot_aiops/ops/analysis.py +167 -0
- ot_aiops-0.1.0/ot_aiops/ops/diagnostics.py +576 -0
- ot_aiops-0.1.0/ot_aiops/ops/ethercat.py +37 -0
- ot_aiops-0.1.0/ot_aiops/ops/ethernetip.py +37 -0
- ot_aiops-0.1.0/ot_aiops/ops/mc_ops.py +138 -0
- ot_aiops-0.1.0/ot_aiops/ops/modbus_ops.py +202 -0
- ot_aiops-0.1.0/ot_aiops/ops/mtconnect_ops.py +237 -0
- ot_aiops-0.1.0/ot_aiops/ops/opcua_ops.py +272 -0
- ot_aiops-0.1.0/ot_aiops/ops/overview.py +131 -0
- ot_aiops-0.1.0/ot_aiops/ops/s7_ops.py +199 -0
- ot_aiops-0.1.0/ot_aiops/ops/sparkplug_ops.py +284 -0
- ot_aiops-0.1.0/ot_aiops/secretstore.py +305 -0
- ot_aiops-0.1.0/pyproject.toml +67 -0
- ot_aiops-0.1.0/server.json +21 -0
- ot_aiops-0.1.0/skills/industrial-diagnostics/SKILL.md +65 -0
- ot_aiops-0.1.0/skills/mc-tap/SKILL.md +52 -0
- ot_aiops-0.1.0/skills/modbus-tap/SKILL.md +49 -0
- ot_aiops-0.1.0/skills/mtconnect-tap/SKILL.md +49 -0
- ot_aiops-0.1.0/skills/opcua-tap/SKILL.md +53 -0
- ot_aiops-0.1.0/skills/s7-tap/SKILL.md +56 -0
- ot_aiops-0.1.0/skills/sparkplug-tap/SKILL.md +53 -0
- ot_aiops-0.1.0/tests/test_diagnostics.py +160 -0
- ot_aiops-0.1.0/tests/test_mc.py +106 -0
- ot_aiops-0.1.0/tests/test_modbus.py +135 -0
- ot_aiops-0.1.0/tests/test_mtconnect.py +152 -0
- ot_aiops-0.1.0/tests/test_opcua_server.py +141 -0
- ot_aiops-0.1.0/tests/test_s7.py +105 -0
- ot_aiops-0.1.0/tests/test_secretstore.py +110 -0
- ot_aiops-0.1.0/tests/test_smoke.py +336 -0
- ot_aiops-0.1.0/tests/test_sparkplug.py +101 -0
ot_aiops-0.1.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.
|
ot_aiops-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: ot-aiops
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Governed, vendor-neutral OT data tap + intelligent troubleshooting for AI agents (OPC-UA / Modbus / S7comm / Mitsubishi MC / MTConnect / MQTT-Sparkplug) with a built-in governance harness (audit, budget, risk tiers, MOC)
|
|
5
|
+
Author-email: wei <zhouwei008@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Keywords: ai-ops,industrial,mcp,mitsubishi,modbus,mqtt,mtconnect,opc-ua,ot,plc,s7comm,scada,siemens,sparkplug,uns
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Requires-Dist: asyncua<2,>=1.0
|
|
11
|
+
Requires-Dist: cryptography>=42.0
|
|
12
|
+
Requires-Dist: mcp[cli]<2.0,>=1.10
|
|
13
|
+
Requires-Dist: paho-mqtt<3,>=2.0
|
|
14
|
+
Requires-Dist: pymcprotocol<1,>=0.3
|
|
15
|
+
Requires-Dist: pymodbus<4,>=3.5
|
|
16
|
+
Requires-Dist: pys7<3,>=2.8
|
|
17
|
+
Requires-Dist: python-dotenv<2.0,>=1.0
|
|
18
|
+
Requires-Dist: pyyaml<7.0,>=6.0
|
|
19
|
+
Requires-Dist: requests<3,>=2.31
|
|
20
|
+
Requires-Dist: rich<16.0,>=13.0
|
|
21
|
+
Requires-Dist: typer<1.0,>=0.12
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# OT-AIops
|
|
25
|
+
|
|
26
|
+
**Governed, vendor-neutral industrial data tap + intelligent troubleshooting for AI agents — across OPC-UA, Modbus-TCP, S7comm, Mitsubishi MC, MTConnect, and MQTT/Sparkplug B.**
|
|
27
|
+
|
|
28
|
+
OT-AIops is the OT/industrial member of [AIops-tools](https://github.com/AIops-tools). 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), and ranks unhealthy tags. 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).
|
|
29
|
+
|
|
30
|
+
> ⚠️ **Preview / v0.1.0** — validated against an **in-process OPC-UA simulator, mocked Modbus/S7/Mitsubishi clients, static MTConnect XML fixtures, and synthetic MQTT/Sparkplug payloads**. **NOT tested against live PLCs / SCADA / brokers.** See *Safety*.
|
|
31
|
+
|
|
32
|
+
## Why
|
|
33
|
+
|
|
34
|
+
OT is exactly where you want an agent on a tight leash: read first, never blind-write. OT-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.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Consolidated capability matrix
|
|
39
|
+
|
|
40
|
+
| Protocol | Tool | Operation | R/W | risk_tier | Returns (key fields) |
|
|
41
|
+
|----------|------|-----------|:---:|:---------:|----------------------|
|
|
42
|
+
| OPC-UA | `opcua_server_info` | server status | R | low | state, product_name, namespaces |
|
|
43
|
+
| OPC-UA | `opcua_browse` | browse node tree | R | low | [{node_id, browse_name, depth}] |
|
|
44
|
+
| OPC-UA | `opcua_read_node` | read one node | R | low | value, datatype, source_timestamp, good |
|
|
45
|
+
| OPC-UA | `opcua_read_many` | batch read | R | low | [{node_id, value, ...}] |
|
|
46
|
+
| OPC-UA | `opcua_subscribe_sample` | bounded sample | R | low | {collected, samples[]} |
|
|
47
|
+
| OPC-UA | `opcua_read_alarms` | alarm surfacing | R | low | {active_alarms[], active_count} |
|
|
48
|
+
| OPC-UA | `health_summary` | threshold classify | R | low | {overall, counts, offenders[]} |
|
|
49
|
+
| OPC-UA | `anomaly_scan` | stddev outliers | R | low | {mean, stddev, outliers[]} |
|
|
50
|
+
| Modbus | `modbus_read_holding` | FC03 | R | low | {raw_registers, decoded[]} |
|
|
51
|
+
| Modbus | `modbus_read_input` | FC04 | R | low | {raw_registers, decoded[]} |
|
|
52
|
+
| Modbus | `modbus_read_coils` | FC01 | R | low | {bits[]} |
|
|
53
|
+
| Modbus | `modbus_read_discrete` | FC02 | R | low | {bits[]} |
|
|
54
|
+
| Modbus | `modbus_health_summary` | threshold classify | R | low | {overall, counts, offenders[]} |
|
|
55
|
+
| S7comm | `s7_cpu_info` | CPU id + run/stop | R | low | {cpu_status, cpu_info} |
|
|
56
|
+
| S7comm | `s7_read_area` | read DB/M/I/Q | R | low | {items:[{address, value}]} |
|
|
57
|
+
| S7comm | `s7_read_db` | read data block | R | low | {items:[{address, value}]} |
|
|
58
|
+
| S7comm | `s7_read_many` | batch addresses | R | low | {items:[{address, value}]} |
|
|
59
|
+
| S7comm | `s7_write_db` | write data block | **W** | **high/MOC** | {before, written, _undo_id} |
|
|
60
|
+
| Mitsubishi MC | `mc_cpu_status` | CPU type | R | low | {cpu_type, cpu_code} |
|
|
61
|
+
| Mitsubishi MC | `mc_read_words` | word devices | R | low | {words[]} |
|
|
62
|
+
| Mitsubishi MC | `mc_read_bits` | bit devices | R | low | {bits[]} |
|
|
63
|
+
| Mitsubishi MC | `mc_read_many` | random read | R | low | {words[], dwords[]} |
|
|
64
|
+
| Mitsubishi MC | `mc_write_words` | write words | **W** | **high/MOC** | {before, written, _undo_id} |
|
|
65
|
+
| MTConnect | `mtconnect_probe` | device model | R | low | {devices:[{components:[{data_items}]}]} |
|
|
66
|
+
| MTConnect | `mtconnect_current` | latest values | R | low | {observations[]} |
|
|
67
|
+
| MTConnect | `mtconnect_sample` | bounded stream | R | low | {observations[]} |
|
|
68
|
+
| MTConnect | `mtconnect_assets` | assets | R | low | {assets[]} |
|
|
69
|
+
| MTConnect | `mtconnect_oee_snapshot` | OEE inputs | R | low | {availability, execution, verdict} |
|
|
70
|
+
| MQTT/Sparkplug | `mqtt_read_topic` | bounded read | R | low | {messages:[{topic, payload}]} |
|
|
71
|
+
| MQTT/Sparkplug | `sparkplug_subscribe_sample` | bounded SpB sample | R | low | {samples:[{sparkplug, payload}]} |
|
|
72
|
+
| MQTT/Sparkplug | `sparkplug_node_list` | node discovery | R | low | {nodes:[{group_id, edge_node_id, devices}]} |
|
|
73
|
+
| MQTT/Sparkplug | `uns_browse` | topic-tree browse | R | low | {topics[], tree{}} |
|
|
74
|
+
| MQTT/Sparkplug | `mqtt_publish` | publish/command | **W** | **high/MOC** | {published_bytes, applied} |
|
|
75
|
+
| Diagnostics | `diagnose_dataflow` | localize no-data | R | low | {verdict, diagnosis, hops[]} |
|
|
76
|
+
| Diagnostics | `alarm_bad_actors` | ISA-18.2 flood | R | low | {flood_verdict, top_offenders[]} |
|
|
77
|
+
| Diagnostics | `tag_health` | offender ranking | R | low | {overall, offenders[]} |
|
|
78
|
+
| Diagnostics | `historian_health` | gap/flatline | R | low | {verdict, gaps[]} |
|
|
79
|
+
| Self | `protocols_supported` | capability map | R | low | {protocols[], diagnostics[]} |
|
|
80
|
+
| Roadmap | `ethernetip_status` | Rockwell stub | R | low | {implemented:false, suggested_dependency} |
|
|
81
|
+
| Roadmap | `ethercat_status` | EtherCAT stub | R | low | {implemented:false, suggested_dependency} |
|
|
82
|
+
|
|
83
|
+
**40 tools** = 33 read · 3 write (MOC) · 4 diagnostics. Run `protocols_supported()` (or `ot-aiops protocols`) for the live map.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Per-protocol reference
|
|
88
|
+
|
|
89
|
+
### OPC-UA
|
|
90
|
+
- **Versions/variants**: binary `opc.tcp://` via `asyncua` (sync facade). Security: **anonymous + username/password**. Certificate message security (Sign / SignAndEncrypt) = **roadmap, not validated**.
|
|
91
|
+
- **Connection params**: `endpoint_url`, `username` (password encrypted), `security_mode`, `security_policy`.
|
|
92
|
+
- **Not supported / planned**: cert security; real Alarms & Conditions event subscriptions (alarms are surfaced best-effort by browsing alarm-like boolean nodes).
|
|
93
|
+
|
|
94
|
+
### Modbus-TCP
|
|
95
|
+
- **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).
|
|
96
|
+
- **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.
|
|
97
|
+
- **Coverage**: many domestic 国产 PLCs (汇川 Inovance / 信捷 Xinje / 和利时 Hollysys / 台达 Delta) and any Modbus-TCP vendor.
|
|
98
|
+
|
|
99
|
+
### S7comm (Siemens + 仿西门子 国产)
|
|
100
|
+
- **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").
|
|
101
|
+
- **Connection params**: `host`, `port` (102), `rack`, `slot` (0/1 for 1200/1500; 0/2 common for 300/400).
|
|
102
|
+
- **Write**: `s7_write_db` = **high risk_tier, MOC, dry-run default**, captures BEFORE value + undo.
|
|
103
|
+
- **Not supported / planned**: optimized/symbolic DB access on 1500 with "optimized block access" can require absolute-addressing config on the CPU.
|
|
104
|
+
|
|
105
|
+
### Mitsubishi MC
|
|
106
|
+
- **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).
|
|
107
|
+
- **Connection params**: `host`, `port` (5007 default; set to the module's open MC port), `plctype`.
|
|
108
|
+
- **Write**: `mc_write_words` = **high/MOC/dry-run default**, captures BEFORE + undo.
|
|
109
|
+
|
|
110
|
+
### MTConnect (ALL CNC machine tools)
|
|
111
|
+
- **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).
|
|
112
|
+
- **Connection params**: `agent_url` (e.g. `http://host:5000`).
|
|
113
|
+
- **Not supported / planned**: MTConnect streaming (long-poll `interval=`); only bounded `count=` samples.
|
|
114
|
+
|
|
115
|
+
### MQTT / Sparkplug B / UNS
|
|
116
|
+
- **Versions/variants**: `paho-mqtt` — **MQTT 3.1.1 & 5**. Sparkplug B topic convention `spBv1.0/{group}/{type}/{edge}/[device]` (NBIRTH/DBIRTH/NDATA/DDATA…). Sparkplug **protobuf payloads decode when an optional decoder (`tahu`) is installed**, else reported as binary with a hex preview + hint (JSON/text payloads always decode). TLS + username/password supported.
|
|
117
|
+
- **Connection params**: `host`/`broker`, `port` (1883 / 8883 TLS), `topic`, `use_tls`, `username` (password encrypted).
|
|
118
|
+
- **Command**: `mqtt_publish` = **high/MOC/dry-run default**; a published command has **no automatic inverse**.
|
|
119
|
+
|
|
120
|
+
### EtherNet/IP (Rockwell / Allen-Bradley) — roadmap stub
|
|
121
|
+
- `ethernetip_status` returns a clear "not implemented" + roadmap. Planned lib: **`pycomm3`** (pure-Python Logix tags). Not bundled to keep the install light.
|
|
122
|
+
|
|
123
|
+
### EtherCAT — roadmap stub
|
|
124
|
+
- `ethercat_status` returns a clear "not implemented" + roadmap. Needs a master stack (**`pysoem`/SOEM**) + a dedicated NIC + slave devices.
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Install
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
uv tool install ot-aiops # or: pip install ot-aiops
|
|
132
|
+
ot-aiops init # interactive: add endpoints, store passwords encrypted
|
|
133
|
+
ot-aiops doctor # config + per-protocol connectivity probe (point at simulators)
|
|
134
|
+
ot-aiops protocols # the capability map
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Master password
|
|
138
|
+
Secrets (per-endpoint passwords, MQTT credentials) are **never** stored in plaintext — they live in `~/.ot-aiops/secrets.enc` (Fernet + scrypt). Export `OT_AIOPS_MASTER_PASSWORD` so the MCP server/CLI can unlock non-interactively:
|
|
139
|
+
```bash
|
|
140
|
+
export OT_AIOPS_MASTER_PASSWORD='…'
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Example `~/.ot-aiops/config.yaml` (one block per protocol)
|
|
144
|
+
```yaml
|
|
145
|
+
endpoints:
|
|
146
|
+
- name: line1
|
|
147
|
+
protocol: opcua
|
|
148
|
+
endpoint_url: opc.tcp://plc.lan:4840
|
|
149
|
+
# username: operator # password stored encrypted via init/secret set
|
|
150
|
+
tags:
|
|
151
|
+
- { ref: "ns=2;i=5", label: temp, warn_high: 70, alarm_high: 90 }
|
|
152
|
+
- name: plc2
|
|
153
|
+
protocol: modbus
|
|
154
|
+
host: 10.0.0.5
|
|
155
|
+
port: 502
|
|
156
|
+
unit_id: 1
|
|
157
|
+
- name: press1
|
|
158
|
+
protocol: s7
|
|
159
|
+
host: 10.0.0.6
|
|
160
|
+
rack: 0
|
|
161
|
+
slot: 1 # S7-1200/1500
|
|
162
|
+
- name: cell3
|
|
163
|
+
protocol: mc
|
|
164
|
+
host: 10.0.0.7
|
|
165
|
+
port: 5007
|
|
166
|
+
plctype: iQ-R
|
|
167
|
+
- name: vmc1
|
|
168
|
+
protocol: mtconnect
|
|
169
|
+
agent_url: http://10.0.0.8:5000
|
|
170
|
+
- name: uns
|
|
171
|
+
protocol: mqtt
|
|
172
|
+
host: broker.lan
|
|
173
|
+
use_tls: true # → port 8883
|
|
174
|
+
topic: spBv1.0/#
|
|
175
|
+
# username: edge1 # password stored encrypted
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### `ot-aiops init` walkthrough (per protocol)
|
|
179
|
+
```
|
|
180
|
+
$ ot-aiops init
|
|
181
|
+
Step 1 — master password: ********
|
|
182
|
+
Step 2 — add an endpoint
|
|
183
|
+
Endpoint name (e.g. line1): press1
|
|
184
|
+
Protocol ('opcua','modbus','s7','mc','mtconnect','mqtt') [opcua]: s7
|
|
185
|
+
S7 PLC host (IP/FQDN): 10.0.0.6
|
|
186
|
+
Port [102]: 102
|
|
187
|
+
Rack (0 for S7-1200/1500) [0]: 0
|
|
188
|
+
Slot (1 for S7-1200/1500, 2 for S7-300/400) [1]: 1
|
|
189
|
+
✓ Saved endpoint 'press1'.
|
|
190
|
+
```
|
|
191
|
+
(MQTT prompts add TLS/topic/username; MTConnect prompts for `agent_url`; OPC-UA/MQTT prompt for a hidden password stored encrypted.)
|
|
192
|
+
|
|
193
|
+
### Test against a simulator (per protocol)
|
|
194
|
+
- **OPC-UA** — an `asyncua` demo server (the test suite runs a real in-process one).
|
|
195
|
+
- **Modbus** — ModbusPal or a `pymodbus` server simulator.
|
|
196
|
+
- **S7** — a pyS7/snap7 S7 server sim (Snap7 server) on `:102`.
|
|
197
|
+
- **MTConnect** — the public MTConnect demo agent, or a local agent.
|
|
198
|
+
- **MQTT** — a local `mosquitto` broker (+ a Sparkplug edge for SpB topics).
|
|
199
|
+
- **Mitsubishi MC** — GX Simulator / an MC 3E server sim.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Usage
|
|
204
|
+
|
|
205
|
+
### CLI (read)
|
|
206
|
+
```bash
|
|
207
|
+
ot-aiops opcua read "ns=2;i=5" -e line1
|
|
208
|
+
ot-aiops modbus holding 0 -e plc2 --count 4 --decode float32
|
|
209
|
+
ot-aiops s7 read-db 1 REAL 4 -e press1 --count 2
|
|
210
|
+
ot-aiops mc words D100 -e cell3 --count 8
|
|
211
|
+
ot-aiops mtconnect oee -e vmc1
|
|
212
|
+
ot-aiops mqtt nodes -e uns --timeout-s 15
|
|
213
|
+
ot-aiops diag dataflow -e line1 --ref "ns=2;i=5" --freshness-s 30
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### CLI (write — dry-run by default, double-confirm on `--apply`)
|
|
217
|
+
```bash
|
|
218
|
+
ot-aiops s7 write-db 1 INT 0 42 -e press1 # dry-run preview
|
|
219
|
+
ot-aiops s7 write-db 1 INT 0 42 -e press1 --apply # double-confirm prompt
|
|
220
|
+
ot-aiops mqtt publish factory/line1/cmd '{"setpoint":50}' -e uns --apply
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### MCP tool calls (JSON args → sample structured return)
|
|
224
|
+
|
|
225
|
+
`s7_read_db`:
|
|
226
|
+
```json
|
|
227
|
+
{ "db": 1, "dtype": "REAL", "start": 4, "endpoint": "press1", "count": 2 }
|
|
228
|
+
```
|
|
229
|
+
```json
|
|
230
|
+
{ "endpoint": "press1", "area": "DB", "db": 1, "dtype": "REAL", "start": 4,
|
|
231
|
+
"count": 2, "items": [ {"address": "DB1,REAL4", "value": 20.5},
|
|
232
|
+
{"address": "DB1,REAL8", "value": 4.2} ] }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`s7_write_db` (dry-run):
|
|
236
|
+
```json
|
|
237
|
+
{ "db": 1, "dtype": "INT", "start": 0, "value": 42, "endpoint": "press1" }
|
|
238
|
+
```
|
|
239
|
+
```json
|
|
240
|
+
{ "address": "DB1,INT0", "dry_run": true, "before": 7, "would_write": 42,
|
|
241
|
+
"note": "Dry run — nothing written. Re-run with dry_run=false AND a recorded approver…" }
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
`mtconnect_oee_snapshot`:
|
|
245
|
+
```json
|
|
246
|
+
{ "availability": "AVAILABLE", "execution": "ACTIVE", "controller_mode": "AUTOMATIC",
|
|
247
|
+
"program": "O1234", "available": true, "running": true, "verdict": "running" }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Diagnostics (multi-dimensional JSON for an agent to visualize)
|
|
251
|
+
|
|
252
|
+
`diagnose_dataflow(endpoint="line1", ref="ns=2;i=5", freshness_threshold_s=30)`:
|
|
253
|
+
```json
|
|
254
|
+
{ "verdict": "comms_ok_value_stale",
|
|
255
|
+
"diagnosis": "Connected with good status, but the value is STALE (age 412s > 30s) — the source/field upstream has stopped updating this point.",
|
|
256
|
+
"recommended_action": "Trace upstream: the device serves the last value fine, so suspect the source/scanner/field signal that should refresh it.",
|
|
257
|
+
"hops": [ {"hop":"connect","protocol":"opcua","ok":true,"detail":"OPC-UA state=0"},
|
|
258
|
+
{"hop":"read_tag","ref":"ns=2;i=5","ok":true,"detail":"5.0"},
|
|
259
|
+
{"hop":"freshness","evaluated":true,"stale":true,"age_seconds":412.0} ] }
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`alarm_bad_actors(events=[…])`:
|
|
263
|
+
```json
|
|
264
|
+
{ "event_count": 55, "window_minutes": 0.82, "alarms_per_hour": 4024.4,
|
|
265
|
+
"isa_18_2": {"ok_max":6,"manageable_max":12,"flood_min":30},
|
|
266
|
+
"flood_verdict": "flood",
|
|
267
|
+
"priority_distribution": {"high":50,"low":5},
|
|
268
|
+
"pareto_sources_for_80pct": ["FIC101"],
|
|
269
|
+
"top_offenders": [ {"source":"FIC101","count":50,"share_pct":90.9,"chattering":true,"standing":false} ],
|
|
270
|
+
"chattering": ["FIC101"], "standing": [] }
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
`tag_health(tags=[…])`:
|
|
274
|
+
```json
|
|
275
|
+
{ "evaluated": 4, "overall": "alarm", "offender_count": 3,
|
|
276
|
+
"offenders": [ {"ref":"hot","latest":99,"flags":["out_of_range_alarm"],"severity":3},
|
|
277
|
+
{"ref":"flat","latest":5,"flags":["flatline"],"severity":2},
|
|
278
|
+
{"ref":"bad","latest":null,"flags":["bad_quality"],"severity":3} ] }
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### MCP server
|
|
282
|
+
```bash
|
|
283
|
+
ot-aiops mcp # stdio transport; or the `ot-aiops-mcp` entry point
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## Safety & governance
|
|
289
|
+
|
|
290
|
+
- **Read-first.** 33 of 36 protocol tools are read-only. The 3 write/command tools (`s7_write_db`, `mc_write_words`, `mqtt_publish`) are **OT-dangerous**: governed at **high risk_tier**, **off by default (dry-run)**, capture the **BEFORE value for undo**, require a **double-confirm in the CLI**, and (via policy) a recorded approver — **MOC discipline**. 未经授权勿对生产控制系统写入.
|
|
291
|
+
- **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.
|
|
292
|
+
- 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.
|
|
293
|
+
- Every tool runs through the vendored governance harness: SQLite **audit** (`~/.ot-aiops/audit.db`), token/call **budget** + runaway breaker, **risk-tier** gate, **undo** recording.
|
|
294
|
+
|
|
295
|
+
## Roadmap
|
|
296
|
+
|
|
297
|
+
- EtherNet/IP read-first Logix tags via an optional `pycomm3` extra.
|
|
298
|
+
- EtherCAT read-only PDO/SDO via an optional `pysoem` extra.
|
|
299
|
+
- OPC-UA certificate security + real Alarms & Conditions subscriptions.
|
|
300
|
+
- Sparkplug B protobuf decode bundled; MTConnect streaming long-poll.
|
|
301
|
+
|
|
302
|
+
**Missing a protocol, device, or feature? 缺功能提 issue/PR 欢迎留言** — open a [GitHub issue or PR](https://github.com/AIops-tools/OT-AIops/issues).
|
|
303
|
+
|
|
304
|
+
## License
|
|
305
|
+
|
|
306
|
+
MIT © wei
|
ot_aiops-0.1.0/README.md
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# OT-AIops
|
|
2
|
+
|
|
3
|
+
**Governed, vendor-neutral industrial data tap + intelligent troubleshooting for AI agents — across OPC-UA, Modbus-TCP, S7comm, Mitsubishi MC, MTConnect, and MQTT/Sparkplug B.**
|
|
4
|
+
|
|
5
|
+
OT-AIops is the OT/industrial member of [AIops-tools](https://github.com/AIops-tools). 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), and ranks unhealthy tags. 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).
|
|
6
|
+
|
|
7
|
+
> ⚠️ **Preview / v0.1.0** — validated against an **in-process OPC-UA simulator, mocked Modbus/S7/Mitsubishi clients, static MTConnect XML fixtures, and synthetic MQTT/Sparkplug payloads**. **NOT tested against live PLCs / SCADA / brokers.** See *Safety*.
|
|
8
|
+
|
|
9
|
+
## Why
|
|
10
|
+
|
|
11
|
+
OT is exactly where you want an agent on a tight leash: read first, never blind-write. OT-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.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Consolidated capability matrix
|
|
16
|
+
|
|
17
|
+
| Protocol | Tool | Operation | R/W | risk_tier | Returns (key fields) |
|
|
18
|
+
|----------|------|-----------|:---:|:---------:|----------------------|
|
|
19
|
+
| OPC-UA | `opcua_server_info` | server status | R | low | state, product_name, namespaces |
|
|
20
|
+
| OPC-UA | `opcua_browse` | browse node tree | R | low | [{node_id, browse_name, depth}] |
|
|
21
|
+
| OPC-UA | `opcua_read_node` | read one node | R | low | value, datatype, source_timestamp, good |
|
|
22
|
+
| OPC-UA | `opcua_read_many` | batch read | R | low | [{node_id, value, ...}] |
|
|
23
|
+
| OPC-UA | `opcua_subscribe_sample` | bounded sample | R | low | {collected, samples[]} |
|
|
24
|
+
| OPC-UA | `opcua_read_alarms` | alarm surfacing | R | low | {active_alarms[], active_count} |
|
|
25
|
+
| OPC-UA | `health_summary` | threshold classify | R | low | {overall, counts, offenders[]} |
|
|
26
|
+
| OPC-UA | `anomaly_scan` | stddev outliers | R | low | {mean, stddev, outliers[]} |
|
|
27
|
+
| Modbus | `modbus_read_holding` | FC03 | R | low | {raw_registers, decoded[]} |
|
|
28
|
+
| Modbus | `modbus_read_input` | FC04 | R | low | {raw_registers, decoded[]} |
|
|
29
|
+
| Modbus | `modbus_read_coils` | FC01 | R | low | {bits[]} |
|
|
30
|
+
| Modbus | `modbus_read_discrete` | FC02 | R | low | {bits[]} |
|
|
31
|
+
| Modbus | `modbus_health_summary` | threshold classify | R | low | {overall, counts, offenders[]} |
|
|
32
|
+
| S7comm | `s7_cpu_info` | CPU id + run/stop | R | low | {cpu_status, cpu_info} |
|
|
33
|
+
| S7comm | `s7_read_area` | read DB/M/I/Q | R | low | {items:[{address, value}]} |
|
|
34
|
+
| S7comm | `s7_read_db` | read data block | R | low | {items:[{address, value}]} |
|
|
35
|
+
| S7comm | `s7_read_many` | batch addresses | R | low | {items:[{address, value}]} |
|
|
36
|
+
| S7comm | `s7_write_db` | write data block | **W** | **high/MOC** | {before, written, _undo_id} |
|
|
37
|
+
| Mitsubishi MC | `mc_cpu_status` | CPU type | R | low | {cpu_type, cpu_code} |
|
|
38
|
+
| Mitsubishi MC | `mc_read_words` | word devices | R | low | {words[]} |
|
|
39
|
+
| Mitsubishi MC | `mc_read_bits` | bit devices | R | low | {bits[]} |
|
|
40
|
+
| Mitsubishi MC | `mc_read_many` | random read | R | low | {words[], dwords[]} |
|
|
41
|
+
| Mitsubishi MC | `mc_write_words` | write words | **W** | **high/MOC** | {before, written, _undo_id} |
|
|
42
|
+
| MTConnect | `mtconnect_probe` | device model | R | low | {devices:[{components:[{data_items}]}]} |
|
|
43
|
+
| MTConnect | `mtconnect_current` | latest values | R | low | {observations[]} |
|
|
44
|
+
| MTConnect | `mtconnect_sample` | bounded stream | R | low | {observations[]} |
|
|
45
|
+
| MTConnect | `mtconnect_assets` | assets | R | low | {assets[]} |
|
|
46
|
+
| MTConnect | `mtconnect_oee_snapshot` | OEE inputs | R | low | {availability, execution, verdict} |
|
|
47
|
+
| MQTT/Sparkplug | `mqtt_read_topic` | bounded read | R | low | {messages:[{topic, payload}]} |
|
|
48
|
+
| MQTT/Sparkplug | `sparkplug_subscribe_sample` | bounded SpB sample | R | low | {samples:[{sparkplug, payload}]} |
|
|
49
|
+
| MQTT/Sparkplug | `sparkplug_node_list` | node discovery | R | low | {nodes:[{group_id, edge_node_id, devices}]} |
|
|
50
|
+
| MQTT/Sparkplug | `uns_browse` | topic-tree browse | R | low | {topics[], tree{}} |
|
|
51
|
+
| MQTT/Sparkplug | `mqtt_publish` | publish/command | **W** | **high/MOC** | {published_bytes, applied} |
|
|
52
|
+
| Diagnostics | `diagnose_dataflow` | localize no-data | R | low | {verdict, diagnosis, hops[]} |
|
|
53
|
+
| Diagnostics | `alarm_bad_actors` | ISA-18.2 flood | R | low | {flood_verdict, top_offenders[]} |
|
|
54
|
+
| Diagnostics | `tag_health` | offender ranking | R | low | {overall, offenders[]} |
|
|
55
|
+
| Diagnostics | `historian_health` | gap/flatline | R | low | {verdict, gaps[]} |
|
|
56
|
+
| Self | `protocols_supported` | capability map | R | low | {protocols[], diagnostics[]} |
|
|
57
|
+
| Roadmap | `ethernetip_status` | Rockwell stub | R | low | {implemented:false, suggested_dependency} |
|
|
58
|
+
| Roadmap | `ethercat_status` | EtherCAT stub | R | low | {implemented:false, suggested_dependency} |
|
|
59
|
+
|
|
60
|
+
**40 tools** = 33 read · 3 write (MOC) · 4 diagnostics. Run `protocols_supported()` (or `ot-aiops protocols`) for the live map.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Per-protocol reference
|
|
65
|
+
|
|
66
|
+
### OPC-UA
|
|
67
|
+
- **Versions/variants**: binary `opc.tcp://` via `asyncua` (sync facade). Security: **anonymous + username/password**. Certificate message security (Sign / SignAndEncrypt) = **roadmap, not validated**.
|
|
68
|
+
- **Connection params**: `endpoint_url`, `username` (password encrypted), `security_mode`, `security_policy`.
|
|
69
|
+
- **Not supported / planned**: cert security; real Alarms & Conditions event subscriptions (alarms are surfaced best-effort by browsing alarm-like boolean nodes).
|
|
70
|
+
|
|
71
|
+
### Modbus-TCP
|
|
72
|
+
- **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).
|
|
73
|
+
- **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.
|
|
74
|
+
- **Coverage**: many domestic 国产 PLCs (汇川 Inovance / 信捷 Xinje / 和利时 Hollysys / 台达 Delta) and any Modbus-TCP vendor.
|
|
75
|
+
|
|
76
|
+
### S7comm (Siemens + 仿西门子 国产)
|
|
77
|
+
- **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").
|
|
78
|
+
- **Connection params**: `host`, `port` (102), `rack`, `slot` (0/1 for 1200/1500; 0/2 common for 300/400).
|
|
79
|
+
- **Write**: `s7_write_db` = **high risk_tier, MOC, dry-run default**, captures BEFORE value + undo.
|
|
80
|
+
- **Not supported / planned**: optimized/symbolic DB access on 1500 with "optimized block access" can require absolute-addressing config on the CPU.
|
|
81
|
+
|
|
82
|
+
### Mitsubishi MC
|
|
83
|
+
- **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).
|
|
84
|
+
- **Connection params**: `host`, `port` (5007 default; set to the module's open MC port), `plctype`.
|
|
85
|
+
- **Write**: `mc_write_words` = **high/MOC/dry-run default**, captures BEFORE + undo.
|
|
86
|
+
|
|
87
|
+
### MTConnect (ALL CNC machine tools)
|
|
88
|
+
- **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).
|
|
89
|
+
- **Connection params**: `agent_url` (e.g. `http://host:5000`).
|
|
90
|
+
- **Not supported / planned**: MTConnect streaming (long-poll `interval=`); only bounded `count=` samples.
|
|
91
|
+
|
|
92
|
+
### MQTT / Sparkplug B / UNS
|
|
93
|
+
- **Versions/variants**: `paho-mqtt` — **MQTT 3.1.1 & 5**. Sparkplug B topic convention `spBv1.0/{group}/{type}/{edge}/[device]` (NBIRTH/DBIRTH/NDATA/DDATA…). Sparkplug **protobuf payloads decode when an optional decoder (`tahu`) is installed**, else reported as binary with a hex preview + hint (JSON/text payloads always decode). TLS + username/password supported.
|
|
94
|
+
- **Connection params**: `host`/`broker`, `port` (1883 / 8883 TLS), `topic`, `use_tls`, `username` (password encrypted).
|
|
95
|
+
- **Command**: `mqtt_publish` = **high/MOC/dry-run default**; a published command has **no automatic inverse**.
|
|
96
|
+
|
|
97
|
+
### EtherNet/IP (Rockwell / Allen-Bradley) — roadmap stub
|
|
98
|
+
- `ethernetip_status` returns a clear "not implemented" + roadmap. Planned lib: **`pycomm3`** (pure-Python Logix tags). Not bundled to keep the install light.
|
|
99
|
+
|
|
100
|
+
### EtherCAT — roadmap stub
|
|
101
|
+
- `ethercat_status` returns a clear "not implemented" + roadmap. Needs a master stack (**`pysoem`/SOEM**) + a dedicated NIC + slave devices.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Install
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv tool install ot-aiops # or: pip install ot-aiops
|
|
109
|
+
ot-aiops init # interactive: add endpoints, store passwords encrypted
|
|
110
|
+
ot-aiops doctor # config + per-protocol connectivity probe (point at simulators)
|
|
111
|
+
ot-aiops protocols # the capability map
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Master password
|
|
115
|
+
Secrets (per-endpoint passwords, MQTT credentials) are **never** stored in plaintext — they live in `~/.ot-aiops/secrets.enc` (Fernet + scrypt). Export `OT_AIOPS_MASTER_PASSWORD` so the MCP server/CLI can unlock non-interactively:
|
|
116
|
+
```bash
|
|
117
|
+
export OT_AIOPS_MASTER_PASSWORD='…'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Example `~/.ot-aiops/config.yaml` (one block per protocol)
|
|
121
|
+
```yaml
|
|
122
|
+
endpoints:
|
|
123
|
+
- name: line1
|
|
124
|
+
protocol: opcua
|
|
125
|
+
endpoint_url: opc.tcp://plc.lan:4840
|
|
126
|
+
# username: operator # password stored encrypted via init/secret set
|
|
127
|
+
tags:
|
|
128
|
+
- { ref: "ns=2;i=5", label: temp, warn_high: 70, alarm_high: 90 }
|
|
129
|
+
- name: plc2
|
|
130
|
+
protocol: modbus
|
|
131
|
+
host: 10.0.0.5
|
|
132
|
+
port: 502
|
|
133
|
+
unit_id: 1
|
|
134
|
+
- name: press1
|
|
135
|
+
protocol: s7
|
|
136
|
+
host: 10.0.0.6
|
|
137
|
+
rack: 0
|
|
138
|
+
slot: 1 # S7-1200/1500
|
|
139
|
+
- name: cell3
|
|
140
|
+
protocol: mc
|
|
141
|
+
host: 10.0.0.7
|
|
142
|
+
port: 5007
|
|
143
|
+
plctype: iQ-R
|
|
144
|
+
- name: vmc1
|
|
145
|
+
protocol: mtconnect
|
|
146
|
+
agent_url: http://10.0.0.8:5000
|
|
147
|
+
- name: uns
|
|
148
|
+
protocol: mqtt
|
|
149
|
+
host: broker.lan
|
|
150
|
+
use_tls: true # → port 8883
|
|
151
|
+
topic: spBv1.0/#
|
|
152
|
+
# username: edge1 # password stored encrypted
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `ot-aiops init` walkthrough (per protocol)
|
|
156
|
+
```
|
|
157
|
+
$ ot-aiops init
|
|
158
|
+
Step 1 — master password: ********
|
|
159
|
+
Step 2 — add an endpoint
|
|
160
|
+
Endpoint name (e.g. line1): press1
|
|
161
|
+
Protocol ('opcua','modbus','s7','mc','mtconnect','mqtt') [opcua]: s7
|
|
162
|
+
S7 PLC host (IP/FQDN): 10.0.0.6
|
|
163
|
+
Port [102]: 102
|
|
164
|
+
Rack (0 for S7-1200/1500) [0]: 0
|
|
165
|
+
Slot (1 for S7-1200/1500, 2 for S7-300/400) [1]: 1
|
|
166
|
+
✓ Saved endpoint 'press1'.
|
|
167
|
+
```
|
|
168
|
+
(MQTT prompts add TLS/topic/username; MTConnect prompts for `agent_url`; OPC-UA/MQTT prompt for a hidden password stored encrypted.)
|
|
169
|
+
|
|
170
|
+
### Test against a simulator (per protocol)
|
|
171
|
+
- **OPC-UA** — an `asyncua` demo server (the test suite runs a real in-process one).
|
|
172
|
+
- **Modbus** — ModbusPal or a `pymodbus` server simulator.
|
|
173
|
+
- **S7** — a pyS7/snap7 S7 server sim (Snap7 server) on `:102`.
|
|
174
|
+
- **MTConnect** — the public MTConnect demo agent, or a local agent.
|
|
175
|
+
- **MQTT** — a local `mosquitto` broker (+ a Sparkplug edge for SpB topics).
|
|
176
|
+
- **Mitsubishi MC** — GX Simulator / an MC 3E server sim.
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Usage
|
|
181
|
+
|
|
182
|
+
### CLI (read)
|
|
183
|
+
```bash
|
|
184
|
+
ot-aiops opcua read "ns=2;i=5" -e line1
|
|
185
|
+
ot-aiops modbus holding 0 -e plc2 --count 4 --decode float32
|
|
186
|
+
ot-aiops s7 read-db 1 REAL 4 -e press1 --count 2
|
|
187
|
+
ot-aiops mc words D100 -e cell3 --count 8
|
|
188
|
+
ot-aiops mtconnect oee -e vmc1
|
|
189
|
+
ot-aiops mqtt nodes -e uns --timeout-s 15
|
|
190
|
+
ot-aiops diag dataflow -e line1 --ref "ns=2;i=5" --freshness-s 30
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### CLI (write — dry-run by default, double-confirm on `--apply`)
|
|
194
|
+
```bash
|
|
195
|
+
ot-aiops s7 write-db 1 INT 0 42 -e press1 # dry-run preview
|
|
196
|
+
ot-aiops s7 write-db 1 INT 0 42 -e press1 --apply # double-confirm prompt
|
|
197
|
+
ot-aiops mqtt publish factory/line1/cmd '{"setpoint":50}' -e uns --apply
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### MCP tool calls (JSON args → sample structured return)
|
|
201
|
+
|
|
202
|
+
`s7_read_db`:
|
|
203
|
+
```json
|
|
204
|
+
{ "db": 1, "dtype": "REAL", "start": 4, "endpoint": "press1", "count": 2 }
|
|
205
|
+
```
|
|
206
|
+
```json
|
|
207
|
+
{ "endpoint": "press1", "area": "DB", "db": 1, "dtype": "REAL", "start": 4,
|
|
208
|
+
"count": 2, "items": [ {"address": "DB1,REAL4", "value": 20.5},
|
|
209
|
+
{"address": "DB1,REAL8", "value": 4.2} ] }
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`s7_write_db` (dry-run):
|
|
213
|
+
```json
|
|
214
|
+
{ "db": 1, "dtype": "INT", "start": 0, "value": 42, "endpoint": "press1" }
|
|
215
|
+
```
|
|
216
|
+
```json
|
|
217
|
+
{ "address": "DB1,INT0", "dry_run": true, "before": 7, "would_write": 42,
|
|
218
|
+
"note": "Dry run — nothing written. Re-run with dry_run=false AND a recorded approver…" }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
`mtconnect_oee_snapshot`:
|
|
222
|
+
```json
|
|
223
|
+
{ "availability": "AVAILABLE", "execution": "ACTIVE", "controller_mode": "AUTOMATIC",
|
|
224
|
+
"program": "O1234", "available": true, "running": true, "verdict": "running" }
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Diagnostics (multi-dimensional JSON for an agent to visualize)
|
|
228
|
+
|
|
229
|
+
`diagnose_dataflow(endpoint="line1", ref="ns=2;i=5", freshness_threshold_s=30)`:
|
|
230
|
+
```json
|
|
231
|
+
{ "verdict": "comms_ok_value_stale",
|
|
232
|
+
"diagnosis": "Connected with good status, but the value is STALE (age 412s > 30s) — the source/field upstream has stopped updating this point.",
|
|
233
|
+
"recommended_action": "Trace upstream: the device serves the last value fine, so suspect the source/scanner/field signal that should refresh it.",
|
|
234
|
+
"hops": [ {"hop":"connect","protocol":"opcua","ok":true,"detail":"OPC-UA state=0"},
|
|
235
|
+
{"hop":"read_tag","ref":"ns=2;i=5","ok":true,"detail":"5.0"},
|
|
236
|
+
{"hop":"freshness","evaluated":true,"stale":true,"age_seconds":412.0} ] }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`alarm_bad_actors(events=[…])`:
|
|
240
|
+
```json
|
|
241
|
+
{ "event_count": 55, "window_minutes": 0.82, "alarms_per_hour": 4024.4,
|
|
242
|
+
"isa_18_2": {"ok_max":6,"manageable_max":12,"flood_min":30},
|
|
243
|
+
"flood_verdict": "flood",
|
|
244
|
+
"priority_distribution": {"high":50,"low":5},
|
|
245
|
+
"pareto_sources_for_80pct": ["FIC101"],
|
|
246
|
+
"top_offenders": [ {"source":"FIC101","count":50,"share_pct":90.9,"chattering":true,"standing":false} ],
|
|
247
|
+
"chattering": ["FIC101"], "standing": [] }
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
`tag_health(tags=[…])`:
|
|
251
|
+
```json
|
|
252
|
+
{ "evaluated": 4, "overall": "alarm", "offender_count": 3,
|
|
253
|
+
"offenders": [ {"ref":"hot","latest":99,"flags":["out_of_range_alarm"],"severity":3},
|
|
254
|
+
{"ref":"flat","latest":5,"flags":["flatline"],"severity":2},
|
|
255
|
+
{"ref":"bad","latest":null,"flags":["bad_quality"],"severity":3} ] }
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
### MCP server
|
|
259
|
+
```bash
|
|
260
|
+
ot-aiops mcp # stdio transport; or the `ot-aiops-mcp` entry point
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Safety & governance
|
|
266
|
+
|
|
267
|
+
- **Read-first.** 33 of 36 protocol tools are read-only. The 3 write/command tools (`s7_write_db`, `mc_write_words`, `mqtt_publish`) are **OT-dangerous**: governed at **high risk_tier**, **off by default (dry-run)**, capture the **BEFORE value for undo**, require a **double-confirm in the CLI**, and (via policy) a recorded approver — **MOC discipline**. 未经授权勿对生产控制系统写入.
|
|
268
|
+
- **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.
|
|
269
|
+
- 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.
|
|
270
|
+
- Every tool runs through the vendored governance harness: SQLite **audit** (`~/.ot-aiops/audit.db`), token/call **budget** + runaway breaker, **risk-tier** gate, **undo** recording.
|
|
271
|
+
|
|
272
|
+
## Roadmap
|
|
273
|
+
|
|
274
|
+
- EtherNet/IP read-first Logix tags via an optional `pycomm3` extra.
|
|
275
|
+
- EtherCAT read-only PDO/SDO via an optional `pysoem` extra.
|
|
276
|
+
- OPC-UA certificate security + real Alarms & Conditions subscriptions.
|
|
277
|
+
- Sparkplug B protobuf decode bundled; MTConnect streaming long-poll.
|
|
278
|
+
|
|
279
|
+
**Missing a protocol, device, or feature? 缺功能提 issue/PR 欢迎留言** — open a [GitHub issue or PR](https://github.com/AIops-tools/OT-AIops/issues).
|
|
280
|
+
|
|
281
|
+
## License
|
|
282
|
+
|
|
283
|
+
MIT © wei
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""MCP server package for ot-aiops."""
|