Микропереключатель с pyserial RS232 запускает / останавливает таймер в потоке tkinter, но продолжает работать даже после остановки - PullRequest
0 голосов
/ 26 июня 2019

Я использовал микропереключатель, подключенный к кабелю последовательного преобразователя RS232 / USB на моем ПК с Windows, чтобы запустить останов и сбросить таймер.

Программа работает без сбоев большую часть времени, но время от времени обновляетсявиджет таймера работает, и таймер не останавливается.

При использовании последовательного протокола я хочу получить 1 байт b '\ x00' для выключения, и все, что не b '\ x00', должно означать.

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

Это может быть проблема сRS232 вызывает ошибку, которую я не вижу, но мои знания об этом отрывочны и исчерпали все возможности, ища в Интернете любую информацию по этому вопросу.

import time
import sys
import serial
import threading
from tkinter import *
from tkinter import ttk

class Process(Frame):
    def __init__(self, root, parent=None, **kw):
        Frame.__init__(self, parent, kw)
        self.root = root
        self._cycStart = 0.0
        self._cycTimeElapsed = 0.0
        self._cycRunning = 0.0
        self.cycTimeStr = StringVar()
        self.cycTime_label_widget()

        self.ser = serial.Serial(
            port='COM4',
            baudrate=1200,
            timeout=0
            )

        self.t1 = threading.Thread(target=self.start_stop, name='t1')
        self.t1.start()

    def initUI(self):
        root.focus_force()
        root.title("")
        root.bind('<Escape>', lambda e: root.destroy())


    def cycTime_label_widget(self):
    # Make the time label
        cycTimeLabel = Label(root, textvariable=self.cycTimeStr, font= 
        ("Ariel 12"))
        self._cycleSetTime(self._cycTimeElapsed)
        cycTimeLabel.place(x=1250, y=200)

        cycTimeLabel_2 = Label(root, text="Cycle Timer:", font=("Ariel 
        12"))
        cycTimeLabel_2.place(x=1150, y=200)

    def _cycleUpdate(self): 
        """ Update the label with elapsed time. """
        self._cycTimeElapsed = time.time() - self._cycStart
        self._cycleSetTime(self._cycTimeElapsed)
        self._cycTimer = self.after(50, self._cycleUpdate)

    def _cycleSetTime(self, elap):
        """ Set the time string to Minutes:Seconds:Hundreths """
        minutes = int(elap/60)
        seconds = int(elap - minutes*60.0)
        hseconds = int((elap - minutes*60.0 - seconds)*100)                
        self.cycTimeStr.set('%02d:%02d:%02d' % (minutes, seconds, 
        hseconds))
        return

    def cycleStart(self):                                                     
        """ Start the stopwatch, ignore if running. """
        if not self._cycRunning:           
            self._cycStart = time.time() - self._cycTimeElapsed
            self._cycleUpdate()
            self._cycRunning = 1
        else:
            self.cycleReset()


     def cycleStop(self):                                    
        """ Stop the stopwatch, ignore if stopped. """
        if self._cycRunning:
            self.after_cancel(self._cycTimer)            
            self._cycTimeElapsed = time.time() - self._cycStart    
            self._cycleSetTime(self._cycTimeElapsed)
            self._cycRunning = 0
            self._cycTimeElapsed = round(self._cycTimeElapsed, 1)
            self.cycleTimeLabel = Label(root, text=(self._cycTimeElapsed, 
            "seconds"), font=("Ariel 35"))
            self.cycleTimeLabel.place(x=900, y=285)
            self.cycleReset()

     def cycleReset(self):                                  
         """ Reset the stopwatch. """
         self._cycStart = time.time()         
         self._cycTimeElapsed = 0   
         self._cycleSetTime(self._cycTimeElapsed)

     def start_stop(self):
         while True :
             try:
                 data_to_read = self.ser.inWaiting()
                 if data_to_read != 0: # read if there is new data
                     data = self.ser.read(size=1).strip()
                     if data == bytes(b'\x00'):
                         self.cycleStop()
                         print("Off")

                     elif data is not bytes(b'\x00'):
                         self.cycleStart()
                         print("On")

             except serial.SerialException as e:
                 print("Error")
if __name__ == '__main__':
    root = Tk()
    application = Process(root)
    root.mainloop()

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

Ответы [ 2 ]

1 голос
/ 02 июля 2019

С лучшим пониманием того, что вы пытаетесь сделать, приходят на ум лучшие решения.

Оказывается, вы не используете свой последовательный порт для отправки или получения последовательных данных.На самом деле вы подключаете переключатель к линии RX и переключаете его вручную с помощью механического переключателя, подавая высокий или низкий уровень в зависимости от положения переключателя.

Итак, вы пытаетесь эмулировать линию цифрового входа с линией RX вашего последовательного порта.Если вы посмотрите , как работает последовательный порт , вы увидите, что при отправке байта линия TX переключается с низкого уровня на высокий со скоростью передачи данных в бодах, но поверх данных вы должны учитыватьстартовые и стоповые биты.Итак, почему ваше решение работает (по крайней мере, иногда): это легко увидеть, если взглянуть на изображение области:

scope capture TX line sending

Это скриншотлиния TX, отправляющая байт \x00, измеренный между контактами 3 (TX) и 5 ​​(GND) без бита четности.Как видите, шаг длится всего 7,5 мс (при скорости 1200 бод).То, что вы делаете со своим переключателем, - нечто подобное, но в идеале бесконечно долго (или до тех пор, пока вы не переключите свой переключатель обратно, что будет происходить через 7,5 мс, независимо от того, насколько быстро вы это сделаете).У меня нет переключателя, чтобы попытаться, но если я открываю терминал на своем порту и использую кабель для короткого замыкания линии RX на вывод 4 (на разъеме SUB-D9), иногда я получаю 0x00 байт, но в основномэто что-то ещеВы можете попробовать этот эксперимент самостоятельно с PuTTy или RealTerm и вашим переключателем, я думаю, вы получите лучшие результаты, но не всегда ожидаемый байт из-за отскакивания контактов.

Другой подход: Я уверен, что могут быть способы улучшить то, что у вас есть, например, снизить скорость передачи данных до 300 или 150 бит / с, проверить разрыв в строке или другие творческие идеи.

Но то, что вы пытаетесь сделать, больше похоже на чтение строки GPIO, и на самом деле последовательный порт имеет несколько цифровых линий, предназначенных (в старые времена) для управления потоком .

Чтобы использовать эти линии, вы должны подключить общий полюс на вашем коммутаторе к линии DSR (контакт 6 на SUB-D9), а полюсы NO и NC к линиям DTR (контакт 4) и RTS (контакт 7).).

Программная сторона будет на самом деле проще, чем чтение байтов: вам просто нужно активировать аппаратное управление потоком:

self.ser = serial.Serial()
self.ser.port='COM4'
self.ser.baudrate=1200  #Baud rate does not matter now
self.ser.timeout=0
self.ser.rtscts=True
self.ser.dsrdtr=True
self.ser.open()

Определите логические уровни для вашего коммутатора:

self.ser.setDTR(False)   # We use DTR for low level state
self.ser.setRTS(True)  # We use RTS for high level state
self.ser.open()         # Open port after setting everything up, to avoid unkwnown states

И используйте ser.getDSR() для проверки логического уровня строки DSR в вашем цикле:

def start_stop(self):
    while True :
        try:
            switch_state = self.ser.getDSR()
            if switch_state == False and self._cycRunning == True:
                self.cycleStop()
                print("Off")

            elif switch_state == True and self._cycRunning == False:
                 self.cycleStart()
                 print("On")

        except serial.SerialException as e:
            print("Error")

Я определил вашу переменную self._cycRunning как логическую (в коде инициализации вы определили ее какплавать, но это было, вероятно, опечатка).

Этот код работает без сбоев даже при использовании зачищенного провода в качестве переключателя.

0 голосов
/ 27 июня 2019

Вы не очень хорошо объясняете, как работает ваш протокол (я имею в виду, что ваш коммутатор должен отправлять, или если он отправляет изменение состояния только один или несколько раз или непрерывно).

Но в вашем коде все равно есть красные флаги:

data = self.ser.read(size=1).strip() вы читаете 1 байт, но сразу же проверяете, получили ли вы 2 байта. Есть ли причина для этого?

- Ваше условие остановки таймера работает по сравнению с символом NULL. Это не должно быть проблемой, но в зависимости от вашей конкретной конфигурации это может произойти (в некоторых конфигурациях символ NULL читается как-то иначе, поэтому разумно убедиться, что вы действительно получаете его правильно).

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

-При замене аппаратного коммутатора программной эмуляцией он работает по назначению, но это неудивительно, поскольку вы, вероятно, навязываете условие. Когда вы читаете с последовательного порта, вам приходится иметь дело с реальными проблемами, такими как шум, ошибки связи или переключение , отскакивающее назад и вперед от ON к OFF. Может быть, для очень простого протокола вам не нужно использовать какой-либо метод проверки ошибок, но кажется разумным по крайней мере проверять наличие ошибок четности. Я не совсем уверен, что было бы просто сделать это с pyserial; на быстрый взгляд я обнаружил эту проблему , которая была открыта некоторое время.

- Опять же, отсутствие информации о вашем протоколе: должны ли вы использовать управление потоком XON-XOFF и два стоповых бита? Я думаю, у вас есть причина для этого, но вы должны хорошо знать, почему и как вы их используете.

РЕДАКТИРОВАТЬ: С комментариями ниже я могу попытаться немного улучшить свой ответ. Это просто идея для вас: вместо того, чтобы сравнивать условие останова точно с 0x00, вы можете посчитать количество битов, равное 1, и остановить счетчик, если он меньше или равен 2. Таким образом, вы можете учесть биты, которые не получены правильно.

Вы можете сделать то же самое с условием запуска, но я не знаю, какое шестнадцатеричное значение вы отправляете.

Кредиты для функции подсчета битов переходят на этот вопрос .

...
def numberOfSetBits(i):
    i = i - ((i >> 1) & 0x55555555)
    i = (i & 0x33333333) + ((i >> 2) & 0x33333333)
    return (((i + (i >> 4) & 0xF0F0F0F) * 0x1010101) & 0xffffffff) >> 24


def start_stop(self):
     while True :
         try:
             data_to_read = self.ser.inWaiting()
             if data_to_read != 0: # read if there is new data
                 data = self.ser.read(size=1).strip()
                 if numberOfSetBits(int.from_bytes(data, "big")) <= 2:
                     self.cycleStop()
                     print("Off")

                 elif numberOfSetBits(int.from_bytes(data, "big")) >= 3:  #change the condition here according to your protocol
                     self.cycleStart()
                     print("On")

         except serial.SerialException as e:
             print("Error")
...