Промышленные сети — это мир, где протоколы живут десятилетиями. Modbus появился в 1979 году и до сих пор работает на миллионах устройств. OPC UA вышел в 2008-м и стал стандартом для современной промышленной интеграции и Industry 4.0. На практике проекту нередко нужно работать с обоими — читать данные со старых устройств по Modbus и передавать их в облако или MES-систему через OPC UA. Разберём оба протокола до уровня байт и покажем, как их соединить.
Modbus: архитектура и карта регистров
Modbus работает по модели «мастер — ведомые». Мастер инициирует запросы, ведомые отвечают. Ни один ведомый не отправляет данные сам по себе.
RTU vs TCP
| Параметр | Modbus RTU | Modbus TCP | |---|---|---| | Физика | RS-485 (2 провода) | Ethernet / WiFi | | Фреймирование | Межсимвольные паузы + CRC16 | MBAP-заголовок + PDU | | Адрес устройства | 1 байт в каждом фрейме | Unit ID в MBAP, опционально | | Транспортная надёжность | Только CRC | TCP гарантирует доставку | | Типичная скорость | 9600–115200 бод | 100 Мбит/с и выше | | Латентность | 5–50 мс | 1–10 мс |
Четыре типа объектов данных
| Тип | Код | Адреса | Доступ | Описание | |---|---|---|---|---| | Coils | 0x | 00001–09999 | RW | Одноbitные выходы | | Discrete Inputs | 1x | 10001–19999 | RO | Одноbitные входы | | Input Registers | 3x | 30001–39999 | RO | 16-bit аналоговые входы | | Holding Registers | 4x | 40001–49999 | RW | 16-bit рабочие регистры |
Коды функций
0x01 — Read Coils
0x02 — Read Discrete Inputs
0x03 — Read Holding Registers
0x04 — Read Input Registers
0x05 — Write Single Coil
0x06 — Write Single Register
0x0F — Write Multiple Coils
0x10 — Write Multiple Registers
Пример фрейма Modbus RTU — запрос чтения 2 holding registers с адреса 0x0064 у устройства с адресом 0x01:
01 03 00 64 00 02 85 D5
│ │ ├──────┘ ├──────┘ └──── CRC16 (little-endian)
│ │ │ └──────── количество регистров = 2
│ │ └───────────────── начальный адрес = 0x0064 (100)
│ └──────────────────── функция 0x03
└─────────────────────── адрес ведомого = 0x01
Modbus на Python: библиотека pymodbus
from pymodbus.client import ModbusSerialClient, ModbusTcpClient
from pymodbus.exceptions import ModbusException
import struct
# RTU через RS-485
client = ModbusSerialClient(
port='/dev/ttyUSB0',
baudrate=9600,
parity='N',
stopbits=1,
bytesize=8,
timeout=1
)
# TCP
# client = ModbusTcpClient('192.168.1.100', port=502)
client.connect()
try:
# Читаем 4 holding registers начиная с 0
result = client.read_holding_registers(address=0, count=4, slave=1)
if result.isError():
raise ModbusException(f"Error: {result}")
regs = result.registers
# Интерпретируем reg[0]+reg[1] как float32 (big-endian)
raw = struct.pack('>HH', regs[0], regs[1])
temperature = struct.unpack('>f', raw)[0]
print(f"Temperature: {temperature:.2f} °C")
print(f"Status: {regs[2]:#06x}")
# Запись single register
client.write_register(address=10, value=0x0001, slave=1)
finally:
client.close()
OPC UA: адресное пространство и узлы
OPC UA — это не просто протокол передачи данных, а полноценная информационная модель. Каждый объект в OPC UA описывается через узлы (Nodes) в адресном пространстве (Address Space).
Типы узлов
| NodeClass | Назначение | |---|---| | Object | Контейнер (аналог папки или устройства) | | Variable | Значение с типом данных (температура, давление) | | Method | Вызываемая функция | | ObjectType | Тип объекта (шаблон) | | VariableType | Тип переменной | | DataType | Определение типа (enum, struct) | | View | Подмножество адресного пространства | | ReferenceType | Тип связи между узлами |
Узлы связываются ссылками (References): HasComponent, HasProperty, Organizes, HasSubtype и др.
NodeId
Каждый узел идентифицируется NodeId:
i=— числовой (e.g.i=2258— ServerStatus)s=— строковый (e.g.s=Temperature)g=— GUIDb=— бинарный
Namespace 0 зарезервирован для стандартных узлов OPC Foundation.
OPC UA клиент на Python (opcua-asyncio)
import asyncio
from asyncua import Client, ua
async def main():
url = "opc.tcp://192.168.1.50:4840/freeopcua/server/"
async with Client(url=url) as client:
# Навигация по адресному пространству
root = client.get_root_node()
objects = client.get_objects_node()
# Получение узла по NodeId
temp_node = client.get_node("ns=2;s=Temperature")
temp_value = await temp_node.read_value()
print(f"Temperature: {temp_value} °C")
# Подписка на изменения
handler = SubHandler()
sub = await client.create_subscription(500, handler)
nodes = [temp_node]
await sub.subscribe_data_change(nodes)
await asyncio.sleep(10)
await sub.delete()
class SubHandler:
def datachange_notification(self, node, val, data):
print(f"Node {node}: {val}")
asyncio.run(main())
OPC UA клиент на Node.js
const { OPCUAClient, AttributeIds, TimestampsToReturn } = require('node-opcua');
async function main() {
const client = OPCUAClient.create({
endpointMustExist: false,
connectionStrategy: { maxRetry: 3 }
});
await client.connect('opc.tcp://192.168.1.50:4840');
const session = await client.createSession();
// Чтение значения
const dataValue = await session.readVariableValue('ns=2;s=Pressure');
console.log('Pressure:', dataValue.value.value, dataValue.statusCode.toString());
// Запись
await session.writeSingleNode('ns=2;s=SetPoint', {
dataType: 'Double',
value: 75.5
});
// Подписка на изменения
const subscription = await session.createSubscription2({
requestedPublishingInterval: 1000,
requestedMaxKeepAliveCount: 10,
maxNotificationsPerPublish: 10,
publishingEnabled: true,
priority: 10
});
const monitoredItem = await subscription.monitor(
{ nodeId: 'ns=2;s=Temperature', attributeId: AttributeIds.Value },
{ samplingInterval: 1000, discardOldest: true, queueSize: 10 },
TimestampsToReturn.Both
);
monitoredItem.on('changed', (dataValue) => {
console.log('Temperature changed:', dataValue.value.value);
});
await new Promise(resolve => setTimeout(resolve, 30000));
await session.close();
await client.disconnect();
}
main().catch(console.error);
Мост Modbus → OPC UA → MQTT → облако
Типичная архитектура промышленного шлюза:
[ПЛК / датчики]
│ Modbus RTU/TCP
[Edge Gateway (Python/Node.js)]
│ │
OPC UA MQTT
│ │
[MES/SCADA] [Облако / Kafka]
Пример шлюза Modbus → MQTT на Python:
import time
import json
import paho.mqtt.client as mqtt
from pymodbus.client import ModbusTcpClient
MODBUS_HOST = '192.168.1.100'
MQTT_BROKER = 'mqtt.factory.local'
POLL_INTERVAL = 5 # секунд
modbus = ModbusTcpClient(MODBUS_HOST, port=502)
modbus.connect()
mqtt_client = mqtt.Client()
mqtt_client.connect(MQTT_BROKER, 1883)
mqtt_client.loop_start()
REGISTER_MAP = [
{'name': 'temperature', 'address': 0, 'count': 2, 'type': 'float32'},
{'name': 'pressure', 'address': 2, 'count': 2, 'type': 'float32'},
{'name': 'status', 'address': 4, 'count': 1, 'type': 'uint16'},
]
def decode_float32(regs):
import struct
return struct.unpack('>f', struct.pack('>HH', regs[0], regs[1]))[0]
while True:
payload = {}
for reg in REGISTER_MAP:
result = modbus.read_holding_registers(reg['address'], reg['count'], slave=1)
if not result.isError():
if reg['type'] == 'float32':
payload[reg['name']] = round(decode_float32(result.registers), 2)
else:
payload[reg['name']] = result.registers[0]
payload['timestamp'] = int(time.time() * 1000)
mqtt_client.publish('factory/line1/sensors', json.dumps(payload))
time.sleep(POLL_INTERVAL)
Безопасность OPC UA: сертификаты
OPC UA поддерживает три режима безопасности:
| Режим | Шифрование | Подпись | Применение | |---|---|---|---| | None | Нет | Нет | Только для тестирования | | Sign | Нет | Да | Защита от подмены | | SignAndEncrypt | AES-256 | Да | Продакшн |
Генерация самоподписанного сертификата для OPC UA сервера:
# Генерация RSA ключа и сертификата
openssl req -x509 -newkey rsa:2048 -keyout server_key.pem \
-out server_cert.pem -days 3650 -nodes \
-subj "/CN=OPC UA Server/O=MyFactory/C=RU" \
-addext "subjectAltName=URI:urn:factory:opcua:server,IP:192.168.1.50"
# Конвертация в DER для OPC UA стека
openssl x509 -in server_cert.pem -out server_cert.der -outform DER
Клиент должен доверять сертификату сервера (поместить в trusted/certs/), сервер — сертификату клиента. В промышленной среде используйте корпоративный CA вместо самоподписанных сертификатов.
Типичные ошибки интеграции
- Порядок байт (endianness): Modbus не стандартизирует порядок байт для float/int32 — разные устройства используют ABCD, CDAB, BADC, DCBA. Всегда уточняйте у производителя.
- Адресация Modbus: некоторые производители используют адрес 1-based (регистр 1 → address 0), другие 0-based. Проверяйте документацию.
- Таймауты OPC UA: слишком короткий таймаут сессии вызывает разрывы при медленных сетях. Рекомендуем ≥ 30 секунд.
- Опрос vs подписка: OPC UA Subscriptions эффективнее постоянного polling — сервер сам уведомляет при изменении значения.
Для понимания работы с промышленными устройствами на низком уровне также смотрите статью о разработке прошивок STM32.
Итог
Modbus остаётся неизбежной реальностью для работы с полевым оборудованием, а OPC UA — современным языком промышленной интеграции. Умение работать с обоими и строить надёжные шлюзы между ними — ключевая компетенция для industrial IoT проектов. Приведённые примеры на Python и Node.js — готовая база для реального шлюза.

