Распаковать IEEE 754 с плавающей точкой - PullRequest
2 голосов
/ 21 марта 2020

Я читаю два 16-битных регистра из tcp-клиента с помощью модуля pymodbus . Два регистра составляют 32-битное IEEE 754 кодированное число с плавающей запятой. В настоящее время у меня есть 32-битное двоичное значение регистров, показанных в коде ниже.

start_address = 0x1112
reg_count = 2
client = ModbusTcpClient(<IP_ADDRESS>)
response = client.read_input_registers(start_address,reg_count)
reg_1 = response.getRegister(0)<<(16 - (response.getRegister(0).bit_length())) #Get in 16 bit format
reg_2 = response.getRegister(1)<<(16 - (response.getRegister(1).bit_length())) #Get in 16 bit format 
volts = (reg_1 << 16) | reg_2 #Get the 32 bit format

Вышеприведенное работает нормально, чтобы получить закодированное значение, проблема заключается в его декодировании. Я собирался кодировать что-то вроде этого видео , но я наткнулся на формат 'f' в модуле struct для кодирования IEEE 754. Я попытался декодировать 32-разрядный код с плавающей запятой, хранящийся в вольт в приведенном выше коде, используя метод unpack в модуле struct, но натолкнулся на следующие ошибки.

val = struct.unpack('f',volts)
>>> TypeError: a bytes-like object is required, not 'int'

Хорошо, попытался преобразовать его в 32-битную двоичную строку.

temp = bin(volts)
val = struct.unpack('f',temp)
>>> TypeError: a bytes-like object is required, not 'str'

Попытка преобразовать его в подобный байту объект, как в этом посте , и отформатировать по-разному.

val = struct.unpack('f',bytes(volts))
>>> TypeError: string argument without an encoding

temp = "{0:b}".format(volts)
val = struct.unpack('f',temp)
>>> ValueError: Unknown format code 'b' for object of type 'str'

val = struct.unpack('f',volts.encode())
>>> struct.error: unpack requires a buffer of 4 bytes

Куда добавить этот буфер и где в документации говорится, что мне нужен этот буфер с помощью метода unpack? В документации сказано:

Строка должна содержать именно тот объем данных, который требуется для формата (len (строка) должна равняться calcsize (fmt)).

Функция calcsize (fmt) возвращает значение в байтах, но len (строка) возвращает значение длины строки, нет?

Любые предложения приветствуются.

РЕДАКТИРОВАТЬ

Существует решение для декодирования ниже, однако лучшее решение для получения 32-битного значения регистра из два 16-битных значения регистра показаны ниже по сравнению с оригиналом в вопросе.

start_address = 0x1112
reg_count = 2
client = ModbusTcpClient(<IP_ADDRESS>)
response = client.read_input_registers(start_address,reg_count)
reg_1 = response.getRegister(0)
reg_2 = response.getRegister(1)
# Shift reg 1 by 16 bits
reg_1s = reg_1 << 16
# OR with the reg_2
total = reg_1s | reg_2

1 Ответ

2 голосов
/ 21 марта 2020

Я нашел решение проблемы, используя BinaryPayloadDecoder.fromRegisters () из модуля pymodbus вместо модуля struct. Обратите внимание, что это решение задает c для устройства интеллектуального счетчика Modbus, которое я использую , поскольку порядок следования байтов и слов регистров в других устройствах может изменяться. Он может все еще работать на других устройствах для декодирования регистров, но я бы посоветовал сначала прочитать документацию по устройству, чтобы быть уверенным. Я оставил в комментариях в коде ниже, но когда я ссылаюсь на страницу 24, это только для моего устройства.

from pymodbus.client.sync import ModbusTcpClient
from pymodbus.constants import Endian
from pymodbus.payload import BinaryPayloadDecoder

start_address = 0x1112
reg_count = 2
client = ModbusTcpClient(<IP_ADDRESS>)
response = client.read_input_registers(start_address,reg_count)
# The response will contain two registers making a 32 bit floating point number
# Use the BinaryPayloadDecoder.fromRegisters() function to decode
# The coding scheme for a 32 bit float is IEEE 754 https://en.wikipedia.org/wiki/IEEE_754
# The MS Bytes are stored in the first address and the LS bytes are stored in the second address,
# this corresponds to a big endian byte order (Second parameter in function)
# The documentation for the Modbus registers for the smart meter on page 24 says that
# the low word is the first priority, this correspond to a little endian word order (Third parameter in function)
decoder = BinaryPayloadDecoder.fromRegisters(response.registers, Endian.Big, wordorder=Endian.Little)
final_val = (decoder.decode_32bit_float())
client.close()

РЕДАКТИРОВАТЬ Кредит juanpa-Arrivillaga и chepner проблему можно решить с помощью модуля struct также с помощью byteorder='little' , Две функции в приведенном ниже коде могут использоваться, если байтер имеет значение little или если порядок байтов равен big в зависимости от реализации.

import struct
from pymodbus.client.sync import ModbusTcpClient

def big_endian(response):
    reg_1 = response.getRegister(0)
    reg_2 = response.getRegister(1)
    # Shift reg 1 by 16 bits
    reg_1s = reg_1 << 16
    # OR with the reg_2
    total = reg_1s | reg_2
    return total

def little_endian(response):
    reg_1 = response.getRegister(0)
    reg_2 = response.getRegister(1)
    # Shift reg 2 by 16 bits
    reg_2s = reg_2 << 16
    # OR with the reg_1
    total = reg_2s | reg_1
    return(total)

start_address = 0x1112
reg_count = 2
client = ModbusTcpClient(<IP_ADDRESS>)
response = client.read_input_registers(start_address,reg_count)

# Little 
little = little_endian(response)
lit_byte = little.to_bytes(4,byteorder='little')
print(struct.unpack('f',lit_byte))

# Big 
big = big_endian(response)
big_byte = big.to_bytes(4,byteorder='big')
print(struct.unpack('f',big_byte))


...