TagTuner music player for NFC tags

OMG, so easy, works just fine.
I only checked github, and this awesome installer is not listed there (or have I not seen it?)

Thanks you so much for the quick help!

I stumbled across this last night while trying (and failing) to do something similar with MAss and the Adonno reader, so it’s saving me a ton of work! I’ve now got it flashed and setup to use a HA Voice PE as the media player, which is itself connected to a regular hifi system via aux cable.

My question is: Can I flash the Voice PE firmware to get the media control functionality while keeping the reader part separate? My gut says yes but I’m not at all familiar with ESP32 development.

Also, would it be possible to add a thing to the blueprint for adding user-defined actions (similar to how this blueprint does it)? My goal is to add an IR blaster to the setup and have it turn on the hifi and switch it to the aux input when a tag is scanned.

Thanks for your work with this!

sure you can flash Voice PE to get media control without NFC. Flash it from ready made firmware or wait till I release 04 update (haven’t checked April version yet).
It will not work since TagTuner events are generated only when NFC reader has set tag presence flag.

But anyway, you can do whatever automation you want triggered by esphome.tagtuner events. Just set TagTuner blueprint automation and “take control” to see all the details needed. Then set your own automations triggered in parallel to TagTuner blueprint.

I’ve been trying to get this working with a ESP32 Wroom 32D, but i just cant get any reaction from the reader.

Ive had to alter some things, like the pins etc from the yamls.
But nothing happens when i place a card on the reader, it’s like the reader is dead. (light is ON on the NFC chip).
Also tried to do a TagReader with the same wroom, but same result, no reaction whatsoever.

Does anybody have any experience on using this on an ESP32 WROOM?

so probably your pins are not correct. Check the logs for pn532 component.

Hi,

I am testing my setup and I got some pandom Mifare 1k tags. I can scan the tag with valid NDEF only like 1 out of 10 tries. Could you please help be with logs what may be the reason?
Are these tags not suitable for reliable reading?

Typical errors:

  • Error, Block authentication failed
  • Error reading block
  • Authentication failed

I would like to know if the situation may get better with different type of tags, or how to make it work with these.

Thanks a lot!

Log:

