Найти - Пользователи
Полная версия: Pyserial не устанавливается уровень логической единицы по линиям DTR, RTS на время передачи посылки
Начало » Python для новичков » Pyserial не устанавливается уровень логической единицы по линиям DTR, RTS на время передачи посылки
1
Javis
Всем доброго времени суток. Имеется проблема с COM-портом. Точнее с установкой уровней на контактах DTR, RTS.
Имеется устройство которое маршрутизирует сигналы передаваемые с компьютера по RS232 на другие устройства в зависимости от адреса, задаваемого с помощью линий DTR, RTS.
Т.е. чтобы передать посылку одному устройству нужно установить DTR = 1, RTS = 0 и отправить в порт посылку. Для другого устройства соответственно (DTR = 0, RTS = 1).
Проблема в том, что во время выполнения программы уровень DTR у меня устанавливается на короткий промежуток времени, затем сбрасывается в 0 и после этого отправляется посылка. Никак не могу разобраться почему так происходит и как перманентно установить уровень на линии DTR (и на линии RTS). Нагуглил только информацию по второму питону, но там устаревшие команды, которые из pyserial были упразднены. Складывается впечатление что в третьем Питоне это нереально. Может кто-нибудь сталкивался с такой проблемой?

Моя функция которая производит запись в порт.
[code python]
def serial_tx(parcel):
try:
ser = serial.Serial(combo.get(), 9600, timeout = 1)
if parcel == 2:
ser.dtr(1)
ser.rts(0)
time.sleep(0.5)
parcel_send = '812000065670'
parcel_full = bytes.fromhex(parcel_send)
ser.write(parcel_full)
serial_rx(parcel)
elif parcel == 1:
ser.rts = 1
ser.dtr = 0
time.sleep(0.5)
parcel_send = '8116052033'
parcel_full = bytes.fromhex(parcel_send)
ser.write(parcel_full)
except Exception:
lbl_error_com = Label(lbl_rx_data_dc, text = "Не удалось открыть COM-порт\nПовторите попытку снова\n \n ", foreground = 'red')
lbl_error_com.place(x=5, y=5)
[/code]

py.user.next
Используй методы правильно
Для установки методы .setRTS() и .setDTR(), а для чтения атрибуты rts и dtr.
  
>>> import serial
>>> 
>>> obj = serial.Serial()
>>> obj.rts
True
>>> obj.dtr
True
>>> obj.setRTS(False)
>>> obj.rts
False
>>> obj.setDTR(False)
>>> obj.dtr
False
>>> obj.setRTS(True)
>>> obj.rts
True
>>> obj.setDTR(True)
>>> obj.dtr
True
>>>

А чтобы байты передавать, используй байтовые литералы.
  
>>> b'\x81\x20\x00\x06\x56\x70'
b'\x81 \x00\x06Vp'
>>> bytes([0x81, 0x20, 0x00, 0x06, 0x56, 0x70])
b'\x81 \x00\x06Vp'
>>>
Javis
Спасибо за наводку. Поправил в программе, но эффект остался. Хотя иногда срабатывает как надо, по крайней мере в comport1.1 Гифка
py.user.next
Полный код скинь сюда.
Javis
Сегодня уже уехал с работы, теперь в среду только попаду туда, отпишусь.
PS: Байтовые литералы не использую, поскольку документация и логи в таком виде у меня, проще ориентироваться. На сколько понимаю, это не критично?!
py.user.next
Javis
На сколько понимаю, это не критично?!
Придёт время, когда надо будет формировать запросы динамически. А в питоне со списками больше возможностей для работы, чем со строками.
  
>>> b = bytearray([1, 2, 3, 4, 5])
>>> b
bytearray(b'\x01\x02\x03\x04\x05')
>>> b[0] = 4
>>> b
bytearray(b'\x04\x02\x03\x04\x05')
>>> b.pop()
5
>>> b
bytearray(b'\x04\x02\x03\x04')
>>> b.append(5)
>>> b
bytearray(b'\x04\x02\x03\x04\x05')
>>>

Javis
Байтовые литералы не использую, поскольку документация и логи в таком виде у меня
Подгонять код под что-нибудь вроде документации или логов - последнее дело. Код должен быть сделан максимально качественно. Логи должны быть сделаны максимально качественно. Документация должна быть сделана максимально качественно. И вот качественность одного не равна качественности другого, а качественность другого не равна качественности третьего. Поэтому и нужно делать “переходники”. То есть код выглядит одним образом, а когда его нужно вывести в лог, информация передаётся “переходнику”, который преобразует эту информацию максимально красиво для лога. Таким образом, у тебя и код хороший, и лог хороший.
Javis
Спасибо за замечание. Действительно со списками работать гораздо удобнее, уже столкнулся с этой проблемой. Код программы ниже.

[code python]import tkinter.ttk as ttk
from tkinter import *
from tkinter.ttk import Combobox, Button, Radiobutton, Label, Entry, Spinbox
import serial
import time
import winreg

version_reply = '81161720434'
speeds = ['1200','2400', '4800', '9600', '19200', '38400', '57600', '115200']

#Функция записи данных в реестр (последняя отправленная посылка, выбранный COM-порт)
def winreestr_push(comport):
software_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software')
winreg.CreateKey(software_key, 'dev3000')
rsmon_key = winreg.OpenKey(software_key, 'dev3000', 0, winreg.KEY_ALL_ACCESS)
winreg.SetValueEx(rsmon_key, "last_com" , None, winreg.REG_SZ, comport)
winreg.CloseKey(rsmon_key)

#Функция получения данных из реестра (последняя отправленная посылка, выбранный COM-порт)
def winreestr_pull():
try:
rsmon_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\dev3000')
comport = winreg.QueryValueEx(rsmon_key, "last_com")
except Exception:
comport = ("COM1", 1) # при получении из реестра значения ключа получаем похожий кортеж
return (comport)

#Функция получения данных из реестра (если стоит программа)
def winreestr_pull_company():
try:
rsmon_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, 'Software\\company\\3Ph_pm\\device_v4.23')
comport = winreg.QueryValueEx(rsmon_key, "device_COM")
except Exception:
comport = ("COM1", 1) # при получении из реестра значения ключа получаем похожий кортеж
combo.set(comport[0])

#Функция находит все свободные COM-порты в системе и добавляет их в список result
def serial_ports():
ports = ['COM%s' % (i + 1) for i in range(256)]
result = []
for port in ports:
try:
s = serial.Serial(port)
s.close()
result.append(port)
except (OSError, serial.SerialException):
pass
return result

#Функция проверяет состояние выбранного COM-порта
def com_port_state(ser):
ser = serial.Serial(combo.get(), 9600)
if ser.cd == True: # Если на линии обнаружен CD - рисуем зеленый квадрат
com_port_state = Canvas(window, width=10, height=10, bg = 'green')
com_port_state.place(x=310, y=40)
else:
com_port_state = Canvas(window, width=10, height=10, bg = 'red')
com_port_state.place(x=310, y=40)

#Функция отправляет посылку устройству
def serial_tx(parcel):
try:
ser = serial.Serial(combo.get(), 9600, timeout = 1)
if parcel == 2:
ser.setDTR(TRUE)
ser.setRTS(FALSE)
time.sleep(0.5) #задержка установлена временно для удобства отладки
parcel_send = '812000700656'
parcel_full = bytes.fromhex(parcel_send)
ser.dtr(True).write(parcel_full)
serial_rx(parcel)

elif parcel == 1:
ser.setRTS(TRUE)
ser.setDTR(FALSE)
time.sleep(0.5)
parcel_send = '8116330520'
parcel_full = bytes.fromhex(parcel_send)
ser.write(parcel_full)
serial_rx(parcel)

elif parcel == 3:
parcel_send = '8110200749FF'
parcel_full = bytes.fromhex(parcel_send)
ser.write(parcel_full)
serial_rx(parcel)

