Arduino Custom Data Station #2 - odczyt danych

Posted on 30 November, 2020 at 18:01

Tags: JavaScript Node.js AVR Arduino


Arduino Custom Data Station #2 - odczyt danych

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.

software-schematic-diagram
Arduino Custom Data Station - schemat ideowy komunikacji za pomocą Web Serial API

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
init-stencil-js-project
Stencil.js - tworzenie nowego projektu

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.

run-stencil-js-project
Stencil.js - uruchomienie projektu
sample-stencil-js-project
Stencil.js - przykładowy projekt

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.

SSL

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).

enable-experimental-web-platform-features
Google Chrome - włączenie obsługi Web Serial API

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.


Please provide a valid nick.
Please provide a valid content.

* These fields are required.