ESP32 + RFID + (JSON) Kodi Steuerung

Ja, korrekt.

Nun dachte ich, einfach alle Scripte und CSS Dateien runter laden und in das SPIFFS packen. Also data Ordner angelegt, Dateien auf den ESP geladen und versucht über die gängigen Anleitungen LITTLEFS hinzuzufügen. CSV Datei angepasst mit der Startadresse wo die Daten hochgeladen wurden.
Wieder kernel-panic… und nun finde mal raus, wo der Fehler liegen könnte… :see_no_evil:

Kann nicht mal was einfach gehen?!

monitor_filters = esp32_exception_decoder

kannst in der platformio.ini mal setzen und alles neu kompilieren, damit du nen gescheiten Stacktrace hast.

Vielleicht habe ich etwas den Überblick verloren aber meinst Du nicht, dass Du besser zum Ziel kommst wenn Du etwas Neues machst?

Du hast doch bereits die Tasenlösung, oder?
Du müsstest doch „nur“ den Tastendruck durch eine RFID - Erkennung ersetzen, also Deinen vorhanden Sketch um eine RFID Routine.
Dann könntest Du
a) Die Zuordnung Karte zu Playlist fest in den Code implementieren. Weil es nicht so viele Änderungen gibt.
b) Die URL die aufgerufen werden muss als Payload auf die Karte schreiben. Dann kannst Du die Karten „extern“ (mit Handy-App) schreiben und der D1 macht nichts anderes als die Karte zu lesen und die URL aufzurufen…

Vielleicht habe ich etwas den Überblick verloren aber meinst Du nicht, dass Du besser zum Ziel kommst wenn Du etwas Neues machst?

Das dachte ich anfangs, aber das ganze Thema Webinterface erstellen, RFID-Zuordnung, das überforderte mich schon beim Lesen. Deswegen wollt ich auf die bestehende Funktionalität aufbauen. Ich bin wirklich kein Entwickler. :sweat_smile: :see_no_evil:

a) Die Zuordnung Karte zu Playlist fest in den Code implementieren. Weil es nicht so viele Änderungen gibt.

Der Bewohner hat etliche Alben, diese fest zu implementieren ist auch einiges an Aufwand.

b) Die URL die aufgerufen werden muss als Payload auf die Karte schreiben. Dann kannst Du die Karten „extern“ (mit Handy-App) schreiben und der D1 macht nichts anderes als die Karte zu lesen und die URL aufzurufen…

Das klingt erstmal einfach, aber geht das auch so einfach? Ich habe mir jetzt die kleinen runden Papieraufkleber geholt (NTAG213), wollte die auf das Cover kleben und damit dann die Aktion auslösen. Weiß nicht, ob da ne URL hinterlegt werden kann. Eigentlich muss ja nur „Player.Open“:„file bzw. directory“:"und der Pfad " da drauf…

Momentan komme ich wirklich von einem Problem ins nächste.
Es geht bisher:

  • ohne SD-Karte
  • KODI-JSON-URL als Filename mit folgenden Inhalten

„192.168.1.140:Player.Open:movieid:41“; – wird theoretisch von mir nicht benötigt
„192.168.1.140:Player.Open:file:/storage/765E-9256/Playlists/Within Temptation.m3u“ – wollte ich erst, aber Ordner ist praktischer
„192.168.1.140:Player.Open:directory:/storage/765E-9256/Chainreactor“ – das soll der Aufruf der Alben-Ordner auf HDD werden

Das mit den Buttons muss ich dann auch noch irgendwie hinbekommen…

„192.168.1.140:Application.SetVolume:increment“;
„192.168.1.140:Application.SetVolume:decrement“;

Scheinbar muss ich gedanklich wirklich nochmal zurück…

@Christian

Danke für die Idee! :+1: Ich habe gerade einen Tag mit den Daten beschrieben, werden dann probieren wie sich das am besten auslesen und an die Funktion übergeben lässt. Dann wäre theoretisch die komplette Funktionalität schon gegeben.

1 „Gefällt mir“

Nur falls es jemand interessiert, der nachfolgende Code startet zumindest schon mal die Wiedergabe des auf dem Tag gespeicherten Textes. Beispiele im Code.
Optimierungen sind natürlich immer willkommen. :slight_smile:

Sofern ich wieder Zeit habe, geht’s an die Buttons…



#include <WiFi.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include "NfcAdapter.h"


const char* ssid     = "Your SSID";
const char* password =  "Your PW";

char *kodihost;
char *kodiport = "8080";
char *json_method;

char kodi_request[255];
WiFiClient client;
HTTPClient kodi_http;

static MFRC522 mfrc522(21, 99);   // Create MFRC522 instance
NfcAdapter nfc = NfcAdapter(&mfrc522);

uint8_t buf[18];
uint8_t size = sizeof(buf);
uint8_t uid[7];
uint8_t uidLength;
byte payload[300];


void kodi_json(char *filename) {

  //NTAG213-Textfeld Beispiele
  //"Player.Open:movieid:41"  //für Filme die :movieid:  
  //oder...
  //"Player.Open:file:/storage/765E-9256/Playlists/Within Temptation.m3u"  //für Playlisten :file: verwenden
  //oder...
  //"Player.Open:directory:/storage/765E-9256/Chainreactor" //für Ordner :directory: verwenden
  
 
  
  char * token;
  Serial.println(filename);
  StaticJsonDocument<256> kodi_doc;
  JsonObject kodi_json_object = kodi_doc.to<JsonObject>();
  kodi_json_object["jsonrpc"] = "2.0";
  kodi_json_object["id"] = millis();

  kodihost="192.168.1.140";  //KodiBox hat Static IP
  printf("kodiHost: %s\n", kodihost);
  token = strtok(strdup(filename), ":" );
  printf("Token: %s\n", token);
  json_method = token;
  if (strcmp(token, "Player.Open") == 0)  {
    Serial.println("Method ist Player.Open!");
    kodi_json_object["method"] = json_method;
    token = strtok( NULL, ":" );
    if (strcmp(token, "file") == 0)   {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["file"] = token;
    }
    else if (strcmp(token, "directory") == 0)    {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["directory"] = token;
    }
    else if (strcmp(token, "movieid") == 0)    {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["movieid"] = token;
    }
  }
  else if (strcmp(token, "Application.SetVolume") == 0)  {
    Serial.println("Method ist Application.SetVolume!");
    kodi_json_object["method"] = json_method;
    token = strtok(NULL, ":" );
    kodi_json_object["params"]["volume"] = token;
  }
  else  {
    Serial.println("Ungültige JSON Angabe");
    exit(1);
  }
  serializeJson(kodi_doc, kodi_request, 255);
  Serial.println(kodi_request);
  kodi_http.addHeader("Content-Type", "application/jsonrpc");

  char serverName[255] = "http://";
  strcat(serverName, kodihost);
  Serial.println(serverName);
  strcat(serverName, ":");
  strcat(serverName, kodiport);
  strcat(serverName, "/jsonrpc");
  Serial.println(serverName);

  kodi_http.begin(client, serverName);
  int httpResponseCode = kodi_http.POST(kodi_request);
  Serial.print("HTTP Response code: ");
  Serial.println(httpResponseCode);
  // Free resources
  kodi_http.end();
}

void Rfid_Init(void) {
  SPI.begin(18, 19, 23, 21);
  SPI.setFrequency(1000000);
  mfrc522.PCD_Init();
  mfrc522.PCD_SetAntennaGain(0x07 << 4);
  delay(50);
  nfc.begin();
}

void RFID()
{
  if (nfc.tagPresent())
  {
    Serial.println("Reading NFC tag");
    NfcTag tag = nfc.read();
    Serial.println(tag.getTagType());
    Serial.print("UID: "); Serial.println(tag.getUidString());
    if (tag.hasNdefMessage()) // every tag won't have a message
    {
      NdefMessage message = tag.getNdefMessage();
      Serial.print("\nThis NFC Tag contains an NDEF Message with ");
      Serial.print(message.getRecordCount());
      Serial.print(" NDEF Record");
      if (message.getRecordCount() != 1) {
        Serial.print("s");
      }
      Serial.println(".");
      // cycle through the records, printing some info from each
      int recordCount = message.getRecordCount();
      for (int i = 0; i < recordCount; i++)
      {
        NdefRecord record = message.getRecord(i);
        int payloadLength = record.getPayloadLength();
        const byte *payload = record.getPayload();
        String payloadAsString = "";
        for (int c = 0; c < payloadLength; c++) {
          payloadAsString += (char)payload[c];
        }
        Serial.print("  Payload (as String): ");
        payloadAsString.remove(0, 3);
        Serial.println(payloadAsString);

        int str_len = payloadAsString.length() + 1;
        char char_array[str_len];
        payloadAsString.toCharArray(char_array, str_len);
        kodi_json(char_array);
      }
    }
  }
  mfrc522.PICC_HaltA();
}


void setup()
{
  Serial.begin(115200);
  Rfid_Init();

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
}


void loop()
{
  RFID();
}
1 „Gefällt mir“

Wäre jemand so nett und könnte mal drauf guckn? Ich bräuchte mal n Tipp bzgl. der Button Abfrage. Das Programm funktioniert, sobald der Tag aufgelegt wird, startet die hinterlegte Wiedergabe. Wird der Tag entfernt, stoppt diese.
Wenn die Wiedergabe läuft werden die 4 Buttons mit ezButton Bibliothek abgefragt.

Wenn ich die Buttons nach dem Beispielcode abfrage, funktioniert alles wie gewollt. Je ein Drücken wird korrekt registriert.

Wenn ich das in mein Programm einfüge, wird der Button zweimal hintereinander abgefragt und die Aktion ausgeführt. Seh nur nicht warum…?! Es hat sich am Code nichts geändert.

19:29:56.661 -> WiFi connected
19:29:56.661 -> IP address: 
19:29:56.661 -> 192.168.1.141
19:29:56.661 -> Warte auf Kodi-Host...
19:30:00.735 -> Kodi-Host online!
19:30:00.831 -> locked! NUID tag: 04 E0 5A 6A 6B 11 90 
19:30:00.831 -> Starte Wiedergabe...
19:30:00.831 -> Reading NFC tag
19:30:00.878 -> 2
19:30:00.878 -> UID: 04 E0 5A 6A 6B 11 90
19:30:00.878 -> 
19:30:00.878 -> This NFC Tag contains an NDEF Message with 1 NDEF Record.
19:30:00.878 ->   Payload (as String): Player.Open:directory:/storage/765E-9256/Chainreactor
19:30:00.878 -> Player.Open:directory:/storage/765E-9256/Chainreactor
19:30:00.878 -> kodiHost: 192.168.1.140
19:30:00.878 -> Token: Player.Open
19:30:00.878 -> Method ist Player.Open!
19:30:00.878 -> Kodi Request: {"jsonrpc":"2.0","id":6847,"method":"Player.Open","params":{"item":{"directory":"/storage/765E-9256/Chainreactor"}}}
19:30:00.926 -> http://192.168.1.140:8080/jsonrpc
19:30:00.926 -> HTTP POST
19:30:01.359 -> HTTP Response code: 200
19:30:04.908 -> The button PausePlay is pressed
19:30:04.908 -> Player.PlayPause
19:30:04.908 -> kodiHost: 192.168.1.140
19:30:04.908 -> Token: Player.PlayPause
19:30:04.908 -> Kodi Request: {"jsonrpc":"2.0","id":10872,"method":"Player.PlayPause","params":{"playerid":0}}
19:30:04.908 -> http://192.168.1.140:8080/jsonrpc
19:30:04.908 -> HTTP POST
19:30:05.244 -> HTTP Response code: 200
19:30:05.292 -> The button PausePlay is pressed
19:30:05.292 -> Player.PlayPause
19:30:05.292 -> kodiHost: 192.168.1.140
19:30:05.292 -> Token: Player.PlayPause
19:30:05.292 -> Kodi Request: {"jsonrpc":"2.0","id":11259,"method":"Player.PlayPause","params":{"playerid":0}}
19:30:05.292 -> http://192.168.1.140:8080/jsonrpc
19:30:05.292 -> HTTP POST
19:30:06.777 -> HTTP Response code: 200
19:30:08.216 -> unlocked! Reason for unlocking: Timeout in communication.
19:30:08.216 -> Player.Stop
19:30:08.216 -> kodiHost: 192.168.1.140
19:30:08.216 -> Token: Player.Stop
19:30:08.216 -> Kodi Request: {"jsonrpc":"2.0","id":14200,"method":"Player.Stop","params":{"playerid":0}}
19:30:08.264 -> http://192.168.1.140:8080/jsonrpc
19:30:08.264 -> HTTP POST
19:30:08.454 -> HTTP Response code: 200

Es geht um den Part am Ende der loop() Schleife.

#include <WiFi.h>
#include <ESP32Ping.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include "NfcAdapter.h"
#include <ezButton.h>

#define KODIHOST      "192.168.1.140"

#define NEXT_BUTTON                      33
#define PAUSEPLAY_BUTTON                 32
#define VOLUME_UP                        26
#define VOLUME_DOWN                      25


ezButton Button_Next(NEXT_BUTTON);
ezButton Button_PausePlay(PAUSEPLAY_BUTTON);
ezButton Button_VolUp(VOLUME_UP);
ezButton Button_VolDown(VOLUME_DOWN);

const char *ssid = "Kodi Json Remote";
const char *password = "********";

char *kodihost;
char *kodiport = "8080";
char *json_method;
char kodi_request[255];

IPAddress local_IP(192, 168, 1, 141);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

byte pStatus2 = -1;

WiFiClient client;
HTTPClient kodi_http;

static MFRC522 mfrc522(21, 99);   // Create MFRC522 instance
NfcAdapter nfc = NfcAdapter(&mfrc522);


uint8_t buf[18];
uint8_t size = sizeof(buf);
uint8_t uid[7];
uint8_t uidLength;
byte payload[300];



void Rfid_Init(void) {
  SPI.begin(18, 19, 23, 21);
  SPI.setFrequency(1000000);
  mfrc522.PCD_Init();
  mfrc522.PCD_SetAntennaGain(0x07 << 4);
  mfrc522.uid.size = 0;
  delay(50);
  nfc.begin();
}


