Spotify Display: AZ Touch Steuerung - Teil 2 - AZ-Delivery

Nachdem der letzte Teil die Grundlagen der Datenabfrage über die Spotify API thematisiert hat, geht es in diesem Teil um eine benutzerfreundliche Darstellung auf einem Display. Als Display eignet sich das AZ-Touch, da es eine Touch-Funktion bietet, das Mikrocontrollerboard lediglich eingesteckt werden muss und am Ende im mitgelieferten Gehäuse optisch ansprechend betrieben werden kann. Über die Touch-Funktion wird eine Steuerung über die API umgesetzt.

Dieser Teil basiert auf dem ersten Teil, die Installation des ESP32 Paket wird vorausgesetzt.

Hardware:

Board mit ESP32-Chip, zum Beispiel: Dev Kit C V2 oder Dev Kit C V4 USB-C

AZ-Touch (2.8” empfohlen)

Software:

Um das ILI9341 Display des AZ-Touch über den ESP zu betreiben, eignet sich die Bibliothek TFT_eSPI von Bodmer. Des Weiteren wird für die Darstellung des Albumcovers die TJpeg decoder Library benötigt. Für das Ermitteln der Touch-Koordinaten wird die XPT2046 Library verwendet.
Die Libraries können über den jeweiligen Link als .zip heruntergeladen und in der Arduino IDE unter Sketch>Include Library>Add .ZIP Library… hinzugefügt werden.
Falls Sie PlatformIO verwenden, kopieren Sie folgendes unter lib_deps in der platformio.ini Datei:

paulstoffregen/XPT2046_Touchscreen@0.0.0-alpha+sha.26b691b2c8

bodmer/TFT_eSPI@^2.5.43

bodmer/TJpg_Decoder@^1.1.0

bblanchon/ArduinoJson@^7.2.1

Die Treiber und Anschlussdefinition müssen im Ordner der TFT_eSPI Bibliothek ausgewählt werden. Kopieren Sie dazu folgende Zeilen in die user_setup.h Datei im Verzeichnis der Bibliothek:

#define USER_SETUP_INFO "User_Setup"

#
define ILI9341_DRIVER       // Generic driver for common displays

#
define TFT_RGB_ORDER TFT_BGR  // Colour order Blue-Green-Red

#
define TFT_WIDTH  240
#
define TFT_HEIGHT 320

#
define TFT_MISO 19
#
define TFT_MOSI 23
#
define TFT_SCLK 18
#
define TFT_CS   5  // Chip select control pin
#
define TFT_DC    4  // Data Command control pin
#
define TFT_RST  22

#
define TFT_BL   15            // LED back-light control pin
#
define TFT_BACKLIGHT_ON LOW  // Level to turn ON back-light (HIGH or LOW)

#
define LOAD_FONT2

#define LOAD_FONT4


#
define SPI_FREQUENCY  27000000

 

Alternativ kann die Datei auch hier heruntergeladen werden.

1 Anzeigen der Metadaten auf dem Display

1.1 Name, Interpret, Album

Da die Daten bereits in der getCurrentPlayingTrack() Funktion, aus dem ersten Teil, ermittelt werden, müssen diese nun lediglich in entsprechenden Variablen zwischengespeichert werden.

String title, artist, album, imgUrl, key;
long progressT, durationT, lastAPIcall;
bool playing, lastStatus;

 

Das Darstellen auf dem Display ist in der printScreen() Funktion realisiert. Hier kann ein Text und ein y-Wert angegeben werden, der Text wird automatisch in der Mitte zentriert.
Des Weiteren steht die Methode cleanString() zur Verfügung. Diese entfernt Klammern und Bindestriche, damit der Text in eine Zeile des Displays passt. Die Länge wird zusätzlich auf 27 Zeichen begrenzt.

Dieses Darstellen der Textinformationen wird im finalen Programmcode in der printCurrentData() Funktion umgesetzt.

1.2 Album Cover

Um die Benutzeroberfläche ansprechender zu gestalten, soll das Album Coverbild auf dem Display angezeigt werden. Die API liefert bereits einen Link zu diesem Bild, dafür muss in der getCurrentPlayingTrack() Funktion die folgende Zeile eingefügt werden:
const char* albumImage = doc["item"]["album"]["images"][2]["url"];
Von der API werden drei verschiedene Bilder zur Verfügung gestellt, diese unterscheiden sich jedoch nur in der Größe und Auflösung. Da das Bild später in das interne Dateisystem im Flash geladen wird und das Herunterladen der Datei byteweise verläuft, empfiehlt es sich, das kleinste Bild (Index 2; 64x64 px) zu verwenden, da das Herunterladen sonst länger dauern und die Größe des internen Dateisystems nicht mehr ausreichen würde.

