Ashish JoyAkash
Published © MIT

Punarnaada - A Smart NFC Gramophone with Spotify

A classic gramophone reborn for the streaming age, where Spotify playlists become physical records.

AdvancedFull instructions provided2 days55
Punarnaada - A Smart NFC Gramophone with Spotify

Things used in this project

Hardware components

ESP32
Espressif ESP32
×2
PCM5102 I2S DAC
×1
RC522 RFID/NFC Module
×1
TPA3116D Stereo Amplifier Board
×1
5W 8Ω Speaker
×1
DC Motor, 12 V
DC Motor, 12 V
×1
MIFARE Classic 1K NFC Tags
×1
Mini MP1684 Buck Converter
×1
12mm Plywood Sheet
×1
Vegan Leather
×1

Software apps and online services

Arduino IDE
Arduino IDE
KiCad
KiCad
Fusion
Autodesk Fusion
Android Studio
Android Studio
SpotifyEsp32 Library
MFRC522 library
BluetoothA2DPSink + AudioTools library

Hand tools and fabrication machines

BambuLab A1 3D Printer
Makera Carvera PCB Milling Machine
Trotec Speedy 400

Story

Read more

Custom parts and enclosures

STEP File for Punarnada

Schematics

Schematic for Main Board

Bluetooth Board

Code

Code for Main Board

Arduino
This is the code for detecting NFC records communicating with Spotify API and running the motor.
#include <Arduino.h>
#include <SPI.h>
#include <MFRC522.h>
#include <WiFi.h>
#include <SpotifyEsp32.h>

// ---------- NFC PINS ----------
#define RST_PIN 13
#define SS_PIN 5
MFRC522 mfrc522(SS_PIN, RST_PIN);
                                                                                                      
// ---------- WIFI ----------
const char *ssid = "";
const char *password = "";

int motorPin = 27; // Pin connected to the motor driver
bool motorFlag = false;



const int pwmChannel = 0;
const int pwmFreq = 10000;    // 10 kHz
const int pwmResolution = 8;  // 0–255

const int MOTOR_RUN_DUTY = 50;  // fixed speed
// ---------- SPOTIFY ----------
const char *CLIENT_ID = "";
const char *CLIENT_SECRET = "";
const char *REFRESH_TOKEN = "";

Spotify sp(CLIENT_ID, CLIENT_SECRET, REFRESH_TOKEN);

// ---------- NFC Polling ----------
unsigned long lastPollTime = 0;
const unsigned long pollInterval = 100; // NFC check every 100ms

// Debounce last card
byte lastUID[10];
byte lastUIDSize = 0;
unsigned long lastCardTime = 0;
const unsigned long cardCooldown = 3000; // 3 seconds cooldown

// ---------- WIFI CONNECT ----------
void connect_to_wifi()
{
  WiFi.begin(ssid, password);
  Serial.print("Connecting to WiFi...");
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(300);
  }
  Serial.println("\nWiFi Connected!");
  Serial.println(WiFi.localIP());
}

// ---------- AUTH + READ BLOCK ----------
bool authenticateAndRead(byte block, byte *buffer, byte &size, MFRC522::MIFARE_Key &key)
{
  MFRC522::StatusCode status;

  status = mfrc522.PCD_Authenticate(MFRC522::PICC_CMD_MF_AUTH_KEY_A,
                                    block, &key, &(mfrc522.uid));
  if (status != MFRC522::STATUS_OK)
    return false;
  motorFlag = true;
  status = mfrc522.MIFARE_Read(block, buffer, &size);

  return (status == MFRC522::STATUS_OK);
}

// ---------- UID CHECK ----------
bool isSameUID()
{
  if (mfrc522.uid.size != lastUIDSize)
    return false;
  for (byte i = 0; i < mfrc522.uid.size; i++)
    if (mfrc522.uid.uidByte[i] != lastUID[i])
      return false;

  return true;
}

void saveUID()
{
  lastUIDSize = mfrc522.uid.size;
  for (byte i = 0; i < mfrc522.uid.size; i++)
    lastUID[i] = mfrc522.uid.uidByte[i];
}

// ---------- PLAY SPOTIFY URI ----------
void playSpotifyURI(const char *uri)
{
  Serial.print("Playing on Spotify: ");
  Serial.println(uri);

  // *** CORRECT METHOD CALL ***
  response resp = sp.start_resume_playback(uri, 0, 0, nullptr);

  if (resp.status_code == 200 || resp.status_code == 204)
  {
    Serial.println("Spotify playback OK!");
  }
  else
  {
    Serial.printf("Spotify failed: %d\n", resp.status_code);
  }
}

// ---------- NFC READ LOGIC ----------
bool read_nfc()
{
  if (!mfrc522.PICC_IsNewCardPresent())
    return false;
  if (!mfrc522.PICC_ReadCardSerial())
    return false;

  // prevent repeat playback for same card
  if (millis() - lastCardTime < cardCooldown && isSameUID())
  {
    mfrc522.PICC_HaltA();
    mfrc522.PCD_StopCrypto1();
    return false;
  }

  saveUID();
  lastCardTime = millis();

  byte buffer1[18], buffer2[18];
  byte size = sizeof(buffer1);

  MFRC522::MIFARE_Key key;
  for (byte i = 0; i < 6; i++)
    key.keyByte[i] = 0xFF;

  if (!authenticateAndRead(1, buffer1, size, key))
    goto exit_nfc;
  if (!authenticateAndRead(2, buffer2, size, key))
    goto exit_nfc;

  char uriRaw[33];
  memcpy(uriRaw, buffer1, 16);
  memcpy(uriRaw + 16, buffer2, 16);
  uriRaw[32] = '\0';

  // trim spaces
  for (int i = 31; i >= 0; i--)
    if (uriRaw[i] == ' ')
      uriRaw[i] = '\0';
    else
      break;

  Serial.print("Card URI: ");
  Serial.println(uriRaw);

  char spotifyURI[48];
  snprintf(spotifyURI, sizeof(spotifyURI), "spotify:%s", uriRaw);

  playSpotifyURI(spotifyURI);

exit_nfc:
  mfrc522.PICC_HaltA();
  mfrc522.PCD_StopCrypto1();
  return true;
}

// ---------- SETUP ----------
void setup()
{
  Serial.begin(115200);



  SPI.begin();
  mfrc522.PCD_Init();
  Serial.println("NFC Reader Ready");

  connect_to_wifi();

  sp.begin();
  while (!sp.is_auth())
    sp.handle_client();
  Serial.println("Spotify Authenticated!");

  ledcSetup(pwmChannel, pwmFreq, pwmResolution);
  ledcAttachPin(motorPin, pwmChannel);


  ledcWrite(pwmChannel, 30);          // run motor at 50% duty
}

// ---------- LOOP ----------
void loop()
{
  unsigned long now = millis();
  if (now - lastPollTime >= pollInterval)
  {
    lastPollTime = now;
    read_nfc();
  }

  sp.handle_client();
}

Credits

Ashish Joy
2 projects • 6 followers
Mechatronics engineer | Exploring the intersection of design, code & machines
Akash
3 projects • 24 followers
I am a Mechanical Engineer with experience in design engineering, Fab Academy graduate, skilled in design for manufacturing.

Comments