Table of Contents

4. Zpracování dat

Před cvičení si prosím nainstalujte Python 3.x s následujícími moduly: pyserial, matplotlib, pyqt6, pysqlite3, bleak, paho-mqtt, numpy

Instalace modulů:

$ pip install pyserial matplotlib pyqt6 pysqlite3 bleak paho-mqtt numpy

V případě, že nemáte administrátorská práva na počítači:

$ pip install --user pyserial matplotlib pyqt6 pysqlite3 bleak paho-mqtt numpy

Cíle cvičení

  1. Vytvořit program pro Thunderboard, který bude v pravidelných intervalech odesílat data po UART
  2. Data v reálném čase vykreslovat pomocí aplikace v Pythonu
  3. Navrhout protokol, který umožní přenos dat z různých senzorů
  4. Data ukládat do databáze na PC k pozdějšímu použití

Ukázkové aplikace v Pythonu

Řada aplikací bude napsána v Pythonu, flexibilním programovacím jazyku vhodném pro rychlé prototypování. Vždy bude připravena kostra programu, kterou lze přímo použít pro vlastní projekt. Pokud byste se chtěli dovědět něco víc o Pythonu, je možné využít např. podklady předmětu BAB37ZPR: 2021/22, 2020/21. Lze také doporučit volně dostupnou knihu Python Programming And Numerical Methods: A Guide For Engineers And Scientists.

Jako zdroj dat lze využít např. generátor harmonického signálu na gitlabu. Aplikace pro PC v jazyce Python jsou také k dispozici na gitlabu.

Komunikace po rozhraní UART

Pro UART komunikaci mezi embedded zařízením a PC použijeme knihovnu PySerial. Připojení proběhne vytvoření instance třídy, parametry konstruktoru jsou název sériového portu a rychlost komunikace. Je celkem vhodné ošetřit připojení pro případ, že spojení nelze uskutečnit. V následujícím kódo je zařízení připojeno k portu COMXXX (za XXX dosaďte podle potřeby) a komunikuje rychlostí 115200 baudů.

import serial
 
try:
    ser = serial.Serial('COMXXX', 115200)
    ser.flushInput()
except:
    print("unable to open COM port")

Se zařízením je možné komunikovat na úrovni bytů nebo znaků. Pokud zařízení posílá data ve formě znaků (tj. posílá byty s významem ASCII kódů), lze data přijmout třeba takto:

data = ser.readline().decode()
print(data)

Pokud je z hlediska aplikace výhodnější používat byty ve s významem čísla (např. v rozsahu 0-255), je možné přijmout a dekódovat takto:

byte = ser.read()
decoded_bytes = int.from_bytes(byte, byteorder = 'little')

Argumentem metody read() je počet bytů, které jsou najednou přečteny. Lze tedy např. předpokládat, že embedded zařízení posílá dva byty současně a tyto dva byty buď mohou reprezentovat číslo typu short, nebo dva samostatné byty a a b, které je třeba dekódovat:

byte = ser.read(2)
decoded_bytes = int.from_bytes(byte, byteorder = 'little')
a = decoded_bytes & 255;
b = (decoded_bytes >> 8) & 255;

Přenos struktrovaných dat

Pokud bychom potřebovali přenést po UART hodnoty různých datových typů, můžeme je na straně mikrokontroléru uložit do struktury.

BufferedSerial pc(USBTX, USBRX, 115200);
//--
typedef struct data {
    unsigned int Si7021_t;
    unsigned int Si7021_h;
    int Si7210_t;
    float BMP280_t; 
};
// ---
data a;
// --
pc.write(&a, sizeof(a));

V Pythonu pak lze pro dekódování dat ve struktuře využít knihovny ctypes nebo struct.

from ctypes import Structure, c_uint, c_int, c_float, sizeof
 
class DataPoint(Structure):
    _pack_ = 1
    _fields_ = [
        ("Si7021_t", c_uint),
        ("Si7021_h", c_uint),
        ("Si7210_t", c_int),
        ("BMP280_t", c_float)
    ]
#
byte = ser.read(16)
buffer = bytearray(byte)
Point = DataPoint.from_buffer(buffer)
# teplota z Si7021 je pak dostupná jako Point.Si7021_t/1000,

Vykreslování dat

K vykreslení dat přicházejících po UART rozhraní použijeme knihovnu matplotlib. Data pro vykreslení jsou uložena ve vektoru vytvořeném pomocí knihovny NumPy. Vektor má délku odpovídající délce okna, a v cyklu jsou na začátek vektoru přidávána data metodou append. V cyklu jsou současně vykreslována data ze začátku vektoru.

import matplotlib.pyplot as plt
import numpy as np
 
plot_window = 20
y_var = np.array(np.zeros([plot_window]))
 
plt.ion()
fig, ax = plt.subplots()
line, = ax.plot(y_var)
ax.set_ylim([0, 255])
 
while True:
    # zde by mel byt kod pro ziskani a dekodovani dat
    y_var = np.append(y_var, data)
    y_var = y_var[1:plot_window+1]
    line.set_ydata(y_var)
    fig.canvas.draw()
    fig.canvas.flush_events()

Pokud bychom chtěli vykreslovat dvě křivky, je třeba v původním kódu udělat následující úpravy:

# konfigurace
fig, ax = plt.subplots(2)
line0, = ax[0].plot(y0_var)
line1, = ax[1].plot(y1_var)
ax[0].set_ylim([0, 255])
ax[1].set_ylim([0, 255])
 
# samotne vykreslovani
y0_var = np.append(y0_var, a)
y1_var = np.append(y1_var, b)
y0_var = y0_var[1:plot_window+1]
y1_var = y1_var[1:plot_window+1]
line0.set_ydata(y0_var)
line1.set_ydata(y1_var)

Ukládání dat do SQL databáze

Pro uložení dat můžeme využít SQLite databázi. Její výhodou je, že pracuje s lokálním binárním souborem a má minimální nároky na paměť a výkon počítače - veškerá potřebná funkcionalita je totiž vytvářena knihovnou, která pracuje s binárním souborem. Pro jednoduchost můžete využít modul nsidb, který vytváří třídu pro ukládání dvou hodnot typu float reprezentující teplotu a vlhkost přečtenoou ze senzoru Si7021. Hodnoty jsou funkci pro zápis předávány jako tuple.

Samotný hlavní program pro uložení dat může vypadat třeba takto:

from nsidb import *
 
conn = db_conn("iot.db")
db_create(conn)
db_insert (conn, (3.14, 25.5))
 
rows = db_fetch (conn)
for row in rows:
    print(row)