Das Coverbild wird mit der Funktion downloadImage() in das interne Dateisystem geladen.

HTTPClient http;
  http.
begin(url);
 
int httpCode = http.GET();

 
if (httpCode == HTTP_CODE_OK) {
   
File file = LittleFS.open(filePath, FILE_WRITE);
   
if (!file) {
     
Serial.println("Fehler beim Erstellen der Datei!");
     
return false;
    }

   
WiFiClient* stream = http.getStreamPtr();
    uint8_t
buffer[128];
    size_t
size;

   
while ((size = stream->available()) > 0) {
     
int c = stream->readBytes(buffer, sizeof(buffer));
      file.
write(buffer, c);
    }

    file.
close();
    http.
end();
   
return true;
  }
else {
   
Serial.printf("HTTP-Fehler: %d\n", httpCode);
    http.
end();
   
return false;
  }

Zuerst wird eine Verbindung zu der URL des Bildes hergestellt. Danach wird ein Stream erstellt und die Daten aus dem Stream in den Buffer, welcher daraufhin in die entsprechende Datei geschrieben wird.

Ist die Datei heruntergeladen, wird diese in printAlbumCover() mit Hilfe der TJpgDec Library auf das Display geladen.

1.3 Fortschrittsbalken

Des Weiteren soll ein Fortschrittsbalken dargestellt werden. In der getCurrentPlayingTrack() Funktion werden bereits die Gesamtdauer und die bereits gespielte Zeit über die API abgefragt und der aktuelle Fortschritt in Prozent berechnet.
Da der Balken aber kontinuierlich aktualisiert werden soll, um eine flüssige Darstellung zu erhalten und der API-Abruf etwas Zeit in Anspruch nimmt, wird bei jeder API-Abfrage die interne Zeit zwischengespeichert. Bei der finalen Berechnung wird die Zeit, welche seit der Abfrage vergangen ist, zur bereits gespielten Zeit dazugerechnet. Als Balken wird ein abgerundetes Rechteck mit einer maximalen Länge von 300 px verwendet. Da diese Länge 100% entspricht, kann die Länge durch Fortschritt*3 berechnet werden.

Dieser Fortschrittsbalken wird in printProgress() berechnet und auf dem Display dargestellt.

 if(!playing) return;
 
float progress;
  progress = (((
float)progressT + (float)millis() - (float)lastAPIcall)/(float)durationT) * 100.0;
 
if(progress > 100) progress = 100;
 
else if(progress < 0) progress = 0;
  tft.fillRoundRect(
10, 110, (int) progress * 3, 10, 5, TFT_GREEN);

Falls nichts abgespielt wird, kann die Methode beendet werden, ansonsten wird der Fortschritt in Prozent berechnet. Zu der aus der API abgerufenen Zeit wird noch die Differenz aus der aktuellen Zeit und der Zeit der letzten API Abfrage addiert, um den Fortschrittsbalken unabhängig von API-Abfragen aktualisieren zu können.

Das komplette Programm können Sie hier herunterladen.

2 Playback Steuerung

Um diese Funktionen zu benutzen, ist ein Spotify Premium Account erforderlich.
Des Weiteren muss die Authentifikation aus dem ersten Teil noch einmal mit zusätzlichen Berechtigungen (scopes) durchgeführt werden. Benutzen Sie dazu folgenden Link:  https://accounts.spotify.com/authorize?response_type=code&client_id=YOUR_CLIENT_ID&scope=user-read-currently-playing%20user-read-playback-state%20user-modify-playback-state%20user-read-playback-state&redirect_uri=http://localhost/callback

Im setup() muss der bisher gespeicherte Authorization Token gelöscht werden. Dafür sind folgende Befehle nötig:

prefs.begin("Token");
prefs.
remove("refresh");
prefs.
end();

 

Nachdem der Token gelöscht ist, entfernen Sie die Befehle wieder und laden das Programm erneut hoch. Kopieren Sie den neuen Refresh Token wie im ersten Teil in den Seriellen Monitor.

Informationen zu den API-Aufrufen finden Sie in der Dokumentation.

Um die Bedienung zu erleichtern, sollen in der untersten Zeile Symbole in Form eines doppelten Dreiecks links und rechts (vorspulen, zurückspulen) und ein Dreieck bzw. Pausenzeichen in der Mitte dargestellt werden.
Diese Symbole werden am einfachsten durch die Grafikbefehle der Display Library realisiert. Die beiden Pfeile nach links und rechts können zusammen mit den Informationen einmalig auf dem Display dargestellt werden.

