NodeMCU – aplikacja odbierająca/wyświetlająca dane z modułu

Dziś opiszę ostatni element mojego projektu z NodeMCU i modułem KAmodLSM303, czyli aplikację na PC, która odbiera dane z akcelerometru za pośrednictwem modułu NodeMCU oraz sieci WiFi i wyświetla na ekranie model modułu KAmodLSM303 obrócony zgodnie z odczytami z czujnika LSM303.

Do napisania aplikacji użyłem środowiska Processing, które umożliwia pisanie aplikacji na PC w sposób przypominający ten z Arduino, dodatkową zaletą jest to, że pisząc jeden kod tworzymy aplikację działającą od razu w systemach Windows, OS X oraz Linuksa. To znaczy – jak to zwykle bywa – prawie tak jest, w filmie chciałem pokazać aplikację działającą na Raspberry Pi, ale niestety problem z obsługą grafiki w aplikacjach Processing uruchamianych w Raspbianie ostudził mój zapał (możliwe jest ominięcie tego problemu, ale znalazłem ciekawsze rozwiązanie do łatwego generowania grafiki 3D na RPi, w miarę możliwości postaram się coś o tym napisać). No ale do rzeczy, środowisko Processing można pobrać ze strony jego twórców, ze względu na użycie biblioteki Object Loader musiałem użyć wersji 2.2.1, ponieważ biblioteka nie działa jeszcze prawidłowo z wersjami 3.0+.

Kod aplikacji Processing składa się zawsze z dwóch funkcji: setup oraz draw (odpowiedniki setup i loop z Arduino), w funkcji setup umieszczamy kod konfiguracyjny (inicjalizacje zmiennych itp.), funkcja draw jest wywoływana okresowo, powinna zawierać kod wykonujący zadania aplikacji. Na początku programu należy zaimportować dodatkowe biblioteki:

import processing.net.*;
import saito.objloader.*;

W pierwszej linii zaimportowana zostaje biblioteka pozwalająca programowi korzystać z połączeń sieciowych, objloader to biblioteka, która umożliwia importowanie modeli 3D w formacie obj.

Dalej deklarowane są zmienne globalne:

Client c;
OBJModel model;
int x, y, z;

Pierwsza zmienna to klient połączenia TCP, druga zmienna przechowuje zaimportowany model 3D, zmienne x, y i z będą zawierać wartości odczytane z akcelerometru modułu KAmodLSM303.

Kod funkcji setup wygląda następująco:

void setup() {    
  size(640, 360, P3D); // wielkosc okna - 640x360 pikseli, P3D - uruchamiamy renderer 3D
  noStroke();  // wylaczamy widocznosc siatki w modelach 3D
  frameRate(30);  // 30 ramek na sekunde

  model = new OBJModel(this, "KAmodLSM303.obj", "absolute", TRIANGLES); // importujemy plik obj z modelem 3D
  model.scale(10);  // skalowanie modelu
  model.translateToCenter();  // przesuniecie modelu do srodka ukladu wspolrzednych
  
  c = new Client(this, "192.168.1.1", 111);  // nawiazujemy polaczenie z NodeMCU pod adresem 192.168.1.1 na porcie 111
}

Do odczytu danych z modułu KAmodLSM303 za pośrednictwem modułu NodeMCU napisałem funkcję readKAmodLSM303, funkcja wysyła znak „A” do modułu (może to być dowolny inny znak), następnie odczytuje 6 bajtów. Bajty są sklejane z sobą w liczby 16-bitowe, następnie, jeśli liczba ma najstarszy bajt o wartości 1, odejmujemy od niej liczbę 65536, ta operacja ma na celu odczytanie liczb w kodowaniu U2. Na koniec odczytane wartości są uśredniane z poprzednio odczytanymi wartościami, ma to na celu zniwelowanie drgań, oczywiście można tu zastosować bardziej zaawansowaną technikę, ale do moich celów taki sposób w zupełności wystarcza.

void readKAmodLSM303() {
  byte coordsBytes[];
  int xn, yn, zn;
  
  coordsBytes = new byte[6];
  
  // wyślij zapytanie
  c.write("A");
  
  // czekaj na odpowiedź
  while (c.available() < 6) delay(10);

  c.readBytes(coordsBytes);
   
  // przelicz bajty na U2
  xn = coordsBytes[0] + (coordsBytes[1] << 8); 
  if (xn > 32767) xn -= 65536;
  
  yn = coordsBytes[2] + (coordsBytes[3] << 8); 
  if (yn > 32767) yn -= 65536;
  
  zn = coordsBytes[4] + (coordsBytes[5] << 8); 
  if (zn > 32767) zn -= 65536;

  // usrednij odczytana wartosc z poprzednia wartoscia  
  x = (x + xn)/2;
  y = (y + yn)/2;
  z = (z + zn)/2;
}

Teraz zostaje tylko napisać funkcję draw, która na podstawie odczytów rysuje odpowiednio obrócony model.

void draw() {
  float roll, pitch;
  
  // odczytaj dane z akcelerometru
  readKAmodLSM303();
  
  // oblicz obrot modelu
  roll = atan2(y, z);
  pitch = atan2(-x, sqrt(y*y + z*z));

  // rysuj tlo
  background(30);

  // wlacz domyslne swiatla
  lights();

  // dodatkowe swiatlo
  directionalLight(128, 128, 128, 0, 0, -1);

  // przesuniecie srodka ukladu wspolrzednych 3D na srodek ekranu
  translate(width/2, height/2, 0);

  // obrot o wyliczone katy
  rotateX(pitch + PI/2);
  rotateY(roll);

  // rysowanie modelu
  model.draw();
}