Использование Python для обработки в реальном масштабе времени информации от датчиков, работающих с Arduino

    Постановка задачи


    Цифровые и аналоговые датчики, подключенные к Arduino, генерируют большие объёмы информации, которая требует обработки в реальном масштабе времени [1].

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

    Данная публикация посвящена программному решению задачи хранения информации от датчиков, работающих с Arduino и её графическому представлению в реальном масштабе времени. В примерах используются широко известными датчиками, такими как потенциометр и датчик движения PIR.

    Использование CSV-файлов для хранения данных полученных от датчиков, работающих с Arduino


    • Для записи данных в CSVфайл можно использовать простой листинг:

      import csv
      data = [[1, 2, 3], ['a', 'b', 'c'], ['Python', 'Arduino', 'Programming']]
      with open('example.csv', 'w') as f:
        	w = csv.writer (f)
       	 for row in data:
      		w.writerow(row)
      
    • Для чтения данных из CSV-файл можно использовать следующий листинг:

      import csv
      with open('example.csv', 'r') as file:
          r = csv.reader(file)
          for row in r:
              print(row)
      

    Рассмотрим сохранения данные Arduino на примере двух датчиков – потенциометра с аналоговым выходным сигналом и движения (PIR) с цифровым выходным сигналом.

    Потенциометр подключён к аналоговому выводу 0, а датчик движения PIR к цифровому выводу 11, как показано на следующей схеме:


    Для работы данной схемы, необходимо загрузить в Python модуль pyFirmata и эскиз StandardFirmata в плату Arduino.

    В любом файле Python разместим следующий код, который запускает и останавливает запись данных от обеих датчиков в файл SensorDataStore.csv:

    Листинг № 1
    #!/usr/bin/python
    import csv
    import pyfirmata
    from time import sleep
    port = 'COM3'
    board = pyfirmata.Arduino(port)
    it = pyfirmata.util.Iterator(board)
    it.start()
    pirPin = board.get_pin('d:11:i')
    a0 = board.get_pin('a:0:i')
    print(pirPin)
    with open('SensorDataStore.csv', 'w+') as f:
        w = csv.writer(f)  
        w.writerow(["Number", "Potentiometer", "Motion sensor"])
        i = 0
        pirData = pirPin.read() 
       m=25
       n=1
        while i < m:
            sleep(1)
            if pirData is not None:
                i += 1
                potData = a0.read()
                pirData = pirPin.read() 
                row = [i, potData, pirData]
                print(row)
                w.writerow(row)
        print ("Done. CSV file is ready!")
        board.exit()
    


    В результате работы листинга №1, получим запись данных в файл 'SensorDataStore.csv':


    Рассмотрим код связанный с хранением данных датчиков. Первая строка записи в CSV файл– строка заголовка, которая объясняет содержимое столбцов: w.writerow([«Number», «Potentiometer», «Motion sensor»]).

    Когда появляется динамика изменения данных, которую для приведенной схемы можно искусственно создать поворотом ручки потенциометра или движением руки возле датчика движения критичным становиться число записей в файл данных. Для восстановления формы сигнала частота записи данных в файл должна быть вдвое больше частоты изменения сигнала. Для регулировки частоты записей может использоваться шаг – n, а для регулировки времени измерения число циклов – m. Однако ограничения на n снизу накладывает быстродействие самих датчиков. Эту функцию в приведенной выше программе выполняет следующий фрагмент кода:

    
    m=25
    n=1             
    while i < m:
        sleep(n)
        if pirData is not None:
            i += 1
            row = [i, potData, pirData]
            w.writerow (row)
    

    Приведенная программы может быть изменена в соответствии с проектными требованиями следующим образом:

    • Можно изменить номера выводов Arduino и количество выводов, которые будут использоваться. Это можно сделать, добавив в код Python и эскиз StandardFirmata в Arduino строки дополнительных значений для новых датчиков.
    • CSV-файл: имя файла и его местоположение можно изменить с SensorDataStore.csv на то, которое относится к вашему приложению.
    • Частоту записей m в файл SensorDataStore.csv можно изменить, изменяя шаг, а длительность записи изменяя число циклов при постоянном шаге.

    Графический анализ данных из файла CSV


    Используя файл SensorDataStore.csv создадим программу для поучений массивов данных от потенциометра и датчика движения и построения графиков по данным массивам:

    Листинг №2
    import sys, csv
    import csv
    from matplotlib import pyplot
    i = []
    mValues = []
    pValues = []
    with open('SensorDataStore.csv', 'r') as f:
        reader = csv.reader(f)
        header = next(reader, None)
        for row in reader:
            i.append(int(row[0]))
            pValues.append(float(row[1]))
            if row[2] == 'True':
                mValues.append(1)
            else:
                mValues.append(0)
    pyplot.subplot(2, 1, 1)
    pyplot.plot(i, pValues, '-')
    pyplot.title('Line plot - ' + header[1])
    pyplot.xlim([1, 25])
    pyplot.xlabel('X Axis')
    pyplot.ylabel('Y Axis')
    pyplot.subplot(2, 1, 2)
    pyplot.bar(i, mValues)
    pyplot.title('Bar chart - ' + header[2])
    pyplot.xlim([1, 25])
    pyplot.xlabel('X Axis')
    pyplot.ylabel('Y Axis')
    pyplot.tight_layout()
    pyplot.show()
    


    В результате работы листинга №2, получим два графика на одной форме для отображения в реальном масштабе времени выходных данных потенциометра и датчика движения.


    В этой программе мы создали два массива значений датчиков — pValues и mValues — путем чтения файла SensorDataStore.csv по строкам. Здесь pValues и mValues представляют данные датчика для потенциометра и датчика движения соответственно. С использованием методов matplotlib построим два графика на одной форме.

    Созданный код после каждого цикла формирования массивов данных pValues и mValues полученным от датчиков Arduino обновляет интерфейс, что позволяет сделать вывод о получении данных в реальном масштабе времени.

    Однако метод хранения и визуализации данных имеет следующие особенности – весь набор данных сначала записываются в файл SensorDataStore.csv (Листинг №1), а затем считываются из этого файла (Листинг №2). Графики должны перерисовываться каждый раз, когда поступают новые значения от Arduino. Поэтому нужно разработать такую программу, в которой планирование и обновление графиков происходит в реальном времени, а не строится весь набор значений датчиков, как в листингах №1,2.[2].

    Пишем программу для потенциометра создавая динамику выходного сигнала путём изменения его активного сопротивления постоянному току.

    Листинг №3
    import sys, csv
    from matplotlib import pyplot
    import pyfirmata
    from time import sleep
    import numpy as np
    # Associate port and board with pyFirmata
    port = '/dev/cu.usbmodemfa1321'
    board = pyfirmata.Arduino(port)
    # Using iterator thread to avoid buffer overflow
    it = pyfirmata.util.Iterator(board)
    it.start()
    # Assign a role and variable to analog pin 0
    a0 = board.get_pin(''a:0:i'')
    # Initialize interactive mode
    pyplot.ion()
    pData = [0] * 25
    fig = pyplot.figure()
    pyplot.title(''Real-time Potentiometer reading'')
    ax1 = pyplot.axes()
    l1, = pyplot.plot(pData)
    pyplot.ylim([0,1])
    # real-time plotting loop
    while True:
        try:
            sleep(1)
            pData.append(float(a0.read()))
            pyplot.ylim([0, 1])
            del pData[0]
            l1.set_xdata([i for i in xrange(25)])
            l1.set_ydata(pData)  # update the data
            pyplot.draw()  # update the plot
        except KeyboardInterrupt:
            board.exit()
            break
    


    В результате работы листинга №3, получим график.


    Планирование в реальном времени в этом упражнении достигается с помощью комбинации функций pyplot ion (), draw (), set_xdata () и set_data (). Метод ion () инициализирует интерактивный режим pyplot. Интерактивный режим помогает динамически изменять значения x и y графиков на рисунке pyplot.ion ().

    Когда интерактивный режим установлен в True, график будет вырисовываться только при вызове метода draw (). Инициализируем плату Arduino с помощью модуля pyFirmata и входных пинов для получения значений датчиков.

    Как вы можете видеть в следующей строке кода, после настройки платы Arduino и интерактивного режима pyplot, мы инициализировали график с набором пустых данных, в нашем случае 0: pData = [0] * 25.

    Этот массив для значений y, pData, затем используется для добавления значений из датчика в цикле while. Цикл while продолжает добавлять новые значения в этот массив данных и перерисовывает график с этими обновленными массивами для значений x и y.

    В листинге №3 мы добавляем новые значения датчиков в конце массива, одновременно удаляя первый элемент массива, чтобы ограничить размер массива:

    
    pData.append(float(a0.read()))
    del pData[0]
    

    Методы set_xdata () и set_ydata () используются для обновления данных осей x и y из этих массивов. Эти обновленные значения наносятся с использованием метода draw () на каждой итерации цикла while:

    l1.set_xdata([i for i in xrange(25)])
    l1.set_ydata(pData)  # update the data
    pyplot.draw()  # update the plot
    

    Вы также заметите, что мы используем функцию xrange () для генерации диапазона значений в соответствии с предоставленной длиной, которая равна 25 в нашем случае. Фрагмент кода [i for i in xrange(25)] будет генерировать список из 25 целых чисел, которые начинаются постепенно с 0 и заканчиваются на 24.

    Интеграция графиков в окне Tkinter


    Благодаря мощным возможностям интеграции Python, очень удобно связать графики, созданные библиотекой matplotlib, с графическим интерфейсом Tkinter. Напишем программу для соединения между Tkinter и matplotlib.

    Листинг №4
    import sys
    from matplotlib import pyplot
    import pyfirmata
    from time import sleep
    import Tkinter
    
    def onStartButtonPress():
        while True:
            if flag.get():
                sleep(1)
                pData.append(float(a0.read()))
                pyplot.ylim([0, 1])
                del pData[0]
                l1.set_xdata([i for i in xrange(25)])
                l1.set_ydata(pData)  # update the data
                pyplot.draw()  # update the plot
                top.update()
            else:
                flag.set(True)
                break
    
    def onPauseButtonPress():
        flag.set(False)
    
    def onExitButtonPress():
        print "Exiting...."
        onPauseButtonPress()
        board.exit()
        pyplot.close(fig)
        top.quit()
        top.destroy()
        print "Done."
        sys.exit()
    
    # Associate port and board with pyFirmata
    port = 'COM4'
    board = pyfirmata.Arduino(port)
    
    # Using iterator thread to avoid buffer overflow
    it = pyfirmata.util.Iterator(board)
    it.start()
    
    # Assign a role and variable to analog pin 0 
    a0 = board.get_pin('a:0:i')
    
    # Tkinter canvas
    top = Tkinter.Tk()
    top.title("Tkinter + matplotlib")
    
    # Create flag to work with indefinite while loop
    flag = Tkinter.BooleanVar(top)
    flag.set(True)
    
    pyplot.ion()
    
    pData = [0.0] * 25
    fig = pyplot.figure()
    pyplot.title('Potentiometer')
    ax1 = pyplot.axes()
    l1, = pyplot.plot(pData)
    pyplot.ylim([0, 1])
    
    # Create Start button and associate with onStartButtonPress method
    startButton = Tkinter.Button(top,
                                 text="Start",
                                 command=onStartButtonPress)
    startButton.grid(column=1, row=2)
    
    # Create Stop button and associate with onStopButtonPress method
    pauseButton = Tkinter.Button(top,
                                 text="Pause",
                                 command=onPauseButtonPress)
    pauseButton.grid(column=2, row=2)
    
    # Create Exit button and destroy the window
    exitButton = Tkinter.Button(top,
                                text="Exit",
                                command=onExitButtonPress)
    exitButton.grid(column=3, row=2)
    top.mainloop()
    


    В результате работы листинга №4, получим встроенный в окно Tkinter. график с элементами кнопочного интерфейса – startButton, pauseButton и exitButton.


    Кнопки «Start» и «Exit» предоставляют контрольные точки для операций matplotlib, та-кие как обновление графика и закрытие графика с помощью соответствующих функций onStartButtonPress () и onExitButtonPress (). Функция onStartButtonPress () также состоит из точки взаимодействия между библиотеками matplotlib и pyFirmata. Как вы можете видеть из следующего фрагмента кода, мы начнем обновлять график, используя метод draw () и окно Tkinter, используя метод update () для каждого наблюдения с аналогового вывода a0, который получается с помощью метода read ().

    Функция onExitButtonPress () реализует функцию exit, как описано в самом имени. Она закрывает фигуру pyplot и окно Tkinter перед отключением платы Arduino от последовательного порта.

    После внесения соответствующих изменений в параметр порта Arduino запустим листинг №4. Вы должны увидеть окно на экране, подобное тому, которое показано на предыдущем скриншоте. С помощью этого кода вы можете теперь управлять графиками в реальном времени, используя кнопки «Start» и «Pause». Нажмите кнопку «Start» и начните вращать ручку потенциометра. Когда вы нажимаете кнопку «Pause», вы можете заметить, что программа остановила построение новых значений. При нажатии кнопки «Пауза» даже вращение ручки не приведет к каким-либо изменениям графика.

    Как только вы снова нажмете на кнопку «Start», вы снова увидите, что график обновляется в реальном времени, отбрасывая значения, сгенерированные во время паузы. Нажмите кнопку «Exit», чтобы безопасно закрыть программу.

    При подготовке материалов данной публикации принимал участие Мисов О. П.

    Выводы


    В этой публикации представлены две основные парадигмы программирования Python: создание, чтение и запись файлов с использованием Python, а также хранение данных в этих файлах и построение значений датчиков и обновление графиков в реальном времени. Мы также изучили методы хранения и отображения данных датчика Arduino в реальном времени. Помимо помощи Вам в проектах с Arduino, эти методы также можно использовать в повседневных проектах Python.

    Ссылки


    1. Python Programming for Arduino.
    2. Язык программирования Си (Керниган и Ритчи).
    • +9
    • 12.8k
    • 2
    Share post

    Similar posts

    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 2

      –1
        0

        У вас с Листинге 3 лишняя кавычка


        # Associate port and board with pyFirmata
        port = '/dev/cu.usbmodemfa1321''
        board = pyfirmata.Arduino(port)

        Only users with full accounts can post comments. Log in, please.