Arduino Custom Data Station #2 - odczyt danych
Posted on 30 November, 2020 at 18:01
Tags: JavaScript Node.js AVR Arduino

Etap #2
Drugi etap projektu zakłada stworzenie aplikacji webowej (Stencil.js + Ionic 4), która będzie odpowiedzialna z odczyt danych z Arduino. Na tym etapie aplikacja będzie jedynie odczytywać dane i prezentować je w formie plain textu. Wykresami zajmiemy się w czwartym rozdziale poświęconemu zagadnieniu prezentacji danych.

Aplikacja webowa
Aplikacja kliencka bazuje na Stencil.js
, czyli na lekkim kompilatorze dla web komponentów.
Główne cechy Stencil.js to:
- Virtual DOM
- Async rendering (inspired by React Fiber)
- Reactive data-binding
- TypeScript
- JSX
- Static Site Generation (SSG)
Oczywiście jeśli masz doświadczenie z innymi frameworkami takimi jak Angular lub React, to możesz je wykorzystać zamiast Stencil.js.
Tworzymy nowy projekt
Przed przystąpieniem do stworzenia nowego projektu aplikacji, należy zainstalować Node.js
w wersji co najmniej 10.
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt-get install -y nodejs
Teraz możemy przystąpić do tworzenia nowego projektu
npm init stencil ionic-pwa

Konieczne jest również dodanie oraz zainstalowanie dodatkowych zależności do naszego pliku package.json
, które są wymagane tylko w procesie developmentu aplikacji. W tym celu należy przejść do
katalogu z naszym projektem (cd ./arduino-custom-data-station) i wykonać poniższe komendy.
npm install puppeteer --save-dev
npm install @types/jest --save-dev
Jeśli projekt został poprawnie stworzony to po wykonaniu komendy npm start
powinna uruchomić się nasza aplikacja w oknie domyślnej przeglądarki.


Web Serial API
Odczyt danych realizowany jest za pośrednictwem Web Serial API
, które pozwala na bezpośrednie wysyłanie oraz odczytywanie danych z Arduino za pośrednictwem interfejsu USB, bez konieczności
instalowania dodatkowych sterowników. Dzięki temu API, aplikacja webowa może komunikować się bezpośrednio z płytką Arduino podpiętą do komputera, bez konieczności wykonywania dodatkowej konfiguracji systemu. Dzięki takiemu podejściu
nasza aplikacja będzie działać zarówno na systemie Windows jak i również Linux. Jednak najważniejszą cechą takiego podejścia jest fakt, że możemy umieścić naszą aplikację na zewnętrznym serwerze/hostingu i w pełni korzystać z jej
funkcjonalności, ponieważ nie jest ona zależna od systemu operacyjnego. Za poprawną konfigurację Serial Communication dba już sama przeglądarka, a nie programista.
Do obsługi Web Serial API wymagane jest połączenie SSL (HTTPS)
Włączenie obsługi Web Serial API
Na początku musimy włączyć Web Serial API w naszej przeglądarce, w tym celu należy przejść do chrome://flags
i wyszukać odpowiedni wpis tak jak pokazano na poniższym screenie
(#enable-experimental-web-platform-features
).

Dodatkowo w niektórych dystrybucjach Linuxa konieczne jest dodanie naszego użytkownika do odpowiedniej grupy, aby móc nawiązać połączenie z serial portem.
sudo usermod -a -G dialout $USER
Konieczne jest również ponowne uruchomienie komputera, aby zmiany zostały poprawnie wprowadzone.
Odczyt danych za Arduino
Teraz możemy przystąpić do pisania kodu odpowiedzialnego za odczyt danych z Arduino. W tym celu wykorzystamy obiekt Serial, który zawiera wszystkie niezbędne metody oraz obiekty, wymagane do nawiązania komunikacji z Arduino oraz pozwala na wymianę danych pomiędzy przeglądarką, a Arduino.
Aplikacja opiera się na architekturze event-listener, gdzie zdefiniowałem 3 główne eventy:
serialConnectionEstablishedEvent
- emitowany w momencie nawiązania lub zerwania/zamknięcia połączenia z urządzeniem.serialNewDataAvailableEvent
- emitowany w momencie, gdy aplikacja odczyta pełen pakiet danych zakończony znakiem LF.serialLogEvent
- emitowany w celu przekazania wszystkich logów z aplikacji do warstwy frontowej.
Pierwszym krokiem jest inicjalizacja połączenia oraz ustawienie odpowiedniej prędkości przesyłu danych. Oczywiście przed zainicjalizowanie połączenia musimy się upewnić, czy nasza przeglądarka obsługuje Web Serial API
(isWebSerialApiSupported
) oraz czy połączenie nie zostało już wcześniej nawiązane (isConnectionEstablished
), w przeciwnym wypadku zostanie rzucony wyjątek.
/**
* Start serial communication.
*
* @param baudrate the baudrate value (e.g. 9600, 115200)
*/
async start(baudrate: number) {
if (!this.isWebSerialApiSupported) {
WebSerial.log('WebSerial API is not supported.', WebSerial.LogLevel.ERROR);
return;
}
if (this.isConnectionEstablished) {
WebSerial.log('Connection already established. Please close the current connection and try again.', WebSerial.LogLevel.ERROR);
return;
}
try {
this.port = await (window as any).navigator.serial.requestPort();
if (navigator.userAgent.indexOf("OPR/") != -1) {
await this.port.open({baudrate: baudrate});
} else {
await this.port.open({baudRate: baudrate});
}
this.emitSerialConnectionEstablishedEvent(true);
await this.startRead();
} catch (error) {
this.close();
WebSerial.log(error.message, WebSerial.LogLevel.ERROR)
}
}
Po zainicjalizowaniu połączenia emitowany jest zdarzenie serialConnectionEstablishedEvent
wraz flagą, która określa czy połączenie zostało nawiązane (true) lub zerwane/zamknięte (false).
Teraz możemy przystąpić do odczytu danych z Arduino.
private async startRead() {
while (this.port.readable) {
const reader = this.port.readable.getReader();
const decoder = new TextDecoder();
WebSerial.log('Started reading data from the device.', WebSerial.LogLevel.INFO)
while (this.isConnectionEstablished) {
await this.processData(reader, decoder);
}
await reader.cancel();
this.port.close();
WebSerial.log('Serial connection closed.', WebSerial.LogLevel.INFO)
}
}
Konieczne jest konwersja danych z reprezentacji 8-bitowej (kod ASCII) na zwykły tekst oraz buforowanie danych, ponieważ dane są odbierane paczkami, co oznacza że musimy sami zadbać o ich konkatenacje. W tym projekcie zakładam, że każda
linia tekstu przesyłana z Arduino zakończona jest znakiem LF (ang. Line Feed), który reprezentowany jest w systemie dziesiętnym jako liczba '10'. Po odczytaniu pełnego pakietu danych emitowane jest
zdarzenie serialNewDataAvailableEvent
, które zawiera informację o odczytanym tekście.
private async processData(reader, decoder) {
try {
let {value, done} = await reader.read();
if (done) {
WebSerial.log(done, WebSerial.LogLevel.INFO)
return;
}
this.buffer = [...this.buffer, ...value]
if (value[value.length - 1] === 10) {
let decodedValue = decoder.decode(Uint8Array.from(this.buffer));
document.dispatchEvent(new CustomEvent('serialNewDataAvailableEvent', {detail: decodedValue}));
this.buffer = [];
}
} catch (error) {
WebSerial.log(error.message, WebSerial.LogLevel.ERROR);
this.close();
return;
}
}
W tym fragmencie postu przedstawiłem i omówiłem jedynie kluczowe aspekty kodu odpowiedzialne za samą logikę aplikacji (WebSerial.ts) i świadomie pominąłem fragmenty dotyczące prezentacji danych (app-home.tsx).
Uruchomienie gotowej aplikacji
Cały kod aplikacji wraz z komentarzami oraz instrukcją instalacji można znaleźć na moim GitHubie. W celu pobrania projektu, zbudowania oraz uruchomienia należy wykonać poniższe komendy w terminalu:
git clone https://github.com/DevTomek-pl/Arduino-Custom-Data-Station.git
cd ./Arduino-Custom-Data-Station
git checkout display-plain-text-data
npm install
npm start
Finalnie aplikacja powinna odczytywać temperaturę i wilgotność powietrza oraz prezentować je w przeglądarce tak jak pokazano na poniższym filmie.
Podsumowanie
Na tym etapie mamy już stworzoną aplikację webową, która odczytuje dane z naszego Arduino i wyświetla je w oknie przeglądarki. Odczyt danych dokonywany jest za pośrednictwem Web Serial API, co oznacza że nie musimy dbać o dodatkowe sterowniki, czy konfigurację środowiska na którym zostanie uruchomiona aplikacja. Poniżej zamieszczam link do kodów źródłowych aplikacji.
Co dalej?
W kolejnym kroku zajmiemy się obróbką i prezentacją danych w formie dynamicznych wykresów.