Squeezebox über RFID-Karten steuern

Da unser Sohn noch nicht lesen kann, aber trotzdem gerne die Squeezebox benutzt um seine Hörspiele, Musik und Streams zu hören, musste ich mir eine Möglichkeit überlegen wie man die Squeezebox kindgerecht bedienen kann.

Da die Squeezebox ans openHAB angebunden ist liegt nichts näher als einen ESP mit RFID-Leser und MQTT dafür zu verwenden. Die Daten fließen dann folgendermaßen:

RFID-Reader -> ESP8266-12/D1Mini -> MQTT -> openHAB -> Squeezebox (Touch in meinem Fall)

Nebenbei schalte ich den Verstärker (da der schon ein paar Jahre auf dem Buckel hat per Funk-Steckdose) an und aus wenn die Squeezebox nicht verwendet wird. Da gehe ich hier aber nicht drauf ein (ist auch nicht so schwer ;))

Damit Junior auch eine Rückmeldung bekommt, dass die Karte angenommen wurde (und er ein bisschen Geduld haben muss bis die Squeezebox reagiert hat) missbrauche ich die LED auf dem Chip als Rückmeldung. Die Karten beklebe ich mit Etiketten mit den entsprechenden Bildern.

Material

ESP8266-12F * Affiliatelink

Jumper-Kabel * Affiliatelink

RFID Kit RC522 13,56MHz * Affiliatelink

13,56MHz RFID-Karten * Affiliatelink

HERMA 5028 Universal Etiketten DIN A4 * Affiliatelink

Anschlüsse

RC522ESP8226-12F
3.3V3V3
RSTD3
GNDG
IRQ
MISOD6
MOSID7
SCKD5
SDAD8
PIN-Belegung
Pinning

Code (ESP)

#include <SPI.h>
#include <MFRC522.h>
#include <MFRC522Extended.h>

#include <ESP8266HTTPClient.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

#include <NTPClient.h>
#include <WiFiUdp.h> 

#include <ESP8266WebServer.h>

#include <time.h>

// #############################################
// WLAN-Zugang
// #############################################
#define wifi_ssid "WLAN-SSID"
#define wifi_password "WLAN-PW"

// #############################################
// MQTT-Server (Openhab)
// #############################################
#define mqtt_server "192.168.2.15"	// Openhabian-MQTT-IP
#define mqtt_user "openhabian"
#define mqtt_password ""

// #############################################
// NTP-Client
// #############################################
#define MY_NTP_SERVER "192.168.2.1"           
#define MY_TZ "CET-1CEST,M3.5.0/02,M10.5.0/03"   

// #############################################
// RFID-Reader
// #############################################
#define SS_PIN 15
#define RST_PIN 0
MFRC522 rfid(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;
String kartennummer;

time_t now;                         
tm tm;                              
String TimeStamp;

ESP8266WebServer server(80);
String htmltext;

WiFiClient espClient;
PubSubClient client(espClient);
long lastMsg = 0;
char msg[50];
int value = 0;

void setup ()
{
  pinMode(LED_BUILTIN, OUTPUT);     // Initialize the LED_BUILTIN pin as an output
  
  SPI.begin();
  rfid.PCD_Init();
  
  Serial.begin(115200); // Serielle Ausgabe mit 115200 bps
  setup_wifi();
  client.setServer(mqtt_server, 1883);
  
  configTime(MY_TZ, MY_NTP_SERVER);
  
  server.on("/", handle_OnData);
  server.on("/f.css", handleCss);
  server.onNotFound(handle_NotFound);
  server.begin();
  
  Serial.println("HTTP server started");
  Serial.println("Boot RFID-Reader...");
}

void loop ()
{
  if (!client.connected()) {
    reconnect();
  }
  server.handleClient();

  time(&now);                       
  localtime_r(&now, &tm);           

  TimeStamp = String(tm.tm_mday) + "." + String(tm.tm_mon + 1) + "." + String(tm.tm_year + 1900) + " " + String(tm.tm_hour) + ":" + String(tm.tm_min) + ":" + String(tm.tm_sec);

  handleRFID();
  if (kartennummer != "") {
    client.publish("rfid/kartennummer", String(kartennummer).c_str(), true);

    digitalWrite(LED_BUILTIN, LOW);   
    delay(100);                      
    digitalWrite(LED_BUILTIN, HIGH);  

    htmltext = "";
    htmltext += "<h2>Uhrzeit</h2>"; 
    htmltext += "<p><b>" + String(TimeStamp) + "</b></p>";
    htmltext += "<h2>Werte</h2>"; 
    htmltext += "<p>letzte Karte: <b>" + String(kartennummer) + "</b></p>";
    delay(1000);
    kartennummer = "";
    client.publish("rfid/kartennummer", String("0").c_str(), true);
  }
}

void handleRFID() {
  if (!rfid.PICC_IsNewCardPresent()) return;
  if (!rfid.PICC_ReadCardSerial()) return;
  Serial.println(printHex(rfid.uid.uidByte, rfid.uid.size));
  kartennummer = printHex(rfid.uid.uidByte, rfid.uid.size);
  rfid.PICC_HaltA();
  rfid.PCD_StopCrypto1();
}
  
String printHex(byte *buffer, byte bufferSize) {
  String id = "";
  for (byte i = 0; i < bufferSize; i++) {
    id += buffer[i] < 0x10 ? "0" : "";
    id += String(buffer[i], HEX);
  }
  return id;
}

void setup_wifi() {
  delay(10);
  // We start by connecting to a WiFi network
  Serial.println();
  Serial.print("Connecting to ");
  Serial.println(wifi_ssid);

  WiFi.begin(wifi_ssid, wifi_password);

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

bool checkBound(float newValue, float prevValue, float maxDiff) {
  return !isnan(newValue) &&
  (newValue < prevValue - maxDiff || newValue > prevValue + maxDiff);
}

void reconnect() {
  // Loop until we're reconnected
  while (!client.connected()) {
    Serial.print("Attempting MQTT connection...");
    // Create a random client ID
    String clientId = "ESP8266-";
    clientId += String(random(0xffff), HEX);
    // Attempt to connect
    if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
      Serial.println("connected");
      // Once connected, publish an announcement...
      client.publish("outTopic", "ESP01 alive");
      // ... and resubscribe
      client.subscribe("inTopic");
    } else {
        Serial.print("failed, rc=");
        Serial.print(client.state());
        Serial.println(" try again in 5 seconds");
        // Wait 5 seconds before retrying
        delay(5000);
      }
   }
}

void handle_OnData() {
  server.send(200, "text/html", SendHTML(htmltext)); 
}
void handle_OnConnect() {
  server.send(200, "text/html", SendHTML("Boot")); 
}

void handle_NotFound(){
  server.send(404, "text/plain", "Not found");
}

String SendHTML(String addtext){
  String ptr = "";
  ptr += "<!DOCTYPE html PUBLIC '-//W3C//DTD HTML 4.01 Transitional//EN'>\n";
  ptr += "<html lang='de'>\n";
  ptr += "<head>\n";
  ptr += "<title>RFID-Leser Webserver</title>\n";
  ptr += "<link rel='stylesheet' type='text/css' href='/f.css'>\n";
  ptr += "<meta content='text/html; charset=UTF-8' http-equiv='content-type'>\n";
  ptr += "</head>\n";
  ptr += "<body>\n";
  ptr += "<h1>RFID-Leser Webserver</h1>\n";
  ptr += addtext;
  ptr += "</body>\n";
  ptr += "</html>";
  
  return ptr;
}

void handleCss()
{
  String message = "";
  message += "*{font-family:sans-serif}\n";
  message += "body{margin:10px;width:300px}\n";
  message += "h1, h2{color:white;background:#8FCE00;text-align:center}\n";
  message += "h1{font-size:1.2em;margin:1px;padding:5px}\n";
  message += "h2{font-size:1.0em}\n";
  message += "h3{font-size:0.9em}\n";
  message += "a{text-decoration:none;color:dimgray;text-align:center}\n";
  message += ".small{font-size:0.6em}\n";
  message += ".value{font-size:1.8em;text-align:center;line-height:50%}\n";
  message += "footer p, .infodaten p{font-size:0.7em;color:dimgray;background:silver;";
  message += "text-align:center;margin-bottom:5px}\n";
  message += "nav{background-color:silver;margin:1px;padding:5px;font-size:0.8em}\n";
  message += "nav a{color:white;padding:10px;text-decoration:none}\n";
  message += "nav a:hover{text-decoration:underline}\n";
  message += "nav p{margin:0px;padding:0px}\n";
  message += ".btn{background-color:#C0C0C0;color:dimgray;text-decoration:none;";
  message += "border-style:solid;border-color:dimgray}\n";
  message += ".on, .off{margin-top:0;margin-bottom:0.2em;margin-left:3em;";
  message += "font-size:1.4em;background-color:#C0C0C0;";
  message += "border-style:solid;width:5em;height:1.5em;text-decoration:none;text-align:center}\n";
  message += ".on{border-color:green}\n)";
  server.send(200, "text/css", message);
}

Die Kartennummer wird gelesen und per MQTT verschickt. Ebenfalls wird die LED kurz aktiviert (0,5s) als Rückmeldung. Nach dem senden der Kartennummer schicke ich die Kartennummer 0 hinterher um auch eine zweite benutzung der Karte detektieren zu können. Ich wähle damit z.B. eine weitere Playlist an falls die aktuelle nicht gefällt oder schon gehört wurde.

Im Webserver (oder einem MQTT-Client) kann man sich die letzte Kartennummer (rfid/kartennummer) ansehen um die Programmierung im openHAB zu erstellen.

Vorbereitung Squeezebox

In der Squeezebox habe ich die Wiedergabelisten erstellt auf die ich dann zukünftig zugreifen will. Damit nicht immer mit dem gleichen Titel begonnen wird (z.B. bei Hörspielen) habe ich diese in verschiedene aufgeteilt die dann zufällig in Blöcken ausgewählt wird. (Wie ich das meine kann man im Code vermutlich ganz gut erkennen)

Vorbereitung openHAB

Die MQTT-Werte und die entsprechenden Squeezebox-Player müssen im Openhab natürlich auch als Items angelegt werden. Mindestens benötigt man folgende:

String MQTT_RFID_Leser_Kartennummer "Kartennummer" {channel="mqtt:topic:7c69bd7575:rfid_reader:MQTT_RFIDReader_Kartennummer"}
String  SB_Wohnzimmer_Stream "Stream url" {channel="squeezebox:squeezeboxplayer:bc95bf4504:0004202375f1:stream"}
Player  SB_Wohnzimmer_Control "" {channel="squeezebox:squeezeboxplayer:bc95bf4504:0004202375f1:control"}

(Ja, den Control braucht man natürlich nicht zwingend, macht aber Sinn ;))

Die Channel müsst ihr natürlich anpassen auf euer Setting und eure IDs. Im VisualStudio geht das ja ganz gut.

Code (openHAB)

// Squeezebox ansteuern
rule "Squeezekarte empfangen"
when
    Item MQTT_RFID_Leser_Kartennummer changed
then
    SB_Wohnzimmer_Power.sendCommand("ON")
    if (MQTT_RFID_Leser_Kartennummer.state == "778e0f32") {
        // Die drei Fragezeichen
        // 8 Playlists => Nummer zwischen 0 und 7
        var Nummer = (Math::random * 7).intValue
        SB_Wohnzimmer_Stream.sendCommand("/listen/Die_drei_Fragezeichen_Kids_" + Nummer)
        // SB_Wohnzimmer_Stream.sendCommand("/listen/Die_drei_Fragezeichen_Kids")
        SB_Wohnzimmer_Control.sendCommand("PLAY")
    }
    else if (MQTT_RFID_Leser_Kartennummer.state == "f4253660") {
        // Absolut Relax
        SB_Wohnzimmer_Stream.sendCommand("https://stream.absolutradio.de/relax/mp3-128/logitech.m3u")
        SB_Wohnzimmer_Control.sendCommand("PLAY")
    }
    else if (MQTT_RFID_Leser_Kartennummer.state == "23796909") {
        // Antenne Bayern Weihnachtshits
        SB_Wohnzimmer_Stream.sendCommand("http://opml.radiotime.com/Tune.ashx?id=s244472&formats=aac,ogg,mp3,wmpro,wma,wmvoice&partnerId=16&serial=01af601506296fc0c85f467f7a7746b0")
        SB_Wohnzimmer_Control.sendCommand("PLAY")
    }

Die m3u liegen bei mir im Ordner „listen“ den ihr im Squeezebox-Server als Wiedergabelisten-Ordner angebt. Ihr gebt hier die Dateinamen ohne „.m3u“ an. Die drei Fragezeichen Kids kann man übrigens sehr gut als 3er-Boxen für ca. 10€ als CD kaufen (z.B. bei Amazon * Affiliatelink, wir haben unsere hauptsächlich aus Kleinanzeigen und von Freunden deren Kinder doch keine so großen Fans waren wie die Eltern ;)).

Beim Eintrag für die drei Fragezeichen kann man sehen, dass ich 8 Playlists (0…7) angelegt habe von der zufällig eine verwendet wird.

Aufbau

Ich war erst mal faul und hab die Komponenten in eine Lego-Kiste verbaut und mit einem kleinen Schalter versehen.

Um das Feedback sichtbar zu machen habe ich das Innenleben so gebaut, dass der ESP direkt am vorderen Rand liegt und die LED direkt am transparenten Stein liegt. Damit ist das lesen der Karte gut sichbar.

Viel Spaß beim nachbauen oder verbessern. Gerne Feedback wenn ihr noch etwas zum verbessern findet.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert