TonUINO Karten Programmer

Hallo zusammen,
da meine Kinder ihren TonUINO immer in Beschlag haben und ich immer „geschimpft“ werde wenn ich am TonUINO was ändern will, habe ich einen kleinen TonUINO-Programmer gebaut um die TonUINO-Karten autark zu programmieren.

Dazu habe ich einen Nano Every + einen RC522 Modul und einen kleinen .ino Sketch geschrieben.
Das Gehäuse habe ich mit unserem 3d-Drucker in der Arbeit ausdrucken lassen.
Bei Interesse kann ich euch den Sketch + die STL-Datei zur Verfügung stellen.

Zur Software:
Es funktionieren Alle Modifikations-Karten und Die Wiedergabe Modis 1-5
Die fehlenden Modis kann man aber leicht hinzufügen.

Viele Grüße Chris

4 „Gefällt mir“

Keine falsche Scheu. Kannste ruhig direkt teilen hier. Ob das jetzt jeder in so ein Gehäuse bastelt ist ja erstmal egal, aber der Sketch ist für andere sicher interessant.

Ja klar kann ich machen
hier der Code:


// ╔══╗───╔╦╦══╦═╦╦═╗╔═╗
// ╚╗╔╩╦═╦╣║╠║║╣║║║║║║╬╠╦╦═╦═╦╦╦═╗╔══╦══╦═╦╦╗
// ─║║╬║║║║║╠║║╣║║║║║║╔╣╔╣╬║╬║╔╣╬╚╣║║║║║║╩╣╔╝
// ─╚╩═╩╩═╩═╩══╩╩═╩═╝╚╝╚╝╚═╬╗╠╝╚══╩╩╩╩╩╩╩═╩╝
// ────────────────────────╚═╝
String Version = "Version 1.5";

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

#define RST_PIN         9
#define SS_PIN          10
#define LED             A5

MFRC522 mfrc522(SS_PIN, RST_PIN);
MFRC522::MIFARE_Key key;
MFRC522::StatusCode status;

String inputString = "read";
String assign_folder = "";
String assign_mode = "";
String assign_file = "";
String special = "";
byte special_2 = 0xEA;

byte trailerBlock   = 7;
byte blockAddr = 4;
byte magicCookie[] = {0x13, 0x37, 0xB3, 0x47};
byte Card_Version = 0x02;
byte cardCookie[5];
byte dataBlock[25];
byte buffer[25];
byte size = sizeof(buffer);
byte len = 16;


void setup() {
  Serial.begin(115200);
  SPI.begin();
  mfrc522.PCD_Init();
  Serial.setTimeout(2000);
  for (byte i = 0; i < 6; i++) {
    key.keyByte[i] = 0xFF;
  }
  show_logo();
  pinMode(A5, OUTPUT);
  digitalWrite(A5, HIGH);
}

void loop() {
  if (Serial.available()) {
    inputString = Serial.readStringUntil('\n');
    inputString.toLowerCase();
  }
  if (inputString == "info") {
    show_logo();
    show_info();
    inputString = "read";
  }
  if (! mfrc522.PICC_IsNewCardPresent()) {
    return;
  }
  if (! mfrc522.PICC_ReadCardSerial()) {
    return;
  }

  if (inputString == "read") {
    read_RFID();
  }

  if (inputString == "clear") {
    for ( int i = 0; i < len; i++) {
      dataBlock[i] = 0x00;
    }
    write_RFID();
    Serial.println(F("Karte gelöscht!"));
    Serial.println();
    delay(250);
    inputString = "read";
  }

  if (inputString == "admin") {
    for ( int i = 0; i < 4; i++) {
      dataBlock[i] = magicCookie[i];
    }
    dataBlock[4] = Card_Version;
    dataBlock[5] = 0x00;
    dataBlock[6] = 0xFF;
    dataBlock[7] = 0x09;
    dataBlock[8] = special_2;
    write_RFID();
    delay(250);
    inputString = "read";
  }

  if (inputString == "toddler") {
    for (int i = 0; i < 4; i++) {
      dataBlock[i] = magicCookie[i];
    }
    dataBlock[4] = Card_Version;
    dataBlock[5] = 0x00;
    dataBlock[6] = 0x04;
    dataBlock[7] = 0x00;
    dataBlock[8] = 0x00;
    write_RFID();
    delay(250);
    inputString = "read";
  }

  if (inputString.substring(0, 11) == "schlummern ") {
    special = inputString.substring(11, 13);
    if (special == "5" || special == "15" || special == "30" || special == "60") {
      for (int i = 0; i < 4; i++) {
        dataBlock[i] = magicCookie[i];
      }
      dataBlock[4] = Card_Version;
      dataBlock[5] = 0x00;
      dataBlock[6] = 0x01;
      dataBlock[7] = special.toInt();
      dataBlock[8] = 0x00;
      write_RFID();
      delay(250);
      inputString = "read";
    }
  }

  if (inputString == "kita") {
    for (int i = 0; i < 4; i++) {
      dataBlock[i] = magicCookie[i];
    }
    dataBlock[4] = Card_Version;
    dataBlock[5] = 0x00;
    dataBlock[6] = 0x05;
    dataBlock[7] = 0x00;
    dataBlock[8] = 0x00;
    write_RFID();
    delay(250);
    inputString = "read";
  }

  if (inputString == "sperren") {
    for (int i = 0; i < 4; i++) {
      dataBlock[i] = magicCookie[i];
    }
    dataBlock[4] = Card_Version;
    dataBlock[5] = 0x00;
    dataBlock[6] = 0x03;
    dataBlock[7] = 0x00;
    dataBlock[8] = 0x00;
    write_RFID();
    delay(250);
    inputString = "read";
  }

  if (inputString == "stoptanz") {
    for (int i = 0; i < 4; i++) {
      dataBlock[i] = magicCookie[i];
    }
    dataBlock[4] = Card_Version;
    dataBlock[5] = 0x00;
    dataBlock[6] = 0x02;
    dataBlock[7] = 0x00;
    dataBlock[8] = 0x00;
    write_RFID();
    delay(250);
    inputString = "read";
  }

  if (inputString.substring(0, 6)  == "write ") {
    assign_folder = inputString.substring(6, 8);
    assign_mode = inputString.substring(9, 10);
    assign_file = inputString.substring(11, 14);
    if (assign_folder.toInt() != 0 && assign_mode.toInt() > 0 && assign_mode.toInt() < 4 || assign_mode.toInt() == 5) {
      for (int i = 0; i < 4; i++) {
        dataBlock[i] = magicCookie[i];
      }
      dataBlock[4] = Card_Version;
      dataBlock[5] = assign_folder.toInt();
      dataBlock[6] = assign_mode.toInt();
      dataBlock[7] = 0x00;
      dataBlock[8] = 0x00;
      write_RFID();
      delay(250);
      inputString = "read";
    }
    else if (assign_mode.toInt() == 4) {
      if ( assign_file.toInt() > 255 || assign_file.toInt() == 0) {
        Serial.println(F("Du kannst nur eine Datei zwischen 1-255 eingeben"));
        Serial.println();
        inputString = "read";
      }
      else if ( assign_folder.toInt() == 0 || assign_folder.toInt() > 99) {
        Serial.println(F("Du kannst nur einen Ordner zwischen 1-99 eingeben"));
        Serial.println();
        inputString = "read";
      }
      else {
        for (int i = 0; i < 4; i++) {
          dataBlock[i] = magicCookie[i];
        }
        dataBlock[4] = Card_Version;
        dataBlock[5] = assign_folder.toInt();
        dataBlock[6] = assign_mode.toInt();
        dataBlock[7] = assign_file.toInt();
        dataBlock[8] = 0x00;
        write_RFID();
        delay(250);
        inputString = "read";
      }
    }
    else if (assign_mode.toInt() > 5) {
      Serial.println(F("Diese Funktion gibt es noch nicht!"));
      Serial.println();
      inputString = "read";
    }
  }
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
}

void read_RFID() {
  MFRC522::StatusCode status;
  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("PCD_Authenticate() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    delay(500);
    return;
  }
  status = (MFRC522::StatusCode) mfrc522.MIFARE_Read(blockAddr, buffer, &size);
  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("MIFARE_Read() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
  }
  for (int i = 0; i < 4; i++) {
    cardCookie[i] = buffer[i];
  }
  // Magic cookie
  if (checkTwo(magicCookie, cardCookie) == true) {
    Serial.println(F("Tonuino Karte erkannt:"));
    Serial.print(F("READ\tBlock 4 = "));
    for (int i = 0; i < len; i++) {
      Serial.print(buffer[i] < 0x10 ? "0" : "");
      Serial.print(buffer[i], HEX);
      if (i == 15) {}
      else {
        Serial.print(F("|"));
      }
    }
    Serial.println(F("-> HEX"));
    assigned_mode(buffer);
  }
  else {
    Serial.println(F("leere Karte!"));
    Serial.print(F("READ\tBlock 4 = "));
    for (int i = 0; i < len; i++) {
      Serial.print(buffer[i] < 0x10 ? "0" : "");
      Serial.print(buffer[i], HEX);
      if (i == 15) {}
      else {
        Serial.print(F("|"));
      }
    }
    Serial.println(F("-> HEX\n"));
  }
}

void write_RFID() {
  status = (MFRC522::StatusCode) mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A, trailerBlock, &key, &(mfrc522.uid));
  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("PCD_Authenticate() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
    delay(500);
    return;
  }
  status = (MFRC522::StatusCode) mfrc522.MIFARE_Write(blockAddr, dataBlock, len);
  if (status != MFRC522::STATUS_OK) {
    Serial.print(F("MIFARE_Write() failed: "));
    Serial.println(mfrc522.GetStatusCodeName(status));
  }
  Serial.print(F("WRITE\tBlock 4 = "));
  for (byte i = 0; i < len; i++) {
    Serial.print(dataBlock[i] < 0x10 ? "0" : "");
    Serial.print(dataBlock[i], HEX);
    if (i == 15) {}
    else {
      Serial.print(F("|"));
    }
  }
  Serial.println(F("-> HEX"));
  assigned_mode(dataBlock);
  delay(1000);
}

void assigned_mode( byte buffer[]) {

  // Wiedergabemodus:
  // Hörspiel Modus = 1
  if (buffer[5] != 0x00 && buffer[6] == 0x01) {
    Serial.print(F("Hörspiel Modus 1 -> eine zufällige Datei aus dem Ordner "));
    Serial.print(buffer[5], DEC);
    Serial.println(F(" abspielen"));
    Serial.println();
  }
  // Album Modus = 2
  if (buffer[5] != 0x00 && buffer[6] == 0x02) {
    Serial.print(F("Album Modus 2 -> den kompletten Ordner "));
    Serial.print(buffer[5], DEC);
    Serial.println(F(" abspielen"));
    Serial.println();
  }
  // Party Modus = 3
  if (buffer[5] != 0x00 && buffer[6] == 0x03) {
    Serial.print(F("Party Modus 3 -> Dateien des Ordners "));
    Serial.print(buffer[5], DEC);
    Serial.println(F(" in zufälliger Reihenfolge abspielen"));
    Serial.println();
  }
  // Einzel Modus = 4
  if (buffer[5] != 0x00 && buffer[6] == 0x04) {
    Serial.print(F("Einzel Modus 4 -> die Datei "));
    Serial.print(buffer[7], DEC);
    Serial.print(F(" aus dem Ordner "));
    Serial.print(buffer[5], DEC);
    Serial.println(F(" abspielen"));
    Serial.println();
  }
  // Hörbuch Modus = 5
  if (buffer[5] != 0x00 && buffer[6] == 0x05) {
    Serial.print(F("Hörbuch Modus 5 -> kompletten Ordner "));
    Serial.print(buffer[5], DEC);
    Serial.println(F(" spielen und Fortschritt merken"));
    Serial.println();
  }

  // Modifikationskarten:
  // Admin Karte
  if (buffer[6] == 0xFF && buffer[7] == 0x09 && buffer[8] == 0xEA) {
    Serial.println(F("Admin Karte"));
    Serial.println();
  }
  // Schlummer Modus
  if (buffer[5] == 0x00 && buffer[6] == 0x01) {
    Serial.print(F("Schlummer-Modus -> "));
    Serial.print(buffer[7], DEC);
    Serial.println(F(" min"));
    Serial.println();
  }
  // Stoptanz Karte
  if (buffer[5] == 0x00 && buffer[6] == 0x02) {
    Serial.println(F("Stoptanz Karte"));
    Serial.println();
  }
  // TonUINO sperren
  if (buffer[5] == 0x00 && buffer[6] == 0x03) {
    Serial.println(F("TonUINO sperren"));
    Serial.println();
  }
  // Toddler Modus
  if (buffer[5] == 0x00 && buffer[6] == 0x04) {
    Serial.println(F("Toddler-Modus"));
    Serial.println();
  }
  // KiTa Karte
  if (buffer[5] == 0x00 && buffer[6] == 0x05) {
    Serial.println(F("KiTa Karte"));
    Serial.println();
  }
}

void show_logo() {
  Serial.println(F(" ╔══╗───╔╦╦══╦═╦╦═╗╔═╗"));
  Serial.println(F(" ╚╗╔╩╦═╦╣║╠║║╣║║║║║║╬╠╦╦═╦═╦╦╦═╗╔══╦══╦═╦╦╗"));
  Serial.println(F(" ─║║╬║║║║║╠║║╣║║║║║║╔╣╔╣╬║╬║╔╣╬╚╣║║║║║║╩╣╔╝"));
  Serial.println(F(" ─╚╩═╩╩═╩═╩══╩╩═╩═╝╚╝╚╝╚═╬╗╠╝╚══╩╩╩╩╩╩╩═╩╝"));
  Serial.println(F(" ────────────────────────╚═╝"));
  Serial.println(F("für TonUINO Karten"));
  Serial.println(F("Designed and created by Christian Kühner 10/2022\n"));
  Serial.println(Version);
  mfrc522.PCD_DumpVersionToSerial();
  Serial.println();
}

void show_info() {
  Serial.println(F("Befehle:"));
  Serial.println(F("  clear\t\t\t-> Sektor 1 / Block 4 der RFID Karte wird auf 0x00 geschrieben,"));
  Serial.println(F("\t\t\t   durch fehlen des magick cookie wird Sie vom TonUINO als neue Karte erkannt."));
  Serial.println(F("  write {xx} {n}\t-> Erstellt TonUINO-Karte aus Ordnernummer {xx} und Wiedergabemodus {n}"));
  Serial.println(F("\t\t\t   und zeigt diese an. z.B. write 01 2 -> spielt den Ordner 1 im Album Modus ab."));
  Serial.println(F("  admin\t\t\t-> Erstellt eine Admin Karte."));
  Serial.println(F("  toddler\t\t-> Erstellt eine Toddler Karte."));
  Serial.println(F("  schlummern {nn}\t-> Erstellt eine Schlummer Karte und zeigt die Minuten {nn} an."));
  Serial.println(F("  stoptanz\t\t-> Erstellt eine Stoptanz Karte."));
  Serial.println(F("  kita\t\t\t-> Erstellt eine KiTa Karte."));
  Serial.println(F("  sperren\t\t-> Erstellt eine TonUINO Sperr-Karte."));
  Serial.println(F("  info\t\t\t-> zeigt diese Info an."));
  Serial.println(F("\n"));
}

bool checkTwo(uint8_t a[], uint8_t b[]) {
  for (uint8_t k = 0; k < 4; k++) {   // Loop 4 times
    if ( a[k] != b[k] ) {     // IF a != b then false, because: one fails, all fail
      return false;
    }
  }
  return true;
}

Ich habe versucht das ganze einfach zu gestalten damit jeder die Funktionen versteht.

2 „Gefällt mir“

und hier der Serielle Monitor

3 „Gefällt mir“

Cool. Ich hab das hier mit aufgenommen:

Natürlich sind auch die .stl Dateien interessant. Ist ja ein ansprechendes Gehäuse.

Edit: Und auch ein Schaltplan wäre nicht schlecht.
Was ist das für ein roter Knopf auf der Oberseite, wozu ist er da? Im geöffneten Gehäuse kann ich ihn nicht entdecken.

Freut mich das es euch gefällt.
Der rote Knopf war bei den Fotos noch grau, den gabs damals noch nicht :blush:, er ist für den Reset-Button des Arduino. Die STL Datei kann ich Morgen hochladen (kann ich die Datei einfach so ins Forum Laden?)
Den Schaltplan gibts NOCH nicht kann ihn aber gerne erstellen und hochladen
:+1: super Community
Grüße Chris

1 „Gefällt mir“

Nein. Am besten machst du das bei thingiverse und teilst hier den Link.

Muss ja nichts großes sein. Es reicht wenn du schreibst, welche Pins des NFC-Readers mit welchen Pins des Arduino verbunden werden müssen. Kannst du auch im Code als Kommentar ergänzen.

und hier meine Readme:

Readme TonUINO Programmer.pdf (13,5 KB)

Das ist ein sehr schönes Projekt und hervorragend dokumentiert. Es hat nur einen kleinen Nachteil: Man braucht zum Programmieren der Karten immer noch einen Computer mit dem das Board per USB Kabel verbunden sein muss.

Der vorgestellte Programmer hat mich daher inspiriert, auch einmal meinen Pocket Programmer vorzustellen, der auf einem Entwurf von Daniel Wilhelm aus diesem Forum hier basiert und den ich entsprechend meiner Bedürfnisse angepasst habe (ist auch bei den Tools aufgeführt). Ich habe Daniels Projekt aus dem Github Repo geforkt und meine Änderungen dokumentiert: GitHub - SchneHa/TonuinoTagWriter-TTW: Pocket device to create Tags for TonUINO project

Der TTW hat den Vorteil, dass er akkubetrieben und damit unabhängig von einem Kabel ist (nur zum Laden des Akkus braucht man natürlich eins). Er verfügt über einen Webserver und kann über WLAN mit einem Handy oder auch einem Tablet oder PC verbunden werden, um die Karten zu programmieren. Wenn er nicht ins häusliche WLAN eingebunden ist, spannt er einen eigenen Accesspoint auf über den man Zugriff bekommt. Das „Herz“ des TTW ist ein ESP32 Dev Module.

Top :+1: schön gemacht.
für mich ist das kein Nachteil da ich meinen Programmer eigentlich nur mit meinem PC brauche.
Ich habe eine TXT-File wo die Ordner + Lieder angegeben sind und bei " Verlust " der Karten kann ich so eine neue erstellen. Oder ich lege eine neue SD-Karte an dann brauche ich eh meinen PC.

Dieser Nachteil sprang mir auch in den Sinn.
Hätte ich Zeit und Bedarf, wäre meine Lösung mit Display und Drehencoder.

Woher weißt Du denn welcher Ordner welche mp3s drinnen hat?

Ich nutze das Excel Tool um die mp3s auf die Karte zu bekommen.

Und mit dem Handy programmiere ich dann die Karten über Tonka

Habe ich Sonderwünsche schreibe ich mit der App

Wenn ich die letzte App nutze, muss ich mich auch irgendwie schlau machen wo sich meine Wunsch Datei befindet.

Hier musst du ja auch etwas nebenher Laufen haben um die Dateien zuzuordnen.

Ja genau. Deshalb ist es dann auch egal, dass man den PC zum konfigurieren braucht. Den braucht man zum Nachgucken ja sowieso. Es geht doch hier nur darum, dass man dem Kind nicht den TonUINO wegnehmen muss und trotzdem nicht einen ganzen 2. TonUINO braucht

Im Gegensatz zur ESP Version braucht man hier nichts, was man vom TonUINO nicht schon hat oder kennt.

1 „Gefällt mir“

Wie gesagt ich lade die mp3s mit der Tonuino-Toolbox von @raphael auf meine SD-Karte und beschreibe meine RFID-Karten mit meinem Tool. Aber das ist ja das Schöne an diesem Projekt, da kann sich jeder einbringen wenn und wie er will.

3 „Gefällt mir“

Das wäre wirklich eine Super Sache mit dem Display und dem Drehencoder!

Die Ordner mit ihrem mp3 Inhalt befinden sich bei mir auf einer (Papier)Liste.

wie soll denn deine Lösung ausschauen?
Beschreib mal was Du alles einbauen / verwenden willst

Wenn ich angesprochen wäre:
Natürlich einen Drehencoder und ein Display, vorzugsweise ein Nokia5110 Display weil das wenig kostet, gut mit Arduino harmoniert und mehr angezeigt werden kann als z.B. auf einem 2x20 LCD Display. Außerdem gibt es dafür eine gut dokumentierte Library. Das alles gesteuert durch einen Arduino oder eine ATmega328P MCU o.ä… Das Menu sollte über den Drehencoder angewählt werden können, also z.B. die Punkte read, write, clear, config… mit den entsprechenden Unterpunkten.