Уведомления

Группа в Telegram: @pythonsu

#1 Дек. 21, 2021 18:12:41

Akhepython
Зарегистрирован: 2021-12-21
Сообщения: 1
Репутация: +  0  -
Профиль   Отправить e-mail  

Опрос теплосчетчика по RS485

Доброго времени суток
Просьба помочь разобраться с ребусом по опросу теплосчетчика SANEXT Mono RM по RS485
Есть описание формата данных
https://imgur.com/J6SwXrK
https://imgur.com/nOzvkYr
Есть Raspberry pi 3 b и rs485 to usb
И огромное желание разобраться как собрать данные )
Планировал использовать готовую библиотеку minimalmodbus для питона с режимом RTU.
Но не осилил синтаксис. Точнее не осилил в целом логику синтаксиса передачи и получения данных по RS485 даже просто в режиме СОМ порта.
Единственное пока, что понял из найденных примеров что нужно передавать данные в НЕХ. Еще как то нужно CRC16 посчитать и куда то деть. В общем непонятно ни чего от слова совсем.
Примеров под мой случай толковых не нашел. Буду очень признателен за помощь в решении этого ребуса. Мне по сути нужно вытащить один показатель только.

https://github.com/n0l/Mercury_remote/blob/master/get_data_python3.py
нашел очень близкий пример по реализации, но не понимаю части логики кода….

Отредактировано Akhepython (Дек. 21, 2021 18:13:19)

Офлайн

#2 Дек. 21, 2021 23:58:45

xam1816
Зарегистрирован: 2020-05-11
Сообщения: 1348
Репутация: +  118  -
Профиль   Отправить e-mail  

Опрос теплосчетчика по RS485

 import crcmod
#
def crc16_calc(chunk): # считает crc16
    crc16 = crcmod.mkCrcFun(0x18005, initCrc=0xFFFF, rev=True, xorOut=0x0000)
    crc_int = crc16(chunk)
    high_byte = crc_int >> 8
    low_byte = crc_int & 0xff
    out_hex = '{:02X}{:02X}'.format(low_byte, high_byte)
    return bytes.fromhex(out_hex)
#
def create_package(addr, f, l, mask_ch, id) -> bytes: # собирает пакет
    hex_chunk = '{:08d}{:02X}{:02X}{:<08}{:02X}'.format(addr, f, l, '{:02X}'.format(mask_ch), id)
    chunk = bytes.fromhex(hex_chunk)
    data = chunk + crc16_calc(chunk)
    return data
#
addr = 12345678 # - сетевой адрес устройства (4байта) в формате BCD, старшим байтом вперёд
f = 1 # - код функции запроса (1 байт) чтение текущих показаний
l = 14 # - общая длина пакета (1 байт) 
mask_ch = 2 # – битовая маска запрашиваемых каналов (4 байта) (чтение второго канала)
id = 24228 # - идентификатор запроса (любые 2 байта)
#
data = create_package(addr, f, l, mask_ch, id) # отправить теплосчетчику,
#
print(data) #b'\x124Vx\x01\x0e\x02\x00\x00\x00^\xa4Ac'

Офлайн

#3 Дек. 22, 2021 00:06:50

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 9844
Репутация: +  853  -
Профиль   Отправить e-mail  

Опрос теплосчетчика по RS485

Akhepython
Единственное пока, что понял из найденных примеров что нужно передавать данные в НЕХ.
Сначала формировать данные в hex. Потом передавать сформированные данные как байты простые. Потом получать ответ от устройства в виде простых байтов. Потом разбирать эти простые байты на поля и получать данные из этих полей.

Для этого всего хватит pyserial, bytearray(), crcmod и struct.



Отредактировано py.user.next (Дек. 22, 2021 00:07:41)

Офлайн

#4 Окт. 24, 2024 16:25:37

Demion
Зарегистрирован: 2024-10-20
Сообщения: 2
Репутация: +  0  -
Профиль   Отправить e-mail  

Опрос теплосчетчика по RS485

Здравствуйте, увидел Ваше сообщения о созданию пакета данных с CRC. Не могли бы Вы помочь в разборе пакета приходящего от счетчика?
Счетчик Пульсар с rs485. Запрос “08 56 16 18 01 0E 03 00 00 00 01 00 FE 71”.
Получаю ответ: “085616180112a3e8103c0000000001005fab”.
Вручную (согласно протокола от Пульсар) разбираю: “08561618” - ADDR (номер счетчика), “01” - код функции запроса, “12” - общая длина пакета, “a3e8103c00000000” - данные, “01 00” - ID (идентификатор запроса (любые 2 байта))
“5fab” - CRC16.
Но как это сделать на Python? Просьба помочь!

Отредактировано Demion (Окт. 24, 2024 16:26:42)

Прикреплённый файлы:
attachment _Для_форума_1.pdf (811,6 KБ)

Офлайн

#5 Окт. 24, 2024 17:23:58