[21:35:50][D][pn532:364]: Mifare classic
[21:35:51][E][pn532.mifare_classic:045]: Error reading block 9
[21:35:51][E][nfc.ndef_message:049]: Corrupt record encountered; NdefMessage constructor aborting
[21:35:51][D][binary_sensor:036]: '=Tag=': Sending state ON
[21:35:51][D][text_sensor:064]: 'Status': Sending state 'Tag 21-20-B8-FB'
[21:35:51][D][text_sensor:064]: 'Status': Sending state 'Plain UID tag'
[21:35:51][D][tagtuner:284]: No TagTuner NDEF, using UID
[21:35:51][D][pn532:200]: Found new tag '21-20-B8-FB'
[21:35:51][D][pn532:204]:   NDEF formatted records:
[21:35:51][D][pn532:206]:     T - TagTuner
[21:35:51][D][pn532:206]:     T - artist/w
[21:35:51][D][pn532:206]:     T - playlist/y
[21:35:51][D][pn532:379]: Waiting to read next tag
[21:35:51][W][component:239]: Component pn532 took a long time for an operation (307 ms).
[21:35:51][W][component:240]: Components should block for at most 30 ms.
[21:35:51][D][text:050]: 'Playlist URI' - Setting text value: Tag 21-20-B8-FB
[21:35:51][D][text:016]: 'Playlist URI': Sending state Tag 21-20-B8-FB
[21:35:51][D][text:050]: 'Playlist artist' - Setting text value: Shabaka
[21:35:51][D][text:016]: 'Playlist artist': Sending state Shabaka
[21:35:51][D][text:050]: 'Playlist name or album title' - Setting text value: We Out Here
[21:35:51][D][text:016]: 'Playlist name or album title': Sending state We Out Here
[21:35:51][D][rtttl:061]: Playing song beep_only
[21:35:51][D][rtttl:390]: State changed from STATE_STOPPED to STATE_RUNNING
[21:35:51][D][binary_sensor:036]: '=Tag=': Sending state OFF
[21:35:51][D][text_sensor:064]: 'Status': Sending state 'Tag removed'
[21:35:51][D][rtttl:390]: State changed from STATE_RUNNING to STATE_STOPPED
[21:35:51][D][rtttl:367]: Playback finished
[21:35:51][D][pn532:364]: Mifare classic
[21:35:51][E][pn532.mifare_classic:101]: Authentication failed - Block 0x04
[21:35:51][E][pn532.mifare_classic:038]: Error, Block authentication failed for 4
[21:35:51][E][pn532.mifare_classic:045]: Error reading block 4
[21:35:51][E][pn532.mifare_classic:045]: Error reading block 5
[21:35:51][E][pn532.mifare_classic:045]: Error reading block 6
[21:35:51][E][pn532.mifare_classic:101]: Authentication failed - Block 0x08
[21:35:52][E][pn532.mifare_classic:038]: Error, Block authentication failed for 8
[21:35:52][E][pn532.mifare_classic:045]: Error reading block 8
[21:35:52][E][pn532.mifare_classic:045]: Error reading block 9
[21:35:52][D][binary_sensor:036]: '=Tag=': Sending state ON
[21:35:52][D][text_sensor:064]: 'Status': Sending state 'Tag 21-20-B8-FB'
[21:35:52][D][text_sensor:064]: 'Status': Sending state 'Plain UID tag'
[21:35:52][D][tagtuner:284]: No TagTuner NDEF, using UID
[21:35:52][D][pn532:200]: Found new tag '21-20-B8-FB'
[21:35:52][D][pn532:379]: Waiting to read next tag
[21:35:52][W][component:239]: Component pn532 took a long time for an operation (589 ms).
[21:35:52][W][component:240]: Components should block for at most 30 ms.
[21:35:52][D][text:050]: 'Playlist URI' - Setting text value: Tag 21-20-B8-FB
[21:35:52][D][text:016]: 'Playlist URI': Sending state Tag 21-20-B8-FB
[21:35:52][D][text:050]: 'Playlist artist' - Setting text value: Shabaka
[21:35:52][D][text:016]: 'Playlist artist': Sending state Shabaka
[21:35:52][D][text:050]: 'Playlist name or album title' - Setting text value: We Out Here
[21:35:52][D][text:016]: 'Playlist name or album title': Sending state We Out Here
[21:35:52][D][rtttl:061]: Playing song beep_only
[21:35:52][D][rtttl:390]: State changed from STATE_STOPPED to STATE_RUNNING
[21:35:52][D][binary_sensor:036]: '=Tag=': Sending state OFF
[21:35:52][D][text_sensor:064]: 'Status': Sending state 'Tag removed'
[21:35:52][D][rtttl:390]: State changed from STATE_RUNNING to STATE_STOPPED
[21:35:52][D][rtttl:367]: Playback finished
[21:35:55][D][text_sensor:064]: 'Status': Sending state 'Waiting for input'
[21:35:56][D][pn532:364]: Mifare classic
[21:35:56][E][pn532.mifare_classic:101]: Authentication failed - Block 0x04
[21:35:56][D][binary_sensor:036]: '=Tag=': Sending state ON
[21:35:56][D][text_sensor:064]: 'Status': Sending state 'Tag 71-69-B5-FB'
[21:35:56][D][text_sensor:064]: 'Status': Sending state 'Plain UID tag'
[21:35:56][D][tagtuner:284]: No TagTuner NDEF, using UID
[21:35:56][D][pn532:200]: Found new tag '71-69-B5-FB'
[21:35:56][D][pn532:379]: Waiting to read next tag
[21:35:56][W][component:239]: Component pn532 took a long time for an operation (71 ms).
[21:35:56][W][component:240]: Components should block for at most 30 ms.
[21:35:56][D][text:050]: 'Playlist URI' - Setting text value: Tag 71-69-B5-FB
[21:35:56][D][text:016]: 'Playlist URI': Sending state Tag 71-69-B5-FB
[21:35:56][D][text:050]: 'Playlist artist' - Setting text value: Shabaka
[21:35:56][D][text:016]: 'Playlist artist': Sending state Shabaka
[21:35:56][D][rtttl:061]: Playing song beep_only
[21:35:56][D][rtttl:390]: State changed from STATE_STOPPED to STATE_RUNNING
[21:35:56][D][text:050]: 'Playlist name or album title' - Setting text value: We Out Here
[21:35:56][D][text:016]: 'Playlist name or album title': Sending state We Out Here
[21:35:57][D][rtttl:390]: State changed from STATE_RUNNING to STATE_STOPPED
[21:35:57][D][rtttl:367]: Playback finished
[21:35:57][D][binary_sensor:036]: '=Tag=': Sending state OFF
[21:35:57][D][text_sensor:064]: 'Status': Sending state 'Tag removed'
[21:35:58][D][pn532:364]: Mifare classic
[21:35:59][D][binary_sensor:036]: '=Tag=': Sending state ON
[21:35:59][D][text_sensor:064]: 'Status': Sending state 'Tag 71-69-B5-FB'
[21:35:59][D][text:050]: 'Playlist artist' - Setting text value: z
[21:35:59][D][text:016]: 'Playlist artist': Sending state z
[21:35:59][D][pn532:200]: Found new tag '71-69-B5-FB'
[21:35:59][D][pn532:204]:   NDEF formatted records:
[21:35:59][D][pn532:206]:     T - TagTuner
[21:35:59][D][pn532:206]:     T - artist/z
[21:35:59][D][pn532:206]:     T - playlist/je
[21:35:59][D][pn532:206]:     U - library://track/1513
[21:35:59][D][pn532:379]: Waiting to read next tag
[21:35:59][W][component:239]: Component pn532 took a long time for an operation (322 ms).
[21:35:59][W][component:240]: Components should block for at most 30 ms.
[21:35:59][D][text:050]: 'Playlist name or album title' - Setting text value: je
[21:35:59][D][text:016]: 'Playlist name or album title': Sending state je
[21:35:59][D][tagtuner:060]: URI NDEF
[21:35:59][D][text:050]: 'Playlist URI' - Setting text value: library://track/1513
[21:35:59][D][text:016]: 'Playlist URI': Sending state library://track/1513
[21:35:59][D][rtttl:061]: Playing song scale_up
[21:35:59][D][rtttl:390]: State changed from STATE_STOPPED to STATE_RUNNING
[21:35:59][W][rtttl:038]: RTTTL Component is already playing: scale_up
[21:35:59][D][binary_sensor:036]: '=Tag=': Sending state OFF
[21:35:59][D][text_sensor:064]: 'Status': Sending state 'Tag removed'
[21:35:59][D][pn532:364]: Mifare classic
[21:35:59][E][pn532.mifare_classic:101]: Authentication failed - Block 0x04
[21:35:59][D][binary_sensor:036]: '=Tag=': Sending state ON
[21:35:59][D][text_sensor:064]: 'Status': Sending state 'Tag 71-69-B5-FB'
[21:35:59][D][text_sensor:064]: 'Status': Sending state 'Plain UID tag'
[21:35:59][D][tagtuner:284]: No TagTuner NDEF, using UID
[21:35:59][D][pn532:200]: Found new tag '71-69-B5-FB'
[21:35:59][D][pn532:379]: Waiting to read next tag
[21:35:59][W][component:239]: Component pn532 took a long time for an operation (129 ms).
[21:35:59][W][component:240]: Components should block for at most 30 ms.
[21:35:59][D][text:050]: 'Playlist URI' - Setting text value: Tag 71-69-B5-FB
[21:35:59][D][text:016]: 'Playlist URI': Sending state Tag 71-69-B5-FB
[21:35:59][D][text:050]: 'Playlist artist' - Setting text value: Zaz
[21:35:59][D][text:016]: 'Playlist artist': Sending state Zaz
[21:35:59][D][text:050]: 'Playlist name or album title' - Setting text value: Zaz
[21:35:59][D][text:016]: 'Playlist name or album title': Sending state Zaz
[21:35:59][W][rtttl:038]: RTTTL Component is already playing: scale_up
[21:35:59][D][rtttl:390]: State changed from STATE_RUNNING to STATE_STOPPED
[21:35:59][D][rtttl:367]: Playback finished
[21:36:00][D][binary_sensor:036]: '=Tag=': Sending state OFF
[21:36:00][D][text_sensor:064]: 'Status': Sending state 'Tag removed'
[21:36:03][D][text_sensor:064]: 'Status': Sending state 'Waiting for input'
[21:40:41][D][esp32_ble_tracker:282]: Starting scan...