except Exception:
lbl_error_com = Label(lbl_rx_data_dc, text = "Не удалось открыть COM-порт\nПовторите попытку снова\n \n ", foreground = 'red')
lbl_error_com.place(x=5, y=5)

#Функция чтения данных из COM-порта (еще в доработке)
def serial_rx(parcel_tx):
try:
display_data_rx = ser.read(25) #читаем 20 байт данных с порта
parcel_hex = display_data_rx.hex() #Переводим полученные данные в HEX-формат (убираем /x)
print(parcel_hex)
parcel_rx_up = parcel_hex.upper() #Переводим все буквы в верхний регистр (для удобства)
lbl_parcel_rx = Label(lbl_rx_data_dc, text = " ")
lbl_parcel_rx.place(x=200, y=320)
lbl_parcel_rx = Label(lbl_rx_data_dc, text = parcel_rx_up) #Выводим в пользовательский интерфейс
lbl_parcel_rx.place(x=200, y=320)
ser.close()
rx_dc1 = rx_dc(parcel_rx_up, parcel_tx)
rx_dc1.crc_plata(parcel_rx_up)
except Exception:
lbl_error_com = Label(lbl_rx_data_dc, text = "Нет принятых данных\nПроверьте соединение\nи настройка COM-порта\n ", foreground = 'red')
lbl_error_com.place(x=5, y=5)

#Main program

window = Tk()
window.title("Test-Operator")
window.geometry('350x300')

btn_opros = Button(window, text="Из реестра", command = winreestr_pull_company)
btn_opros.place(x=210, y=35)

reestr = winreestr_pull() # присваиваем переменной кортеж значений (0-последняя посылка из реестра (кортеж), 1-последний выбраный COM-порт (кортеж))
lbl0 = Label(window, text = "Выберите COM-порт:").place(x=15, y=15)
combo = Combobox(window, values = serial_ports())
combo.place(x=15, y=35)
combo.bind('<<ComboboxSelected>>', com_port_state) #вызываем функцию отображения состояния порта
combo.set(reestr[0]) # устанавливае при запуске программы значение COM-порта из реестра

btn_send = Button(window, text="Посылка 1", width = 20, command = lambda: serial_tx(1))
btn_send.place(x=15, y=70)
btn_send = Button(window, text="Посылка 2 ", width = 20, command = lambda: serial_tx(2))
btn_send.place(x=15, y=110)
btn_stop = Button(window, text="Остановить", command = lambda: serial_tx(3))
btn_stop.place(x=15, y=150)

lbl_rx_data_dc = LabelFrame(window, text = "Принятые данные")
lbl_rx_data_dc.place(x=15, y=190, width = 300, heigh = 100)

window.mainloop()


[/code]
Javis
В распоряжении имеется сторонняя программа. Гифка с примером работы здесь

С ней устройства работают корректно. Но она не очень удобна для пользователей. Поэтому возникла идея написать что-то попроще с двумя кнопками.

PS: Для тестов на рабочем месте был собран шнурок (нуль модемный - картинка с интернета) COM-COM который связывает два COM-порта на одном компьютере COM1 и COM4.
py.user.next
Javis
  
        if parcel == 2:
            ser.setDTR(TRUE)
            ser.setRTS(FALSE) 
            time.sleep(0.5) #задержка установлена временно для удобства отладки
            parcel_send = '812000700656'
            parcel_full = bytes.fromhex(parcel_send)
            ser.dtr(True).write(parcel_full)
            serial_rx(parcel)
Передавай не TRUE и FALSE, а True и False. TRUE и FALSE взяты из модуля tkinter, который никак не относится к pyserial.
Пиши всё правильно, иначе из-за какой-то тупой опечатки навроде этой будешь потом искать три часа проблему в устройстве.

Javis
  