Rodegast
От: Пятигорск
Зарегистрирован: 2007-12-28
Сообщения: 2740
Репутация: +  183  -
Профиль   Отправить e-mail  

Опрос теплосчетчика по RS485

> Но как это сделать на Python? Просьба помочь!

Что тебе нужно сделать? CRC16 вычислить? Но в твоём протоколе есть же готовый пример его вычисления…



С дураками и сектантами не спорю, истину не ищу.
Ели кому-то правда не нравится, то заранее извиняюсь.

Офлайн

#6 Окт. 24, 2024 17:27:12

Demion
Зарегистрирован: 2024-10-20
Сообщения: 2
Репутация: +  0  -
Профиль   Отправить e-mail  

Опрос теплосчетчика по RS485

Мне нужна помощь в написании кода на питоне для разбора ответа от прибора, например: ответ: “085616180112a3e8103c0000000001005fab”. Но ответ может быть и другой.

Офлайн

#7 Окт. 27, 2024 05:31:34

py.user.next
От:
Зарегистрирован: 2010-04-29
Сообщения: 9844
Репутация: +  853  -
Профиль   Отправить e-mail  

Опрос теплосчетчика по RS485

Demion
Здравствуйте, увидел Ваше сообщения о созданию пакета данных с CRC. Не могли бы Вы помочь в разборе пакета приходящего от счетчика?
Счетчик Пульсар с rs485. Запрос “08 56 16 18 01 0E 03 00 00 00 01 00 FE 71”.
Получаю ответ: “085616180112a3e8103c0000000001005fab”.
Вручную (согласно протокола от Пульсар) разбираю: “08561618” - ADDR (номер счетчика), “01” - код функции запроса, “12” - общая длина пакета, “a3e8103c00000000” - данные, “01 00” - ID (идентификатор запроса (любые 2 байта))
“5fab” - CRC16.
Но как это сделать на Python? Просьба помочь!
Можешь составить кортеж из частей:
  
>>> def get_data():
...     text = '085616180112a3e8103c0000000001005fab'
...     out = bytes.fromhex(text)
...     return out
... 
>>> def parse_data(data):
...     addr = data[:4]
...     func = data[4:5]
...     code = data[5:6]
...     payload = data[6:-4]
...     ident = data[-4:-2]
...     crc = data[-2:]
...     out = (
...         addr,
...         func,
...         code,
...         payload,
...         ident,
...         crc
...     )
...     return out
... 
>>> data = get_data()
>>> 
>>> data_parsed = parse_data(data)
>>> print(data_parsed)
(b'\x08V\x16\x18', b'\x01', b'\x12', b'\xa3\xe8\x10<\x00\x00\x00\x00', b'\x01\x00', b'_\xab')
>>> 
>>> print(list(data_parsed[0]))
[8, 86, 22, 24]
>>> print(list(data_parsed[1]))
[1]
>>> print(list(data_parsed[2]))
[18]
>>> print(list(data_parsed[3]))
[163, 232, 16, 60, 0, 0, 0, 0]
>>> print(list(data_parsed[4]))
[1, 0]
>>> print(list(data_parsed[5]))
[95, 171]
>>>
Можешь составить словарь из частей:
  
>>> def get_data():
...     text = '085616180112a3e8103c0000000001005fab'
...     out = bytes.fromhex(text)
...     return out
... 
>>> def parse_data(data):
...     addr = data[:4]
...     func = data[4:5]
...     code = data[5:6]
...     payload = data[6:-4]
...     ident = data[-4:-2]
...     crc16 = data[-2:]
...     out = {
...         'address': addr,
...         'function': func,
...         'code': code,
...         'payload': payload,
...         'id': ident,
...         'crc': crc16
...     }
...     return out
... 
>>> data = get_data()
>>> 
>>> data_parsed = parse_data(data)
>>> print(data_parsed)
{'address': b'\x08V\x16\x18', 'function': b'\x01', 'code': b'\x12', 'payload': b'\xa3\xe8\x10<\x00\x00\x00\x00', 'id': b'\x01\x00', 'crc': b'_\xab'}
>>> 
>>> print(list(data_parsed['address']))
[8, 86, 22, 24]
>>> print(list(data_parsed['function']))
[1]
>>> print(list(data_parsed['code']))
[18]
>>> print(list(data_parsed['payload']))
[163, 232, 16, 60, 0, 0, 0, 0]
>>> print(list(data_parsed['id']))
[1, 0]
>>> print(list(data_parsed['crc']))
[95, 171]
>>>
Можешь составить объект из частей:
  