void setup()
{
  kodihost = KODIHOST;

  pinMode(NEXT_BUTTON, INPUT_PULLUP);
  pinMode(PAUSEPLAY_BUTTON, INPUT_PULLUP);
  pinMode(VOLUME_UP, INPUT_PULLUP);
  pinMode(VOLUME_DOWN, INPUT_PULLUP);

  Button_Next.setDebounceTime(50);
  Button_PausePlay.setDebounceTime(50);
  Button_VolUp.setDebounceTime(50);
  Button_VolDown.setDebounceTime(50);

  Serial.begin(115200);

  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);

  if (!WiFi.config(local_IP, gateway, subnet)) {
    Serial.println("STA Failed to configure");
  }

  WiFi.begin(ssid, password);

  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  bool success = false;


  while (!success) {
    Serial.println("Warte auf Kodi-Host...");
    delay(1000);
    success = Ping.ping(kodihost, 3);
  }

  Serial.println("Kodi-Host online!");

  Rfid_Init();
}

bool locked = false;

void loop() {
  // Wake up all cards present within the sensor/reader range.
  bool cardPresent = PICC_IsAnyCardPresent();

  // Reset the loop if no card was locked an no card is present.
  // This saves the select process when no card is found.
  if (! locked && ! cardPresent)
    return;
  // When a card is present (locked) the rest ahead is intensive (constantly checking if still present).
  // Consider including code for checking only at time intervals.


  // Ask for the locked card (if rfid.uid.size > 0) or for any card if none was locked.
  // (Even if there was some error in the wake up procedure, attempt to contact the locked card.
  // This serves as a double-check to confirm removals.)
  // If a card was locked and now is removed, other cards will not be selected until next loop,
  // after rfid.uid.size has been set to 0.
  MFRC522::StatusCode result = mfrc522.PICC_Select(&mfrc522.uid, 8 * mfrc522.uid.size);

  if (!locked && result == MFRC522::STATUS_OK)
  {
    locked = true;
    // Action on card detection.
    Serial.print(F("locked! NUID tag: "));
    printHex(mfrc522.uid.uidByte, mfrc522.uid.size);
    Serial.println();
    Serial.println("Starte Wiedergabe...");
    readTagPayload_and_startPlayback();

   } else if (locked && result != MFRC522::STATUS_OK)  {
    locked = false;
    mfrc522.uid.size = 0;
    // Action on card removal.
    Serial.print(F("unlocked! Reason for unlocking: "));
    Serial.println(mfrc522.GetStatusCodeName(result));
    //Wenn Tag entfernt, stoppe die Wiedergabe
    kodi_json("Player.Stop");
  } else if (!locked && result != MFRC522::STATUS_OK) {
    // Clear locked card data just in case some data was retrieved in the select procedure
    // but an error prevented locking.
    mfrc522.uid.size = 0;
  }
  Button_Next.loop();
  Button_PausePlay.loop();
  Button_VolUp.loop();
  Button_VolDown.loop();
  check_Buttons();
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
}


void check_Buttons() {

  if (Button_PausePlay.isPressed())  {
    Serial.println("The button PausePlay is pressed");
    kodi_json("Player.PlayPause");
  }
  if (Button_VolUp.isPressed())  {
    Serial.println("The button VolUp is pressed");
    kodi_json("Application.SetVolume:increment");
  }
  if (Button_VolDown.isPressed())  {
    Serial.println("The button VolDown is pressed");
    kodi_json("Application.SetVolume:decrement");
  }
  if (Button_Next.isPressed())  {
    Serial.println("The button Next is pressed");
    kodi_json("Player.GoTo");
  }
}


void readTagPayload_and_startPlayback()
{
  //if (nfc.tagPresent())
  //{
  Serial.println("Reading NFC tag");
  NfcTag tag = nfc.read();
  Serial.println(tag.getTagType());
  Serial.print("UID: "); Serial.println(tag.getUidString());
  if (tag.hasNdefMessage()) // every tag won't have a message
  {
    NdefMessage message = tag.getNdefMessage();
    Serial.print("\nThis NFC Tag contains an NDEF Message with ");
    Serial.print(message.getRecordCount());
    Serial.print(" NDEF Record");
    if (message.getRecordCount() != 1) {
      Serial.print("s");
    }
    Serial.println(".");
    // cycle through the records, printing some info from each
    int recordCount = message.getRecordCount();
    for (int i = 0; i < recordCount; i++)
    {
      NdefRecord record = message.getRecord(i);
      int payloadLength = record.getPayloadLength();
      const byte *payload = record.getPayload();
      String payloadAsString = "";
      for (int c = 0; c < payloadLength; c++) {
        payloadAsString += (char)payload[c];
      }
      Serial.print("  Payload (as String): ");
      payloadAsString.remove(0, 3);
      Serial.println(payloadAsString);

      int str_len = payloadAsString.length() + 1;
      char char_array[str_len];
      payloadAsString.toCharArray(char_array, str_len);
      //start Playback
      kodi_json(char_array);
    }
  }
  //}

}


void kodi_json(char *action) {

  //NTAG213-Textfeld Beispiele
  //"Player.Open:movieid:41";  //für Filme die :movieid:
  //oder...
  //"Player.Open:file:/storage/765E-9256/Playlists/Within Temptation.m3u";  //für Playlisten :file: verwenden
  //oder...
  //"Player.Open:directory:/storage/765E-9256/Chainreactor"; //für Ordner :directory: verwenden

  //...für die Buttons...
  //"Application.SetVolume:increment";
  //"Application.SetVolume:decrement";

  char * token;
  Serial.println(action);
  StaticJsonDocument<256> kodi_doc;
  JsonObject kodi_json_object = kodi_doc.to<JsonObject>();
  kodi_json_object["jsonrpc"] = "2.0";
  kodi_json_object["id"] = millis();


  printf("kodiHost: %s\n", kodihost);
  token = strtok(strdup(action), ":" );
  printf("Token: %s\n", token);
  json_method = token;
  if (strcmp(token, "Player.Open") == 0)  {
    Serial.println("Method ist Player.Open!");
    kodi_json_object["method"] = json_method;
    token = strtok( NULL, ":" );
    if (strcmp(token, "file") == 0)   {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["file"] = token;
    }
    else if (strcmp(token, "directory") == 0)    {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["directory"] = token;
    }
    else if (strcmp(token, "movieid") == 0)    {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["movieid"] = token;
    }
  }
  else if (strcmp(token, "Application.SetVolume") == 0)  {
    Serial.println("Method ist Application.SetVolume!");
    kodi_json_object["method"] = json_method;
    token = strtok(NULL, ":" );
    kodi_json_object["params"]["volume"] = token;
  }
  else if (strcmp(token, "Player.PlayPause") == 0) {
    kodi_json_object["method"] = json_method;
    kodi_json_object["params"]["playerid"] = 0;
  }
  else if (strcmp(token, "Player.Stop") == 0) {
    kodi_json_object["method"] = json_method;
    kodi_json_object["params"]["playerid"] = 0;
  }
  else if (strcmp(token, "Player.GoTo") == 0) {
    kodi_json_object["method"] = json_method;
    kodi_json_object["params"]["playerid"] = 0;
    kodi_json_object["params"]["to"] = "next";
  }
  else  {
    Serial.println("Ungültige JSON Angabe");
    exit(1);
  }
  serializeJson(kodi_doc, kodi_request, 255);
  Serial.print("Kodi Request: ");
  Serial.println(kodi_request);

  char serverName[255] = "http://";
  strcat(serverName, kodihost);
  strcat(serverName, ":");
  strcat(serverName, kodiport);
  strcat(serverName, "/jsonrpc");
  Serial.println(serverName);
  Serial.println("HTTP POST");
  kodi_http.addHeader("Content-Type", "application/jsonrpc");
  kodi_http.begin(client, serverName);
  int httpResponseCode = kodi_http.POST(kodi_request);
  Serial.print("HTTP Response code: ");
  Serial.println(httpResponseCode);
  // Free resources
  kodi_http.end();

}