I would resolder the pins first. Maybe replace the wires? Then I’d make the i2c bus slower.
And if that won’t make it, try another tags (read-only like Disney Infinity or Amiibo or Philips Sonicare or MoodBlocks) and then try another nfc reader.

Actual tag scanning seems to be reliable, but reading the data from the tag not. It often does not recognize the NDEF definition.
I tried slowing down the I2C and that didn’t help.

If you had any other ideas, please let me know.

I’ll try checking I2C signals with scope and adding some bypass capacitor for the nfc module.

Hi, has anyone got this reliably working with the Adonno Tag reader setup. I get it to read card values but it only works like 5% of the time. Some days I think it’s fixed but then it turns flakey all over again.

i have the same kind of issues, but using the standard tagtuner version, not tagreader setup.
when it works, it works fine. But more often than not I get different ‘bugs’:

  • when placing a tag, it starts playing for a second, and then immedialty pauses the song.
  • removing a tag sometimes does not stop the player.
  • adding “/shuffle” at the end of a URL doesnt enable the playlist shuffle.

I don’t have Adonno Tagreader but I’m running 8266 nodemcu version daily.
Have you tried this Tag Tuner light yaml version?

Which controller are you using? When not working, what logs do you see? Have you looked at automation trace? Any error there?

Now looking at the automation traces, I can see that upon placing a tag-card, it does recognize it and starts all the “tag-scanned” actions. But immediately after, within the same second, it reads “tag-removed” and starts all actions accordingly, thus pausing the player, and setting shuffle back to off. So maybe I have something setup wrong with the card reader? Or we should add some seconds after reading a tag before it can start a tag removed action.

I’m using a wroom32, that’s the only difference with your D1 setup, and had to only change that entry in your code.

Have you tried putting the tag flat on the Tag Tuner? Try playing with tag distance from the reader. You could also disable „stop on tag removed” in the automation but only if you stop the player any other way - I’m using Alexa for that on my 8266 reader. This is a really simple and cheap nfc reader, probably Chinese knockoff even so have some understanding and use your diy spirit! :wink:

