аватар question@mail.ru · 01.01.1970 03:00

прием строки символов из последовательного порта

Это не курсач и не диплом! Я давно уже не студент. Это попытка написать еще одну бесплатную прогу для ""домашней"" диагностики автомобилей. И я не очень силен в Питоне. Поэтому прошу помощи.

Исходные данные:

Имеется некоторое устройство, которое подключено к компу через USB-порт. Комп и устройство обмениваются данными в символьном виде. По сути, это классическое соединение: устройство -- играет роль сервера, а комп -- роль терминала. Если кому-то что-то это скажет, то это устройство -- это адаптер OBD-II (On-Board Diagnostics -- устройство для диагности автомобилей) ELM327.

Система работает по очень простому принципу -- на запрос (команду) клиента (терминала), сервер возвращает ответ. Вот пример запроса-ответа:

> atiELM327 v1.5> atdpAUTO, ISO 15765-4 (CAN 11/250)

Вот так выглядит строка ответа ""ELM327 v1.5"" в hex виде:

45 4C 4D 33 32 37 20 76 31 2E 35 0D 0D 3E

А так строка ответа ""AUTO, ISO 15765-4 (CAN 11/250)"":

41 55 54 4F 2C 20 49 53 4F 20 31 35 37 36 35 2D 34 20 28 43 41 4E 20 31 31 2F 32 35 30 29 0D 0D 3E

Во первых, в строке ответа вместо символа '\n' используется символ '\r'. (Тут нет проблемы. Это легко исправляется.) Во вторых, ответ заканчивается симвлом '>' (hex-код = 0x3E), что приводит к усложнению программного кода обработки ответа. Тут тоже нет неразрешимой проблемы.

Проблема, которую я не могу одолеть, заключается в том, что в Питоне-3 я не могу толком получить из порта строку символов. В Питоне-2.7 запрос-ответ работает без проблем.

Вот код для второго Питона:

def show_response(port):  while True:    resp = ''    while True:      ch = port.read(1)      if len(ch) == 1:        # print ch        if ch == '\r':          resp += '\n'        else:          resp += ch      if resp[-2:] == '\n>':        print resp[:-2],        break

Для третьего Питона это код (поняное дело!) не подходит. Я не имею в виду замену оператора print для Питон-2 на функцию print() для Питон-3. Это всё легко правится.

Проблема -- в приёме данных от последовательного порта. Если во втором Питоне достаточно сделать (упрощенно говоря) так:

ch = port.read(1)print ch

, то в третьем Питоне функция read() возвращает не строку символов, а строку байт.

Я -- Си-шник. И для меня байт-- от и в Африке байт. С моей точки зрения -- что байт, что ASCII-символ (не многобайтовые символы типа utf-8!!!, а именно одно-байтовый ASCII-символ из первой половины таблицы кодов) -- это один и тот же набор битов. Поэтому функция read() в модуле serial для второго Питона, возвращающая строку символов, и функция read() в модуле serial для третьего Питона, возвращающая строку байт, -- должны давать одинаковый результат.

Но на практике вместо строки байт ""45 4C 4D 33 32 ..."" (ответ на команду ati во втором Питоне), в третьем Питоне я получаю всего три байта ""7F BF ED"". И более уже ничего не могу получить. Обмен между ELM327 и компом происходит байтами (символами ?), находящимися в диапазоне 0..0x7F. Ни какой кириллицей и не пахнет! Откуда такие странные коды -- 0xBF и 0xED ?

Что я делаю не так?

Вот код для третьего Питона:

def show_response(port):  #print('show_response')  while True:    resp = ''    while True:      b = port.read(1)  if len(b) != 0:    print('0x{0:02X}'.format(b[0]))    #if ch == '\r':    #  resp += '\n'    #else:    #  resp += ch  if resp[-2:] == '\n>':    print(resp[:-2], end='')    break

Инициализация порта для второго и третьего Питона практически одинаковая:

#!/usr/bin/env python3#coding:utf8import serialimport timefrom multiprocessing import Process...if __name__ == '__main__':  try:    port = serial.Serial(""/dev/ttyUSB0"", 38400, timeout=0.2)  except serial.SerialException:    print('Соединение не удалось')    exit(1)  port.flushOutput()  port.flushInput()  p1 = Process(target=show_response, args=(port,))  p1.daemon = True  p1.start()  command(port)  print('Пока-пока!')

Пакет serial устанавливал из стандартных репозиториев -- на одном компе (Ubuntu-10.04) это была уставнока пакета для второго Питона ($ sudo apt-get install python-serial), на другом компе (Debian-8) -- для третьего Питона (# apt-get install python3-serial).

Не понимаю, куда копать? Кто-нибудь выпишите мне волшебный пендель в нужном направлении, а то ведь так и помру, не поняв сути Питоновских махинаций с байтами.

UPDATE 08.08.2015 - 00:53

Я изменил прогу, благо она вообще микроскопическая. Сейчас тупо открывается порт, тупо дается команда девасу -- 'ati\r' и тупо выводится на консоль всё, что сыплется из порта в ответ на эту команду -- никаких процессов, никаких ухищрений, ничего лишнего! И тем не менее результат вывода на консоль точно такой же.

Вот текст этой проги:

#!/usr/bin/env python3#coding:utf8import serialimport timeif __name__ == '__main__':  try:    port = serial.Serial(""/dev/ttyUSB0"", 38400, timeout=1)  except serial.SerialException:    print('Соединение не удалось')    exit(1)  port.flushOutput()  port.flushInput()  # Послать запрос  cmd = bytes('ati\r', 'utf-8')  port.write(cmd)  time.sleep(0.1)  # Принять ответ и вывести его на консоль  while True:    print('.')    resp = port.readline()    # Вариант вывода 1    if len(resp) > 0:      print('[{0:d}] = '.format(len(resp)), end = '')      for b in resp:        print('0x{0:02X} '.format(b), end='')    '''    # Вариант вывода 2    string = str(port.readline())    if len(string) > 0:      print('[{0:d}] = '.format(len(string)), end = '')      for ch in string:        print('0x{0:02X} '.format(ord(ch)), end='')    '''    '''    # Вариант вывода 3    string = str(port.readline())    if len(string) > 0:      print('[{0:d}] = '.format(len(string)), end = '')      print(string)   '''

Вывод по первому варианту такой (скриншот):

$ ./myOBDm2.py.[2] = 0x7F 0xBF ....

Вывод по второму варианту -- такой:

$ ./myOBDm2.py.[3] = 0x62 0x27 0x27 .[3] = 0x62 0x27 0x27 .[3] = 0x62 0x27 0x27 .[3] = 0x62 0x27 0x27 .[3] = 0x62 0x27 0x27 .[3] = 0x62 0x27 0x27 .

Вывод по третьему варианту -- такой:

$ ./myOBDm2.py.[3] = b''.[3] = b''.[3] = b''.

Более детальное изучение вывода по третьему варианту намекает, что функция readline() возвращает пустую строку байтов -- ведь три символа b'' -- это ничто иное как строка байтов в Питоне. Интересно, чтобы это значило? Почему serial в Питон-3 так странно работает?

Также я пробовал перебирать варианты кодировок при посылке запроса:

  # Послать запрос  cmd = bytes('ati\r', 'utf-8') # 'cp866', 'cp1251', 'ascii'  port.write(cmd)

Ничего не менялось, кодировка никак не влияет.

На девайсе есть светодиода, которые моргают при приёме и передаче данных по USB-интерфейсу. Судя по морганию -- обмен идёт.

UPDATE 08.08.2015 - 16:47

Я сделал два изменения в проге:

...  # Послать запрос  cmd = bytes('ati\r\n', 'ascii')  # -1- Добавил '\n'  port.write(cmd)  time.sleep(1)  # Принять ответ и вывести его на консоль  while True:    print('.')    resp = port.read()    # -2- Изменил функцию (была readline)    # Вариант вывода 1    if len(resp) > 0:      print('[{0:d}] = '.format(len(resp)), end = '')      for b in resp:        print('0x{0:02X} '.format(b), end='')...

В результате на консоль получил правильный результат:

$ ./myOBDm2.py.[1] = 0x61 .[1] = 0x74 .[1] = 0x69 .[1] = 0x0D .[1] = 0x45 .[1] = 0x4C .[1] = 0x4D .[1] = 0x33 .[1] = 0x32 .[1] = 0x37 .[1] = 0x20 .[1] = 0x76 .[1] = 0x31 .[1] = 0x2E .[1] = 0x35 .[1] = 0x0D .[1] = 0x0D .[1] = 0x3E ........

Но рапортовать о решении проблемы рано, так как проблема исчезляа только для первого (после включения компа) запуска проги. Повтороное и все последующие запуски проги выдавали одинаковые неправильные результаты:

$ ./myOBDm2.py.[1] = 0x7F .[1] = 0xBF .[1] = 0xFE .....

Следует заметить, что при возврате ответа от девайса, строки заканчиваются символом '\r', а не '\n' и не их комбинацией '\r\n'.

По моим представлениям девайс игнорирует символ '\n'. В описании вообще-то сказано, что девайс игнорирует также и другие ""белые"" символы (пробел, табуляция, ..). Например, девайс одинково правильно понимает команды 'ati\r' и 'at i\r'.

Таким образом наличие или отсутствие в конце команда символа '\n' никак не влияет на работоспособность девайса, что и подтверждается на практике.

UPDATE 08.08.2015 - 19:31

Хорошо. Перезагружаю комп и смотрю на настройки порта. Настройки порта перед запуском программы следующие:

$ stty -F /dev/ttyUSB0speed 57600 baud; line = 0;eof = ^A; min = 1; time = 0;-brkint -icl -imaxbel-opost -onlcr-icanon -echo -echoe

Затем запускаю прогу. Она отрабатывает нормально. Выхожу из проги, и снова смотрю настройки порта:

$ stty -F /dev/ttyUSB0speed 38400 baud; line = 0;eof = ^A; min = 0; time = 0;-brkint -icl -imaxbel-opost -onlcr-isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

Оп-па! Что называется -- ""Найди семь отличий!""

Методом перебора включения/выключения настроек порта (точнее сказать -- порта_терминала), удалось обнаружить, что ""мешается"" параметр -iexten.

Если перед запуском проги выключить этот параметр

$ stty -F /dev/ttyUSB0 iexten$ stty -F /dev/ttyUSB0  speed 38400 baud; line = 0;    eof = ^A; min = 0; time = 0;    -brkint -icl -imaxbel    -opost -onlcr    -isig -icanon -echo -echoe -echok -echoctl -echoke

, то прога снова нормально отрабатывает.

Иначе говоря, модуль serial в Python-3 при открытие порта меняет его настройки. Модуль serial в Python-2 точно так же меняет настройки порта, но здесь (во втором Питоне) эти настройки не приводят к фатальным последствиям.

Теперь осталось найти способ, как при открытии порта в модуле serial для Python-3 указать, что параметр IEXTEN не нужно устанавливать.

Проблема все еще не решена. Продолжаю копать. По мере ""лечения"" буду публиковать свои шаги.

UPDATE от 28.08.2015-02:01

В результате неспешных попыток заставить девайс ELM327 работать под Питоном-3, я пришел к пониманию, что возможно это не Питон не способен принять ответ от девайса, а наоборот -- Питон не способен передать девайсц команду. Точнее так: Питон передает искаженную команду, девай получает хрен-знает-что и выдает Питону ответ типа ""я не понимаю"". Поскольку, Питон работает не правильно, то и ответ он тоже понимает в искаженном виде. Косвенным подтверждением этому является то, что через 30 скунд девас отправляет в комп какую-то короткую ""отрыжку"". Я предположил, что это может быть что-то типа ""незаконченная команда снимается по тайм-ауту"". По крайней мере такого явления (""отрыжки"") при работе под Питоном-2 не наблюдается. Другими словами, Питон-2 посылает в девайс правильную команду, а Питон-3 искаженную. Соответственно девас так и реагирует.

Что бы проверить то, что Питон-3 посылает бессмыслицу, я поставил несложный (для электронщиков, а я -- электронщик) эксперимент. Я взял два китайских конвертера USB-UART типа CH340G и соединил их по схеме нуль-модема. Затем, подключил один к рабочему компу, на котором прога крутится под Питон-3, а другой к нотиику, на котором точно такая же (ну, за исключением print и некоторых других отличий) прога крутится под Питон-2.

Тогда, если Питон-3 искажает команды, я увижу эти искажения на втором компе.

Да! Существенное дополнение -- в девайсе ELM-327 используется микросхема конвертера CH340T, это клон CH340G.

В результате я увидел, что при передаче данных в обоих направдлениях никаких искажений нет и в помине! Пробовал на разных скоростях обмена. Все работает чётко. Отсюда вывод -- проблемы в связке девайса и Питона-3.

Устройство ELM327 -- неразборное, чем-то напоминает блоки питания для нотиков. Пришлось крушить корпус молотком. Вскрыл более-менее нормально.

А вот далее начинается вторая серия детектива.

Я подключил осциллограф к выходу UART-а микросхемы CH340T, чтобы посмотреть, что она передает непосредственно в микроконтроллер.

Держитесь за стул! Оказывается, что микросхема передает правильные данные, но не на той скорости, какую мы ожидаем. Точнее так, в питоновской программе при инициализации последовательного порта указывается скорость работы. Программа, работающая под Питон-2, настраивает микросхему CH340T на заданную скорость. А вот программа, работающая под Питон-3, по какой-то неведомой причине не может настроить эту микросхему. В результате получается, что прога из-под Питона-2 обращается к микроконтроллеру на скорости 38400, а прога из-под Питона-3 -- на скорости 9600 Бод.

Микроконтроллер ожидает, что к нему будут ""стучаться"" на 38400, и по этому он, естественно, не правильно понимает команду от компа. Далее микроконтроллер выдает ответ типа ""Вы что там, совсем охренели?"" на скорости 38400, но CH340T принимает это на скорости 9600 и передает в комп мусор. А через 30 секунд микрконтроллер посылает в комп еще один пакет типа ""Да ну вас нахрен! Эту команду я снимаю. Вводите следующую!"".

Теперь мне нужно понять -- почему Питон-3 не способен правильно проинициализировать микросхему конвертера CH340T.

UPDATE от 28.08.2015-04:05

Скажу сразу -- проблему победить не удалось, но удалось нащупать ""обходной путь"". Рецепт прост -- в Питоне-3 не следует производить одновременно инициализацию порта и установку скорости.

Так делать не надо:

  try:    port = serial.Serial(""/dev/ttyUSB0"", 38400, timeout=0.2)  except serial.SerialException:    print('Соединение не удалось')    exit(1)

Следует делать так:

  try:    port = serial.Serial(""/dev/ttyUSB0"")    port.baudrate = 38400    port.timeout = 0.2  except serial.SerialException:    print('Соединение не удалось')    exit(1)

Я так понимаю, что это накосячено где-то в конструкторе класса Serial. Но заниматься ремонтом этого дурдома меня что-то больше не прёт.

аватар answer@mail.ru · 01.01.1970 03:00

Похожая ситуация на Pyton2 при приеме информации с тестера UT60D, который только передает сигналы в порт.

При открытии порта в рекомендованных настройках, на входе порта +10 вольт и пачки сигналов пропадают.

Помогло:

ser.rtsToggle=True

Последние

Похожие