String urlencode(String str)
{
  /*
    ESP8266 Hello World urlencode by Steve Nelson
  */
  String encodedString = "";
  char c;
  char code0;
  char code1;
  char code2;
  for (int i = 0; i < str.length(); i++) {
    c = str.charAt(i);
    if (c == ' ') {
      encodedString += '+';
    } else if (isalnum(c)) {
      encodedString += c;
    } else {
      code1 = (c & 0xf) + '0';
      if ((c & 0xf) > 9) {
        code1 = (c & 0xf) - 10 + 'A';
      }
      c = (c >> 4) & 0xf;
      code0 = c + '0';
      if (c > 9) {
        code0 = c - 10 + 'A';
      }
      code2 = '\0';
      encodedString += '%';
      encodedString += code0;
      encodedString += code1;
      //encodedString+=code2;
    }
    yield();
  }
  return encodedString;
}


int getActivePlayerId() {

  int ret = 0;

  String jsonString;
  StaticJsonDocument<256> jsonBuffer;
  JsonObject root = jsonBuffer.to<JsonObject>();

  root["method"] = "Player.GetActivePlayers";
  root["id"] = "1";
  root["jsonrpc"] = "2.0";
  serializeJson(root, kodi_request, 255);

  if (WiFi.status() == WL_CONNECTED) {

    char serverName[255] = "http://";
    strcat(serverName, kodihost);
    Serial.println(serverName);
    strcat(serverName, ":");
    strcat(serverName, kodiport);
    strcat(serverName, "/jsonrpc");
    Serial.println(serverName);
    strcat(serverName, "?request=");
    String url = serverName;
    url += urlencode(kodi_request);
    Serial.println(url);
    kodi_http.begin(client, url);
    int httpCode = kodi_http.GET();
    if (httpCode > 0) {

      if (httpCode == HTTP_CODE_OK) {
        String payload = kodi_http.getString();
        StaticJsonDocument<256> jsonResult;
        auto error = deserializeJson(jsonResult, payload);
        if (!error) {
          if (jsonResult.containsKey("result")) {
            int counter[10];
            JsonArray array = jsonResult.as<JsonArray>();
            if (array.size() > 0) {
              for (JsonVariant i : array) {
                JsonObject resultObject = jsonResult["result"][i];
                if (resultObject.containsKey("playerid") && resultObject.containsKey("type")) {
                  int playerid = resultObject["playerid"].as<int>();
                  String type = resultObject["type"].as<String>();
                  Serial.print("PlayerID: ");
                  Serial.println(playerid);
                  //if ((playerid > 0) && (type == "video")) {
                  //  ret = playerid;
                  //  break;
                  //}
                }
              }
            }
          }
        }
      }
    }
    kodi_http.end();
  }
  yield();
  return ret;
}

byte getPlayerStatus() {

  byte ret = 0;

  String jsonString;
  StaticJsonDocument<256> jsonBuffer;
  JsonObject root = jsonBuffer.to<JsonObject>();

  root["method"] = "XBMC.GetInfoBooleans";
  root["id"] = "1";
  root["jsonrpc"] = "2.0";
  root["params"]["booleans"][0] = "Player.Playing";
  root["params"]["booleans"][1] = "Player.Paused";
  serializeJson(root, kodi_request, 255);

  if (WiFi.status() == WL_CONNECTED) {
    char serverName[255] = "http://";
    strcat(serverName, kodihost);
    Serial.println(serverName);
    strcat(serverName, ":");
    strcat(serverName, kodiport);
    strcat(serverName, "/jsonrpc");
    Serial.println(serverName);
    strcat(serverName, "?request=");
    String url = serverName;
    url += urlencode(kodi_request);
    Serial.println(url);
    kodi_http.begin(client, url);
    int httpCode = kodi_http.GET();
    if (httpCode > 0) {
      if (httpCode == HTTP_CODE_OK) {
        String payload = kodi_http.getString();
        StaticJsonDocument<256> jsonResult;
        auto error = deserializeJson(jsonResult, payload);
        if (!error) {
          if (jsonResult.containsKey("result")) {
            JsonObject resultObject = jsonResult["result"];
            if (resultObject.containsKey("Player.Playing")) {
              bool play = jsonResult["result"]["Player.Playing"].as<bool>();
              if (play) {
                ret = 1;
              }
            }
            if (resultObject.containsKey("Player.Paused")) {
              bool paused = jsonResult["result"]["Player.Paused"].as<bool>();
              if (paused) {
                ret = 2;
              }
            }
          }
        }
      }
    }
    kodi_http.end();
  }
  yield();
  return ret;
}



/**
   Helper routine to dump a byte array as hex values to Serial.
*/
void printHex(byte *buffer, byte bufferSize) {
  for (byte i = 0; i < bufferSize; i++) {
    Serial.print(((buffer[i]) >> 4) & 0x0F,  HEX);
    Serial.print(buffer[i] & 0x0F, HEX);
    Serial.print(" ");
  }
}

// This convenience function could be added to the library in the future

/**
   Returns true if a PICC responds to PICC_CMD_WUPA.
   All cards in state IDLE or HALT are invited.

   @return bool
*/
bool PICC_IsAnyCardPresent() {
  byte bufferATQA[2];
  byte bufferSize = sizeof(bufferATQA);

  // Reset baud rates
  mfrc522.PCD_WriteRegister(mfrc522.TxModeReg, 0x00);
  mfrc522.PCD_WriteRegister(mfrc522.RxModeReg, 0x00);
  // Reset ModWidthReg
  mfrc522.PCD_WriteRegister(mfrc522.ModWidthReg, 0x26);

  MFRC522::StatusCode result = mfrc522.PICC_WakeupA(bufferATQA, &bufferSize);
  return (result == MFRC522::STATUS_OK || result == MFRC522::STATUS_COLLISION);
} // End PICC_IsAnyCardPresent()

Wenn ich in check_Buttons() den http-Request nicht mache, dann klappt das mit dem Button. Der Text wird nur einmal angezeigt. Scheinbar wird dadurch eine Verzögerung / Blockade? eingefügt, die die Button Abfrage stört…

Wie kann ich das alternativ machen? Button abfragen und Aktion einmalig ausführen?

Falls noch jemand mitliest, es ist fertig, zumindest der Code…
Die ezButtons-Bibliothek habe ich verworfen und die Button Abfrage von Espuino genommen, jetzt geht alles wie gewünscht.
Play/Stop durch Auflegen des Tags sowie extra Buttons für Pause, Next und Lautstärke.

