ESP32 + RFID + (JSON) Kodi Steuerung

Moin,

ich fange die Frage mal mit dem Hintergrund dazu an…
Ich habe für meine Großmutter eine Kodi-Remote Box mit einem 7-Segment-Display und einem Tastenfeld gebaut, gefrickelt und gedruckt (auf thingiverse zu finden). Sie gibt von einer Film-Liste die Nummer des Films ein und mittels Stern/Raute wird der Film gestartet/gestoppt, der TV jeweils per Infrarot umgeschaltet HDMI/TV. Die Filme liegen auch auf HDD.

Ich arbeite im Pflegeheim, und ein Bewohner ist handtechnisch ziemlich eingeschränkt. Bisher hatte er noch DVD-Player und Stereoanlage, beides ist nun kaputt und auch seine Fähigkeiten der verbleibenden Hand lassen immer weiter nach. Ziemlich schlimm für jemanden der immer gern Filme geschaut und Musik gehört hat.

Jetzt zur Idee: Ich würde die Alben rippen und auf eine ext. HDD an eine Kodi-Box anschließen. Ähnlich der Zahlen-Box für Großmutter, stelle ich mir hier aber die RFID Lösung vor. Seine Album-Cover könnten in Folie geschweißt werden mit einem RFID Aufkleber drauf.
Jetzt ist die Frage, läßt sich ESPuino soweit abändern, dass es statt die Musik zu starten, ein JSON Befehl an Kodi sendet, oder wäre es besser bei NULL anzufangen? Mir geht es speziell um die einfache ID / Titel/Album Zuordnung und Verwaltung.
Gibt es evtl. schon ein Projekt in diese Richtung oder hat jemand einen Vorschlag zur Herangehensweise?

MfG & Danke
Carsten

Hallo Carsten,

die „Kodi-Box“ ist doch vermutlich ein Raspberry Pi, oder?
Meine Idee wäre folgende: ESPuino unterstützt ja MQTT und wenn eine Karte aufgelegt wurde, dann wird über das Topic, welches in topicRfidState konfiguriert wurde, die ID der aktuell aufgelegten Karte gepusht (12 stellige Nummer). Solltest du eh einen Raspberry Pi haben, dann kannst du dort mit mosquitto einen MQTT-Broker installieren. Da muss man nix konfigurieren: Den installiert und startet man einfach.
Der ESPuino registriert sich dort als Sender und Empfänger. Und dann brauchst du noch ein Script, welches als Empfänger am MQTT-Broker fungiert und dann eben auf das o.g. Topic hört.
Im Script würdest du dann eben ein Mapping machen zu einer beliebigen Aktion. Z.B.: Kommt ein Push für RFID 123456789012 rein, dann schicke ein JSON xyz an was auch immer.
Anlernen müsstest du Karten in ESPuino dann gar nicht mehr, aber dein Script braucht natürlich eine entsprechende Zuordnungstabelle, damit es die Transformation durchführen kann.

Einziges Problem: Du müsstest im Code ausbauen, dass der ESP32 in den Deepsleep geht.

Dein Vorschlag wäre die Blitzlösung, wenn ich einen Pi hätte. Aber nein, ich nutze einen Wemos D1 mini lite für Großmutters Box. https://www.thingiverse.com/thing:5401172
Die Kodi Box ist eine X96 Max Plus S905x3 Box auf Coreelec.

Einen Pi wollte ich nicht nutzen, da der Bew. die Steckdosenleiste zur Nacht ausgeschaltet haben will und das ganze mit PI auch zu teuer wird, hat zwar Verwahrgeld, aber das ist nicht so üppig. MQTT und Scripte usw. wären zwar einfach, aber auch zu overpowered.

Mir fehlt eine Idee, wie ich die Zuordnung von RFID Karte zu Album (Playlist) einfach umsetze. Schön
wäre ein Webinterface, aber ich denke, dass auch eine einfache Textdatei mit der Zuordnung reicht.
„RFID-Code“ - „Album-Playlist.m3u“ Und Kodi kann ja mit Playlisten umgehen.
Es kommen ja auch glaube keine neuen Alben mehr hinzu.
Werde mir noch ein SD-Card Modul holen und damit probieren.
Achso, und das ganze soll über USB-Netzteil betrieben werden.

Vielleicht kommt mir noch die eine erleuchtende Idee, momentan schlaucht einfach die Arbeit.

Naja also theoretisch könnte man vielleicht auch den Modus Webradio nehmen und dort über die WebGUI, anstelle einer URL, halt einen JSON-String eintragen. Der sollte dann aber schon kurz sein (kleiner 255 Zeichen).
Und dann, vor der Zeile ESPuino/AudioPlayer.cpp at fff303c953a715b9fc45c12d40f352dc9e56d9ec · biologist79/ESPuino · GitHub den JSON-String abschicken. ESPuino wird dann direkt in einen Fehler laufen, weil der JSON-String halt nicht als URL parsbar ist und die Audio-Instanz dann nix abspielen kann, aber das sollte dann nicht stören. Genau genommen macht das sogar Sinn, weil sonst bringst du das ganze Zustands-Management durcheinander.

Aber auch hier: Musst dafür sorgen, dass er sich nicht abschaltet. Oder musst, wem auch immer, beibringen, dass das Ding erst booten muss :slight_smile:

Das wollte ich heute probieren, aber soweit komme ich gar nicht und laufe in einen mir nicht erklärbaren Fehler.
ESP ist ein DEVKIT v1, SD-Modul (Catalex Clone) ist per SPI angeschlossen nach Anleitung, RFID Modul noch nicht dran.
CS - D15
SCK - D14
MOSI - D13
MISO - D16(RX2)

Leider kommt

[E][sd_diskio.cpp:123] sdSelectCard(): Select Failed
[E][sd_diskio.cpp:775] sdcard_mount(): f_mount failed: (3) The physical drive cannot work
[E][sd_diskio.cpp:123] sdSelectCard(): Select Failed
SD-Karte konnte nicht gemountet werden.

Das Modul habe ich über ein Testscript mit anderer Belegung getestet, da funktioniert es. SD-Karte wurde korrekt ausgelesen, also ist das Modul in Ordnung.
CS - 5
MOSI - 23
MISO - 19
SCK - 18
Diese Belegung ist aber dann für das RFID Modul.

Passt das Profil, das du kompiliert hast, zur Datei, die du bearbeitet hast? Bzw. welches Profil hast du genommen? SD via SPI ist du, laut Log, offenbar passend gewählt.

Jap, alles korrekt ausgewählt und kommentiert/auskommentiert.
Habe es für Lolin32 sowie AZ Delivery v4 kompiliert, gleiches Ergebnis.
Mit SD_MMC Settings und Verdrahtung auch, geht natürlich nicht, weil kein entfernbarer Widerstand auf dem Modul ist.
Ich glaube ich habe mir da ein Problem SD Modul besorgt, soweit ich jetzt diverse Berichte gelesen habe. Das andere ist aber schon bestellt.

Also wenn du sdmmc verwendest, dann müssen halt die gpios 2, 14 und 15 verwendet werden. Man muss den PullUp-Widerstand nicht unbedingt entfernen, so lange man beim Flashen per USB die SD-Karte entfernt.

Am besten sind die ganz einfachen SD-Module ohne eigenen Festspannungsregler. Das sind im Prinzip einfach nur Adapter mit PullUps und Koppelkondensatoren.

Auch das habe ich gemacht, auch mit Kabel beim Flashen abziehen. Aber auch hier kommt es zum Fehler, irgendwas mit PullUp Widerstand.

E (19982) sdmmc_common: sdmmc_init_ocr: send_op_cond (1) returned 0x107
[E][SD_MMC.cpp:85] begin(): Failed to initialize the card (263). Make sure SD card lines have pull-up resistors in place.

Der Tipp von hier funktioniert leider auch nicht.

Hast das jetzt auf nem Breadboard aufgebaut?
Falls ja: ESPuino/PCBs/AZDelivery_ESP32_NodeMCU at master · biologist79/ESPuino · GitHub
Kannst dir auch selbst PCBs bestellen. Weitere Infos gibt es hier: AZ Delivery ESP32 NodeMCU / Devkit C mit SD_MMC und PN5180 als RFID-Leser.
Eignet sich halt nur nicht gut für Akkubetrieb, weil die Boards von AZ so nen schlechten LDO haben (AMS1117).

Ich nutzte noch die Jumperkabelfreiflug Variante… erstmal gucken was geht und wie, dann wird über einen „Aufbau“ nachgedacht.

… neuer SD-Adapter ist da und dran, RFID auch, ESPuino läuft. Soweit so gut

Wenn ich die komplette JSON Zeile als URL einfüge, klappt das wie schon gesagt nicht, weil nicht untersützt.

Ich bin jetzt an oben besagter Stelle am gucken, wo ich den Wert/die URL aus dem Textfeld her bekomme. In welcher Variable ist dieser hinterlegt?

Ich habe diese Funktion für die XBMC-JSON http Anfrage

void xbmc(char *method, char *params)
{
  if (client.connect(xbmchost, 9090))
  {
    //Serial.print("Connected to host!");
    client.print("GET /jsonrpc?request={\"jsonrpc\":\"2.0\",\"method\":\"");
    client.print(method);
    client.print("\",\"params\":{\"");
    client.print(params);
    client.println("},\"id\":1}  HTTP/1.1");
    client.println("Host: XBMC");
    client.println("Connection: close");
    client.println();
    client.stop();
  }
  else
  {
    //Serial.println("Can't establish connection!");
    client.stop();
  }
}

In dem anderen Projekt rufe ich die Funktion wie folgt auf:

sprintf(buffer, "item\":{\"movieid\":%u}", number);
        xbmc("Player.Open", buffer);

wobei „number“ dort über das Tastenfeld eingegeben wird.
Ich möchte quasi über den erkannten RFID-Tag erstmal testweise ein Video starten, nur zum Funktionstest. Wird dann auf mp3-Playlisten geändert.

Ich muss irgendwie die Funktion jetzt aufgerufen bekommen, aber sehe aufgrund der Masse an Quelltext kein Land … :sweat_smile: Hast da zufällig ne weitere Idee?

Ok, es geht sogar, der Film wird gestartet. :+1:

Leider weiß ich nicht, wo die Zuordnung gespeichert wird. Auf der SD-Karte ist nichts.
Backupt.txt ist vorhanden aber leer.

#http://192.168.1.107:8080/jsonrpc?request=%7B%22jsonrpc%22%3A%222.0%22%2C%22method%22%3A%22Player.Open%22%2C%22params%22%3A%7B%22movieid%22%3A40%7D%2C%22id%22%3A1%7D#0#8#0
E (42436) sdmmc_cmd: sdmmc_read_sectors_dma: sdmmc_send_cmd returned 0x109
E (42439) diskio_sdmmc: sdmmc_read_blocks failed (265)
E (42444) sdmmc_cmd: sdmmc_read_sectors_dma: sdmmc_send_cmd returned 0x107
E (42450) diskio_sdmmc: sdmmc_read_blocks failed (263)
E (42456) sdmmc_req: handle_idle_state_events unhandled: 00000008 00000002
E (42462) sdmmc_cmd: sdmmc_read_sectors_dma: sdmmc_send_cmd returned 0x107
E (42468) diskio_sdmmc: sdmmc_read_blocks failed (263)
[E][vfs_api.cpp:270] VFSFileImpl(): fopen(/sdcard/backup.txt) failed
rfidAssign

Gespeichert wird das serialisiert im NVS des ESP32 und dann in das backup-File gedumpt.

Da kannst du sehen, wie sich das zusammensetzt:
Feld 1: RFID-ID
Feld 2: Kompletter Pfad von Titel/Verzeichnis oder URL
Feld 3: Startposition in Bytes
Feld 4: Playmodus
Feld 5: Tracknummer der Playlist, bei der begonnen werden soll zu spielen
Feld 6: Gesamtanzahl der Tracks einer Playlist

size_t AudioPlayer_NvsRfidWriteWrapper(const char *_rfidCardId, const char *_track, const uint32_t _playPosition, const uint8_t _playMode, const uint16_t _trackLastPlayed, const uint16_t _numberOfTracks) {

Serialisiert wird mittels # als Trenner. Mit der Serialisierung hat ein Benutzer aber eigentlich nix zu tun, also den String musst du nirgendwo (außer im Backup-File, und das wird ja automatisch geschrieben) so eintippen.

Also am einfachsten wirst du es auf jeden Fall haben, wenn du einen Webcall machen kannst. Deine gepostete URL sieht ja so aus. Die steckst du dann einfach in die URL rein und lernst das als Webradio an.

Ich muss hier leider die Segel streichen. Mit dem ESP Devkit v1 läuft zwar der RFID Reader aber das SD-Modul nur sporadisch, kann nichts speichern, teilweise nicht lesen, oft muss ich die Karte fest reindrücken, dann wird was gelesen aber trotzdem nicht gespeichert. Auch ist komisch, dass trotz Erase-Flash die interne Zuordnung von Code zu Film nicht gelöscht wird. Egal was ich anlerne, es wird immer der anfänglich hinzugefügte Film gestartet.
Mit dem AZ-Delivery Devkit v4 läuft das SD-Modul, jedoch der RFID Reader nicht, trotz identischer Belegung.

Trotzdem Vielen Dank für die Hilfe!

Es empfiehlt sich auf jeden Fall, den Kram aufzulöten. Jumperwires sind unzuverlässig.

Also das wäre mir neu. Die sind hier immer weg, wenn ich ein Erase mache.

Kann mir bitte jemand die Reihenfolge der Funktionen aufzeigen, welche nach Initialisierung des AccessPoints aufgerufen werden? Welche wichtigen Variablen werden noch gesetzt?
Ich möchte eigentlich nur einen AccessPoint als Webserver starten, zu welchem sich der Kodi Player dann verbindet, IP und PW vorgegeben. Router gibt es nicht.
Ich hatte schon diverse Zeilen auskommentiert bzw. per #define ausgeblendet, jedoch mit folgendem Ergebnis:

Access-Point geöffnet
IP-Adresse: 192.168.4.4
HTTP-Server gestartet.
Freier Heap-Speicher nach Setup-Routine: 185380
PSRAM: 0 bytes
Flash-size: 4194304 bytes
Access-Point geöffnet
IP-Adresse: 192.168.4.4
HTTP-Server gestartet.
Access-Point geöffnet
IP-Adresse: 192.168.4.4
HTTP-Server gestartet.

Nachdem ich mich mit dem AP verbunden habe, kam es aber zum Kernel-Panic, vermutlich weil diverse Dinge aus dem NVS gelesen werden sollten, die nicht geschrieben wurden.

Web_Init() - kann ich schon mal überspringen, das ist für die Zugangsdaten zum Netzwerk mit Router.

Wird „if (WiFi.status() == WL_CONNECTED)“ → „WL_CONNECTED“ auch gesetzt, wenn der AccessPoint aktiv ist oder erst wenn erfolgreich mit Router verbunden? Ich vermute mal erst wenn mit Netzwerk verbunden, das würde den ständigen Aufruf von „accessPointStart“ erklären.

Ich hoffe es ist irgendwie verständlich was ich vorhabe und wo ich aktuell hängen bleibe… :sweat_smile:

Die (einfache Lösung) klappt auch nicht, in der Funktion „accessPointStart()“ folgendes zu machen

//Web_Init();
  Web_Cyclic();

dann noch…

bool Wlan_IsConnected(void) {
    #ifdef ACCESS_POINT
    return (true);
    #endif
    #ifndef ACCESS_POINT
    return (WiFi.status() == WL_CONNECTED);
    #endif
}

Die Seite wird zwar angezeigt, aber ohne Formatierung, ist nicht bedienbar.
Beim Aufrufen der Webseite kommt…

.............Access-Point geöffnet
IP-Adresse: 192.168.4.1
Freier Heap-Speicher nach Setup-Routine: 184920
PSRAM: 0 bytes
Flash-size: 4194304 bytes
RSSI: 0 dBm
!!!!WICHTIG!!!! Beachte bitte https://forum.espuino.de/t/wechsel-zum-refactoring-branch-was-ist-zu-beachten/510 !!!!WICHTIG!!!!
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: Hostname NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttServer NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttUser NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttPassword NOT_FOUND
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: Hostname NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttServer NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttUser NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttPassword NOT_FOUND
logo request
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][vfs_api.cpp:72] exists(): File system is not mounted
favicon request
[E][vfs_api.cpp:72] exists(): File system is not mounted
RSSI: 0 dBm
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: Hostname NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttServer NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttUser NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttPassword NOT_FOUND
logo request
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][vfs_api.cpp:72] exists(): File system is not mounted
favicon request
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: Hostname NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttServer NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttUser NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttPassword NOT_FOUND
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: Hostname NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttServer NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttUser NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttPassword NOT_FOUND
RSSI: 0 dBm
logo request
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][vfs_api.cpp:72] exists(): File system is not mounted
favicon request
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: Hostname NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttServer NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttUser NOT_FOUND
[E][Preferences.cpp:472] getString(): nvs_get_str len fail: mqttPassword NOT_FOUND
logo request
[E][vfs_api.cpp:72] exists(): File system is not mounted
[E][vfs_api.cpp:72] exists(): File system is not mounted
favicon request
[E][vfs_api.cpp:72] exists(): File system is not mounted
RSSI: 0 dBm

Nein, das wird nur gesetzt, wenn sich der ESP32 zu einem Netzwerk verbunden hat.
Also Ausgangspunkt für dich ist die Wlan.cpp. Hier wird erstmal versucht, sich mit dem gespeicherten Wlan zu verbinden:

Das wird dann ne Weile probiert (siehe while-Schleife ab Zeile 80). Klappt das nicht, dann wird der AP gestartet:

Wie du siehst, wird hier Web_Init aufgerufen. Und da kannst du an der Stelle sehen, was ausgeliefert wird:

Nämlich die accesspoint_HTML.

Anders sieht es aus, wenn der ESP32 sich in einem WLAN anmelden konnte. Dann wird das Mgmt-Interface aufgerufen:

Und hier werden nun die ganzen Handler initialisiert. Und dabei wird vor allem ein anderes File ausgeliefert:

Also im Grunde ist der Server-Lib, die verwendet wird, natürlich in beiden Fällen die gleiche. Es werden halt nur unterschiedliche Handler registriert, so dass unterschiedliche Sachen ausgeliefert werden. Die Access-GUI ist ziemlich spartanisch, während die Mgmt-GUI recht umfangreich ist. Ausgeliefert werden entsprechend:

Wenn ich mir das so angucke, ist das aber genau der Ablauf den ich versucht habe.

Ich habe im Accespoint mit „Web_Cyclic();“ gleich die Mgmt-GUI aufgerufen, statt der Access-GUI, diese habe ich mit „//Web_Init();“ auskommentiert.
Und „WL_CONNECTED“ habe ich auf TRUE gesetzt, somit sind doch alle Bedingungen erfüllt?!

Achso, das wird dann daran liegen, dass da diverse JS-Sourcen extern nachgeladen werden. Das ist noch so ein offener Punkt, dass man das alles (z.B. mit Webpack) mit in den ESP32 flasht und keine externern Calls mehr macht.

Nur noch mal zur Sicherheit, ob ich das richtig verstanden habe… Du lädst also externe Ressourcen nach in dem Mgmt-Formular?

Dann bin ich ja bisher auf dem richtigen Weg und muss nur gucken, welche JS Funktionalität mir da fehlt und ob ich die anderweitig ersetzt bekomme.