====== 4. Zpracování dat ====== Před cvičení si prosím nainstalujte Python 3.x s následujícími moduly: [[https://pypi.org/project/pyserial/|pyserial]], [[https://matplotlib.org/|matplotlib]], [[https://pypi.org/project/PyQt6/|pyqt6]], [[https://pypi.org/project/pysqlite3/|pysqlite3]], [[https://bleak.readthedocs.io/en/latest/|bleak]], [[https://pypi.org/project/paho-mqtt/|paho-mqtt]], [[https://numpy.org/|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í ===== - Vytvořit program pro Thunderboard, který bude v pravidelných intervalech odesílat data po UART - Data v reálném čase vykreslovat pomocí aplikace v Pythonu - Navrhout protokol, který umožní přenos dat z různých senzorů - 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: [[https://cw.fel.cvut.cz/b211/courses/bab37zpr/start|2021/22]], [[https://cw.fel.cvut.cz/b201/courses/bab37zpr/solutions/start|2020/21]]. Lze také doporučit volně dostupnou knihu [[https://pythonnumericalmethods.berkeley.edu/notebooks/Index.html|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 [[https://gitlab.fel.cvut.cz/viteks/nsi-codes/-/tree/main/01-generator|gitlabu]]. Aplikace pro PC v jazyce Python jsou také k dispozici na [[https://gitlab.fel.cvut.cz/viteks/nsi-codes/-/tree/main/tut-04-local|gitlabu]]. ==== Komunikace po rozhraní UART ==== Pro UART komunikaci mezi embedded zařízením a PC použijeme knihovnu [[https://pypi.org/project/pyserial/|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 [[https://docs.python.org/3/library/ctypes.html|ctypes]] nebo [[https://docs.python.org/3/library/struct.html|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 [[https://matplotlib.org/stable/plot_types/index.html|matplotlib]]. Data pro vykreslení jsou uložena ve vektoru vytvořeném pomocí knihovny [[https://numpy.org/doc/stable/reference/arrays.html|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 [[https://www.sqlite.org/index.html|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 [[https://gitlab.fel.cvut.cz/viteks/nsi-codes/-/blob/main/tut-04-local/nsidb.py|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)