Es geht sicher alles noch schöner und aufgeräumter, auch können diverse „Ausnahmen“ auftreten, die noch nicht abgefangen sind.

#include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include <WiFi.h>
#include <ESP32Ping.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include "NfcAdapter.h"

#define KODIHOST      "192.168.1.140"

#define NEXT_BUTTON                      33
#define PAUSEPLAY_BUTTON                 32
#define VOLUME_UP                        26
#define VOLUME_DOWN                      25



typedef struct {
  bool lastState : 1;
  bool currentState : 1;
  bool isPressed : 1;
  bool isReleased : 1;
  unsigned long lastPressedTimestamp;
  unsigned long lastReleasedTimestamp;
  unsigned long firstPressedTimestamp;
} t_button;

bool gButtonInitComplete = false;
t_button gButtons[4];         // Next + PausPlay + VolUp + VolDown

static volatile SemaphoreHandle_t Button_TimerSemaphore;

hw_timer_t *Button_Timer = NULL;
static void IRAM_ATTR onTimer();
static void Button_DoButtonActions(void);



const char *ssid = "Kodi Json Remote";
const char *password = "*******";

char *kodihost;
char *kodiport = "8080";
char *json_method;
char kodi_request[255];

IPAddress local_IP(192, 168, 1, 141);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

byte pStatus2 = -1;

WiFiClient client;
HTTPClient kodi_http;

static MFRC522 mfrc522(21, 99);   // Create MFRC522 instance
NfcAdapter nfc = NfcAdapter(&mfrc522);


uint8_t buf[18];
uint8_t size = sizeof(buf);
uint8_t uid[7];
uint8_t uidLength;
byte payload[300];


unsigned long prevTime = millis();


void Rfid_Init(void) {
  SPI.begin(18, 19, 23, 21);
  SPI.setFrequency(1000000);
  mfrc522.PCD_Init();
  mfrc522.PCD_SetAntennaGain(0x07 << 4);
  mfrc522.uid.size = 0;
  nfc.begin();
  delay(50);
}

void setup()
{
  kodihost = KODIHOST;
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  if (!WiFi.config(local_IP, gateway, subnet)) {
    Serial.println("STA Failed to configure");
  }
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  bool success = false;
  while (!success) {
    Serial.println("Warte auf Kodi-Host...");
    delay(1000);
    success = Ping.ping(kodihost, 3);
  }
  Serial.println("Kodi-Host online!");
  Button_Init();
  Rfid_Init();
}

bool locked = false;

void loop() {
  // Wake up all cards present within the sensor/reader range.
  bool cardPresent = PICC_IsAnyCardPresent();

  // Reset the loop if no card was locked an no card is present.
  // This saves the select process when no card is found.
  if (! locked && ! cardPresent)
    return;
  // When a card is present (locked) the rest ahead is intensive (constantly checking if still present).
  // Consider including code for checking only at time intervals.


  // Ask for the locked card (if rfid.uid.size > 0) or for any card if none was locked.
  // (Even if there was some error in the wake up procedure, attempt to contact the locked card.
  // This serves as a double-check to confirm removals.)
  // If a card was locked and now is removed, other cards will not be selected until next loop,
  // after rfid.uid.size has been set to 0.
  MFRC522::StatusCode result = mfrc522.PICC_Select(&mfrc522.uid, 8 * mfrc522.uid.size);

  if (!locked && result == MFRC522::STATUS_OK)  {
    locked = true;
    // Action on card detection.
    Serial.print(F("locked! NUID tag: "));
    printHex(mfrc522.uid.uidByte, mfrc522.uid.size);
    Serial.println();
    Serial.println("Starte Wiedergabe...");
    readTagPayload_and_startPlayback();

  } else if (locked && result != MFRC522::STATUS_OK)  {
    locked = false;
    mfrc522.uid.size = 0;
    // Action on card removal.
    Serial.print(F("unlocked! Reason for unlocking: "));
    Serial.println(mfrc522.GetStatusCodeName(result));
    //Wenn Tag entfernt, stoppe die Wiedergabe
    kodi_json("Player.Stop");
  } else if (!locked && result != MFRC522::STATUS_OK) {
    // Clear locked card data just in case some data was retrieved in the select procedure
    // but an error prevented locking.
    mfrc522.uid.size = 0;
  }

  Button_Cyclic();
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
}

void Button_Init() {
  pinMode(NEXT_BUTTON, INPUT_PULLUP);
  pinMode(PAUSEPLAY_BUTTON, INPUT_PULLUP);
  pinMode(VOLUME_UP, INPUT_PULLUP);
  pinMode(VOLUME_DOWN, INPUT_PULLUP);

  // Create 1000Hz-HW-Timer (currently only used for buttons)
  Button_TimerSemaphore = xSemaphoreCreateBinary();
  Button_Timer = timerBegin(0, 240, true); // Prescaler: CPU-clock in MHz
  timerAttachInterrupt(Button_Timer, &onTimer, true);
  timerAlarmWrite(Button_Timer, 10000, true); // 100 Hz
  timerAlarmEnable(Button_Timer);
}

// If timer-semaphore is set, read buttons (unless controls are locked)
void Button_Cyclic() {
  if (xSemaphoreTake(Button_TimerSemaphore, 0) == pdTRUE) {
    unsigned long currentTimestamp = millis();
    gButtons[0].currentState = digitalRead(NEXT_BUTTON);
    gButtons[1].currentState = digitalRead(PAUSEPLAY_BUTTON);
    gButtons[2].currentState = digitalRead(VOLUME_UP);
    gButtons[3].currentState = digitalRead(VOLUME_DOWN);

    // Iterate over all buttons in struct-array
    for (uint8_t i = 0; i < sizeof(gButtons) / sizeof(gButtons[0]); i++) {
      if (gButtons[i].currentState != gButtons[i].lastState && currentTimestamp - gButtons[i].lastPressedTimestamp > 50) {
        if (!gButtons[i].currentState) {
          gButtons[i].isPressed = true;
          gButtons[i].lastPressedTimestamp = currentTimestamp;
          if (!gButtons[i].firstPressedTimestamp) {
            gButtons[i].firstPressedTimestamp = currentTimestamp;
          }
        } else {
          gButtons[i].isReleased = true;
          gButtons[i].lastReleasedTimestamp = currentTimestamp;
          gButtons[i].firstPressedTimestamp = 0;
        }
      }
      gButtons[i].lastState = gButtons[i].currentState;
    }
  }
  gButtonInitComplete = true;
  Button_DoButtonActions();
}

void Button_DoButtonActions(void) {
  for (uint8_t i = 0; i < sizeof(gButtons) / sizeof(gButtons[0]); i++) {
    if (gButtons[i].isPressed) {
      if (gButtons[i].lastReleasedTimestamp > gButtons[i].lastPressedTimestamp) {
        switch (i) { // Short-press-actions
          case 0:
            Serial.println("The button Next is pressed");
            kodi_json("Player.GoTo");
            gButtons[i].isPressed = false;
            break;

          case 1:
            Serial.println("The button PausePlay is pressed");
            kodi_json("Player.PlayPause");
            gButtons[i].isPressed = false;
            break;

          case 2:
            Serial.println("The button VolUp is pressed");
            kodi_json("Application.SetVolume:increment");
            gButtons[i].isPressed = false;
            break;

          case 3:
            Serial.println("The button VolDown is pressed");
            kodi_json("Application.SetVolume:decrement");
            gButtons[i].isPressed = false;
            break;
        }
      }
    }
  }
}

