Arduino Custom Data Station #3 - prezentacja danych

Posted on 7 December, 2020 at 19:27

Tags: JavaScript Node.js AVR Arduino


Arduino Custom Data Station #3 - prezentacja danych

Etap #3

Trzeci etap projektu zakłada dodanie obsługi wykresów na którym będą prezentowane w czasie rzeczywistym odczytane z Arduino wartości temperatury oraz wilgotności powietrza.

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

Chart.js

Do prezentacji danych wykorzystam popularną bibliotekę Chart.js. Na początku należy zainstalować pakiet chart.js w tym celu z poziomu terminala wykonujemy poniższe komendy:

npm install chart.js --save
npm install @types/chart.js --save-dev

Pierwsza komenda odpowiada za zainstalowanie oraz dodanie pakietu chart.js do naszego pliku package.json do sekcji dependencies. Druga natomiast odpowiada za dodanie pliku z definicją typów, które są używane przez pakiet chart.js, dzięki temu nasze IDE będzie rozpoznawało typy zdefiniowane w nowym pakiecie. Pakiet ten jest opcjonalny i potrzebny tylko w fazie developmentu, dlatego został dodany przełącznik "--save-dev", który odpowiada za dodanie pakietu do pliku package.json w sekcji devDependencies.

TimeSeriesChart - komponent do prezentacji danych

Dobrą praktyką jest stworzenie własnego komponentu, aby uniknąć w przyszłości duplikacji kodu (DRY), dlatego stworzyłem komponent TimeSeriesChart, który będzie odpowiedzialny za prezentację danych w formie wykresu, gdzie na osi Y będą prezentowane wartości odczytane z Arduino (temperatura i wilgotność powietrza), natomiast na osi X będzie umieszczona wartość czasu.

Komponent TimeSeriesChart posiada 6 właściwości (ang. properties):

  • header - nagłówek wykresu,
  • label - etykieta danych,
  • color - kolor wykresu,
  • xAxisLabel - etykieta osi X,
  • AxisLabel - etykieta osi Y,
  • size - maksymalna liczba punktów (rozmiar bufora na dane).

Dodatkowo zdefiniowałem lokalny stan do przechowywania poszczególnych punktów (x,y), które wyświetlane są na wykresie:

  • data - stan, zdefiniowany jako słownik przechowywujący wartości X oraz Y, które reprezentowane są jako tablice typu string oraz number.

Inicjalizacja oraz konfiguracja komponentu Chart odbywa się w metodzie componentDidLoad(), gdzie tworzony jest nowy obiekt Chart. Podczas jego tworzenia przekazywana jest odpowiednia konfiguracja, która jest tworzona na podstawie przekazanych przez nas właściwości.

componentDidLoad() {
    this.canvas = this.el.querySelector('canvas');
    this.context = this.canvas.getContext('2d');
    const chartConfig: any = {
        type: 'line',
        data: {
            labels: this.data.x,
            datasets: [{
                label: this.label,
                backgroundColor: this.color,
                borderColor: this.color,
                fill: false,
                data: this.data.y,
            }]
        },
        options: {
            responsive: true,
            title: {
                display: true,
                text: this.header
            },
            scales: {
                xAxes: [{
                    display: true,
                    scaleLabel: {
                        display: true,
                        labelString: this.xAxisLabel
                    }
                }],
                yAxes: [{
                    display: true,
                    scaleLabel: {
                        display: true,
                        labelString: this.yAxisLabel
                    }
                }]
            }
        }
    };

    this.chart = new Chart(this.context, chartConfig);
}

Punkty do wykresu dodawane są za pomocą metody addValue(value: number), która jako argument przyjmuje wartość typu number. W naszym przypadku jest to wartość temperatury lub wilgotności powietrza. Natomiast wartość X, czyli czas, obliczany jest już w samej metodzie za pomocą statycznej metody TimeSeriesChart.getCurrentTime().

@Method()
async addValue(value: number) {
    // update X values
    this.data.x = [...this.data.x, TimeSeriesChart.getCurrentTime()];
    if (this.data.x.length > this.size) {
        this.data.x.shift();
    }
    this.chart.data.labels = this.data.x;

    // update Y values
    this.data.y = [...this.data.y, value];
    if (this.data.y.length > this.size) {
        this.data.y.shift();
    }
    this.chart.data.datasets.forEach((dataset: any) => {
        dataset.data = this.data.y;
    });

    // display new data
    this.chart.update();
}

Metoda addValue wywoływana jest w momencie, gdy zostanie wyemitowane zdarzenie serialNewDataAvailableEvent, które informuje nas, że zostały odczytane nowe dane z Arduino. Ważne jest, aby dane wysyłane z Arduino były za każdym razem inne od poprzednich, bo w przeciwnym wypadku zdarzenie to nie zostanie "złapane" przez nasz listener. Dlatego w strukturze ramki umieściłem dodatkowe pole przechowujące wartość timestamp, które gwarantuje, że każda kolejna ramka z danymi będzie miała inną wartośc w stosunku do poprzedniej.

@Listen('serialNewDataAvailableEvent', {target: 'document'})
serialNewDataAvailableEventListener(event) {
    let array = event.detail.split('|');
    this.sensor = new Sensor(array[0], Number(array[1]), Number(array[2]));
    this.temperatureChart.addValue(this.sensor.temperature);
    this.humidityChart.addValue(this.sensor.humidity);
}
Dlaczego nie Property Binding?

W tym przypadku nie mogłem zastosować zwykłego "Property Binding", ponieważ taka architektura nie sprawdziłaby się w tym przypadku, gdyż dane muszą być dodawane zawsze do wykresu, nawet jeśli są takie same jak poprzednie. Tradycyjne bindnowanie sprawdza się tylko, gdy chcemy wyrenderować komponent z nowymi danymi/wartościami, które są różne od poprzednich. Reasumując, "Binding state to props" sprawdza się tylko, gdy za każdym razem otrzymujemy różne wartości w stosunku do poprzednich, a w naszym przypadku tak nie jest (możemy odczytać kilkukrotnie tą samą wartość temperatury i wilgotności powietrza).

Cały kod komponentu TimeSeriesChart można znaleźć tutaj: time-series-chart.tsx, natomiast cały gotowy projekt, który możesz uruchomić na swoim komputerze dostepny jest na moim GitHubie

Uruchomienie projektu

Poniżej zamieszczam krótki wideoporadnik pokazujący, jak w szybki i prosty sposób można uruchomić ten projektu na własnym komputrze.

git clone https://github.com/DevTomek-pl/Arduino-Custom-Data-Station.git
cd ./Arduino-Custom-Data-Station
git checkout display-time-series-chart-data
npm install
npm start

Podsumowanie

Na tym etapie mamy już stworzoną całą aplikację, która odczytuje dane z Arduino za pomocą Web Serial API oraz prezentuje odczytane dane w czasie rzeczywistym na wykresach.

Co dalej?

Kolejny post z tego cyklu będzie już dotyczył procesu deploymentu aplikacji na hosting Firebase.


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

* These fields are required.