Это не курсач и не диплом! Я давно уже не студент. Это попытка написать еще одну бесплатную прогу для ""домашней"" диагностики автомобилей. И я не очень силен в Питоне. Поэтому прошу помощи.
Исходные данные:
Имеется некоторое устройство, которое подключено к компу через 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. Но заниматься ремонтом этого дурдома меня что-то больше не прёт.
question@mail.ru
·