>>> class Data:
...     def __init__(self, addr, func, code,
...                  payload, ident, crc16):
...         self._addr = addr
...         self._func = func
...         self._code = code
...         self._payload = payload
...         self._ident = ident
...         self._crc16 = crc16
...     def __str__(self):
...         t = (self._addr, self._func, self._code,
...              self._payload, self._ident, self._crc16)
...         return '{}{}'.format(__class__.__name__, t)
...     def addr(self):
...         return self._addr
...     def func(self):
...         return self._func
...     def code(self):
...         return self._code
...     def payload(self):
...         return self._payload
...     def ident(self):
...         return self._ident
...     def crc16(self):
...         return self._crc16
... 
>>> def get_data():
...     text = '085616180112a3e8103c0000000001005fab'
...     out = bytes.fromhex(text)
...     return out
... 
>>> def parse_data(data):
...     addr = data[:4]
...     func = data[4:5]
...     code = data[5:6]
...     payload = data[6:-4]
...     ident = data[-4:-2]
...     crc16 = data[-2:]
...     out = Data(
...         addr,
...         func,
...         code,
...         payload,
...         ident,
...         crc16
...     )
...     return out
... 
>>> data = get_data()
>>> 
>>> data_parsed = parse_data(data)
>>> print(data_parsed)
Data(b'\x08V\x16\x18', b'\x01', b'\x12', b'\xa3\xe8\x10<\x00\x00\x00\x00', b'\x01\x00', b'_\xab')
>>> 
>>> print(list(data_parsed.addr()))
[8, 86, 22, 24]
>>> print(list(data_parsed.func()))
[1]
>>> print(list(data_parsed.code()))
[18]
>>> print(list(data_parsed.payload()))
[163, 232, 16, 60, 0, 0, 0, 0]
>>> print(list(data_parsed.ident()))
[1, 0]
>>> print(list(data_parsed.crc16()))
[95, 171]
>>>
Что нужно составлять и что не нужно составлять, зависит от задачи. Задача определяет наилучший вариант.

Например, у тебя есть три инструмента: молоток, шуруповёрт и болгарка. Какой из этих инструментов наилучший, если нужно забить гвоздь? Какой из этих инструментов наилучший, если нужно закрутить шуруп? Какой из этих инструментов наилучший, если нужно разделить железную трубу на части? Какой из этих инструментов наилучший, если нужно разжечь костёр? То есть задача определяет, что хорошо и что плохо в данном конкретном случае, в рамках, границах, в области этой задачи.

Бери вариант со словарём, чтобы не путаться. Внутри словаря этим полям можно давать любые имена.

Также вместо bytes() можно применить bytearray(), так как он даёт больше возможностей для работы с бинарными данными. В нём, в отличие от bytes(), можно менять набор значений, добавлять к ним что-то, удалять из них что-то, менять в них что-то с одного на другое.

Пример:
Это тип bytes(), его можно только читать:
  
>>> data = bytes([0x01, 0x02, 0x03])
>>> data
b'\x01\x02\x03'
>>> 
>>> data[1]
2
>>> 
>>> data[1] = 4
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
>>>
Это тип bytearray(), его можно как читать, так и писать:
  
>>> data = bytearray([0x01, 0x02, 0x03])
>>> data
bytearray(b'\x01\x02\x03')
>>> 
>>> data[1]
2
>>> 
>>> data[1] = 4
>>> data
bytearray(b'\x01\x04\x03')
>>> 
>>> data[1]
4
>>>
Если тебе нужно что-то создать (сформировать), пиши функцию для создания. Если тебе нужно что-то разобрать (разделить), пиши функцию для разбора. На каждое действие пиши функцию.



Отредактировано py.user.next (Окт. 27, 2024 23:05:45)

Офлайн

#8 Ноя. 1, 2024 23:32:40

xam1816
Зарегистрирован: 2020-05-11
Сообщения: 1348
Репутация: +  118  -
Профиль   Отправить e-mail  

Опрос теплосчетчика по RS485

Demion
Мне нужна помощь в написании кода на питоне для разбора ответа от прибора, например: ответ: “085616180112a3e8103c0000000001005fab”. Но ответ может быть и другой.

  
import struct
 
def calculate_crc16_modbus(data):
    crc = 0xFFFF
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if (crc & 0x0001):
                crc = (crc >> 1) ^ 0xA001
            else:
                crc >>= 1
    return crc.to_bytes(2, byteorder='little')
 
def parse(data):
    hex_data = bytes.fromhex(data)
    addr = hex_data[:4]
    f = hex_data[4]
    l = hex_data[5]
    ch = hex_data[6:-4]
    id = hex_data[-4:-2]
    crc = hex_data[-2:]
    if crc != calculate_crc16_modbus(hex_data[:-2]):
        print('bad data')
        return None
 
    out = {}
    out['addr'] = ''.join(f'{byte >> 4}{byte & 0x0F}' for byte in addr)
    out['func'] = int(f)
    out['len'] = int(l)
    out['value'] = out['func'] == 1 and struct.unpack('ff', ch)[0] or None
    out['id'] = id.hex()
    return out
 
data = parse('085616180112a3e8103c0000000001005fab')
print(f"addr: {data['addr']}")
print(f"func_num: {data['func']}")
print(f"byte_received: {data['len']}")
print(f"value: {data['value']}")
print(f"id: {data['id']}")

Офлайн

Board footer

Модераторировать

Powered by DjangoBB

Lo-Fi Version