Python, последовательный порт, нуль-модемный кабель и Qt

Продолжим разговор про создание многопоточных программ, начатый в предыдущей теме «Python, последовательный порт и нуль-модемный кабель» http://wp.me/p1H7g0-1nb.

В этой части мы создадим графическую программу для обмена текстовыми сообщениями между двумя компьютерами, которые соединены через нуль-модемный кабель, подключенный к последовательному порту.

На самом деле я использовал для отладки программ не последовательный порт, а USB-интерфейс, в который был «завёрнут» последоватеьный порт. Компы соединялись через два конвертера USB-UART, подключенные к USB-портам.

Кроме того, я должен немного объяснить почему я выбрал графическую библиотеку Qt, а не TkInter или wx.

Во первых, я не очень хорошо пишу программы на Python. Я не умею мыслить по-Питоновски. Я упорно мыслю аллегориями Си и Си++. Тут уж ничего не поделаешь! Но это не проблема, хотя и сильно усложняет мою работу. На сколько я понял, проблема у Питона в том, что его интерпретатор внутри организован как один поток. Конечно, Питон позволяет создать несколько потоков. Но питоновские потоки — это не тоже самое, что настоящие потоки ядра операционной системы. Все питоновсике потоки выполняются в одном исполняемом потоке системы. Другими словами — питоновская программа всегда будет выполняться на одном ядре, не смотря на то, что у процессора может быть несколько ядер.

Во вторых, я пробовал писать графические программы на Питоне на всех трех названных выше графических библиотеках. TkInter хороша для новичков и для небольших, я бы сказал — для не очень серьёзных программ. К тому же, эта библиотека доволь-но таки слабенькая против других двух. В общем, отпадает.

Библиотека wx — хороша! Это серьезный инструмент с большим набором самых различных виджетов. Виджеты очень крачивые, если не сказать изысканные. Писать на wx — одно удовольствие! Но, к сожалению, у wx точно так же как и у TkInter довольно-таки тяжело обстоят дела с потоками исполнения. Если в описании TkInter вообще ничего не сказано про потоки, то в описании wx потокам уделено две странички, из которых я понял, что wx не очень приветствует создание много-поточных приложений. (Я говорю о wx для Питона!)

И только библиотека Qt имеет полновесные средства для создания и управления потоками.

Я не планировал устраивать ликбез по освоению Qt. Я планировал рассказать о создании графической многопоточной программы на питоне с использованием Qt.

Собственно, эта программа выполняет те же действия, что и программа из предыдущей статьи. Функционально программы совпадают, но по коду — они совершенно разные.

Графическая программа состоит из трех файлов: главного модуля (gtet-a-tet.py), с которого запускаятся программа, и двух модулей классов (tetatet.py и treadin.py).

#!/usr/bin/env python
#coding:utf-8

''' gtet-a-tet.py '''

import sys
from PyQt4 import QtGui
import tetatet


if __name__ == "__main__":
  app = QtGui.QApplication(sys.argv)
  wnd = tetatet.Tetatet()
  wnd.show()
  sys.exit(app.exec_())
#!/usr/bin/env python
#coding:utf-8

''' tetatet.py '''

from PyQt4 import QtCore, QtGui
import threadin

class Tetatet(QtGui.QWidget):
  '''
  '''
  def __init__(self, parent=None):
    QtGui.QWidget.__init__(self, parent)
    
    self.lbl1 = QtGui.QLabel(u"История сообщений")
    self.lbl1.setAlignment(QtCore.Qt.AlignCenter)
    
    self.txtHist = QtGui.QTextEdit()
    self.txtHist.setReadOnly(True)
    
    self.vbox = QtGui.QVBoxLayout()
    self.vbox.addWidget(self.lbl1)
    self.vbox.addWidget(self.txtHist)
    
    self.txtOut = QtGui.QLineEdit()
    self.btnSend = QtGui.QPushButton(u"Отправить")

    self.hbox = QtGui.QHBoxLayout()
    self.hbox.addWidget(self.txtOut)
    self.hbox.addWidget(self.btnSend)

    self.vbox.addLayout(self.hbox)
    
    self.setLayout(self.vbox)
    
    self.threadIn = threadin.ThreadIn()
    self.threadIn.start()
    
    self.connect(self.btnSend, QtCore.SIGNAL("clicked()"), self.onSend)
    self.connect(self.threadIn, QtCore.SIGNAL("msgIn(QString)"), self.onRcvd, QtCore.Qt.QueuedConnection)
    
    self.setWindowTitle(u"Связь компов через нуль-модемное соединение")
    self.resize(500, 250)


  '''
  '''
  def onSend(self):
    msg = self.txtOut.text()
    self.threadIn.send(str(msg.toUtf8()))
    self.txtHist.append(msg)
    self.txtOut.clear()


  '''
  '''
  def onRcvd(self, msg):
    self.txtHist.append(unicode(msg, encoding="UTF-8"))
#!/usr/bin/env python
#coding:utf-8

''' threadin.py '''

from PyQt4 import QtCore
import serial

PORT = "/dev/ttyUSB0"
BAUD = 115200

class ThreadIn(QtCore.QThread):
  def __init__(self, parent=None):
    QtCore.QThread.__init__(self, parent)

    try:
      self.ser = serial.Serial(PORT)
    except: # SerialException:
      print "Проблема: не могу открыть порт", PORT
      exit(0)
      
    self.ser.baudrate = BAUD
    self.ser.bytesize = serial.EIGHTBITS
    self.ser.parity   = serial.PARITY_NONE
    self.ser.stopbits = serial.STOPBITS_ONE
    self.ser.timeout  = None

   

  '''
  Функция потока для приёма входящих сообщений
  '''
  def run(self):
    msg = ""                      # Заготовка для новой строки

    while True:
      byte = str(self.ser.read())    # Читаем один байт
      if len(byte) == 1:
        msg += byte               # Составляем строку
        
        # Вывод ответа произведём только тогда, когда строка будет сформирована полностью
        if len(msg) >= 1:         
          if msg[-1:] == "\n":    # Окончание строки
            #print msg[:-1]       # Выводим строку на терминал. Символ '\n' будет добавлен оператором print
            self.emit(QtCore.SIGNAL("msgIn(QString)"), msg[:-1])
            msg = ""              # Заготовка для новой строки          


  '''
  '''
  def send(self, msg):
    self.ser.write(msg + '\n')

Коды программы выложены в репозитории на Гитхабе https://github.com/zhevak/tet-a-tet.git.

Команда для клонирования репозитория к себе на комп:

$ git clone https://github.com/zhevak/gtet-a-tet.git

Программа проверена на связке двух компов. На одном из них был установлен Debian-8.3, на другом — Ubuntu-10.04.4 LTS. Вот скриншоты работы программы:

gtetatet-1

gtetatet-2

На первый взгдляд может показаться, что программа написана весьма посредственно. Программа не очень дружественна к пользователю и имеет определённые «умственные» отклонения.

Я не преследовал цели сделать программу качественной. У меня была цель написать так, чтобы в программе было как можно меньше несущественного кода.

Ну, например, отравка сообщения осуществляется нажатием мышки на кнопку «Отправить», хотя желание отправлять сообщение через нажатие на Enter выглядит очень логично. Но чтобы добавить эту нужную функциональность придется дописать соответствующий код. А это затуманит идею, ради которой всё это делаллось.

Хотелось бы так же как-то выделять цветом (или ещё как-то) входящие и исходящие сообщения в истории переписки. Ну, можно также предусмотреть кнопку для очистки истории. То есть прогу можно дорабатывать и дорабатывать до кондиции.

Обратите внимание на кошмар при преобразовании типов текстовых строк. Честно говоря, меня это тже высаживает.

Был когда-то стандартный код КОИ-8Р, был CP866. Ну придумали на какой-то хрен CP-1251. Ну успокойтесь уже и живите дальше! Нет, придумали UNICODE… Я думал, на этом уже всё. Кого-там! Это только начало всеобщего бардака. В Qt имеется свой формат для текстовых строк — QString. Конвертировать строки из одного формата в другой — это та ещё веселуха! Вы, думаете, проблема только у нас с кириллице? Ха! Почитайте Интернет, там даже сами англоговорящие воют на каждом углу, дескать в Питное дяже преобразование стандартной ASCII из первой половины таблицы не работает! А что уж говорить про нашу кириллицу! Короче, мне худо-бедно удалось заставить работать эту уродину (Python+Qt+UNICODE+кириллица), так что берите методику и пользуйтесь на здоровье!

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s