Yeah, another Sonos hack. I just can't stay away. This time, I was inspired by one of the coolest hacks I've ever seen to create a super simple method for my 5 year old to play his music and audiobooks. Wanting something a little more scalable (and MUCH easier to make!) I went with RFID cards instead of those cool NFC-embedded wood blocks.

Architecture

I've been enjoying hacking with the Spark Core lately, so I went with it again for this project. Briefly, the Core is an Arduino-compatible processor with built in WiFi capability, and very cool cloud backend support. You should check it out.

I bought an RC522 board and a bunch of compatible cards to go along with it. Each of these cards has a unique id, and the code on my Spark Core reads that id, looks for a key on a local redis server with that id, and uses the value returned as the final part of a url I send to my local node-sonos-http-api server. For example, if a card with a uid of 3d67dc01 is read, and the redis server returns a value of "/playlist/KnightAtDawn" for that id, I'd construct a url of http://192.168.0.100:5005/family%20room/playlist/KnightAtDawn, which would cause the server to begin playing the Knight at Dawn playlist on the Family Room Sonos. Have a look.

Code

The code, running on the Spark Core, uses Wiring, the Arduino programming language. I'll talk about a couple specific parts here, but check out the whole thing on github. In addition to the stock HttpClient library (which I've included in the repo, in case you're programming the Spark locally versus via the Web-based "Build" IDE), I had to use two third part libraries. The first, is an Arduino RFID library modified for use with the Spark. Note that the same author has a newer, better version of this library that is a lot more robust, and looks like it allows for programming of RFID cards. I didn't need that, and his older library was smaller, so I went with the original.

I also used a redis client that again was orignally built for Arduino and modified to use Sparks TCPClient instead of the original EthernetClient. I had to slightly modify this library, as it was crashing my spark core when there was no value for a key.

So in the loop() of the code, we look for a card next to the reader, and if one is found, we grab the value, check it out on redis, and send any result off to the Sonos API server.

If no card is found on a loop, but one was found on the previous loop (noted by the gotCard variable), we send a stop command to the Sonos API. The only hitch in the flow is that, for some reason, the RFID reader would miss the proximate card every other loop. Rather than getting to the bottom of this problem, I just implemented a noCardCounter to skip these one-off misreads. Lazy, I know, but it works.

Here's the meat of the app:

    RC522.readCardSerial();


    char a[11];
    /* Output the serial number to the UART */
    for(byte i = 0; i < sizeof(RC522.serNum); i++)
    {
      char aa[3];
      itoa(RC522.serNum[i],aa,16);
      if (RC522.serNum[i]<0x10) {
        strcat(a,"0");
      }
      strcat(a,aa);
    }
    strcpy(uid,a);
    uint8_t g = client.GET(a);
    if (g == 1) {
      uint16_t resultSize = client.resultBulk(buffer, 100);
      if (resultSize < 5) {
        return;
      }
      char finalPath[128];
      sprintf(finalPath,"/%s%s",sonosRoom,buffer);
      request.path = finalPath;

      http.get(request, response,headers);
      Serial.print("Application>\tResponse status: ");
      Serial.println(response.status);

      Serial.print("Application>\tHTTP Response Body: ");
      Serial.println(response.body);

    }
  }

In the first section, we construct the uid of the card by converting the int values returned from the RFID class to their corresponding hex values. We send this contructed uid off to the redis server to get a value back. If this value is more than 5 characters long (another hack to get around a problem with the redis library), we sprintf it to our base url to create the final url for the playlist or album.

Wrap Up

I bought a bunch of ink-printable stickers that are roughly the size of the RFID cards, and make stickers the album or audiobook covers to put on the cards. My son just picks out what he wants to listen to, and slides it in to the sweet Lego enclosure he helped me build.

Next up, I'm thinking I'll use the state command from the Sonos API to grab the track number and time of the playlist when the card is removed and the Sonos is stopped. That way, the next time the card is put in, my son can pick up where he left off. I'm also going to order another RFID reader, and put one of these up in the playroom, paired with the Sonos up there. Tunes upstairs and down!

Again check out the code on github and let me know if you come up with any cool modifications.