void IRAM_ATTR onTimer() {
  xSemaphoreGiveFromISR(Button_TimerSemaphore, NULL);
}


void readTagPayload_and_startPlayback()
{
  Serial.println("Reading NFC tag");
  NfcTag tag = nfc.read();
  Serial.println(tag.getTagType());
  Serial.print("UID: "); Serial.println(tag.getUidString());
  if (tag.hasNdefMessage()) // every tag won't have a message
  {
    NdefMessage message = tag.getNdefMessage();
    Serial.print("\nThis NFC Tag contains an NDEF Message with ");
    Serial.print(message.getRecordCount());
    Serial.print(" NDEF Record");
    if (message.getRecordCount() != 1) {
      Serial.print("s");
    }
    Serial.println(".");
    // cycle through the records, printing some info from each
    int recordCount = message.getRecordCount();
    for (int i = 0; i < recordCount; i++)
    {
      NdefRecord record = message.getRecord(i);
      int payloadLength = record.getPayloadLength();
      const byte *payload = record.getPayload();
      String payloadAsString = "";
      for (int c = 0; c < payloadLength; c++) {
        payloadAsString += (char)payload[c];
      }
      Serial.print("  Payload (as String): ");
      payloadAsString.remove(0, 3);
      Serial.println(payloadAsString);

      int str_len = payloadAsString.length() + 1;
      char char_array[str_len];
      payloadAsString.toCharArray(char_array, str_len);
      //start Playback
      kodi_json(char_array);
    }
  }
}


void kodi_json(char *action) {
  /*
    NTAG213-Textfeld Beispiele
    "Player.Open:movieid:41";  //für Filme die :movieid:
    oder...
    "Player.Open:file:/storage/765E-9256/Playlists/Within Temptation.m3u";  //für Playlisten :file: verwenden
    oder...
    "Player.Open:directory:/storage/765E-9256/Chainreactor"; //für Ordner :directory: verwenden

    ...für die Buttons...
    "Application.SetVolume:increment";
    "Application.SetVolume:decrement";
  */
  char * token;
  Serial.println(action);
  StaticJsonDocument<256> kodi_doc;
  JsonObject kodi_json_object = kodi_doc.to<JsonObject>();
  kodi_json_object["jsonrpc"] = "2.0";
  kodi_json_object["id"] = millis();


  printf("kodiHost: %s\n", kodihost);
  token = strtok(strdup(action), ":" );
  printf("Token: %s\n", token);
  json_method = token;
  if (strcmp(token, "Player.Open") == 0)  {
    Serial.println("Method ist Player.Open!");
    kodi_json_object["method"] = json_method;
    token = strtok( NULL, ":" );
    if (strcmp(token, "file") == 0)   {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["file"] = token;
    }
    else if (strcmp(token, "directory") == 0)    {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["directory"] = token;
    }
    else if (strcmp(token, "movieid") == 0)    {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["movieid"] = token;
    }
  }
  else if (strcmp(token, "Application.SetVolume") == 0)  {
    Serial.println("Method ist Application.SetVolume!");
    kodi_json_object["method"] = json_method;
    token = strtok(NULL, ":" );
    kodi_json_object["params"]["volume"] = token;
  }
  else if (strcmp(token, "Player.PlayPause") == 0) {
    kodi_json_object["method"] = json_method;
    kodi_json_object["params"]["playerid"] = 0;
  }
  else if (strcmp(token, "Player.Stop") == 0) {
    kodi_json_object["method"] = json_method;
    kodi_json_object["params"]["playerid"] = 0;
  }
  else if (strcmp(token, "Player.GoTo") == 0) {
    kodi_json_object["method"] = json_method;
    kodi_json_object["params"]["playerid"] = 0;
    kodi_json_object["params"]["to"] = "next";
  }
  else  {
    Serial.println("Ungültige JSON Angabe");
    exit(1);
  }
  serializeJson(kodi_doc, kodi_request, 255);
  Serial.print("Kodi Request: ");
  Serial.println(kodi_request);

  char serverName[255] = "http://";
  strcat(serverName, kodihost);
  strcat(serverName, ":");
  strcat(serverName, kodiport);
  strcat(serverName, "/jsonrpc");
  Serial.println(serverName);
  Serial.println("HTTP POST");
  kodi_http.addHeader("Content-Type", "application/jsonrpc");
  kodi_http.begin(client, serverName);
  int httpResponseCode = kodi_http.POST(kodi_request);
  Serial.print("HTTP Response code: ");
  Serial.println(httpResponseCode);
  // Free resources
  kodi_http.end();

}


/**
   Helper routine to dump a byte array as hex values to Serial.
*/
void printHex(byte *buffer, byte bufferSize) {
  for (byte i = 0; i < bufferSize; i++) {
    Serial.print(((buffer[i]) >> 4) & 0x0F,  HEX);
    Serial.print(buffer[i] & 0x0F, HEX);
    Serial.print(" ");
  }
}

// This convenience function could be added to the library in the future

/**
   Returns true if a PICC responds to PICC_CMD_WUPA.
   All cards in state IDLE or HALT are invited.

   @return bool
*/
bool PICC_IsAnyCardPresent() {
  byte bufferATQA[2];
  byte bufferSize = sizeof(bufferATQA);

  // Reset baud rates
  mfrc522.PCD_WriteRegister(mfrc522.TxModeReg, 0x00);
  mfrc522.PCD_WriteRegister(mfrc522.RxModeReg, 0x00);
  // Reset ModWidthReg
  mfrc522.PCD_WriteRegister(mfrc522.ModWidthReg, 0x26);

  MFRC522::StatusCode result = mfrc522.PICC_WakeupA(bufferATQA, &bufferSize);
  return (result == MFRC522::STATUS_OK || result == MFRC522::STATUS_COLLISION);
} // End PICC_IsAnyCardPresent()
2 „Gefällt mir“

Nabend…

nachdem mein Umzug mich eine ganze Weile vom Projekt abgehalten hatte, konnte nun endlich das Gehäuse dafür fertig stellen.
Es ist soweit modular aufgebaut, schon allein wegen besserer Druckbarkeit. Es passt eine CD-Hülle hinein, auf welcher hinten der Tag klebt. Ich rippe gerade die CDs vom Heimbewohner und dann geht es an den Feldtest.



Der Code wurde auch noch ein wenig abgeändert und funktioniert bisher ohne Probleme.

#include <Arduino.h>
#include "freertos/FreeRTOS.h"
#include <WiFi.h>
#include <ESP32Ping.h>
#include <ArduinoJson.h>
#include <HTTPClient.h>
#include <SPI.h>
#include <MFRC522.h>
#include "NfcAdapter.h"
#include <Adafruit_NeoPixel.h>

#define KODIHOST      "192.168.1.140"

#define NEXT_BUTTON                      32
#define PAUSEPLAY_BUTTON                 33
#define VOLUME_UP                        14
#define VOLUME_DOWN                      25

#define RFID_SCAN_INTERVAL               100

#define LED_PIN                          27

Adafruit_NeoPixel pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800);

typedef struct {
  bool lastState : 1;
  bool currentState : 1;
  bool isPressed : 1;
  bool isReleased : 1;
  unsigned long lastPressedTimestamp;
  unsigned long lastReleasedTimestamp;
  unsigned long firstPressedTimestamp;
} t_button;

bool gButtonInitComplete = false;
t_button gButtons[4];         // Next + PausPlay + VolUp + VolDown

static volatile SemaphoreHandle_t Button_TimerSemaphore;

hw_timer_t *Button_Timer = NULL;
static void IRAM_ATTR onTimer();
static void Button_DoButtonActions(void);



const char *ssid = "Kodi Json Remote";
const char *password = "******";

char *kodihost;
char *kodiport = "8080";
char *json_method;
char kodi_request[255];

IPAddress local_IP(192, 168, 1, 141);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

byte pStatus2 = -1;

WiFiClient client;
HTTPClient kodi_http;

static MFRC522 mfrc522(21, 99);   // Create MFRC522 instance
NfcAdapter nfc = NfcAdapter(&mfrc522);


uint8_t buf[18];
uint8_t size = sizeof(buf);
uint8_t uid[7];
uint8_t uidLength;
byte payload[300];


unsigned long prevTime = millis();


void Rfid_Init(void) {
  SPI.begin(18, 19, 23, 21);
  SPI.setFrequency(1000000);
  mfrc522.PCD_Init();
  mfrc522.PCD_SetAntennaGain(0x05 << 4);
  mfrc522.PCD_AntennaOn();
  mfrc522.uid.size = 0;
  nfc.begin();
  delay(50);
}

void setup()
{
  pixels.begin();
  pixels.clear();
  pixels.setBrightness(5);
  kodihost = KODIHOST;
  Serial.begin(115200);
  WiFi_Init();
  Button_Init();
  Rfid_Init();

}

bool locked = false;

void loop() {
  Button_Cyclic();
  if (Ping.ping(kodihost, 1) == false) {
      ESP.restart();
    }

  // Wake up all cards present within the sensor/reader range.
  bool cardPresent = PICC_IsAnyCardPresent();
  //bool cardPresent = mfrc522.PICC_IsNewCardPresent();

  // Reset the loop if no card was locked an no card is present.
  // This saves the select process when no card is found.
  if (! locked && ! cardPresent)
    return;
  // When a card is present (locked) the rest ahead is intensive (constantly checking if still present).
  // Consider including code for checking only at time intervals.


  // Ask for the locked card (if rfid.uid.size > 0) or for any card if none was locked.
  // (Even if there was some error in the wake up procedure, attempt to contact the locked card.
  // This serves as a double-check to confirm removals.)
  // If a card was locked and now is removed, other cards will not be selected until next loop,
  // after rfid.uid.size has been set to 0.
  MFRC522::StatusCode result = mfrc522.PICC_Select(&mfrc522.uid, 8 * mfrc522.uid.size);
    if (!locked && result == MFRC522::STATUS_OK)  {
      locked = true;
      // Action on card detection.
      Serial.print(F("locked! NUID tag: "));
      printHex(mfrc522.uid.uidByte, mfrc522.uid.size);
      Serial.println();
      Serial.println("Starte Wiedergabe...");
      readTagPayload_and_startPlayback();

    } else if (locked && result != MFRC522::STATUS_OK)  {
      locked = false;
      mfrc522.uid.size = 0;
      // Action on card removal.
      Serial.print(F("unlocked! Reason for unlocking: "));
      Serial.println(mfrc522.GetStatusCodeName(result));
      //Wenn Tag entfernt, stoppe die Wiedergabe
      kodi_json("Player.Stop");
    } else if (!locked && result != MFRC522::STATUS_OK) {
      // Clear locked card data just in case some data was retrieved in the select procedure
      // but an error prevented locking.
      mfrc522.uid.size = 0;
    }

    mfrc522.PICC_HaltA();
    mfrc522.PCD_StopCrypto1();

}

void WiFi_Init() {
  pixels.setPixelColor(0, pixels.Color(255, 0, 0));
  pixels.show();
  Serial.print("Connecting to ");
  Serial.println(ssid);
  if (!WiFi.config(local_IP, gateway, subnet)) {
    Serial.println("STA Failed to configure");
  }
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());
  bool success = false;
  pixels.setPixelColor(0, pixels.Color(255, 69, 0));
  pixels.show();
  while (!success) {
    Serial.println("Warte auf Kodi-Host...");
    delay(1000);
    success = Ping.ping(kodihost, 3);
  }
  Serial.println("Kodi-Host online!");
  pixels.setPixelColor(0, pixels.Color(0, 255, 0));
  pixels.show();
}

void Button_Init() {
  pinMode(NEXT_BUTTON, INPUT_PULLUP);
  pinMode(PAUSEPLAY_BUTTON, INPUT_PULLUP);
  pinMode(VOLUME_UP, INPUT_PULLUP);
  pinMode(VOLUME_DOWN, INPUT_PULLUP);

  // Create 1000Hz-HW-Timer (currently only used for buttons)
  Button_TimerSemaphore = xSemaphoreCreateBinary();
  Button_Timer = timerBegin(0, 240, true); // Prescaler: CPU-clock in MHz
  timerAttachInterrupt(Button_Timer, &onTimer, true);
  timerAlarmWrite(Button_Timer, 10000, true); // 100 Hz
  timerAlarmEnable(Button_Timer);
}

// If timer-semaphore is set, read buttons (unless controls are locked)
void Button_Cyclic() {
  if (xSemaphoreTake(Button_TimerSemaphore, 0) == pdTRUE) {
    unsigned long currentTimestamp = millis();
    gButtons[0].currentState = digitalRead(NEXT_BUTTON);
    gButtons[1].currentState = digitalRead(PAUSEPLAY_BUTTON);
    gButtons[2].currentState = digitalRead(VOLUME_UP);
    gButtons[3].currentState = digitalRead(VOLUME_DOWN);

    // Iterate over all buttons in struct-array
    for (uint8_t i = 0; i < sizeof(gButtons) / sizeof(gButtons[0]); i++) {
      if (gButtons[i].currentState != gButtons[i].lastState && currentTimestamp - gButtons[i].lastPressedTimestamp > 50) {
        if (!gButtons[i].currentState) {
          gButtons[i].isPressed = true;
          gButtons[i].lastPressedTimestamp = currentTimestamp;
          if (!gButtons[i].firstPressedTimestamp) {
            gButtons[i].firstPressedTimestamp = currentTimestamp;
          }
        } else {
          gButtons[i].isReleased = true;
          gButtons[i].lastReleasedTimestamp = currentTimestamp;
          gButtons[i].firstPressedTimestamp = 0;
        }
      }
      gButtons[i].lastState = gButtons[i].currentState;
    }
  }
  gButtonInitComplete = true;
  Button_DoButtonActions();
}