Great project!!! I would love to try as I already have nfc tage reader/write but it’s RC522. Would this be working on RC522?

Yes, it will work with pn522 but without ndef support (as described here RC522 NFC/RFID — ESPHome). So TagTuner with pn522 will work like with read-only tags TagTuner music player | Powered by ESPHome and ESP Web Tools

1 Like

I haven’t seen this light version before so I may wire up a 8266 again and try.

In the meantime I had a ESP32 WROOM dev lying around. Modified the adonno tagtuner code to work with this and it is flawless. I think it may no be worth recommending the adonno with 8266 unless someone can confirm it actually works reliably.

Tried this yesterday, but with modified adonno tagtuner code (as I had the buzzer and not a rotary encoder yet). Found it worked quite well. Make sure you the correct pins for the pn532 to work. i.e sda: GPIO21 scl: GPIO22.

I hope this helps.

edit: this was with an ESP-WROOM-32 dev board.

esp32 boards will work with TagTuner code easily (TagTuner/tagtuner-D1-custom1.yaml at 980c3545319d15eb3a98ceb4a086121fc2a8de3f · luka6000/TagTuner · GitHub) with passive bluetooth proxy as an extra addon and I fully recommend this kind of setup. And yes, you have to set i2c pins correctly since esp32 has more than one i2c controller.

Been struggling with code recent days…I wanted to add small display but on esp32-s3-dev board (incl. rotary encoder and with PN532 nfc board). Had to modify code a bit but now I’m having problem with writing tags. It seem write process can’t write to tag. I’m using ‘original’ fob provided with nfc module.

[17:31:24][D][button:010]: 'Write Tag' Pressed.
[17:31:24][D][light:036]: 'led1' Setting:
[17:31:24][D][light:047]:   State: ON
[17:31:24][D][light:051]:   Brightness: 70%
[17:31:24][D][light:059]:   Red: 100%, Green: 0%, Blue: 0%
[17:31:24][D][light:109]:   Effect: 'RedTagWriteEffect'
[17:31:24][D][ndef:538]: Writing payload: spotify://playlist/6UiFjIT31XYHqnwhlE8sj6
[17:31:24][D][pn532:392]: Waiting to write next tag
[17:31:24][D][text_sensor:064]: 'Status': Sending state 'Place tag'
[17:31:24][D][binary_sensor:036]: '=Reading=': Sending state OFF
[17:31:24][D][binary_sensor:036]: '=Writing=': Sending state ON
[17:31:26][D][pn532:224]:   Tag writing...
[17:31:26][D][pn532:225]:   Tag formatting...
[17:31:27][D][pn532:229]:   Writing NDEF data
[17:31:27][E][pn532.mifare_ultralight:125]: Message length exceeds tag capacity 108 > 0
[17:31:27][E][pn532:231]:   Failed to write message to tag
[17:31:27][D][pn532:233]:   Finished writing NDEF data
[17:31:27][D][pn532:379]: Waiting to read next tag
[17:31:27][W][component:257]: Component pn532 took a long time for an operation (988 ms).
[17:31:27][W][component:258]: Components should block for at most 30 ms.
[17:31:27][D][text_sensor:064]: 'Status': Sending state 'Finished writing tag attempt'
[17:31:27][D][light:036]: 'led1' Setting:
[17:31:27][D][light:109]:   Effect: 'GreenSuccessEffect'
[17:31:27][D][binary_sensor:036]: '=Reading=': Sending state ON
[17:31:27][D][binary_sensor:036]: '=Writing=': Sending state OFF
[17:31:27][D][text_sensor:064]: 'Status': Sending state 'Tag removed'
[17:31:27][D][light:036]: 'led1' Setting:
[17:31:27][D][light:109]:   Effect: 'BlueConfirmEffect'
[17:31:28][D][light:036]: 'led1' Setting:
[17:31:28][D][light:047]:   State: OFF
[17:31:28][D][light:109]:   Effect: 'None'
[17:31:28][D][light:036]: 'led1' Setting:
[17:31:30][D][text_sensor:064]: 'Status': Sending state 'Waiting for input'

My yaml code in Esphome:


substitutions:
  name: "tagtuner-s3"
  friendly_name: "TagTuner S3"
  media_player_entity_id: "media_player.louder_esp32s3_airplay"
  magic: "250308"

globals:
  - id: artist
    type: std::string
    initial_value: '""'
  - id: playlist
    type: std::string
    initial_value: '""'
  - id: uri
    type: std::string
    initial_value: '""'

