From d9893645011d173ff70c823f558458f59c14b60f Mon Sep 17 00:00:00 2001 From: Luka Gra Date: Tue, 5 Nov 2024 20:53:56 +0100 Subject: [PATCH] TagTuner for tagreader Adonno tagreader device support --- .github/workflows/ci.yml | 3 +- .github/workflows/publish.yml | 1 + README.md | 6 +- static/index.md | 8 +- tagtuner-for-tagreader.factory.yaml | 28 ++ tagtuner-for-tagreader.yaml | 469 ++++++++++++++++++++++++++++ 6 files changed, 509 insertions(+), 6 deletions(-) create mode 100644 tagtuner-for-tagreader.factory.yaml create mode 100644 tagtuner-for-tagreader.yaml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c46596..60d8d57 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,8 @@ jobs: max-parallel: 3 matrix: file: - - tagtuner-atom-grove.yaml + - tagtuner-atom-grove + - tagtuner-for-tagreader esphome-version: - stable - beta diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 93423de..61fa101 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,6 +19,7 @@ jobs: with: files: | tagtuner-atom-grove.factory.yaml + tagtuner-for-tagreader.factory.yaml esphome-version: 2024.10.2 combined-name: firmware release-summary: ${{ github.event_name == 'release' && github.event.release.body || '' }} diff --git a/README.md b/README.md index 8495026..503c294 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ The ladybug icon is your guide. ## How to get tags for TagTuner ### Buy tags -Choose NTAG216 or 888-byte tags if you want to include the playlist name and artist. Otherwise, NTAG213 (144 bytes) will suffice for just links to playlists +Choose NTAG215 (504 bytes) or NTAG216 (888 bytes) tags if you want to include the playlist name and artist. Otherwise, NTAG213 (144 bytes) will suffice for just links to playlists - [Plain white cards](https://s.click.aliexpress.com/e/_Deb0eeV) - [Wooden cards](https://s.click.aliexpress.com/e/_DdGYnJf) @@ -171,7 +171,9 @@ Use short (<5mm) M2 screw for Atom and longer (10mm) M2.5 screws for everything #### Wiring TBD ### Firmware options -- tagtuner-atom-grove.yaml: based on m5stack Atom Echo and grove connectors + +- [tagtuner-atom-grove.yaml](https://github.com/luka6000/TagTuner/blob/main/tagtuner-atom-grove.yaml): based on m5stack Atom Echo and grove connectors +- [tagtuner-for-tagreader.yaml](https://github.com/luka6000/TagTuner/blob/main/tagtuner-for-tagreader.yaml): TagTuner firmware for [Adonno tagreader](https://github.com/adonno/tagreader) device (buzzer only, no led support) - tagtuner-esp32.yaml: custom-built TBD ## A little history diff --git a/static/index.md b/static/index.md index f7fc1ed..9390500 100644 --- a/static/index.md +++ b/static/index.md @@ -1,4 +1,4 @@ -# About TagTuner music player +# TagTuner music player TagTuner is a device that enables you to access music playlists or albums using NFC tags.\ It only works when integrated with Home Assistant media players and the Music Assistant music library is optional. This setup seamlessly blends your digital only music collection with the tactile experience of playing a physical record, tape or cd.\ Physical music media offer several advantages: @@ -30,7 +30,7 @@ You can use the button below to install the pre-built firmware directly to your ## Getting started To start using TagTuner, you’ll need the following: -- [Home Assistant](https://www.home-assistant.io) 2024.x +- [Home Assistant](https://www.home-assistant.io) 2024.10.x - [Music Assistant](https://music-assistant.io) 2.x or [Sonos](https://www.sonos.com/) speaker - configured MAss music [library](https://music-assistant.io/usage/#the-library) and/or a streaming subscription - TagTuner device configured in HAss @@ -179,7 +179,9 @@ Use short (<5mm) M2 screw for Atom and longer (10mm) M2.5 screws for everything #### Wiring TBD ### Firmware options -- tagtuner-atom-grove.yaml: based on m5stack Atom Echo and grove connectors + +- [tagtuner-atom-grove.yaml](https://github.com/luka6000/TagTuner/blob/main/tagtuner-atom-grove.yaml): based on m5stack Atom Echo and grove connectors +- [tagtuner-for-tagreader.yaml](https://github.com/luka6000/TagTuner/blob/main/tagtuner-for-tagreader.yaml): TagTuner firmware for [Adonno tagreader](https://github.com/adonno/tagreader) device (buzzer only, no led support) - tagtuner-esp32.yaml: custom-built TBD ## A little history diff --git a/tagtuner-for-tagreader.factory.yaml b/tagtuner-for-tagreader.factory.yaml new file mode 100644 index 0000000..a0a25b1 --- /dev/null +++ b/tagtuner-for-tagreader.factory.yaml @@ -0,0 +1,28 @@ +# These substitutions allow the end user to override certain values +substitutions: + name: "tagtuner" + friendly_name: "TagTuner" + +packages: + # Include all of the core configuration + core: !include tagtuner-for-tagreader.yaml + +esphome: + name: "${name}" + friendly_name: ${friendly_name} + # Automatically add the mac address to the name + # so you can use a single firmware for all devices + name_add_mac_suffix: true + + project: + name: LukaGra.TagTuner + version: dev + +# This should point to the public location of this yaml file. +dashboard_import: + package_import_url: github://luka6000/TagTuner/tagtuner-for-tagreader.yaml@main + import_full_config: true + +# Sets up the improv via serial client for Wi-Fi provisioning +improv_serial: + next_url: https://luka6000.github.io/TagTuner/ diff --git a/tagtuner-for-tagreader.yaml b/tagtuner-for-tagreader.yaml new file mode 100644 index 0000000..5ea83ce --- /dev/null +++ b/tagtuner-for-tagreader.yaml @@ -0,0 +1,469 @@ +# this is a modified PN532 to make it work with 888byte tags +external_components: + - source: github://luka6000/TagTuner@main + components: [ pn532 ] + refresh: 1min + +# These substitutions allow the end user to override certain values +substitutions: + name: "tagtuner" + friendly_name: "TagTuner" + d1_mini: d1_mini + buzpin: D7 + ledpin: D8 + +esphome: + name: "${name}" + friendly_name: ${friendly_name} + # Automatically add the mac address to the name + # so you can use a single firmware for all devices + name_add_mac_suffix: true + + # hello world + on_boot: + priority: -100 + then: + # - light.turn_on: + # id: led1 + # effect: HelloWorld + - wait_until: + condition: + api.connected: + timeout: 20s + - text_sensor.template.publish: + id: status + state: "Ready" + # - light.turn_off: led1 + +esp8266: + board: $d1_mini + framework: + version: recommended + +# To be able to get logs from the device via serial and api. +logger: + # level: VERBOSE + # level: DEBUG + # level: WARN + level: ERROR + # logs: + # light: WARN + # pn532: DEBUG + # pn532_i2c: DEBUG + +# API is a requirement of the dashboard import. +api: + +# OTA is required for Over-the-Air updating +ota: + platform: esphome + +# Sets up the improv via serial client for Wi-Fi provisioning +wifi: +improv_serial: + +binary_sensor: + - platform: template + name: "=Reading=" + device_class: running + entity_category: DIAGNOSTIC + lambda: |- + if ( !id(pn532_board).is_writing() ) { + return true; + } else { + return false; + } + - platform: template + name: "=Writing=" + device_class: running + entity_category: DIAGNOSTIC + lambda: |- + if ( id(pn532_board).is_writing() ) { + return true; + } else { + return false; + } + +output: + - platform: esp8266_pwm + pin: $buzpin + id: buzzer + +rtttl: + output: buzzer + +# light: +# - platform: neopixelbus +# id: led1 +# variant: WS2812 +# pin: $ledpin +# num_leds: 1 +# type: grb +# restore_mode: ALWAYS_OFF +# default_transition_length: 0s +# flash_transition_length: 0s +# effects: +# - strobe: +# name: HelloWorld +# colors: +# - state: true +# brightness: 100% +# red: 100% +# green: 0% +# blue: 0% +# duration: 250ms +# - state: true +# brightness: 100% +# red: 0% +# green: 100% +# blue: 0% +# duration: 250ms +# - state: true +# brightness: 100% +# red: 0% +# green: 0% +# blue: 100% +# duration: 250ms +# - state: true +# brightness: 100% +# red: 100% +# green: 100% +# blue: 100% +# duration: 250ms +# - strobe: +# name: TagWrite +# colors: +# - state: true +# brightness: 100% +# red: 100% +# green: 0% +# blue: 0% +# duration: 250ms +# - state: false +# duration: 50ms + +script: + - id: wait_input + then: + - delay: 3s + - text_sensor.template.publish: + id: status + state: "Waiting for input" + + - id: set_tag + then: + - text.set: + id: playlist_uri + value: !lambda |- + return id(uri); + - delay: 10ms + - text.set: + id: playlist_artist + value: !lambda |- + return id(artist); + - delay: 10ms + - text.set: + id: playlist_info + value: !lambda |- + return id(playlist); + + # - id: led_blink + # then: + # - light.turn_on: + # id: led1 + # brightness: 60% + # red: 100% + # green: 100% + # blue: 100% + # flash_length: 100ms + # - id: led_ok + # then: + # - light.turn_on: + # id: led1 + # brightness: 100% + # red: 0% + # green: 100% + # blue: 0% + # flash_length: 50ms + # - delay: 100ms + # - light.turn_on: + # id: led1 + # brightness: 100% + # red: 0% + # green: 100% + # blue: 0% + # flash_length: 50ms + # - id: led_success + # then: + # - light.turn_on: + # id: led1 + # brightness: 100% + # red: 0% + # green: 100% + # blue: 0% + # flash_length: 200ms + # - delay: 250ms + # - light.turn_on: + # id: led1 + # brightness: 100% + # red: 0% + # green: 100% + # blue: 0% + # flash_length: 200ms + # - delay: 250ms + # - light.turn_on: + # id: led1 + # brightness: 100% + # red: 0% + # green: 100% + # blue: 0% + # flash_length: 500ms + +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: " " + +text_sensor: + - 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 + +button: + - platform: restart + name: "${friendly_name} Restart" + entity_category: DIAGNOSTIC + + - platform: template + name: Cancel writing + id: cancel_writing + icon: "mdi:broadcast-off" + entity_category: CONFIG + on_press: + then: + - lambda: 'id(pn532_board).read_mode();' + - wait_until: + timeout: 30s + condition: + not: + pn532.is_writing: + - delay: 5ms + - text_sensor.template.publish: + id: status + state: "Cancel writing tag" + # - light.turn_off: led1 + # - script.execute: led_ok + + - platform: template + name: Erase Tag + id: erase_tag + icon: "mdi:nfc-search-variant" + entity_category: CONFIG + on_press: + then: + # - light.turn_on: + # id: led1 + # effect: TagWrite + - text.set: + id: playlist_artist + value: '' + - text.set: + id: playlist_info + value: '' + - text.set: + id: playlist_uri + value: '' + - lambda: 'id(pn532_board).format_mode();' + - rtttl.play: "write:d=24,o=5,b=100:b" + - text_sensor.template.publish: + id: status + state: "Place tag" + - wait_until: + timeout: 30s + condition: + not: + pn532.is_writing: + - if: + condition: + pn532.is_writing: + then: + - lambda: 'id(pn532_board).read_mode();' + - text_sensor.template.publish: + id: status + state: "Finished erasing tag" + - rtttl.play: "write:d=24,o=5,b=100:b,b" + # - light.turn_off: led1 + # - script.execute: led_success + + - platform: template + name: Write Tag + id: write_tag + icon: "mdi:cast-audio-variant" + entity_category: CONFIG + on_press: + then: + # - light.turn_on: + # id: led1 + # effect: TagWrite + - lambda: |- + auto message = new nfc::NdefMessage(); + message->add_text_record("TagTuner"); + std::string uri = ""; + std::string artist = "artist/"; + std::string playlist = "playlist/"; + uri += id(playlist_uri).state; + artist += id(playlist_artist).state; + playlist += id(playlist_info).state; + if ( artist != "" ) { + message->add_text_record(artist); + } + if ( playlist != "" ) { + message->add_text_record(playlist); + } + if ( uri != "" ) { + message->add_uri_record(uri); + } + ESP_LOGD("ndef", "Writing payload: %s", uri.c_str()); + id(pn532_board).write_mode(message); + - rtttl.play: "write:d=24,o=5,b=100:b" + - text_sensor.template.publish: + id: status + state: "Place tag" + - wait_until: + timeout: 30s + condition: + not: + pn532.is_writing: + - if: + condition: + pn532.is_writing: + then: + - lambda: 'id(pn532_board).read_mode();' + - logger.log: "Finished writing tag" + - text_sensor.template.publish: + id: status + state: "Finished writing tag" + - rtttl.play: "write:d=24,o=5,b=100:b,b" + # - light.turn_off: led1 + # - script.execute: led_success + +globals: + - id: artist + type: std::string + - id: playlist + type: std::string + - id: uri + type: std::string + +i2c: + scan: False + frequency: 100kHz + timeout: 13ms #to prevent pn532 timeout + +pn532_i2c: + id: pn532_board + update_interval: 350ms + on_tag_removed: + - logger.log: "on_tag_removed" + - text_sensor.template.publish: + id: status + state: "Tag removed" + - wait_until: + condition: + api.connected: + timeout: 20s + - homeassistant.event: + event: esphome.tagtuner + data: + action: "tag_removed" + uid: !lambda 'return x;' + # - script.execute: led_blink + + on_tag: + - logger.log: "on_tag" + - text_sensor.template.publish: + id: status + state: !lambda 'return "Tag "+x;' + - lambda: |- + id(playlist)=""; + id(artist)=""; + id(uri)=""; + if (tag.has_ndef_message()) { + for (auto &record : tag.get_ndef_message()->get_records() ) { + std::string payload = record->get_payload(); + std::string type = record->get_type(); + + if ( payload.substr(0, 7) == "artist/" ) { + id(artist)=payload.substr(7); + } + else if ( payload.substr(0, 9) == "playlist/" ) { + id(playlist)=payload.substr(9); + } + else if (type == "U" && payload.substr(0, 20) != "https://mb.senic.com" ) { + id(uri)=payload; + } + + } + } + - if: + condition: + lambda: 'return id(uri) == "" ;' + then: + - text_sensor.template.publish: + id: status + state: "Plain UID tag" + - homeassistant.tag_scanned: !lambda |- + ESP_LOGD("tagtuner", "No TagTuner NDEF, using UID"); + return x; + else: + - wait_until: + condition: + api.connected: + timeout: 20s + - homeassistant.event: + event: esphome.tagtuner + data: + action: "tag_scanned" + uid: !lambda 'return x;' + uri: !lambda 'return id(uri);' + artist: !lambda 'return id(artist);' + playlist: !lambda 'return id(playlist);' + - script.execute: set_tag + - rtttl.play: "success:d=24,o=5,b=100:c,g,b" + # - script.execute: led_blink