ser.dtr(True).write(parcel_full)
Устанавливай DTR через метод .setDTR(). Этот метод для того и сделан, чтобы через него устанавливали значение.
Метод .write() вызывай отдельно - то есть раздели строки из одной в две. Когда строки разделены, в случае ошибки всегда видно, до какой он строки дошёл, а на какой выпал. Так можно быстро различить, где произошла ошибка - в методе .dtr() или в методе .write().

Javis
  
    except Exception:
        lbl_error_com = Label(lbl_rx_data_dc, text = "Не удалось открыть COM-порт\nПовторите попытку снова\n                \n                ", foreground = 'red')
        lbl_error_com.place(x=5, y=5)
Не создавай метки (Label) динамически. Особенно это в tkinter нельзя делать, так как он работает через одно место. Это может приводить к таким эффектам, что у тебя устройство пришлёт что-нибудь, а tkinter покажет вообще что-то другое и ты будешь думать, что дело в устройстве, а дело будет в какой-нибудь динамически сгенерированной хрени в графическом интерфейсе.

В общем, сделай всё в консоли сначала. Убедись в консоли, что ты можешь управлять устройством и оно правильно работает. Когда ты убедишься, что устройство правильно работает, тогда ты можешь писать графический интерфейс для этого. Иначе ты будешь из-за ошибки в графическом интерфейсе думать, что причина ошибки находится в устройстве, или, наоборот, из-за ошибки в устройстве будешь думать, что причина ошибки находится в графическом интерфейсе. И на все эти поиски и разгадывания уйдёт куча времени.
Javis
py.user.next
Передавай не TRUE и FALSE, а True и False.
Это реально тупая опечатка была.

py.user.next
Устанавливай DTR через метод .setDTR().
Это я уже от безысходности наваял, когда пробовал и так и этак.

py.user.next
Не создавай метки (Label) динамически
Учту на будущее. Правда, наиболее красиво для пользователя, на мой взгляд, через label получается, хоть и приходится костыли городить часто.

py.user.next
В общем, сделай всё в консоли сначала.
А вот тут началось самое интересное. Стоит отметить, что заранее я установил и использовал программу COM Port Toolkit для отслеживания обмена пакетами между устройствами. Эта прога позволяет слушать COM-порт, не занимая при этом его.
В общем, поставил я интерпритатор на компьютер пользователя и начал экспериментировать в консоли пошагово. Импортировал serial, инициализировал COM1, установил уровни DTR и RTS (командами ser.setDTR(True) и ser.setRTS(False)), увидел на устройстве (которое маршрутизирует сигналы), что оно перешло на нужный мне канал. С помощью команды ser.write() отправил нужную мне посылку. Увидел, что посылка ушла и устройство даже ответило, судя по индикации на самом устройстве. Но почему-то в логах COM Port Toolkit отправленная посылка была, а принятой не было. Долго думал. Потом вспомнил, что у COM-порта есть так называемый приемный буфер и после очередной отправленной посылки попробовал ввести в консоли ser.read(), а потом еще и еще раз повторил ser.read(). В результате побайтово мне в лог COM Port Toolkit вывалилась вся моя принятая посылка, которую я никак не мог получить.
До этого я считал, что достаточно просто отправить посылку, а программа COM Port Toollkit сама покажет мне все, что сыпется на контакт RX COM-порта (принятые данные). Исходя из своего заблуждения особо не заморачивался с функцией приема пакетов, оставшейся у меня еще с предыдущей программы немного недопиленной. Это и была, по всей видимости, моя основная ошибка. Я не передавал функции обрабатывающей прием пакетов переменную ser - в которой инициализировал COM-порт. В результате она у меня не работала и прием пакетов не происходил должным образом. После того как настроил нормально эту функцию, все пошло.
При этом методы .setDTR .setRTS по всей видимости работали корректно, просто программа SoftElectro Comport 1.1 не успевала отобразить реальное состояние на пинах COM-порта, поскольку все выполнялось достаточно быстро.

Спасибо py.user.next за грамотные и полезные советы. Вопрос можно считать закрытым.

This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB