omgkit 2.13.0 → 2.16.0
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.
- package/README.md +129 -10
- package/package.json +2 -2
- package/plugin/agents/api-designer.md +5 -0
- package/plugin/agents/architect.md +8 -0
- package/plugin/agents/brainstormer.md +4 -0
- package/plugin/agents/cicd-manager.md +6 -0
- package/plugin/agents/code-reviewer.md +6 -0
- package/plugin/agents/copywriter.md +2 -0
- package/plugin/agents/data-engineer.md +255 -0
- package/plugin/agents/database-admin.md +10 -0
- package/plugin/agents/debugger.md +10 -0
- package/plugin/agents/devsecops.md +314 -0
- package/plugin/agents/docs-manager.md +4 -0
- package/plugin/agents/domain-decomposer.md +181 -0
- package/plugin/agents/embedded-systems.md +397 -0
- package/plugin/agents/fullstack-developer.md +12 -0
- package/plugin/agents/game-systems-designer.md +375 -0
- package/plugin/agents/git-manager.md +10 -0
- package/plugin/agents/journal-writer.md +2 -0
- package/plugin/agents/ml-engineer.md +284 -0
- package/plugin/agents/observability-engineer.md +353 -0
- package/plugin/agents/oracle.md +9 -0
- package/plugin/agents/performance-engineer.md +290 -0
- package/plugin/agents/pipeline-architect.md +6 -0
- package/plugin/agents/planner.md +12 -0
- package/plugin/agents/platform-engineer.md +325 -0
- package/plugin/agents/project-manager.md +3 -0
- package/plugin/agents/researcher.md +5 -0
- package/plugin/agents/scientific-computing.md +426 -0
- package/plugin/agents/scout.md +3 -0
- package/plugin/agents/security-auditor.md +7 -0
- package/plugin/agents/sprint-master.md +17 -0
- package/plugin/agents/tester.md +10 -0
- package/plugin/agents/ui-ux-designer.md +12 -0
- package/plugin/agents/vulnerability-scanner.md +6 -0
- package/plugin/commands/data/pipeline.md +47 -0
- package/plugin/commands/data/quality.md +49 -0
- package/plugin/commands/domain/analyze.md +34 -0
- package/plugin/commands/domain/map.md +41 -0
- package/plugin/commands/game/balance.md +56 -0
- package/plugin/commands/game/optimize.md +62 -0
- package/plugin/commands/iot/provision.md +58 -0
- package/plugin/commands/ml/evaluate.md +47 -0
- package/plugin/commands/ml/train.md +48 -0
- package/plugin/commands/perf/benchmark.md +54 -0
- package/plugin/commands/perf/profile.md +49 -0
- package/plugin/commands/platform/blueprint.md +56 -0
- package/plugin/commands/security/audit.md +54 -0
- package/plugin/commands/security/scan.md +55 -0
- package/plugin/commands/sre/dashboard.md +53 -0
- package/plugin/registry.yaml +787 -0
- package/plugin/skills/ai-ml/experiment-tracking/SKILL.md +338 -0
- package/plugin/skills/ai-ml/feature-stores/SKILL.md +340 -0
- package/plugin/skills/ai-ml/llm-ops/SKILL.md +454 -0
- package/plugin/skills/ai-ml/ml-pipelines/SKILL.md +390 -0
- package/plugin/skills/ai-ml/model-monitoring/SKILL.md +398 -0
- package/plugin/skills/ai-ml/model-serving/SKILL.md +386 -0
- package/plugin/skills/event-driven/cqrs-patterns/SKILL.md +348 -0
- package/plugin/skills/event-driven/event-sourcing/SKILL.md +334 -0
- package/plugin/skills/event-driven/kafka-deep/SKILL.md +252 -0
- package/plugin/skills/event-driven/saga-orchestration/SKILL.md +335 -0
- package/plugin/skills/event-driven/schema-registry/SKILL.md +328 -0
- package/plugin/skills/event-driven/stream-processing/SKILL.md +313 -0
- package/plugin/skills/game/game-audio/SKILL.md +446 -0
- package/plugin/skills/game/game-networking/SKILL.md +490 -0
- package/plugin/skills/game/godot-patterns/SKILL.md +413 -0
- package/plugin/skills/game/shader-programming/SKILL.md +492 -0
- package/plugin/skills/game/unity-patterns/SKILL.md +488 -0
- package/plugin/skills/iot/device-provisioning/SKILL.md +405 -0
- package/plugin/skills/iot/edge-computing/SKILL.md +369 -0
- package/plugin/skills/iot/industrial-protocols/SKILL.md +438 -0
- package/plugin/skills/iot/mqtt-deep/SKILL.md +418 -0
- package/plugin/skills/iot/ota-updates/SKILL.md +426 -0
- package/plugin/skills/microservices/api-gateway-patterns/SKILL.md +201 -0
- package/plugin/skills/microservices/circuit-breaker-patterns/SKILL.md +246 -0
- package/plugin/skills/microservices/contract-testing/SKILL.md +284 -0
- package/plugin/skills/microservices/distributed-tracing/SKILL.md +246 -0
- package/plugin/skills/microservices/service-discovery/SKILL.md +304 -0
- package/plugin/skills/microservices/service-mesh/SKILL.md +181 -0
- package/plugin/skills/mobile-advanced/mobile-ci-cd/SKILL.md +407 -0
- package/plugin/skills/mobile-advanced/mobile-security/SKILL.md +403 -0
- package/plugin/skills/mobile-advanced/offline-first/SKILL.md +473 -0
- package/plugin/skills/mobile-advanced/push-notifications/SKILL.md +494 -0
- package/plugin/skills/mobile-advanced/react-native-deep/SKILL.md +374 -0
- package/plugin/skills/simulation/numerical-methods/SKILL.md +434 -0
- package/plugin/skills/simulation/parallel-computing/SKILL.md +382 -0
- package/plugin/skills/simulation/physics-engines/SKILL.md +377 -0
- package/plugin/skills/simulation/validation-verification/SKILL.md +479 -0
- package/plugin/skills/simulation/visualization-scientific/SKILL.md +365 -0
- package/plugin/stdrules/ALIGNMENT_PRINCIPLE.md +240 -0
- package/plugin/workflows/ai-engineering/agent-development.md +3 -3
- package/plugin/workflows/ai-engineering/fine-tuning.md +3 -3
- package/plugin/workflows/ai-engineering/model-evaluation.md +3 -3
- package/plugin/workflows/ai-engineering/prompt-engineering.md +2 -2
- package/plugin/workflows/ai-engineering/rag-development.md +4 -4
- package/plugin/workflows/ai-ml/data-pipeline.md +188 -0
- package/plugin/workflows/ai-ml/experiment-cycle.md +203 -0
- package/plugin/workflows/ai-ml/feature-engineering.md +208 -0
- package/plugin/workflows/ai-ml/model-deployment.md +199 -0
- package/plugin/workflows/ai-ml/monitoring-setup.md +227 -0
- package/plugin/workflows/api/api-design.md +1 -1
- package/plugin/workflows/api/api-testing.md +2 -2
- package/plugin/workflows/content/technical-docs.md +1 -1
- package/plugin/workflows/database/migration.md +1 -1
- package/plugin/workflows/database/optimization.md +1 -1
- package/plugin/workflows/database/schema-design.md +3 -3
- package/plugin/workflows/development/bug-fix.md +3 -3
- package/plugin/workflows/development/code-review.md +2 -1
- package/plugin/workflows/development/feature.md +3 -3
- package/plugin/workflows/development/refactor.md +2 -2
- package/plugin/workflows/event-driven/consumer-groups.md +190 -0
- package/plugin/workflows/event-driven/event-storming.md +172 -0
- package/plugin/workflows/event-driven/replay-testing.md +186 -0
- package/plugin/workflows/event-driven/saga-implementation.md +206 -0
- package/plugin/workflows/event-driven/schema-evolution.md +173 -0
- package/plugin/workflows/fullstack/authentication.md +4 -4
- package/plugin/workflows/fullstack/full-feature.md +4 -4
- package/plugin/workflows/game-dev/content-pipeline.md +218 -0
- package/plugin/workflows/game-dev/platform-submission.md +263 -0
- package/plugin/workflows/game-dev/playtesting.md +237 -0
- package/plugin/workflows/game-dev/prototype-to-production.md +205 -0
- package/plugin/workflows/microservices/contract-first.md +151 -0
- package/plugin/workflows/microservices/distributed-tracing.md +166 -0
- package/plugin/workflows/microservices/domain-decomposition.md +123 -0
- package/plugin/workflows/microservices/integration-testing.md +149 -0
- package/plugin/workflows/microservices/service-mesh-setup.md +153 -0
- package/plugin/workflows/microservices/service-scaffolding.md +151 -0
- package/plugin/workflows/omega/1000x-innovation.md +2 -2
- package/plugin/workflows/omega/100x-architecture.md +2 -2
- package/plugin/workflows/omega/10x-improvement.md +2 -2
- package/plugin/workflows/quality/performance-optimization.md +2 -2
- package/plugin/workflows/research/best-practices.md +1 -1
- package/plugin/workflows/research/technology-research.md +1 -1
- package/plugin/workflows/security/penetration-testing.md +3 -3
- package/plugin/workflows/security/security-audit.md +3 -3
- package/plugin/workflows/sprint/sprint-execution.md +2 -2
- package/plugin/workflows/sprint/sprint-retrospective.md +1 -1
- package/plugin/workflows/sprint/sprint-setup.md +1 -1
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
# Industrial Protocols
|
|
2
|
+
|
|
3
|
+
Industrial IoT protocols including Modbus, OPC UA, BACnet, and protocol bridging for industrial automation.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Industrial protocols enable communication with PLCs, sensors, and control systems in manufacturing, building automation, and industrial environments.
|
|
8
|
+
|
|
9
|
+
## Core Concepts
|
|
10
|
+
|
|
11
|
+
### Protocol Categories
|
|
12
|
+
- **Fieldbus**: Modbus, Profibus, CAN
|
|
13
|
+
- **Industrial Ethernet**: EtherNet/IP, PROFINET, Modbus TCP
|
|
14
|
+
- **Building Automation**: BACnet, KNX, LonWorks
|
|
15
|
+
- **Universal**: OPC UA (platform independent)
|
|
16
|
+
|
|
17
|
+
### Communication Models
|
|
18
|
+
- **Master/Slave**: Modbus, BACnet MS/TP
|
|
19
|
+
- **Producer/Consumer**: EtherNet/IP
|
|
20
|
+
- **Client/Server**: OPC UA
|
|
21
|
+
- **Publisher/Subscriber**: OPC UA PubSub
|
|
22
|
+
|
|
23
|
+
## Modbus
|
|
24
|
+
|
|
25
|
+
### Modbus TCP Client (Python)
|
|
26
|
+
```python
|
|
27
|
+
from pymodbus.client import ModbusTcpClient
|
|
28
|
+
from pymodbus.exceptions import ModbusException
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from typing import List, Optional
|
|
31
|
+
import struct
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class ModbusDevice:
|
|
35
|
+
host: str
|
|
36
|
+
port: int = 502
|
|
37
|
+
unit_id: int = 1
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class RegisterMapping:
|
|
41
|
+
address: int
|
|
42
|
+
count: int
|
|
43
|
+
name: str
|
|
44
|
+
data_type: str # 'int16', 'uint16', 'int32', 'float32'
|
|
45
|
+
scale: float = 1.0
|
|
46
|
+
unit: str = ''
|
|
47
|
+
|
|
48
|
+
class ModbusReader:
|
|
49
|
+
def __init__(self, device: ModbusDevice):
|
|
50
|
+
self.device = device
|
|
51
|
+
self.client = ModbusTcpClient(
|
|
52
|
+
host=device.host,
|
|
53
|
+
port=device.port,
|
|
54
|
+
timeout=3
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def connect(self) -> bool:
|
|
58
|
+
return self.client.connect()
|
|
59
|
+
|
|
60
|
+
def disconnect(self):
|
|
61
|
+
self.client.close()
|
|
62
|
+
|
|
63
|
+
def read_holding_registers(
|
|
64
|
+
self,
|
|
65
|
+
mapping: RegisterMapping
|
|
66
|
+
) -> Optional[float]:
|
|
67
|
+
"""Read holding registers and convert to value"""
|
|
68
|
+
try:
|
|
69
|
+
result = self.client.read_holding_registers(
|
|
70
|
+
address=mapping.address,
|
|
71
|
+
count=mapping.count,
|
|
72
|
+
slave=self.device.unit_id
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
if result.isError():
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
return self._convert_registers(result.registers, mapping)
|
|
79
|
+
|
|
80
|
+
except ModbusException as e:
|
|
81
|
+
print(f"Modbus error: {e}")
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
def read_input_registers(
|
|
85
|
+
self,
|
|
86
|
+
mapping: RegisterMapping
|
|
87
|
+
) -> Optional[float]:
|
|
88
|
+
"""Read input registers"""
|
|
89
|
+
try:
|
|
90
|
+
result = self.client.read_input_registers(
|
|
91
|
+
address=mapping.address,
|
|
92
|
+
count=mapping.count,
|
|
93
|
+
slave=self.device.unit_id
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if result.isError():
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
return self._convert_registers(result.registers, mapping)
|
|
100
|
+
|
|
101
|
+
except ModbusException as e:
|
|
102
|
+
print(f"Modbus error: {e}")
|
|
103
|
+
return None
|
|
104
|
+
|
|
105
|
+
def read_coils(self, address: int, count: int = 1) -> Optional[List[bool]]:
|
|
106
|
+
"""Read coil status (discrete outputs)"""
|
|
107
|
+
result = self.client.read_coils(
|
|
108
|
+
address=address,
|
|
109
|
+
count=count,
|
|
110
|
+
slave=self.device.unit_id
|
|
111
|
+
)
|
|
112
|
+
return result.bits[:count] if not result.isError() else None
|
|
113
|
+
|
|
114
|
+
def write_register(self, address: int, value: int) -> bool:
|
|
115
|
+
"""Write single holding register"""
|
|
116
|
+
result = self.client.write_register(
|
|
117
|
+
address=address,
|
|
118
|
+
value=value,
|
|
119
|
+
slave=self.device.unit_id
|
|
120
|
+
)
|
|
121
|
+
return not result.isError()
|
|
122
|
+
|
|
123
|
+
def write_coil(self, address: int, value: bool) -> bool:
|
|
124
|
+
"""Write single coil"""
|
|
125
|
+
result = self.client.write_coil(
|
|
126
|
+
address=address,
|
|
127
|
+
value=value,
|
|
128
|
+
slave=self.device.unit_id
|
|
129
|
+
)
|
|
130
|
+
return not result.isError()
|
|
131
|
+
|
|
132
|
+
def _convert_registers(
|
|
133
|
+
self,
|
|
134
|
+
registers: List[int],
|
|
135
|
+
mapping: RegisterMapping
|
|
136
|
+
) -> float:
|
|
137
|
+
"""Convert raw registers to typed value"""
|
|
138
|
+
if mapping.data_type == 'int16':
|
|
139
|
+
value = registers[0] if registers[0] < 32768 else registers[0] - 65536
|
|
140
|
+
elif mapping.data_type == 'uint16':
|
|
141
|
+
value = registers[0]
|
|
142
|
+
elif mapping.data_type == 'int32':
|
|
143
|
+
raw = (registers[0] << 16) | registers[1]
|
|
144
|
+
value = raw if raw < 2147483648 else raw - 4294967296
|
|
145
|
+
elif mapping.data_type == 'float32':
|
|
146
|
+
raw = struct.pack('>HH', registers[0], registers[1])
|
|
147
|
+
value = struct.unpack('>f', raw)[0]
|
|
148
|
+
else:
|
|
149
|
+
value = registers[0]
|
|
150
|
+
|
|
151
|
+
return value * mapping.scale
|
|
152
|
+
|
|
153
|
+
# Usage example
|
|
154
|
+
device = ModbusDevice(host='192.168.1.100', unit_id=1)
|
|
155
|
+
reader = ModbusReader(device)
|
|
156
|
+
|
|
157
|
+
mappings = [
|
|
158
|
+
RegisterMapping(address=0, count=2, name='temperature', data_type='float32', scale=0.1, unit='°C'),
|
|
159
|
+
RegisterMapping(address=2, count=2, name='pressure', data_type='float32', scale=0.01, unit='bar'),
|
|
160
|
+
RegisterMapping(address=4, count=1, name='status', data_type='uint16'),
|
|
161
|
+
]
|
|
162
|
+
|
|
163
|
+
if reader.connect():
|
|
164
|
+
for mapping in mappings:
|
|
165
|
+
value = reader.read_holding_registers(mapping)
|
|
166
|
+
print(f"{mapping.name}: {value} {mapping.unit}")
|
|
167
|
+
reader.disconnect()
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## OPC UA
|
|
171
|
+
|
|
172
|
+
### OPC UA Client
|
|
173
|
+
```python
|
|
174
|
+
from asyncua import Client, ua
|
|
175
|
+
from asyncua.common.subscription import DataChangeNotif
|
|
176
|
+
from typing import Dict, Any, Callable
|
|
177
|
+
import asyncio
|
|
178
|
+
|
|
179
|
+
class OPCUAClient:
|
|
180
|
+
def __init__(self, endpoint: str):
|
|
181
|
+
self.endpoint = endpoint
|
|
182
|
+
self.client = Client(endpoint)
|
|
183
|
+
self.subscriptions: Dict[str, Any] = {}
|
|
184
|
+
|
|
185
|
+
async def connect(self, username: str = None, password: str = None):
|
|
186
|
+
"""Connect to OPC UA server"""
|
|
187
|
+
if username and password:
|
|
188
|
+
self.client.set_user(username)
|
|
189
|
+
self.client.set_password(password)
|
|
190
|
+
|
|
191
|
+
await self.client.connect()
|
|
192
|
+
print(f"Connected to {self.endpoint}")
|
|
193
|
+
|
|
194
|
+
async def disconnect(self):
|
|
195
|
+
await self.client.disconnect()
|
|
196
|
+
|
|
197
|
+
async def browse_nodes(self, node_id: str = None) -> list:
|
|
198
|
+
"""Browse available nodes"""
|
|
199
|
+
if node_id:
|
|
200
|
+
node = self.client.get_node(node_id)
|
|
201
|
+
else:
|
|
202
|
+
node = self.client.get_root_node()
|
|
203
|
+
|
|
204
|
+
children = await node.get_children()
|
|
205
|
+
result = []
|
|
206
|
+
|
|
207
|
+
for child in children:
|
|
208
|
+
browse_name = await child.read_browse_name()
|
|
209
|
+
node_class = await child.read_node_class()
|
|
210
|
+
result.append({
|
|
211
|
+
'node_id': child.nodeid.to_string(),
|
|
212
|
+
'browse_name': browse_name.Name,
|
|
213
|
+
'node_class': node_class.name
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
return result
|
|
217
|
+
|
|
218
|
+
async def read_value(self, node_id: str) -> Any:
|
|
219
|
+
"""Read a single value"""
|
|
220
|
+
node = self.client.get_node(node_id)
|
|
221
|
+
value = await node.read_value()
|
|
222
|
+
return value
|
|
223
|
+
|
|
224
|
+
async def read_multiple(self, node_ids: list) -> Dict[str, Any]:
|
|
225
|
+
"""Read multiple values efficiently"""
|
|
226
|
+
nodes = [self.client.get_node(nid) for nid in node_ids]
|
|
227
|
+
values = await self.client.read_values(nodes)
|
|
228
|
+
return dict(zip(node_ids, values))
|
|
229
|
+
|
|
230
|
+
async def write_value(self, node_id: str, value: Any, data_type: ua.VariantType = None):
|
|
231
|
+
"""Write a value to a node"""
|
|
232
|
+
node = self.client.get_node(node_id)
|
|
233
|
+
|
|
234
|
+
if data_type:
|
|
235
|
+
variant = ua.Variant(value, data_type)
|
|
236
|
+
await node.write_value(variant)
|
|
237
|
+
else:
|
|
238
|
+
await node.write_value(value)
|
|
239
|
+
|
|
240
|
+
async def subscribe(
|
|
241
|
+
self,
|
|
242
|
+
node_ids: list,
|
|
243
|
+
callback: Callable[[str, Any], None],
|
|
244
|
+
interval_ms: int = 1000
|
|
245
|
+
):
|
|
246
|
+
"""Subscribe to data changes"""
|
|
247
|
+
handler = DataChangeHandler(callback)
|
|
248
|
+
|
|
249
|
+
subscription = await self.client.create_subscription(
|
|
250
|
+
interval_ms,
|
|
251
|
+
handler
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
nodes = [self.client.get_node(nid) for nid in node_ids]
|
|
255
|
+
await subscription.subscribe_data_change(nodes)
|
|
256
|
+
|
|
257
|
+
return subscription
|
|
258
|
+
|
|
259
|
+
async def call_method(
|
|
260
|
+
self,
|
|
261
|
+
object_id: str,
|
|
262
|
+
method_id: str,
|
|
263
|
+
*args
|
|
264
|
+
) -> Any:
|
|
265
|
+
"""Call an OPC UA method"""
|
|
266
|
+
obj = self.client.get_node(object_id)
|
|
267
|
+
method = self.client.get_node(method_id)
|
|
268
|
+
result = await obj.call_method(method, *args)
|
|
269
|
+
return result
|
|
270
|
+
|
|
271
|
+
class DataChangeHandler:
|
|
272
|
+
def __init__(self, callback: Callable[[str, Any], None]):
|
|
273
|
+
self.callback = callback
|
|
274
|
+
|
|
275
|
+
def datachange_notification(self, node: Any, val: Any, data: DataChangeNotif):
|
|
276
|
+
node_id = node.nodeid.to_string()
|
|
277
|
+
self.callback(node_id, val)
|
|
278
|
+
|
|
279
|
+
# Usage
|
|
280
|
+
async def main():
|
|
281
|
+
client = OPCUAClient("opc.tcp://localhost:4840")
|
|
282
|
+
await client.connect()
|
|
283
|
+
|
|
284
|
+
# Browse
|
|
285
|
+
nodes = await client.browse_nodes("ns=2;s=Machine1")
|
|
286
|
+
|
|
287
|
+
# Read
|
|
288
|
+
temp = await client.read_value("ns=2;s=Machine1.Temperature")
|
|
289
|
+
print(f"Temperature: {temp}")
|
|
290
|
+
|
|
291
|
+
# Subscribe
|
|
292
|
+
def on_change(node_id: str, value: Any):
|
|
293
|
+
print(f"{node_id} changed to {value}")
|
|
294
|
+
|
|
295
|
+
await client.subscribe(
|
|
296
|
+
["ns=2;s=Machine1.Temperature", "ns=2;s=Machine1.Pressure"],
|
|
297
|
+
on_change,
|
|
298
|
+
interval_ms=500
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Keep running
|
|
302
|
+
await asyncio.sleep(60)
|
|
303
|
+
await client.disconnect()
|
|
304
|
+
|
|
305
|
+
asyncio.run(main())
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Protocol Gateway
|
|
309
|
+
|
|
310
|
+
### Modbus to MQTT Bridge
|
|
311
|
+
```python
|
|
312
|
+
import asyncio
|
|
313
|
+
import json
|
|
314
|
+
from dataclasses import asdict
|
|
315
|
+
from aiomqtt import Client as MQTTClient
|
|
316
|
+
from pymodbus.client import AsyncModbusTcpClient
|
|
317
|
+
|
|
318
|
+
class ModbusMQTTGateway:
|
|
319
|
+
def __init__(
|
|
320
|
+
self,
|
|
321
|
+
modbus_host: str,
|
|
322
|
+
mqtt_broker: str,
|
|
323
|
+
device_id: str
|
|
324
|
+
):
|
|
325
|
+
self.modbus_host = modbus_host
|
|
326
|
+
self.mqtt_broker = mqtt_broker
|
|
327
|
+
self.device_id = device_id
|
|
328
|
+
self.modbus = None
|
|
329
|
+
self.mqtt = None
|
|
330
|
+
self.running = False
|
|
331
|
+
|
|
332
|
+
async def start(self):
|
|
333
|
+
"""Start the gateway"""
|
|
334
|
+
self.modbus = AsyncModbusTcpClient(self.modbus_host)
|
|
335
|
+
await self.modbus.connect()
|
|
336
|
+
|
|
337
|
+
async with MQTTClient(self.mqtt_broker) as mqtt:
|
|
338
|
+
self.mqtt = mqtt
|
|
339
|
+
self.running = True
|
|
340
|
+
|
|
341
|
+
# Subscribe to commands
|
|
342
|
+
await mqtt.subscribe(f"devices/{self.device_id}/commands")
|
|
343
|
+
|
|
344
|
+
# Start tasks
|
|
345
|
+
await asyncio.gather(
|
|
346
|
+
self.poll_and_publish(),
|
|
347
|
+
self.handle_commands()
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
async def poll_and_publish(self):
|
|
351
|
+
"""Poll Modbus and publish to MQTT"""
|
|
352
|
+
while self.running:
|
|
353
|
+
try:
|
|
354
|
+
# Read Modbus registers
|
|
355
|
+
result = await self.modbus.read_holding_registers(0, 10, slave=1)
|
|
356
|
+
|
|
357
|
+
if not result.isError():
|
|
358
|
+
payload = {
|
|
359
|
+
'device_id': self.device_id,
|
|
360
|
+
'timestamp': datetime.utcnow().isoformat(),
|
|
361
|
+
'registers': result.registers
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await self.mqtt.publish(
|
|
365
|
+
f"devices/{self.device_id}/telemetry",
|
|
366
|
+
json.dumps(payload)
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
except Exception as e:
|
|
370
|
+
print(f"Polling error: {e}")
|
|
371
|
+
|
|
372
|
+
await asyncio.sleep(1)
|
|
373
|
+
|
|
374
|
+
async def handle_commands(self):
|
|
375
|
+
"""Handle commands from MQTT"""
|
|
376
|
+
async for message in self.mqtt.messages:
|
|
377
|
+
try:
|
|
378
|
+
command = json.loads(message.payload)
|
|
379
|
+
|
|
380
|
+
if command['action'] == 'write_register':
|
|
381
|
+
await self.modbus.write_register(
|
|
382
|
+
command['address'],
|
|
383
|
+
command['value'],
|
|
384
|
+
slave=1
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
elif command['action'] == 'write_coil':
|
|
388
|
+
await self.modbus.write_coil(
|
|
389
|
+
command['address'],
|
|
390
|
+
command['value'],
|
|
391
|
+
slave=1
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
except Exception as e:
|
|
395
|
+
print(f"Command error: {e}")
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Best Practices
|
|
399
|
+
|
|
400
|
+
1. **Error Handling**: Industrial protocols can be unreliable
|
|
401
|
+
2. **Timeout Management**: Set appropriate timeouts
|
|
402
|
+
3. **Polling Intervals**: Balance responsiveness with load
|
|
403
|
+
4. **Security**: Use OPC UA security features
|
|
404
|
+
5. **Data Validation**: Validate all read values
|
|
405
|
+
|
|
406
|
+
## Protocol Selection Guide
|
|
407
|
+
|
|
408
|
+
```
|
|
409
|
+
High-speed, Real-time: EtherNet/IP, PROFINET
|
|
410
|
+
Legacy Equipment: Modbus RTU/TCP
|
|
411
|
+
New Installations: OPC UA
|
|
412
|
+
Building Automation: BACnet
|
|
413
|
+
Simple Sensors: Modbus
|
|
414
|
+
Secure/Interoperable: OPC UA
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Anti-Patterns
|
|
418
|
+
|
|
419
|
+
- No error handling
|
|
420
|
+
- Ignoring timeouts
|
|
421
|
+
- Polling too frequently
|
|
422
|
+
- No security (OPC UA anonymous)
|
|
423
|
+
- Not handling disconnects
|
|
424
|
+
|
|
425
|
+
## When to Use
|
|
426
|
+
|
|
427
|
+
- Industrial automation
|
|
428
|
+
- Building management
|
|
429
|
+
- Manufacturing equipment
|
|
430
|
+
- Legacy system integration
|
|
431
|
+
- SCADA systems
|
|
432
|
+
|
|
433
|
+
## When NOT to Use
|
|
434
|
+
|
|
435
|
+
- Consumer IoT (use MQTT)
|
|
436
|
+
- Web applications (use REST)
|
|
437
|
+
- Mobile apps
|
|
438
|
+
- Non-industrial use cases
|