Добавить в корзинуПозвонить
Найти в Дзене
GGG Studio

Пишем свой универсальный сканер Modbus RTU на Python.

В прошлой статье мы боролись с анемометром и чтобы применить его в хозяйстве, пришлось разрабатывать скрипт-сканер. Но, как оказалось, он совсем не универсальный и требует постоянной точечной настройки в самом коде Python практически под каждое новое устройство. Что ж, будем исправлять эту ситуацию. Проблема: есть датчик, которым уже пользовались. Его сняли, положили на склад, прошли месяцы. И вот он понадобился. А какой у него адрес? Какая скорость? Где взять таблицу регистров? Документация утеряна, шильдик закрашен, штатная программа не видит устройство. С такой ситуацией я столкнулся на днях. И поскольку это уже не первый раз, когда приходится плясать с бубном над каждым новым (или не очень новым) RS-485 устройством, то нужен универсальный сканер- один скрипт на Python, который сам перебирает все скорости на порту, находит устройство и сразу читает все его регистры. Нам понадобится переходник RS485-USB и понимание на каком COM порту появится наш датчик, посмотреть это можно в диспет

В прошлой статье мы боролись с анемометром и чтобы применить его в хозяйстве, пришлось разрабатывать скрипт-сканер. Но, как оказалось, он совсем не универсальный и требует постоянной точечной настройки в самом коде Python практически под каждое новое устройство. Что ж, будем исправлять эту ситуацию.

Проблема: есть датчик, которым уже пользовались. Его сняли, положили на склад, прошли месяцы. И вот он понадобился. А какой у него адрес? Какая скорость? Где взять таблицу регистров? Документация утеряна, шильдик закрашен, штатная программа не видит устройство.

С такой ситуацией я столкнулся на днях. И поскольку это уже не первый раз, когда приходится плясать с бубном над каждым новым (или не очень новым) RS-485 устройством, то нужен универсальный сканер- один скрипт на Python, который сам перебирает все скорости на порту, находит устройство и сразу читает все его регистры.

Нам понадобится переходник RS485-USB

-2

и понимание на каком COM порту появится наш датчик, посмотреть это можно в диспетчере устройств

-3

Еще вам потребуется установленный Python и библиотека pyserial.

Что делает скрипт:

  • Сканирует адреса от 1 до 247 на пяти скоростях: 4800, 9600, 19200, 38400, 115200
  • Находит устройство и показывает его адрес, скорость и значение в регистре 0
  • Сразу выводит полную таблицу всех доступных регистров (0...199)

Вам остаётся только прописать в коде номер COM-порта, на котором висит адаптер. Остальное скрипт сделает сам.

Код — full_scanner.py:

import serial
import time

def crc16(data):
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc

PORT = 'COM4'
# <-- УКАЖИТЕ СВОЙ ПОРТ
BAUD_RATES = [4800, 9600, 19200, 38400, 115200]
TIMEOUT = 0.15
REG_SCAN_LIMIT = 200

print("=" * 60)
print("УНИВЕРСАЛЬНЫЙ СКАНЕР MODBUS RTU v2.0")
print("=" * 60)

device_found = False

for baud in BAUD_RATES:
if device_found:
break
print(f"\nСканирую адреса на скорости {baud}...")
try:
ser = serial.Serial(PORT, baud, timeout=TIMEOUT)
time.sleep(0.2)
except:
print(f" Не могу открыть порт на {baud}")
continue

for addr in range(1, 248):
request = bytearray([addr, 3, 0, 0, 0, 1])
crc = crc16(request)
request.append(crc & 0xFF)
request.append((crc >> 8) & 0xFF)

try:
ser.write(request)
time.sleep(0.05)
if ser.in_waiting >= 5:
response = ser.read(ser.in_waiting)
if len(response) >= 5:
value = (response[3] << 8) | response[4]
print(f"\n [OK] Найдено устройство!")
print(f" Адрес: {addr}")
print(f" Скорость: {baud}")
print(f" Регистр 0 = {value}")

print(f"\n Сканирую регистры 0...{REG_SCAN_LIMIT-1}...")
found_regs = []
for reg in range(0, REG_SCAN_LIMIT):
req = bytearray([addr, 3, (reg >> 8) & 0xFF, reg & 0xFF, 0, 1])
c = crc16(req)
req.append(c & 0xFF)
req.append((c >> 8) & 0xFF)
try:
ser.write(req)
time.sleep(0.03)
if ser.in_waiting >= 5:
resp = ser.read(ser.in_waiting)
if len(resp) >= 5:
val = (resp[3] << 8) | resp[4]
found_regs.append((reg, val))
except:
pass

if found_regs:
print(f" Найдено регистров: {len(found_regs)}")
print(f" {'Регистр':>8} {'Hex':>8} {'Значение':>8}")
print(f" {'-'*8} {'-'*8} {'-'*8}")
for reg, val in found_regs:
print(f" {reg:8d} 0x{reg:04X} {val:8d}")
else:
print(" Регистры не найдены.")

device_found = True
break
except:
pass

ser.close()

if not device_found:
print("\nУстройств не найдено.")
print("Проверьте питание, подключение и номер COM-порта.")

print("\n" + "=" * 60)
print("Сканирование завершено.")
print("=" * 60)

***********************************************************************************

Ну что запускаем и вот наша потеряшка висит на 96 адресе, скорость 9600

-4

Также мы получили таблицу регистров, и нас интересует блок, куда прописать новый адрес, а именно 0x001B.

Чтобы прописать адрес какому-либо устройству, универсального скрипта не придумал — он чуть переделанный с предыдущей статьи.

Скрипт для смены адреса устройства. Тут универсальности нет — нужно вручную прописать старый адрес, новый адрес, скорость и COM-порт. Работает через запись в регистр 0x1B (адрес Modbus). Проверил и на другом датчике..

-5

но с другими устройствами может отличаться регистр хранения адреса возможно его нужно будет корректировать. Ниже скрипт для записи своего адреса в нужный регистр датчика..

import serial
import time

def crc16(data):
crc = 0xFFFF
for byte in data:
crc ^= byte
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
return crc

PORT = 'COM4'
# свой порт
BAUD = 9600
# текущая скорость датчика
OLD_ADDR = 1
# текущий адрес
NEW_ADDR = 99
# новый адрес

print(f"Меняю адрес {OLD_ADDR} -> {NEW_ADDR}...")
ser = serial.Serial(PORT, BAUD, timeout=0.5)
time.sleep(0.1)
request = bytearray([OLD_ADDR, 6, 0, 0x1B, 0, NEW_ADDR])
crc = crc16(request)
request.append(crc & 0xFF)
request.append((crc >> 8) & 0xFF)
ser.write(request)
time.sleep(0.5)
ser.close()

print(f"Проверяю адрес {NEW_ADDR}...")
ser = serial.Serial(PORT, BAUD, timeout=0.3)
time.sleep(0.1)
request = bytearray([NEW_ADDR, 3, 0, 0, 0, 1])
crc = crc16(request)
request.append(crc & 0xFF)
request.append((crc >> 8) & 0xFF)
ser.write(request)
time.sleep(0.2)
if ser.in_waiting >= 5:
response = ser.read(ser.in_waiting)
if len(response) >= 5:
value = (response[3] << 8) | response[4]
print(f"ГОТОВО! Адрес {NEW_ADDR} ответил, регистр 0 = {value}")
ser.close()

Пользуйтесь. Работает на любом Python с библиотекой pyserial. Экономит часы времени и километры нервов.