void Button_DoButtonActions(void) {
  for (uint8_t i = 0; i < sizeof(gButtons) / sizeof(gButtons[0]); i++) {
    if (gButtons[i].isPressed) {
      if (gButtons[i].lastReleasedTimestamp > gButtons[i].lastPressedTimestamp) {
        switch (i) { // Short-press-actions
          case 0:
            Serial.println("The button Next is pressed");
            kodi_json("Player.GoTo");
            gButtons[i].isPressed = false;
            break;

          case 1:
            Serial.println("The button PausePlay is pressed");
            kodi_json("Player.PlayPause");
            gButtons[i].isPressed = false;
            break;

          case 2:
            Serial.println("The button VolUp is pressed");
            kodi_json("Application.SetVolume:increment");
            gButtons[i].isPressed = false;
            break;

          case 3:
            Serial.println("The button VolDown is pressed");
            kodi_json("Application.SetVolume:decrement");
            gButtons[i].isPressed = false;
            break;
        }
      }
    }
  }
}

void IRAM_ATTR onTimer() {
  xSemaphoreGiveFromISR(Button_TimerSemaphore, NULL);
}


void readTagPayload_and_startPlayback()
{
  Serial.println("Reading NFC tag");
  NfcTag tag = nfc.read();
  Serial.println(tag.getTagType());
  Serial.print("UID: "); Serial.println(tag.getUidString());
  if (tag.hasNdefMessage()) // every tag won't have a message
  {
    NdefMessage message = tag.getNdefMessage();
    Serial.print("\nThis NFC Tag contains an NDEF Message with ");
    Serial.print(message.getRecordCount());
    Serial.print(" NDEF Record");
    if (message.getRecordCount() != 1) {
      Serial.print("s");
    }
    Serial.println(".");
    // cycle through the records, printing some info from each
    int recordCount = message.getRecordCount();
    for (int i = 0; i < recordCount; i++)
    {
      NdefRecord record = message.getRecord(i);
      int payloadLength = record.getPayloadLength();
      const byte *payload = record.getPayload();
      String payloadAsString = "";
      for (int c = 0; c < payloadLength; c++) {
        payloadAsString += (char)payload[c];
      }
      Serial.print("  Payload (as String): ");
      if (payloadAsString == "") {
        Serial.println("Tag empty -Nothing to play!");
        break;
      }
      payloadAsString.remove(0, 3);
      Serial.println(payloadAsString);

      int str_len = payloadAsString.length() + 1;
      char char_array[str_len];
      payloadAsString.toCharArray(char_array, str_len);
      //start Playback
      kodi_json(char_array);
    }
  }
}


void kodi_json(char *action) {
  /*
    NTAG213-Textfeld Beispiele
    "Player.Open:movieid:41";  //für Filme die :movieid:
    oder...
    "Player.Open:file:/storage/765E-9256/Playlists/Within Temptation.m3u";  //für Playlisten :file: verwenden
    oder...
    "Player.Open:directory:/storage/765E-9256/Chainreactor"; //für Ordner :directory: verwenden

    ...für die Buttons...
    "Application.SetVolume:increment";
    "Application.SetVolume:decrement";
  */
  char * token;
  Serial.println(action);
  StaticJsonDocument<256> kodi_doc;
  JsonObject kodi_json_object = kodi_doc.to<JsonObject>();
  kodi_json_object["jsonrpc"] = "2.0";
  kodi_json_object["id"] = millis();


  printf("kodiHost: %s\n", kodihost);
  token = strtok(strdup(action), ":" );
  printf("Token: %s\n", token);
  json_method = token;
  if (strcmp(token, "Player.Open") == 0)  {
    Serial.println("Method ist Player.Open!");
    kodi_json_object["method"] = json_method;
    token = strtok( NULL, ":" );
    if (strcmp(token, "file") == 0)   {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["file"] = token;
    }
    else if (strcmp(token, "directory") == 0)    {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["directory"] = token;
    }
    else if (strcmp(token, "movieid") == 0)    {
      token = strtok(NULL, ":" );
      kodi_json_object["params"]["item"]["movieid"] = token;
    }
  }
  else if (strcmp(token, "Application.SetVolume") == 0)  {
    Serial.println("Method ist Application.SetVolume!");
    kodi_json_object["method"] = json_method;
    token = strtok(NULL, ":" );
    kodi_json_object["params"]["volume"] = token;
  }
  else if (strcmp(token, "Player.PlayPause") == 0) {
    kodi_json_object["method"] = json_method;
    kodi_json_object["params"]["playerid"] = 0;
  }
  else if (strcmp(token, "Player.Stop") == 0) {
    kodi_json_object["method"] = json_method;
    kodi_json_object["params"]["playerid"] = 0;
  }
  else if (strcmp(token, "Player.GoTo") == 0) {
    kodi_json_object["method"] = json_method;
    kodi_json_object["params"]["playerid"] = 0;
    kodi_json_object["params"]["to"] = "next";
  }
  else  {
    Serial.println("Ungültige JSON Angabe");
    exit(1);
  }
  serializeJson(kodi_doc, kodi_request, 255);
  Serial.print("Kodi Request: ");
  Serial.println(kodi_request);

  char serverName[255] = "http://";
  strcat(serverName, kodihost);
  strcat(serverName, ":");
  strcat(serverName, kodiport);
  strcat(serverName, "/jsonrpc");
  Serial.println(serverName);
  Serial.println("HTTP POST");
  kodi_http.addHeader("Content-Type", "application/jsonrpc");
  kodi_http.begin(client, serverName);
  int httpResponseCode = kodi_http.POST(kodi_request);
  Serial.print("HTTP Response code: ");
  Serial.println(httpResponseCode);
  if (httpResponseCode== -1) ESP.restart();
  // Free resources
  kodi_http.end();

}

void set_pixel(byte red, byte green, byte blue) {
  pixels.clear();
  pixels.setPixelColor(0, pixels.Color(red, green, blue));
  pixels.show();
}


/**
   Helper routine to dump a byte array as hex values to Serial.
*/
void printHex(byte *buffer, byte bufferSize) {
  for (byte i = 0; i < bufferSize; i++) {
    Serial.print(((buffer[i]) >> 4) & 0x0F,  HEX);
    Serial.print(buffer[i] & 0x0F, HEX);
    Serial.print(" ");
  }
}

// This convenience function could be added to the library in the future

/**
   Returns true if a PICC responds to PICC_CMD_WUPA.
   All cards in state IDLE or HALT are invited.

   @return bool
*/
bool PICC_IsAnyCardPresent() {
  byte bufferATQA[2];
  byte bufferSize = sizeof(bufferATQA);

  // Reset baud rates
  mfrc522.PCD_WriteRegister(mfrc522.TxModeReg, 0x00);
  mfrc522.PCD_WriteRegister(mfrc522.RxModeReg, 0x00);
  // Reset ModWidthReg
  mfrc522.PCD_WriteRegister(mfrc522.ModWidthReg, 0x26);

  MFRC522::StatusCode result = mfrc522.PICC_WakeupA(bufferATQA, &bufferSize);
  return (result == MFRC522::STATUS_OK || result == MFRC522::STATUS_COLLISION);
} // End PICC_IsAnyCardPresent()
3 „Gefällt mir“