text_sensor: 
  - platform: wifi_info
    ip_address:
      id: wifi_ip_address_sensor 
      name: "${friendly_name} IP Address" 
      entity_category: diagnostic
      disabled_by_default: true 
  
  - platform: homeassistant
    id: media_player_title
    entity_id: ${media_player_entity_id}
    attribute: media_title
    internal: true
  - platform: homeassistant
    id: media_player_artist
    entity_id: ${media_player_entity_id}
    attribute: media_artist
    internal: true
  - platform: homeassistant
    id: media_player_state_text
    entity_id: ${media_player_entity_id}
    internal: true

  - platform: template
    id: status 
    name: "Status"
    icon: mdi:ladybug
    entity_category: DIAGNOSTIC
    on_value:
      if:
        condition:
          lambda: 'return id(status).state != "Waiting for input";'
        then:
        - script.stop: wait_input
        - script.execute: wait_input

binary_sensor:
  - platform: gpio
    id: toggle        
    pin:
      number: GPIO12 
      inverted: true
      mode:
        input: true
        pullup: true
    on_multi_click: 
    - timing: 
        - ON for at most 1s
        - OFF for at most 0.25s
        - ON for at most 1s
        - OFF for at most 0.25s
        - ON for at most 1s
      then: 
        - script.execute: led_blink
        - text_sensor.template.publish:
            id: status
            state: "clickTriple"
        - script.execute:
            id: action_event
            action: "clickTriple"
    - timing: 
        - ON for at most 1s
        - OFF for at most 0.25s
        - ON for at most 1s
        - OFF for at least 0.25s 
      then: 
        - script.execute: led_blink
        - text_sensor.template.publish:
            id: status
            state: "clickDouble"
        - script.execute:
            id: action_event
            action: "clickDouble"
    - timing: 
        - ON for 0.5s to 2s 
        - OFF for at least 50ms
      then: 
        - script.execute: led_blink
        - text_sensor.template.publish:
            id: status
            state: "clickLong"
        - script.execute:
            id: action_event
            action: "clickLong"
    - timing: 
        - ON for at most 0.5s
        - OFF for at least 260ms
      then: 
        - script.execute: led_blink 
        - text_sensor.template.publish:
            id: status
            state: "clickSingle"
        - script.execute:
            id: tagtuner_event 
            action: "clickSingle"
            uid: ""
            uri: ""
            artist: ""
            playlist: ""

  - platform: template
    name: "=Tag="
    id: is_tag 
    device_class: vibration
    entity_category: DIAGNOSTIC
    disabled_by_default: true

  - platform: template
    name: "=Reading="
    id: txt_reading
    device_class: running
    entity_category: DIAGNOSTIC
    lambda: |-
      if ( !id(pn532_board).is_writing() ) {
        return true;
      } else {
        return false;
      }
  - platform: template
    name: "=Writing="
    id: txt_writing
    device_class: running
    entity_category: DIAGNOSTIC
    lambda: |-
      if ( id(pn532_board).is_writing() ) {
        return true;
      } else {
        return false;
      }
      
sensor:
  - platform: rotary_encoder
    id: rotary 
    pin_a: GPIO10 
    pin_b: GPIO11 
    on_clockwise:
      - {text_sensor.template.publish: {id: status, state: "Volume up"}}
      - {script.execute: {id: tagtuner_event, action: "volumeUp", uid: "", uri: "", artist: "", playlist: ""}}
      - {script.execute: led_blink}
    on_anticlockwise:
      - {text_sensor.template.publish: {id: status, state: "Volume down"}}
      - {script.execute: {id: tagtuner_event, action: "volumeDown", uid: "", uri: "", artist: "", playlist: ""}}
      - {script.execute: led_blink}
      
  - platform: homeassistant
    id: media_player_duration
    entity_id: ${media_player_entity_id}
    attribute: media_duration
    internal: true 
    
  - platform: homeassistant
    id: media_player_position
    entity_id: ${media_player_entity_id}
    attribute: media_position
    internal: true
    
text:
  - platform: template
    id: playlist_artist
    name: "Playlist artist"
    icon: mdi:account-music
    entity_category: CONFIG
    optimistic: true
    min_length: 0
    max_length: 50
    mode: text
    initial_value: " "
  - platform: template
    id: playlist_info
    name: "Playlist name or album title"
    icon: mdi:playlist-music
    entity_category: CONFIG
    optimistic: true
    min_length: 0
    max_length: 100
    mode: text
    initial_value: " "
  - platform: template
    id: playlist_uri
    name: "Playlist URI"
    icon: mdi:link-variant
    entity_category: CONFIG
    optimistic: true
    min_length: 0
    max_length: 255
    mode: text
    initial_value: " "

light:
  - platform: esp32_rmt_led_strip 
    id: led1            
    pin: GPIO48              
    chipset: ws2812        
    num_leds: 1
    rgb_order: GRB
    rmt_channel: 0
    restore_mode: ALWAYS_OFF
    default_transition_length: 0s 
    effects:
      - addressable_rainbow:
          name: RainbowBootEffect
          speed: 50
          width: 20
      - strobe:
          name: BlueConfirmEffect 
          colors:
            - red: 0% 
              green: 0%
              blue: 100%
              brightness: 100% 
              state: true      
              duration: 100ms
            - state: false
              duration: 100ms
      - strobe:
          name: GreenOkEffect 
          colors:
            - red: 0%
              green: 100%
              blue: 0%
              brightness: 100%
              state: true
              duration: 150ms
            - state: false
              duration: 150ms
      - strobe:
          name: GreenSuccessEffect 
          colors:
            - red: 0%
              green: 100%
              blue: 0%
              brightness: 100%
              state: true
              duration: 150ms
            - state: false
              duration: 150ms
            - red: 0% 
              green: 100%
              blue: 0%
              brightness: 100%
              state: true
              duration: 150ms
            - state: false
              duration: 150ms
            - red: 0% 
              green: 100%
              blue: 0%
              brightness: 100%
              state: true
              duration: 150ms
            - state: false 
              duration: 150ms
      - addressable_scan: 
          name: RedTagWriteEffect
          move_interval: 80ms
          scan_width: 1

script:
  - id: led_boot_sequence 
    then:
      - light.turn_on: {id: led1, effect: RainbowBootEffect}
      - delay: 2000ms 
      - light.turn_off: led1
  - id: led_blink 
    then:
      - light.turn_on: {id: led1, effect: BlueConfirmEffect}
      - delay: 250ms 
      - light.turn_off: led1
  - id: led_ok 
    then:
      - light.turn_on: {id: led1, effect: GreenOkEffect}
      - delay: 350ms 
      - light.turn_off: led1
  - id: led_success 
    then:
      - light.turn_on: {id: led1, effect: GreenSuccessEffect}
      - delay: 950ms 
      - light.turn_off: led1
  - id: led_tagwrite 
    then:
      - light.turn_on:
          id: led1
          effect: RedTagWriteEffect
          red: 1.0      
          green: 0.0    
          blue: 0.0     
          brightness: 0.7 

  - id: wait_input
    then:
      - delay: 3s
      - text_sensor.template.publish: {id: status, state: "Waiting for input"}
  - id: set_tag
    then:
      - text.set: {id: playlist_artist, value: !lambda "return id(artist);"} # POPRAVEK: Odstranjen .get_state()
      - delay: 10ms
      - text.set: {id: playlist_info, value: !lambda "return id(playlist);"} # POPRAVEK: Odstranjen .get_state()
      - delay: 10ms
      - text.set: {id: playlist_uri, value: !lambda "ESP_LOGD(\"tagtuner_script\", \"Setting URI from global: %s\", id(uri).c_str()); return id(uri);"} # POPRAVEK: Odstranjen .get_state(), uporabljen .c_str() za ESP_LOGD

  - id: action_event
    parameters: {action: string}
    mode: queued
    max_runs: 3
    then:
      - homeassistant.event:
          event: esphome.tagtuner
          data: {magic: "${magic}", action: !lambda "return action;"}
  - id: tagtuner_event
    parameters: {action: string, uid: string, uri: string, artist: string, playlist: string}
    mode: queued
    max_runs: 3
    then:
      - homeassistant.event:
          event: esphome.tagtuner
          data: {magic: "${magic}", action: !lambda "return action;", uid: !lambda "return uid;", uri: !lambda "return uri;", artist: !lambda "return artist;", playlist: !lambda "return playlist;"}

esphome:
  min_version: 2025.2.0
  name: "${name}" 
  friendly_name: ${friendly_name} 
  name_add_mac_suffix: true
  project:
    name: LukaGra.${friendly_name}
    version: dev
  on_boot:
    priority: -100
    then:
      - wait_until:
          condition:
            api.connected
          timeout: 20s
      - text_sensor.template.publish: 
          id: status
          state: "Ready"

api:
  encryption:
    key: "ASFaaMAAhGyeSVXF7xb/YlQitEV/RjDM5YLUjN2jSC0="

ota:
  - platform: esphome
    password: "ee34dbbf1eeceee5dd23d21c85842557"

wifi:
  id: wifi_state # <<< SPREMENJEN ID, DA SE IZOGNEMO KONFLIKTU Z INTEGRACIJO 'wifi'
  networks:
  - ssid: !secret wifi_ext
    password: !secret wifipassext
  - ssid: !secret wifi_ssid_ap
    password: !secret wifi_password
  - ssid: !secret wifi_ssid
    password: !secret wifi_pass
  - ssid: !secret wifi_ssid_ap2
    password: !secret wifi_pass
  - ssid: !secret wifi_ssid_ap3
    password: !secret wifi_pass
  manual_ip: 
    static_ip: 192.168.31.223
    gateway: 192.168.31.1
    subnet: 255.255.255.0

  ap:
    ssid: "Nfc-Esp32-S3-1 Fallback Hotspot"
    password: "IbtQaxMvMz48"
captive_portal:

esp32:
  board: esp32-s3-devkitc-1
  framework:
    type: arduino

logger:
  level: DEBUG
  logs:
    pn532: DEBUG

i2c:
  id: bus_i2c
  sda: GPIO8
  scl: GPIO9
  scan: true
  frequency: 100kHz

pn532_i2c:
  id: pn532_board 
  i2c_id: bus_i2c
  update_interval: 350ms 
  on_tag:
    - lambda: |-
        id(playlist)=""; 
        id(artist)="";
        id(uri)="";
        if (tag.has_ndef_message()) { 
          ESP_LOGD("on_tag", "Tag has NDEF message. Records: %d", tag.get_ndef_message()->get_records().size());
          for (auto &record : tag.get_ndef_message()->get_records() ) {
            std::string payload_str = record->get_payload();
            std::string type_str = record->get_type();
            ESP_LOGD("on_tag", "Record type: %s, payload: %s", type_str.c_str(), payload_str.c_str());
            if ( payload_str.rfind("artist/", 0) == 0 ) { 
              id(artist)=payload_str.substr(7);
            }
            else if ( payload_str.rfind("playlist/", 0) == 0 ) { 
              id(playlist)=payload_str.substr(9);
            }
            else if (type_str == "U" 
            && (payload_str.length() < 20 || payload_str.substr(0, 20) != "https://mb.senic.com")
            && (payload_str.length() < 23 || payload_str.substr(0, 23) != "https://www.philips.com")
            ) {
              id(uri)=payload_str;
            }
          }
        } else {
          ESP_LOGD("on_tag", "Tag does not have an NDEF message.");
        }
    - binary_sensor.template.publish:
        id: is_tag 
        state: true
    - text_sensor.template.publish:
        id: status 
        state: !lambda 'return "Tag "+x;' 
    - if:
        condition:
          lambda: 'return id(uri) == "";' # POPRAVEK: Odstranjen .get_state()
        then:
          - text_sensor.template.publish:
              id: status 
              state: "Plain UID tag"
          - homeassistant.tag_scanned: !lambda |-
              ESP_LOGD("tagtuner_event", "No TagTuner NDEF, using UID: %s", x.c_str());
              return x; 
        else:
          - script.execute:
              id: tagtuner_event 
              action: "tagScanned"
              uid: !lambda 'return x;' 
              uri: !lambda 'return id(uri);' # POPRAVEK: Odstranjen .get_state()
              artist: !lambda 'return id(artist);' # POPRAVEK: Odstranjen .get_state()
              playlist: !lambda 'return id(playlist);' # POPRAVEK: Odstranjen .get_state()
          - script.execute: set_tag 
    - script.execute: led_ok 

  on_tag_removed:
    - binary_sensor.template.publish:
        id: is_tag 
        state: false
    - text_sensor.template.publish:
        id: status 
        state: "Tag removed"
    - script.execute:
        id: tagtuner_event 
        action: "tagRemoved"
        uid: !lambda 'return x;' 
        uri: ""
        artist: ""
        playlist: ""
    - script.execute: led_blink 

button:
  - platform: restart
    name: "Restart"
    id: btn_restart
    entity_category: DIAGNOSTIC

  - platform: template
    name: Cancel writing
    id: btn_cancel_writing
    icon: "mdi:broadcast-off"
    entity_category: CONFIG
    on_press:
      then:
      - text_sensor.template.publish: {id: status, state: "Cancel writing (manual tag removal needed)"}
      - script.execute: led_ok

  - platform: template
    name: Erase Tag
    id: btn_erase_tag
    icon: "mdi:nfc-search-variant"
    entity_category: CONFIG
    on_press:
      then:
      - script.execute: led_tagwrite 
      - text.set: {id: playlist_artist, value: ''}
      - text.set: {id: playlist_info, value: ''}
      - text.set: {id: playlist_uri, value: ''}
      - lambda: |-
          auto message = new nfc::NdefMessage();
          ESP_LOGD("ndef", "Attempting to erase tag by writing an empty NDEF message.");
          id(pn532_board).write_mode(message); 
      - text_sensor.template.publish: {id: status, state: "Place tag to erase"}
      - wait_until:
          timeout: 30s
          condition: {not: {pn532.is_writing: null}} 
      - text_sensor.template.publish: {id: status, state: "Finished erasing tag attempt"}
      - script.execute: led_success 

  - platform: template
    name: Write Tag
    id: btn_write_tag
    icon: "mdi:cast-audio-variant"
    entity_category: CONFIG
    on_press:
      then:
      - script.execute: led_tagwrite 
      - lambda: |-
          auto message = new nfc::NdefMessage();
          message->add_text_record("TagTuner");
          std::string uri_val = id(playlist_uri).state; // .state je pravilen za text entitete
          std::string artist_val_full = "artist/" + id(playlist_artist).state; // .state je pravilen za text entitete
          std::string playlist_val_full = "playlist/" + id(playlist_info).state; // .state je pravilen za text entitete

          if (id(playlist_artist).state.length() > 0 && id(playlist_artist).state != " ") {
            message->add_text_record(artist_val_full);
          }
          if (id(playlist_info).state.length() > 0 && id(playlist_info).state != " ") {
            message->add_text_record(playlist_val_full);
          }
          if (uri_val.length() > 0 && uri_val != " ") {
            message->add_uri_record(uri_val);
          }
          ESP_LOGD("ndef", "Writing payload: %s", uri_val.c_str());
          id(pn532_board).write_mode(message); 
      - text_sensor.template.publish: {id: status, state: "Place tag"}
      - wait_until:
          timeout: 30s
          condition: {not: {pn532.is_writing: null}} 
      - text_sensor.template.publish: {id: status, state: "Finished writing tag attempt"}
      - script.execute: led_success 

font: 
  - file: "gfonts://Roboto" 
    id: font_small
    size: 12
    glyphs: "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ °абвгдежзийклмнопрстуфхцчшщъыьэюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯČŠŽčšž" 
  - file: "gfonts://Roboto"
    id: font_medium
    size: 16
    glyphs: "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~ °абвгдежзийклмнопрстуфхцчшщъыьэюяАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯČŠŽčšž"

display:
  - platform: ssd1306_i2c
    id: oled_display
    model: "SSD1306 128x64"
    address: 0x3C 
    i2c_id: bus_i2c
    rotation: 0 
    update_interval: 1s 
    lambda: |-
      it.clear(); 
      auto mp_state = id(media_player_state_text).state;
      auto mp_title = id(media_player_title).state;
      auto mp_artist = id(media_player_artist).state;

      if ( !mp_title.empty() && (mp_state == "playing" || mp_state == "paused" || mp_state == "on") ) { 
        if (!mp_artist.empty()) {
          std::string artist_str = mp_artist;
          if (artist_str.length() > 18) artist_str = artist_str.substr(0, 16) + ".."; 
          it.printf(0, 0, id(font_medium), "%s", artist_str.c_str());
        } else {
          it.print(0, 0, id(font_medium), App.get_friendly_name().c_str());
        }
        std::string title_str = mp_title;
        if (title_str.length() > 40) { 
          it.printf(0, 18, id(font_small), "%.20s", title_str.c_str()); 
          it.printf(0, 30, id(font_small), "%.20s", title_str.substr(20).c_str()); 
        } else if (title_str.length() > 20) {
          it.printf(0, 18, id(font_small), "%.20s", title_str.c_str());
          it.printf(0, 30, id(font_small), "%s", title_str.substr(20).c_str());
        } else {
          it.printf(0, 18, id(font_small), "%s", title_str.c_str()); 
        }
        if (mp_state == "playing") {
          it.printf(55, 48, id(font_medium), ">"); 
        } else if (mp_state == "paused") {
          it.printf(55, 48, id(font_medium), "||"); 
        } else if (mp_state == "on") { 
          it.printf(50, 48, id(font_small), "Ready");
        }
      } else {
        it.print(0, 0, id(font_medium), App.get_friendly_name().c_str());
        std::string current_status = id(status).state; 
        if (current_status.length() > 20) current_status = current_status.substr(0, 18) + "..";
        it.printf(0, 18, id(font_small), "S: %s", current_status.c_str());
        if (id(is_tag).state) { 
          std::string p_uri = id(playlist_uri).state; // .state je pravilen za text entitete
          if (p_uri.length() > 20) p_uri = p_uri.substr(0, 18) + "..";
          it.printf(0, 32, id(font_small), "U: %s", p_uri.c_str());
          std::string p_info_combined;
          std::string artist_str_tag = id(playlist_artist).state; // .state je pravilen za text entitete
          std::string playlist_str_tag = id(playlist_info).state; // .state je pravilen za text entitete
          if (artist_str_tag.length() > 1 && artist_str_tag != " ") {
            p_info_combined += artist_str_tag.substr(0,8);
          }
          if (playlist_str_tag.length() > 1 && playlist_str_tag != " ") {
            if (!p_info_combined.empty()) p_info_combined += "-";
            p_info_combined += playlist_str_tag.substr(0,10);
          }
          if (p_info_combined.length() > 20) p_info_combined = p_info_combined.substr(0, 18) + "..";
          it.printf(0, 46, id(font_small), "P: %s", p_info_combined.c_str());
        } else if (id(wifi_state).is_connected()) { 
          it.printf(0, 32, id(font_small), "IP: %s", id(wifi_ip_address_sensor).state.c_str()); 
          it.print(0, 46, id(font_small), "TagTuner Ready");
        } else {
          it.print(0, 32, id(font_small), "WiFi: N/A");
          it.print(0, 46, id(font_small), "TagTuner Offline");
        }
      }

The code is from @luka6000 but is modified to fir on esp32 s3 and with display.

Any idea why write process is not successful? Any hint would be very useful since I’m banging my head to this already for few days… Thanks!