Странное декодирование после использования функции разбиения python (например: \ x00) - PullRequest
1 голос
/ 12 февраля 2020

Это была очень странная ситуация, функция split изменяет формат строки. Пожалуйста, посмотрите на код ниже,

Код:

COM_Port = serial.Serial(COM_PortName)
with COM_Port as port:
    while True:
         RxedData = port.readline()
         line = RxedData.decode('utf-8')
         print("Line 1: ", line)
         row = line.split(',')[1:-1]
         print("Line 2: ", row)

Выход:

Line 1: "* , 0 0 0 0 0 5 7 5 , 2 3 : 0 3 : 4 7 , 1 1 / 0 2 / 2 0 , 1 2 . 3 4 5 , K P A , 0 0 0 0 6 . 8 3 , S L P M , T B ,                 , $ "

Line 2: ['\x000\x000\x000\x000\x000\x006\x002\x001\x00', '\x002\x000\x00:\x004\x006\x00:\x005\x001\x00', '\x001\x002\x00/\x000\x002\x00/\x002\x000\x00', '\x001\x002\x00.\x003\x004\x005\x00', '\x00K\x00P\x00A\x00', '\x000\x000\x000\x000\x000\x00.\x000\x000\x00', '\x00C\x00C\x00P\x00M\x00', '\x00T\x00G\x00', '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00']

Как Line 2, попасть в \x000\x000...? Что это за формат кодирования? Как получить его в нужном формате?

Редактировать 1:

print([hex(i) for i in RxedData])

Вывод:

['0x2a', '0x0', '0x2c', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x31', '0x0', '0x2c', '0x0', '0x31', '0x0', '0x31', '0x0', '0x3a', '0x0', '0x35', '0x0', '0x31', '0x0', '0x3a', '0x0', '0x35', '0x0', '0x30', '0x0', '0x2c', '0x0', '0x31', '0x0', '0x33', '0x0', '0x2f', '0x0', '0x30', '0x0', '0x32', '0x0', '0x2f', '0x0', '0x32', '0x0', '0x30', '0x0', '0x2c', '0x0', '0x31', '0x0', '0x32', '0x0', '0x2e', '0x0', '0x33', '0x0', '0x34', '0x0', '0x35', '0x0', '0x2c', '0x0', '0x4b', '0x0', '0x50', '0x0', '0x41', '0x0', '0x2c', '0x0', '0x31', '0x0', '0x32', '0x0', '0x33', '0x0', '0x34', '0x0', '0x35', '0x0', '0x2e', '0x0', '0x36', '0x0', '0x36', '0x0', '0x2c', '0x0', '0x53', '0x0', '0x4c', '0x0', '0x50', '0x0', '0x48', '0x0', '0x2c', '0x0', '0x0', '0x0', '0x0', '0x0', '0x2c', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2c', '0x0', '0x24', '0x0', '0xa']

Ответы [ 2 ]

3 голосов
/ 13 февраля 2020

Хорошо, из hexdump полученных байтов видно, что за каждым символом ASCII следует байт NULL (\x00). Это просто представление символов в UTF-16-LE. Декодер UTF-8 просто сохраняет кодовые точки начальных байтов, потому что все они ниже 128, оставляя все перемежающиеся нули. И вы не можете просто декодировать байтовую строку как UTF-16 (что это на самом деле), потому что вы получили ее через readline, который просто остановился после символа новой строки и не прочитал следующий ноль.

Если Вы могли бы прочитать другую строку, она, вероятно, начиналась бы с того нулевого символа, в результате чего строка появлялась бы в кодировке UTF-16-BE ...

Что можно сделать тогда?

Тривиальный обходной путь это просто избавиться от нулевых символов. Если вы можете быть уверены, что получите только простые символы ASCII (без акцентированных символов, таких как é, без смайликов, без греческих или кириллических c и т. Д. c.), Этого будет достаточно:

     RxedData = port.readline()
     line = RxedData.replace(b'\x00', b'').decode('ascii')
     print("Line 1: ", line)
     row = line.split(',')[1:-1]
     print("Line 2: ", row)

С этими значениями: ['0x2a', '0x0', '0x2c', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x30', '0x0', '0x31', '0x0', '0x2c', '0x0', '0x31', '0x0', '0x31', '0x0', '0x3a', '0x0', '0x35', '0x0', '0x31', '0x0', '0x3a', '0x0', '0x35', '0x0', '0x30', '0x0', '0x2c', '0x0', '0x31', '0x0', '0x33', '0x0', '0x2f', '0x0', '0x30', '0x0', '0x32', '0x0', '0x2f', '0x0', '0x32', '0x0', '0x30', '0x0', '0x2c', '0x0', '0x31', '0x0', '0x32', '0x0', '0x2e', '0x0', '0x33', '0x0', '0x34', '0x0', '0x35', '0x0', '0x2c', '0x0', '0x4b', '0x0', '0x50', '0x0', '0x41', '0x0', '0x2c', '0x0', '0x31', '0x0', '0x32', '0x0', '0x33', '0x0', '0x34', '0x0', '0x35', '0x0', '0x2e', '0x0', '0x36', '0x0', '0x36', '0x0', '0x2c', '0x0', '0x53', '0x0', '0x4c', '0x0', '0x50', '0x0', '0x48', '0x0', '0x2c', '0x0', '0x0', '0x0', '0x0', '0x0', '0x2c', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2d', '0x0', '0x2c', '0x0', '0x24', '0x0', '0xa'] вы должны получить:

Line 1:  *,00000001,11:51:50,13/02/20,12.345,KPA,12345.66,SLPH,,--------,$

Line 2:  ['00000001', '11:51:50', '13/02/20', '12.345', 'KPA', '12345.66', 'SLPH', '', '--------']

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


Ожидание, соответствующее кодированию, будет заключаться в использовании TextIOWrapper вокруг последовательного порта и указании в нем кодировки UTF-16-LE. Я не смог протестировать его (нет серийного номера на моем боксе и не нужно), поэтому только догадываясь, что нужно сделать.

COM_Port = serial.Serial(COM_PortName)
with io.TextIOWrapper(io.BufferedRWPair(COM_Port, COM_Port), encoding = 'utf-16-le') as port:
    while True:
         line = port.readline()
         print("Line 1: ", line)
         row = line.split(',')[1:-1]
         print("Line 2: ", row)

Здесь TextIOWrapper позаботится о нулевом байте, следующем за байтом новой строки, и даст вам прямо истинные строки Unicode.

1 голос
/ 12 февраля 2020

Решил превратить мой комментарий в ответ (в основном, чтобы я мог включить код).

Две строки печатаются по-разному, потому что вы печатаете разные вещи. На Line 1 вы печатаете строку напрямую, поэтому функция print (или, возможно, сама консоль, не уверенная) может отображать байты ASCII в качестве символов. На Line 2 вы теперь печатаете список, поэтому интерпретация байтов не происходит во время print.

Ваша строка line после декодирования, скорее всего, имеет \x00 (NULL) байтов, встроенных в него вместо пробелов ASCII (\x20).

>>> x = '*\x00,\x000\x000\x000\x000\x000\x005\x007\x005\x00'
>>> print(x)
'* , 0 0 0 0 0 5 7 5 ,'
>>> print(x.split(','))
['*\x00', '\x000\x000\x000\x000\x000\x005\x007\x005\x00']

Чтобы изменить мой цитируемый комментарий, похоже, что он основан на любой консоли, которая печатает символы. Я получаю вышеуказанный вывод из cmd и PowerShell, но вместо этого Jupyter Notebook печатает это: *,00000575. Обратите внимание, что «пробелы» теперь исчезли.

Если я заменим несколько из \x00 байтов на \x20, Jupyter напечатает то, что вы видите выше (в местах, где они были заменены). по крайней мере). Это просто для того, чтобы показать, что символы NULL и пробелы могут визуально выглядеть одинаково, в зависимости от того, как их отображает консоль.

>>> x = '*\x20,\x200\x200\x000\x000\x000\x005\x007\x005\x00'
>>> print(x)
* , 0 0000575

Редактировать для вашего комментария:

Как сделать так, чтобы он правильно интерпретировал?

Зависит от того, что для вас значит «правильно». По сути, все имеет правильно интерпретировано - ваш последовательный порт просто посылает через NULL-байты вместо пробельных символов.

Если вы предпочитаете использовать пробелы ASCII вместо NULL-байтов, вы можете сделать простую замену строки (напечатано с Jupyter, который отображает NULL как ничто). Вы также можете просто использовать ' ' вместо '\x20', если хотите.

>>> print(x.replace('\x00', '\x20').split(','))
['* ', ' 0 0 0 0 0 5 7 5 ']
...