Anzeigen der Doppelpfeile in der printCurrentData() Funktion:

  tft.fillTriangle(30, 190, 60, 175, 60, 205, TFT_WHITE);
  tft.fillTriangle(
45, 190, 85, 175, 85, 205, TFT_WHITE);

  tft.fillTriangle(
320-30, 190, 320-60, 175, 320-60, 205, TFT_WHITE);
  tft.fillTriangle(
320-45, 190, 320-85, 175, 320-85, 205, TFT_WHITE);

 

 

Das Symbol der mittleren Start/Stopp-Taste muss je nach Zustand geändert werden. Dies wird wie bei der Funktion printCurrentData() mit einer Hilfsvariable umgesetzt, welche mit dem aktuellen Wert verglichen und die Funktion nur bei einer Änderung aufgerufen wird.

Für das Pausen/Wiedergabe-Zeichen gibt es eine extra Funktion printMidButton():

  tft.fillRect(140, 165, 50, 50, TFT_BLACK);
 
if(playing) {
    tft.fillRect(
150, 175, 10, 30, TFT_WHITE);
    tft.fillRect(
170, 175, 10, 30, TFT_WHITE);
  }
 
else {
    tft.fillTriangle(
175, 190, 145, 175, 145, 205, TFT_WHITE);
  }

Zuerst wird das bisher vorhandene Symbol mit einem schwarzen Quadrat überschrieben. Im Anschluss das entsprechende Symbol gezeichnet.

Um die Benutzereingaben zu verarbeiten, müssen die Touch-Koordinaten abgefragt und zugeordnet werden. Dies passiert in der Methode touch(): 

  if(digitalRead(27)) return;
 
tone(21, 1000, 100);
  p = ts.getPoint();
 
Serial.println("X: " + (String)p.x + " Y: " + (String)p.y);
 
if(p.y < 2000) {
    HTTPClient http;
 
   
int responsecode;
   
   
if(p.x > 2000) {
     
Serial.println("rew");
      http.
begin("https://api.spotify.com/v1/me/player/previous");
      http.addHeader(
"Authorization", "Bearer " + accessToken);
      http.addHeader(
"Content-Length", "0");
      responsecode = http.POST(
"");

    }
   
else if(p.x > 1200) {
     
Serial.println("pause");
     
if(playing) http.begin("https://api.spotify.com/v1/me/player/pause");
     
else http.begin("https://api.spotify.com/v1/me/player/play");
      http.addHeader(
"Authorization", "Bearer " + accessToken);
      http.addHeader(
"Content-Length", "0");
      playing = !playing;
      printMidButton();

      responsecode = http.PUT(
"");
    }
   
else if(p.x > 300) {
     
Serial.println("fw");
      http.
begin("https://api.spotify.com/v1/me/player/next");
      http.addHeader(
"Authorization", "Bearer " + accessToken);
      http.addHeader(
"Content-Length", "0");
      responsecode = http.POST(
"");

    }
   
   
Serial.println(responsecode);
   
Serial.println(http.getString());
  }

Wurde das Display nicht berührt (Interrupt HIGH), wird die Funktion abgebrochen. Sonst werden die Touch-Koordinaten ermittelt. Befindet sich der berührte Punkt in einem festgelegten Bereich, so wird eine Verbindung zur entsprechenden URL aufgebaut. Bei jeder Verbindung werden noch Header mit der Autorisierung und der Content Length hinzugefügt.

Überprüfen Sie mit den Ausgaben im Seriellen Monitor, ob die Koordinaten mit den Bereichen übereinstimmen und korrigieren Sie diese bei Bedarf. 

Das komplette Programm können Sie hier herunterladen.                                                                       

Nachdem das Projekt getestet ist, kann die Rückseite des AZ-Touch-Gehäuses an eine Wand geschraubt und mit einem Netzteil standalone betrieben werden. Hiermit ist das Projekt abgeschlossen. Gerne können Sie auch noch weitere Funktionen der Spotify API integrieren. Auch die Farben der Symbole und des Fortschrittsbalkens können beliebig geändert werden.

Viel Spaß beim Nachbauen :)

Spotify und Spotify Premium sind eingetragene Marken von Spotify AB. Dieser Blog steht in keiner Verbindung zu Spotify AB und wird weder von Spotify AB gesponsert noch unterstützt. Alle in diesem Beitrag verwendeten Markennamen, Logos und Warenzeichen gehören ihren jeweiligen Eigentümern und werden ausschließlich zur Beschreibung oder Identifikation verwendet.                        

Esp-32Für arduinoProjekte für fortgeschrittene

Laisser un commentaire

Tous les commentaires sont modérés avant d'être publiés

Articles de blog recommandés

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery