NFC Movie Library

Hi everyone!

Wanted to share a project I made for my kids: a movie library based on NFC tags.
I wanted to bring back the physicality of VHS tapes and DVDs.

The idea is simple: they browse through our catalog, pick a card, put it on the scanner, and watch a movie.

On the hardware side, I used an ESP32 (C3 Super Mini) and the RC522 NFC reader. The ESP32 is flashed with ESPHome for seamless integration with Home Assistant and local control.

ESPHome Configuration
---
substitutions:
  devicename: "nfc-scanner"
  friendly_name: "NFC Scanner"

packages:
  esphome: !include common/esphome.yaml
  api: !include common/api.yaml
  logger: !include common/logger.yaml
  wifi: !include common/wifi.yaml

esphome:
  # Magic variables to get the ESP32C3 Super Mini to work
  platformio_options:
    board_build.f_flash: 40000000L
    board_build.flash_mode: dio
    board_build.flash_size: 4MB
  on_boot:
    priority: 600
    then:
      - rtttl.play: 'short:d=4,o=5,b=100:16e6,16e6'

esp32:
  variant: ESP32C3
  board: esp32-c3-devkitm-1
  framework:
    type: esp-idf

status_led:
  pin:
    number: GPIO8
    inverted: true

output:
  - platform: ledc
    pin: GPIO3
    id: buzzer

rtttl:
  output: buzzer

spi:
  clk_pin: GPIO4
  mosi_pin: GPIO6
  miso_pin: GPIO5

rc522_spi:
  cs_pin: GPIO7
  on_tag:
    then:
      - rtttl.stop:
      - homeassistant.tag_scanned: !lambda 'return x;'
      - rtttl.play: 'short:d=4,o=5,b=100:16e6,16e6'




When an NFC tag is scanned, ESPHome emits a tag_scanned event to Home Assistant. This is being used to play the correct movie on our Apple TV via Plex. To do that, I maintain a “mapping” between tag IDs and Plex IDs.

The automation only works between certain hours to prevent the kids from abusing the system and watching TV all the time :wink:

Home Assistant automation
- alias: "NFC Reader - Plex"
  description: ""
  mode: single
  trigger:
  - platform: event
    event_type: tag_scanned

  # Only allow movies to be played in the morning and evening.
  # Broad time window to allow for some flexibility in schedule.
  condition:
  - condition: or
    conditions:
    - condition: time
      after: '05:00:00'
      before: '09:00:00'
    - condition: time
      after: '18:00:00'
      before: '19:50:00'  # Bed time!
  action:
  - variables:
	  # Map the ID of each tag to a Plex ID. The "name" attribute is
	  # not used but nice to have for debugging.
      NFC_MAPPING:
        53-77-08-69-71-00-01:
          name: Ratatouille
          plex_id: 37353
        53-72-08-69-71-00-01:
          name: Coco
          plex_id: 3135
		# ...
  - if:
    # Make sure the scanned tag is in the mapping
    - alias: "NFC tag is in the mapping"
      condition: template
      value_template: "{{ trigger.event.data.tag_id in NFC_MAPPING }}"
    then:
    # Turn on the Apple TV (and the TV itself) when it's in standby
    - if:
      - condition: state
        entity_id: media_player.appletv_living
        state: standby
      then:
      - service: media_player.turn_on
        data: {}
        target:
          entity_id: media_player.appletv_living
      - delay:
          seconds: 5

	# Take the Plex ID from the mapping and send a deep link to the 
	# Apple TV. This will automatically open the Plex app and start
	# or resume the given movie.
    - service: media_player.play_media
      target:
        entity_id: media_player.appletv_living
      data:
        media_content_type: url
        media_content_id: "plex://play/?metadataKey=%2Flibrary%2Fmetadata%2F{{ NFC_MAPPING[trigger.event.data.tag_id].plex_id }}&server=xxxxxxxxxxxxxx"

	# Set an appropriate (low) volume on our Sonos Beam
	# Volume controls on Apple TV only support up/down when using an eARC
	# speaker.
    - service: media_player.volume_set
	  data:
	    volume_level: 0.17
	  target:
	    entity_id: media_player.sonos_tv



Using “physical” media has many benefits in my opinion:

  • You have a limited choice of movies to watch. When I was a kid, we didn’t have an infinite catalog of movies to watch. We had a handful of VHS tapes and DVDs. We watched the same movies over and over again, each time discovering new details we would have missed otherwise.

  • It gives my kids autonomy. They can decide what they want to watch and are not dependent on us to operate the remote and navigate to the correct app. (Autonomy within certain time periods, we don’t want them watching a lot of TV)

  • With autonomy comes responsibility. When allowed, they can watch any movie they like. They can even switch between movies. But the timeframe is fixed. They can watch a good chunk of one movie, or they can watch a tiny bit of 10 different movies. It’s completely up to them.

  • They will need to learn to collaborate and compromise. We only have one TV and two half-hour slots to watch it. They must quickly agree on a movie or forfeit their viewing time. Or perhaps they use their creativity to come up with a system to decide who gets to choose the movie, eg: “You can pick in the morning, I’ll pick in the evening.”

Other benefits: it’s cheaper to buy second hand Blurays than keeping a Netflix or Disney+ subscription. My kids keep watching the same movies over and over again. Plus, the Blurays come with dubbed versions (horrible, I knnow) and many extras that are often fun to watch. I specifically love behind the scenes content.


My kids love this system, and it’s super reliable. The only issue I’m having is with TV Shows. I currently use Plex deep links to play a specific movie on my Apple TV. If I want to play TV Shows, I need to go through the Plex integration, but for some reason it complains that my Apple TV doesn’t accept playback controls.

→ I still have this issue, but I found a workaround by using smart playlists and regular deep links: NFC Movie Library - #9 by Savjee


You can read the full write-up here. My 3D models are also available if you want to recreate this setup.

9 Likes

Love the idea. Keep up the good work

This is fantastic, great work!

Hello @Savjee i´m trying to recreat this NFC Tag Reader, but i´m having trouble with installing the code onto the ESP

Can you provide more information what code are you having inside esphome: !include common/esphome.yaml
api: !include common/api.yaml
logger: !include common/logger.yaml

Sure! These files are shared among pretty much all of my ESPHome devices.

esphome.yaml

---
esphome:
  name: $devicename
  comment: $friendly_name
  build_path: .builds/$devicename

api.yaml

---
# Enable Home Assistant API
api:
  encryption:
    key: !secret esphome_encryption_key

ota:
  password: !secret esphome_api_password

logger.yaml

---
# Enable logging
logger:

wifi.yaml

---
# Wi-Fi component package
# https://esphome.io/components/wifi.html

wifi:
  ssid: !secret wifi_iot_ssid
  password: !secret wifi_iot_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${friendly_name} AP
    password: !secret esphome_fallback_ap_password
1 Like

Trying to get something like this working but not really understanding deep links… what goes after server? Is it the token, the ip address, the name of the server? I have tried all of those as well as the Machine identifier and can’t even get them to work in a web browser - ha. Just verifying what it’s supposed to be. thank you!

1 Like

Oh wait you explain in your writeup… and I did go to the devices page, but it lists so many different clientIdentifiers and all of them are different, even for the same device. Must be something odd with my setup… or how did you choose the one to use? There are two different ones listed for my server and neither works.

1 Like

You probably have more than 1 Plex server attached to your account.

An alternative way to get the server token is by opening your Plex server in a web browser.
The URL will look like this:

http://192.168.2.216:32400/web/index.html#!/server/xxxxxxxxxxxxxxxxxxxxxxx/details?key=%2Flibrary%2Fmetadata%2F5&context=source%3Acontent.library~0~0

The server ID is right after the /server/ part.

Update: I still can’t the Plex integration to work with my Apple TV. It keeps complaining about not accepting playback controls. However, I found a way to play a random TV show episode with Plex deep links!

You can create a smart playlist that automatically shuffles itself when opened. Instructions to make a playlist are simple: [TIP] --- how to create an autoplaylist with random sort - Tips, Tricks & How-Tos - Plex Forum

Once that playlist is created, copy the ID from the URL. Example:

http://192.168.2.1:32400/web/index.html#!/server/{SERVER_ID}/playlist?key=%2Fplaylists%2F{PLAYLIST_ID}&context=source%3Acontent.playlists.video~0~0

Now adapt the automation to add support for plex_id and playlist_id attributes:

- alias: "NFC Reader - Plex"
  description: ""
  mode: single
  trigger:
  - platform: event
    event_type: tag_scanned

  # Only allow movies to be played in the morning and evening.
  # Broad time window to allow for some flexibility in schedule.
  condition:
  - condition: or
    conditions:
    - condition: time
      after: '05:00:00'
      before: '09:00:00'
    - condition: time
      after: '18:00:00'
      before: '19:50:00'  # Bed time!
  action:
  - variables:
	  # Map the ID of each tag to a Plex ID. The "name" attribute is
	  # not used but nice to have for debugging.
      NFC_MAPPING:
        53-77-08-69-71-00-01:
          name: Ratatouille
          plex_id: 37353
        53-72-08-69-71-00-01:
          name: Coco
          plex_id: 3135
        04-D3-F2-FD-9F-61-81:
          name: Bing
          playlist_id: 4586
        04-36-F6-32-5F-61-80:
          name: Bumba
          playlist_id: 4587
		# ...

  - if:
    # Make sure the scanned tag is in the mapping
    - alias: "NFC tag is in the mapping"
      condition: template
      value_template: "{{ trigger.event.data.tag_id in NFC_MAPPING }}"
    then:
    # Turn on the Apple TV (and the TV itself) when it's in standby
    - if:
      - condition: state
        entity_id: media_player.appletv_living
        state: standby
      then:
      - service: media_player.turn_on
        data: {}
        target:
          entity_id: media_player.appletv_living
      - delay:
          seconds: 5
	
    # If the matched tag has a "plex_id", play it as movie.
    - if:
        - condition: template
          value_template: "{{ \"plex_id\" in NFC_MAPPING[trigger.event.data.tag_id] }}"
      then:
        - action: media_player.play_media
          data:
            media_content_type: url
            media_content_id: >-
              plex://play/?metadataKey=%2Flibrary%2Fmetadata%2F{{NFC_MAPPING[trigger.event.data.tag_id].plex_id}}&server=xxxxxxxxxxxxxx
        target:
          entity_id: media_player.appletv_living
		  
    # If the matched tag has a "playlist_id", play a random item of it.
    - if:
        - condition: template
          value_template: "{{ \"playlist_id\" in NFC_MAPPING[trigger.event.data.tag_id] }}"
      then:
        - action: media_player.play_media
          data:
            media_content_type: url
            media_content_id: >-
              plex://play/?metadataKey=%2Fplaylists%2F{{NFC_MAPPING[trigger.event.data.tag_id].playlist_id}}&server=xxxxxxxxx
        target:
          entity_id: media_player.appletv_living

	# Set an appropriate (low) volume on our Sonos Beam
	# Volume controls on Apple TV only support up/down when using an eARC
	# speaker.
    - service: media_player.volume_set
	  data:
	    volume_level: 0.17
	  target:
	    entity_id: media_player.sonos_tv

That’s it! Every time the kids scan an NFC card of a TV Show, Plex will re-shuffle the playlist and start playing episodes in a random order.

(Also updated my original blog post in case you want more details)

Thank you for sharing this project @Savjee ! I really like it and started to copy it for my kid.
I just ordered the vinyl stickers, looks like a really good solution - I’ve been trying with different normal stickers but didn’t really like that. I was impressed with your design, did you make those yourself or find them somewhere online?

For anyone trying to implement this on an Android TV (Shield): I couldn’t get Plex deeplinking to work on android tv - and the plex integration wasn’t too reliable in my testing.
So I went with VLC. You can SMB to the share where you host your Plex files, and deeplink using vlc://smb://

Hi Rend!

I made the design for the cards myself. I’m not a designer, so I kept things simple and clean. Here’s the source file (Pixelmator): https://file.io/epakIJ17MekG

1 Like

Hello. Wondering if you can help me with a few things. This is all new to me but after reading your guide here and on the blog post, I thought this was going to be easy but now realize I’m a bit over my head.


I cannot for the life of me get the esp32-c3 device to read the config yaml files. I put them each in their own file like you mentioned above but no matter what path I use, when I click install after editing the code, I get an error saying no file or directory. I even tried taking out the include tag and specifying the path directly but it says it doesn’t like that.

Also, the ESP32-C3 is powered on but even though I followed your pins, I noticed the nfc reader is not powered on unless I provide 3.3v of power to it (at least the LED turns red on the nfc reader) are the 4 pins connected to the esp32 supposed to be enough? I am not sure how else to know the nfc reader is working until I can figure out my config file issue.

Lastly, I would also like to get speaker feedback for my kids when they scan. Would you please share the pins to wire the buzzer/speaker to?

I feel like If I can just get the config file working and home assistant to see when the tags are scanned, I can figure out the rest pretty easily, as I am more familiar with Plex and automations within home assistant. Thank you.

First up: welcome to the Home Assistant community :wink:

I’m a bit over my head.

No worries, we’ve all been there. It took a while before ESPHome “clicked” for me as well.


I put them each in their own file like you mentioned above but no matter what path I use, when I click install after editing the code, I get an error saying no file or directory.

This is my bad. I separate the common parts of my ESPHome configurations into separate files and I haven’t included those in the blog post. Here’s the contents of each file. It’s not required to put them in separate files, but it does allow you to re-use them across configurations.

common/esphome.yaml

---
esphome:
  name: $devicename
  comment: $friendly_name
  build_path: .builds/$devicename

common/api.yaml

---
api:
  encryption:
    key: !secret esphome_encryption_key

ota:
  - platform: esphome
    password: !secret esphome_api_password

common/logger.yaml

---
# Enable logging
logger:

common/wifi.yaml

---
# Wi-Fi component package
# https://esphome.io/components/wifi.html

wifi:
  ssid: !secret wifi_iot_ssid
  password: !secret wifi_iot_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: ${friendly_name} AP
    password: !secret esphome_fallback_ap_password

are the 4 pins connected to the esp32 supposed to be enough?

No, the 4 SPI pins are only for communicating with the reader. You will also need to connect 3.3V and GND to power the board.


Would you please share the pins to wire the buzzer/speaker to?

I connected the buzzer to GPIO3 and GND.


Hope it helps!

Thank you so much for responding. I wasn’t sure if you were active on this project anymore, haha.

I finally got the files working. the path for me was homeassistant/esphome/common even though esphome was saying path should be config/esphome/common. I then had another error but turned out I removed the !include right before the esphome.yaml path. It is finally compiling and installing the config file. Thank you!

For powering the reader, I connected the 3.3v and GND from the reader to the esp32 board pins. Is this correct? The reader powered on, and the esp32 looked like it was still working, but I wasn’t sure if that was proper wiring so I disconnected.

Yes, that’s the goal.

3.3V on the reader to 3.3V on the ESP32.
GND on the reader to GND on the ESP32.

Keep it running and open the logs of your device. You should see events in there when you scan an NFC tag. They will also get forwarded to Home Assistant.

So, I did a thing… And wanted to share:

I got everything working! Thank you so much. I had to change the URL since I was doing mine through my Google TV. The Plex URL ended up being something like
Plex://6fhahdbe94738xgf/55826
The first string after Plex:// being the server I’d and the second string after the / being the movie ID. It worked out perfectly.

One issue I will point out - I guess somehow I put the esp32 into some sort of reset loop. As soon as I cut power, it would not work again until I flashed the firmware. I eventually fixed this but it was one of two things I tried:
Hold down the boot button for 3 seconds and then plug into usb-c while holding the button for another 5 seconds, then flash firmware.

I also tried to manually upload the firmware which saved and downloaded a bin file that I then used to upload to the device. Previously I was just using install on its own to write code. Not sure if that made a difference, just pointing out what I did.

Eventually it all started working fine, even after losing power!

So here’s the fun part, but slightly embarrassing: I initially soldered the pins that came with the boards for testing. I should have left well alone but decided to desolder the pins to solder wires directly. In doing so, I ended up using too much hot air and melting the esp32 board (oops). Other components fell off. Thankfully, it’s so cheap that I just ordered a new board. :grin: And now everything else is set up so I’ll just wire everything and flash the config file to the new board once it arrives.

Thank you so much for this. While testing, my kids were super excited to play with it, so I know they are gonna have a blast with the completed model.