amiibo-reader 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2026 Pascal Béger
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
@@ -0,0 +1,236 @@
1
+ Metadata-Version: 2.4
2
+ Name: amiibo-reader
3
+ Version: 0.1.0
4
+ Summary: Python library for reading amiibo tags using an MFRC522 NFC reader on Raspberry Pi
5
+ Author: Pascal Béger
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/p-beger/amiibo-reader
8
+ Requires-Python: >=3.13
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: mfrc522-python==0.0.9
12
+ Dynamic: license-file
13
+
14
+ # amiibo-reader
15
+
16
+ Python library for reading amiibo tags with an MFRC522 RFID reader.
17
+
18
+ The library detects NFC tags, reads amiibo data from NTAG215 tags, and identifies amiibo figures using a local amiibo database.
19
+
20
+ `amiibo-reader` is an independant Python library for reading and parsing NFC data from amiibo tags. It is not affiliated with or endorsed by Nintendo.
21
+
22
+ ## Features
23
+
24
+ - Read amiibo tags using an MFRC522 RFID reader
25
+ - Read NTAG215 data
26
+ - Extract amiibo IDs
27
+ - Identify amiibo figures from the local database
28
+ - Distinguish between recognized amiibo and unknown tags
29
+ - Expose a small and simple Python API
30
+
31
+ ## How it works
32
+
33
+ When a tag is placed on the MFRC522 reader:
34
+
35
+ 1. The reader detects the tag.
36
+ 2. The tag UID is retrieved.
37
+ 3. The library performs ISO14443A anticollision and selection.
38
+ 4. The NTAG215 data is read.
39
+ 5. The amiibo ID is extracted from the tag data.
40
+ 6. The amiibo ID is looked up in the local amiibo database.
41
+ 7. The corresponding `Amiibo` is returned if it is known.
42
+ 8. Otherwise, an `UnknownTag` is returned.
43
+
44
+ If no tag is detected, `read_tag()` returns `None`.
45
+
46
+ ## Requirements
47
+
48
+ - Python 3.13+
49
+ - Raspberry Pi or another compatible Linux system
50
+ - MFRC522 RFID reader
51
+ - amiibo figures or compatible NTAG215 tags
52
+ - SPI interface enabled
53
+
54
+ The library uses the `mfrc522` Python package to communicate with the MFRC522 reader.
55
+
56
+ ## Installation
57
+
58
+ Clone the repository:
59
+
60
+ ```bash
61
+ git clone https://github.com/p-beger/amiibo-reader.git
62
+ cd amiibo-reader
63
+ ```
64
+
65
+ Create and activate a virtual environment:
66
+
67
+ ```bash
68
+ python3 -m venv venv
69
+ source venv/bin/activate
70
+ ```
71
+
72
+ If creating the virtual environment fails, make sure `python3-full` is installed:
73
+
74
+ ```bash
75
+ sudo apt-get install python3-full
76
+ ```
77
+
78
+ Install the library:
79
+
80
+ ```bash
81
+ python -m pip install .
82
+ ```
83
+
84
+ For development, install the development dependencies:
85
+
86
+ ```bash
87
+ python -m pip install --group dev
88
+ ```
89
+
90
+ ## MFRC522 configuration
91
+
92
+ The library currently uses the following MFRC522 configuration:
93
+ - SPI bus: 0
94
+ - SPI device: 0
95
+ - SPI speed: 1 MHz
96
+ - Reset pin: 22
97
+ SPI must be enabled on the Raspberry Pi.
98
+
99
+ The reader is automatically initialized when the library is imported.
100
+
101
+ ## Usage
102
+
103
+ The main API is exposed directly from the `amiibo_reader` package:
104
+
105
+ ```python
106
+ from amiibo_reader import close_reader, read_tag
107
+ ```
108
+
109
+ Read a tag:
110
+
111
+ ```python
112
+ tag = read_tag()
113
+
114
+ if tag is None:
115
+ print("No tag detected")
116
+ else:
117
+ print(tag)
118
+ ```
119
+
120
+ ## Recognized amiibo
121
+
122
+ When the tag is recognized, `read_tag()` returns an `Amiibo` object:
123
+
124
+ ```python
125
+ from amiibo_reader import Amiibo, read_tag
126
+
127
+ tag = read_tag()
128
+
129
+ if isinstance(tag, Amiibo):
130
+ print(f"ID: {tag.id}")
131
+ print(f"Series: {tag.series}")
132
+ print(f"Character: {tag.character}")
133
+ ```
134
+
135
+ For example:
136
+ ```
137
+ ID: 0000000000000002
138
+ Series: Super Smash Bros.
139
+ Character: Mario
140
+ ```
141
+
142
+ ## Unknown Tags
143
+
144
+ If a tag is detected but cannot be identified as a known amiibo, `read_tag()` returns an `UnknownTag` object:
145
+
146
+ ```python
147
+ from amiibo_reader import UnknownTag, read_tag
148
+
149
+ tag = read_tag()
150
+
151
+ if isinstance(tag, UnknownTag):
152
+ print(f"Unknown tag ID: {tag.id}")
153
+ ```
154
+
155
+ The `id` contains the most relevant identifier available:
156
+ - the tag UID if the amiibo data could not be read
157
+ - the amiibo ID if the tag was successfully read but is not present in the local database
158
+
159
+ ## No tag detected
160
+
161
+ If not tag is present:
162
+
163
+ ```python
164
+ tag = read_tag()
165
+
166
+ if tag is None:
167
+ print("No tag detected")
168
+ ```
169
+
170
+ ## Closing the reader
171
+
172
+ When the reader is no longer needed, close it with:
173
+
174
+ ```python
175
+ from amiibo_reader import close_reader
176
+
177
+ close_reader()
178
+ ```
179
+
180
+ ## amiibo database
181
+
182
+ The amiibo database is stored in `amiibos.py`.
183
+
184
+ Each entry associates an amiibo ID with an `Amiibo` object.
185
+
186
+ ```python
187
+ AMIIBOS: dict[str, Amiibo] = {
188
+ "0000000000000002": Amiibo(
189
+ "0000000000000002",
190
+ AmiiboSeries.SUPER_SMASH_BROS,
191
+ "Mario",
192
+ ),
193
+ }
194
+ ```
195
+
196
+ Additional amiibo can be added to the database by adding their amiibo ID, series and character.
197
+
198
+ ## Project structure
199
+
200
+ ```
201
+ amiibo-reader/
202
+ ├── src/
203
+ │ └── amiibo_reader/
204
+ │ ├── __init__.py
205
+ │ ├── amiibo.py
206
+ │ ├── amiibos.py
207
+ │ ├── read_tag.py
208
+ │ └── unknown_tag.py
209
+ ├── .gitignore
210
+ ├── pyproject.toml
211
+ └── README.md
212
+ ```
213
+
214
+ ## Development
215
+
216
+ The project uses:
217
+ - Ruff for formatting and linting
218
+ - mypy for static type checking
219
+
220
+ Format the code with Ruff:
221
+
222
+ ```bash
223
+ ruff format .
224
+ ```
225
+
226
+ Lint the code:
227
+
228
+ ```bash
229
+ ruff check .
230
+ ```
231
+
232
+ Run mypy:
233
+
234
+ ```bash
235
+ mypy src
236
+ ```
@@ -0,0 +1,223 @@
1
+ # amiibo-reader
2
+
3
+ Python library for reading amiibo tags with an MFRC522 RFID reader.
4
+
5
+ The library detects NFC tags, reads amiibo data from NTAG215 tags, and identifies amiibo figures using a local amiibo database.
6
+
7
+ `amiibo-reader` is an independant Python library for reading and parsing NFC data from amiibo tags. It is not affiliated with or endorsed by Nintendo.
8
+
9
+ ## Features
10
+
11
+ - Read amiibo tags using an MFRC522 RFID reader
12
+ - Read NTAG215 data
13
+ - Extract amiibo IDs
14
+ - Identify amiibo figures from the local database
15
+ - Distinguish between recognized amiibo and unknown tags
16
+ - Expose a small and simple Python API
17
+
18
+ ## How it works
19
+
20
+ When a tag is placed on the MFRC522 reader:
21
+
22
+ 1. The reader detects the tag.
23
+ 2. The tag UID is retrieved.
24
+ 3. The library performs ISO14443A anticollision and selection.
25
+ 4. The NTAG215 data is read.
26
+ 5. The amiibo ID is extracted from the tag data.
27
+ 6. The amiibo ID is looked up in the local amiibo database.
28
+ 7. The corresponding `Amiibo` is returned if it is known.
29
+ 8. Otherwise, an `UnknownTag` is returned.
30
+
31
+ If no tag is detected, `read_tag()` returns `None`.
32
+
33
+ ## Requirements
34
+
35
+ - Python 3.13+
36
+ - Raspberry Pi or another compatible Linux system
37
+ - MFRC522 RFID reader
38
+ - amiibo figures or compatible NTAG215 tags
39
+ - SPI interface enabled
40
+
41
+ The library uses the `mfrc522` Python package to communicate with the MFRC522 reader.
42
+
43
+ ## Installation
44
+
45
+ Clone the repository:
46
+
47
+ ```bash
48
+ git clone https://github.com/p-beger/amiibo-reader.git
49
+ cd amiibo-reader
50
+ ```
51
+
52
+ Create and activate a virtual environment:
53
+
54
+ ```bash
55
+ python3 -m venv venv
56
+ source venv/bin/activate
57
+ ```
58
+
59
+ If creating the virtual environment fails, make sure `python3-full` is installed:
60
+
61
+ ```bash
62
+ sudo apt-get install python3-full
63
+ ```
64
+
65
+ Install the library:
66
+
67
+ ```bash
68
+ python -m pip install .
69
+ ```
70
+
71
+ For development, install the development dependencies:
72
+
73
+ ```bash
74
+ python -m pip install --group dev
75
+ ```
76
+
77
+ ## MFRC522 configuration
78
+
79
+ The library currently uses the following MFRC522 configuration:
80
+ - SPI bus: 0
81
+ - SPI device: 0
82
+ - SPI speed: 1 MHz
83
+ - Reset pin: 22
84
+ SPI must be enabled on the Raspberry Pi.
85
+
86
+ The reader is automatically initialized when the library is imported.
87
+
88
+ ## Usage
89
+
90
+ The main API is exposed directly from the `amiibo_reader` package:
91
+
92
+ ```python
93
+ from amiibo_reader import close_reader, read_tag
94
+ ```
95
+
96
+ Read a tag:
97
+
98
+ ```python
99
+ tag = read_tag()
100
+
101
+ if tag is None:
102
+ print("No tag detected")
103
+ else:
104
+ print(tag)
105
+ ```
106
+
107
+ ## Recognized amiibo
108
+
109
+ When the tag is recognized, `read_tag()` returns an `Amiibo` object:
110
+
111
+ ```python
112
+ from amiibo_reader import Amiibo, read_tag
113
+
114
+ tag = read_tag()
115
+
116
+ if isinstance(tag, Amiibo):
117
+ print(f"ID: {tag.id}")
118
+ print(f"Series: {tag.series}")
119
+ print(f"Character: {tag.character}")
120
+ ```
121
+
122
+ For example:
123
+ ```
124
+ ID: 0000000000000002
125
+ Series: Super Smash Bros.
126
+ Character: Mario
127
+ ```
128
+
129
+ ## Unknown Tags
130
+
131
+ If a tag is detected but cannot be identified as a known amiibo, `read_tag()` returns an `UnknownTag` object:
132
+
133
+ ```python
134
+ from amiibo_reader import UnknownTag, read_tag
135
+
136
+ tag = read_tag()
137
+
138
+ if isinstance(tag, UnknownTag):
139
+ print(f"Unknown tag ID: {tag.id}")
140
+ ```
141
+
142
+ The `id` contains the most relevant identifier available:
143
+ - the tag UID if the amiibo data could not be read
144
+ - the amiibo ID if the tag was successfully read but is not present in the local database
145
+
146
+ ## No tag detected
147
+
148
+ If not tag is present:
149
+
150
+ ```python
151
+ tag = read_tag()
152
+
153
+ if tag is None:
154
+ print("No tag detected")
155
+ ```
156
+
157
+ ## Closing the reader
158
+
159
+ When the reader is no longer needed, close it with:
160
+
161
+ ```python
162
+ from amiibo_reader import close_reader
163
+
164
+ close_reader()
165
+ ```
166
+
167
+ ## amiibo database
168
+
169
+ The amiibo database is stored in `amiibos.py`.
170
+
171
+ Each entry associates an amiibo ID with an `Amiibo` object.
172
+
173
+ ```python
174
+ AMIIBOS: dict[str, Amiibo] = {
175
+ "0000000000000002": Amiibo(
176
+ "0000000000000002",
177
+ AmiiboSeries.SUPER_SMASH_BROS,
178
+ "Mario",
179
+ ),
180
+ }
181
+ ```
182
+
183
+ Additional amiibo can be added to the database by adding their amiibo ID, series and character.
184
+
185
+ ## Project structure
186
+
187
+ ```
188
+ amiibo-reader/
189
+ ├── src/
190
+ │ └── amiibo_reader/
191
+ │ ├── __init__.py
192
+ │ ├── amiibo.py
193
+ │ ├── amiibos.py
194
+ │ ├── read_tag.py
195
+ │ └── unknown_tag.py
196
+ ├── .gitignore
197
+ ├── pyproject.toml
198
+ └── README.md
199
+ ```
200
+
201
+ ## Development
202
+
203
+ The project uses:
204
+ - Ruff for formatting and linting
205
+ - mypy for static type checking
206
+
207
+ Format the code with Ruff:
208
+
209
+ ```bash
210
+ ruff format .
211
+ ```
212
+
213
+ Lint the code:
214
+
215
+ ```bash
216
+ ruff check .
217
+ ```
218
+
219
+ Run mypy:
220
+
221
+ ```bash
222
+ mypy src
223
+ ```
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=70"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "amiibo-reader"
7
+ version = "0.1.0"
8
+ authors = [
9
+ {name = "Pascal Béger" }
10
+ ]
11
+ description = "Python library for reading amiibo tags using an MFRC522 NFC reader on Raspberry Pi"
12
+ readme = "README.md"
13
+ requires-python = ">=3.13"
14
+ license = { text = "MIT" }
15
+ dependencies = [
16
+ "mfrc522-python==0.0.9",
17
+ ]
18
+
19
+ [project.urls]
20
+ Homepage = "https://github.com/p-beger/amiibo-reader"
21
+
22
+ [dependency-groups]
23
+ dev = [
24
+ "mypy",
25
+ "ruff",
26
+ ]
27
+
28
+ [tool.setuptools.packages.find]
29
+ where = ["src"]
30
+
31
+ [tool.ruff]
32
+ line-length = 120
33
+
34
+ [tool.ruff.lint]
35
+ select = ["E", "F", "I", "UP"]
36
+
37
+ [tool.ruff.format]
38
+ quote-style = "double"
39
+
40
+ [tool.mypy]
41
+ python_version = "3.13"
42
+ check_untyped_defs = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ from .amiibo import Amiibo, AmiiboSeries
2
+ from .read_tag import close_reader, read_tag
3
+ from .unknown_tag import UnknownTag
4
+
5
+ __all__ = [
6
+ "Amiibo",
7
+ "AmiiboSeries",
8
+ "UnknownTag",
9
+ "read_tag",
10
+ "close_reader",
11
+ ]
@@ -0,0 +1,36 @@
1
+ from dataclasses import dataclass
2
+ from enum import StrEnum
3
+
4
+
5
+ class AmiiboSeries(StrEnum):
6
+ ANIMAL_CROSSING = "Animal Crossing"
7
+ DARK_SOULS = "Dark Souls"
8
+ DETECTIVE_PIKACHU = "Detective Pikachu"
9
+ DIABLO = "Diablo"
10
+ FIRE_EMBLEM = "Fire Emblem"
11
+ KIRBY = "Kirby"
12
+ MEGA_MAN = "Mega Man"
13
+ METROID = "Metroid"
14
+ MONSTER_HUNTER = "Monster Hunter"
15
+ PIKMIN = "Pikmin"
16
+ POWER_UP_BANDS = "Power-Up Bands"
17
+ SPLATOON = "Splatoon"
18
+ SUPER_MARIO = "Super Mario"
19
+ SUPER_MARIO_30TH = "Super Mario 30th"
20
+ SUPER_SMASH_BROS = "Super Smash Bros."
21
+ THE_LEGEND_OF_ZELDA = "The Legend of Zelda"
22
+ THE_LEGEND_OF_ZELDA_30TH = "The Legend of Zelda 30th"
23
+ THE_LEGEND_OF_ZELDA_BOTW = "The Legend of Zelda: Breath of the Wild"
24
+ THE_LEGEND_OF_ZELDA_LA = "The Legend of Zelda: Link's Awakening"
25
+ THE_LEGEND_OF_ZELDA_SS = "The Legend of Zelda: Skyward Sword"
26
+ THE_LEGEND_OF_ZELDA_TOTK = "The Legend of Zelda: Tears of the Kingdom"
27
+ THE_LEGEND_OF_ZELDA_TP = "The Legend of Zelda: Twilight Princess"
28
+ XENOBLADE_CHRONICLES = "Xenoblade Chronicles"
29
+ YOSHI_WOOLY_WORLD = "Yoshi's Wooly World"
30
+
31
+
32
+ @dataclass(frozen=True)
33
+ class Amiibo:
34
+ id: str
35
+ series: AmiiboSeries
36
+ character: str
@@ -0,0 +1,119 @@
1
+ from .amiibo import Amiibo, AmiiboSeries
2
+
3
+ AMIIBOS: dict[str, Amiibo] = {
4
+ # Animal Crossing
5
+ # Not implemented yet
6
+ #
7
+ # Dark Souls
8
+ # Not implemented yet
9
+ #
10
+ # Detective Pikachu
11
+ # Not implemented yet
12
+ #
13
+ # Diablo
14
+ # Not implemented yet
15
+ #
16
+ # Fire Emblem
17
+ # Not implemented yet
18
+ #
19
+ # Kirby
20
+ # Not implemented yet
21
+ #
22
+ # Mega Man
23
+ # Not implemented yet
24
+ #
25
+ # Metroid
26
+ # Not implemented yet
27
+ #
28
+ # Monster Hunter
29
+ # Not implemented yet
30
+ #
31
+ # Pikmin
32
+ # Not implemented yet
33
+ #
34
+ # Power-Up Bands
35
+ # Not implemented yet
36
+ #
37
+ # Splatoon
38
+ # Not implemented yet
39
+ #
40
+ # Super Mario
41
+ # Not implemented yet
42
+ #
43
+ # Super Mario 30th
44
+ # Not implemented yet
45
+ #
46
+ # Super Smash Bros.
47
+ # Not completed yet
48
+ "0000000000000002": Amiibo("0000000000000002", AmiiboSeries.SUPER_SMASH_BROS, "Mario"),
49
+ "0002000000010002": Amiibo("0002000000010002", AmiiboSeries.SUPER_SMASH_BROS, "Peach"),
50
+ "0003000000020002": Amiibo("0003000000020002", AmiiboSeries.SUPER_SMASH_BROS, "Yoshi"),
51
+ "0008000000030002": Amiibo("0008000000030002", AmiiboSeries.SUPER_SMASH_BROS, "Donkey Kong"),
52
+ "0100000000040002": Amiibo("0100000000040002", AmiiboSeries.SUPER_SMASH_BROS, "Link"),
53
+ "0580000000050002": Amiibo("0580000000050002", AmiiboSeries.SUPER_SMASH_BROS, "Fox"),
54
+ "05C0000000060002": Amiibo("05C0000000060002", AmiiboSeries.SUPER_SMASH_BROS, "Samus"),
55
+ "0700000000070002": Amiibo("0700000000070002", AmiiboSeries.SUPER_SMASH_BROS, "Wii Fit Trainer"),
56
+ "0180000000080002": Amiibo("0180000000080002", AmiiboSeries.SUPER_SMASH_BROS, "Villager"),
57
+ "1919000000090002": Amiibo("1919000000090002", AmiiboSeries.SUPER_SMASH_BROS, "Pikachu"),
58
+ "1F000000000A0002": Amiibo("1F000000000A0002", AmiiboSeries.SUPER_SMASH_BROS, "Kirby"),
59
+ "21000000000B0002": Amiibo("21000000000B0002", AmiiboSeries.SUPER_SMASH_BROS, "Marth"),
60
+ "01010000000E0002": Amiibo("01010000000E0002", AmiiboSeries.SUPER_SMASH_BROS, "Zelda"),
61
+ "00090000000D0002": Amiibo("00090000000D0002", AmiiboSeries.SUPER_SMASH_BROS, "Diddy Kong"),
62
+ "00010000000C0002": Amiibo("00010000000C0002", AmiiboSeries.SUPER_SMASH_BROS, "Luigi"),
63
+ "06C00000000F0002": Amiibo("06C00000000F0002", AmiiboSeries.SUPER_SMASH_BROS, "Little Mac"),
64
+ "0740000000100002": Amiibo("0740000000100002", AmiiboSeries.SUPER_SMASH_BROS, "Pit"),
65
+ "0600000000120002": Amiibo("0600000000120002", AmiiboSeries.SUPER_SMASH_BROS, "Captain Falcon"),
66
+ "0004010000130002": Amiibo("0004010000130002", AmiiboSeries.SUPER_SMASH_BROS, "Rosalina"),
67
+ "0005000000140002": Amiibo("0005000000140002", AmiiboSeries.SUPER_SMASH_BROS, "Bowser"),
68
+ "1AC0000000110002": Amiibo("1AC0000000110002", AmiiboSeries.SUPER_SMASH_BROS, "Lucario"),
69
+ "0100010000160002": Amiibo("0100010000160002", AmiiboSeries.SUPER_SMASH_BROS, "Toon Link"),
70
+ "0101010000170002": Amiibo("0101010000170002", AmiiboSeries.SUPER_SMASH_BROS, "Sheik"),
71
+ "2101000000180002": Amiibo("2101000000180002", AmiiboSeries.SUPER_SMASH_BROS, "Ike"),
72
+ "22400000002B0002": Amiibo("22400000002B0002", AmiiboSeries.SUPER_SMASH_BROS, "Shulk"),
73
+ "3200000000300002": Amiibo("3200000000300002", AmiiboSeries.SUPER_SMASH_BROS, "Sonic"),
74
+ "3480000000310002": Amiibo("3480000000310002", AmiiboSeries.SUPER_SMASH_BROS, "Mega Man"),
75
+ "1F02000000280002": Amiibo("1F02000000280002", AmiiboSeries.SUPER_SMASH_BROS, "King Dedede"),
76
+ "1F01000000270002": Amiibo("1F01000000270002", AmiiboSeries.SUPER_SMASH_BROS, "Meta Knight"),
77
+ "21030000002A0002": Amiibo("21030000002A0002", AmiiboSeries.SUPER_SMASH_BROS, "Robin"),
78
+ "2102000000290002": Amiibo("2102000000290002", AmiiboSeries.SUPER_SMASH_BROS, "Lucina"),
79
+ "00070000001A0002": Amiibo("00070000001A0002", AmiiboSeries.SUPER_SMASH_BROS, "Wario"),
80
+ "1906000000240002": Amiibo("1906000000240002", AmiiboSeries.SUPER_SMASH_BROS, "Charizard"),
81
+ "22800000002C0002": Amiibo("22800000002C0002", AmiiboSeries.SUPER_SMASH_BROS, "Ness"),
82
+ "3340000000320002": Amiibo("3340000000320002", AmiiboSeries.SUPER_SMASH_BROS, "Pac Man"),
83
+ "1B92000000250002": Amiibo("1B92000000250002", AmiiboSeries.SUPER_SMASH_BROS, "Greninja"),
84
+ "07420000001F0002": Amiibo("07420000001F0002", AmiiboSeries.SUPER_SMASH_BROS, "Palutena"),
85
+ "0741000000200002": Amiibo("0741000000200002", AmiiboSeries.SUPER_SMASH_BROS, "Dark Pit"),
86
+ "05C00100001D0002": Amiibo("05C00100001D0002", AmiiboSeries.SUPER_SMASH_BROS, "Zero Suit Samus"),
87
+ "01020100001B0002": Amiibo("01020100001B0002", AmiiboSeries.SUPER_SMASH_BROS, "Ganondorf"),
88
+ "0000010000190002": Amiibo("0000010000190002", AmiiboSeries.SUPER_SMASH_BROS, "Dr. Mario"),
89
+ "0006000000150002": Amiibo("0006000000150002", AmiiboSeries.SUPER_SMASH_BROS, "Bowser Jr."),
90
+ "06400100001E0002": Amiibo("06400100001E0002", AmiiboSeries.SUPER_SMASH_BROS, "Olimar"),
91
+ #
92
+ # The Legend of Zelda
93
+ # Not implemented yet
94
+ #
95
+ # The Legend of Zelda 30th
96
+ # Not implemented yet
97
+ #
98
+ # The Legend of Zelda: Breath of the Wild
99
+ # Not implemented yet
100
+ #
101
+ # The Legend of Zelda: Link's Awakening
102
+ # Not implemented yet
103
+ #
104
+ # The Legend of Zelda: Skyward Sword
105
+ # Not implemented yet
106
+ #
107
+ # The Legend of Zelda: Tears of the Kingdom
108
+ # Not implemented yet
109
+ #
110
+ # The Legend of Zelda: Twilight Princess
111
+ # Not implemented yet
112
+ #
113
+ # Xenoblade Chronicles
114
+ # Not implemented yet
115
+ #
116
+ # Yoshi's Wooly World
117
+ # Not implemented yet
118
+ #
119
+ }
@@ -0,0 +1,176 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from .amiibo import Amiibo
6
+ from .amiibos import AMIIBOS
7
+ from .unknown_tag import UnknownTag
8
+
9
+ if TYPE_CHECKING:
10
+ from mfrc522 import MFRC522 # type: ignore[import-untyped]
11
+
12
+ reader: MFRC522 | None = None
13
+
14
+
15
+ def _get_reader() -> MFRC522:
16
+ global reader
17
+
18
+ if reader is None:
19
+ from mfrc522 import MFRC522 # type: ignore[import-untyped]
20
+
21
+ reader = MFRC522(bus=0, device=0, spd=1000000, pin_mode=10, pin_rst=22)
22
+
23
+ return reader
24
+
25
+
26
+ def close_reader() -> None:
27
+ global reader
28
+
29
+ if reader is not None:
30
+ reader.Close()
31
+ reader = None
32
+
33
+
34
+ def _read_ntag_page(page: int) -> list[int] | None:
35
+ reader = _get_reader()
36
+
37
+ command = [0x30, page]
38
+
39
+ crc = reader.CalulateCRC(command)
40
+ command += crc[:2]
41
+
42
+ reader.WriteReg(reader.BitFramingReg, 0x00)
43
+
44
+ status, data, _ = reader.MFRC522_ToCard(reader.PCD_TRANSCEIVE, command)
45
+
46
+ if status != reader.MI_OK:
47
+ return None
48
+
49
+ return data
50
+
51
+
52
+ def _anticoll_level(level: int) -> list[int] | None:
53
+ """
54
+ Performs ISO14443A anticollision.
55
+ This function is used to retrieve the UID of a tag at a specific level (1 or 2).
56
+
57
+ level 1 = 0x93
58
+ level 2 = 0x95
59
+ """
60
+
61
+ reader = _get_reader()
62
+
63
+ command = [level, 0x20]
64
+
65
+ reader.WriteReg(reader.BitFramingReg, 0x00)
66
+
67
+ status, data, _ = reader.MFRC522_ToCard(reader.PCD_TRANSCEIVE, command)
68
+
69
+ if status != reader.MI_OK:
70
+ return None
71
+
72
+ if len(data) != 5:
73
+ return None
74
+
75
+ # BCC (Block Check Character) is the XOR of the first 4 bytes of the UID.
76
+ bcc = data[0] ^ data[1] ^ data[2] ^ data[3]
77
+
78
+ if bcc != data[4]:
79
+ print("Invalid BCC")
80
+ return None
81
+
82
+ return data
83
+
84
+
85
+ def _select_level(level: int, uid_part: list[int]) -> bool:
86
+ """
87
+ Selects an ISO14443A UID level.
88
+
89
+ uid_part = 5 bytes:
90
+ - level 1: 88 + 3 bytes UID + BCC
91
+ - level 2: 4 bytes UID + BCC
92
+ """
93
+
94
+ reader = _get_reader()
95
+
96
+ command = [
97
+ level,
98
+ 0x70, # SELECT
99
+ *uid_part,
100
+ ]
101
+
102
+ crc = reader.CalulateCRC(command)
103
+ command += crc[:2]
104
+
105
+ reader.WriteReg(reader.BitFramingReg, 0x00)
106
+
107
+ status, _, _ = reader.MFRC522_ToCard(reader.PCD_TRANSCEIVE, command)
108
+
109
+ return status == reader.MI_OK
110
+
111
+
112
+ def _select_amiibo() -> bool:
113
+ """
114
+ Select an amiibo by performing anticollision and SELECT operations
115
+ for both UID levels.
116
+
117
+ Amiibo UIDs use two ISO14443A cascade levels, so both levels must
118
+ be processed before reading the NTAG215 data.
119
+ """
120
+
121
+ level1 = _anticoll_level(0x93)
122
+
123
+ if not level1:
124
+ print("Anticollision failed at level 1")
125
+ return False
126
+
127
+ if not _select_level(0x93, level1):
128
+ print("Select failed at level 1")
129
+ return False
130
+
131
+ level2 = _anticoll_level(0x95)
132
+
133
+ if not level2:
134
+ print("Anticollision failed at level 2")
135
+ return False
136
+
137
+ if not _select_level(0x95, level2):
138
+ print("Select failed at level 2")
139
+ return False
140
+
141
+ return True
142
+
143
+
144
+ def _get_amiibo_id(data: list[int]) -> str:
145
+ """Extract the amiibo ID (first 8 bytes) from the tag data."""
146
+
147
+ return bytes(data[:8]).hex().upper()
148
+
149
+
150
+ def read_tag() -> Amiibo | UnknownTag | None:
151
+ """
152
+ Read a tag and return the corresponding Amiibo object if recognized,
153
+ otherwise return an UnknownTag object. Returns None if no tag is detected.
154
+ """
155
+
156
+ reader = _get_reader()
157
+
158
+ status, _ = reader.Request(reader.PICC_REQIDL)
159
+
160
+ if status != reader.MI_OK:
161
+ return None
162
+
163
+ _, uid = reader.Anticoll()
164
+ uid = bytes(uid).hex().upper()
165
+
166
+ if not _select_amiibo():
167
+ return UnknownTag(uid)
168
+
169
+ data = _read_ntag_page(21)
170
+
171
+ if not data:
172
+ return UnknownTag(uid)
173
+
174
+ amiibo_id = _get_amiibo_id(data)
175
+
176
+ return AMIIBOS.get(amiibo_id, UnknownTag(amiibo_id))
@@ -0,0 +1,6 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(frozen=True)
5
+ class UnknownTag:
6
+ id: str
@@ -0,0 +1,236 @@
1
+ Metadata-Version: 2.4
2
+ Name: amiibo-reader
3
+ Version: 0.1.0
4
+ Summary: Python library for reading amiibo tags using an MFRC522 NFC reader on Raspberry Pi
5
+ Author: Pascal Béger
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/p-beger/amiibo-reader
8
+ Requires-Python: >=3.13
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: mfrc522-python==0.0.9
12
+ Dynamic: license-file
13
+
14
+ # amiibo-reader
15
+
16
+ Python library for reading amiibo tags with an MFRC522 RFID reader.
17
+
18
+ The library detects NFC tags, reads amiibo data from NTAG215 tags, and identifies amiibo figures using a local amiibo database.
19
+
20
+ `amiibo-reader` is an independant Python library for reading and parsing NFC data from amiibo tags. It is not affiliated with or endorsed by Nintendo.
21
+
22
+ ## Features
23
+
24
+ - Read amiibo tags using an MFRC522 RFID reader
25
+ - Read NTAG215 data
26
+ - Extract amiibo IDs
27
+ - Identify amiibo figures from the local database
28
+ - Distinguish between recognized amiibo and unknown tags
29
+ - Expose a small and simple Python API
30
+
31
+ ## How it works
32
+
33
+ When a tag is placed on the MFRC522 reader:
34
+
35
+ 1. The reader detects the tag.
36
+ 2. The tag UID is retrieved.
37
+ 3. The library performs ISO14443A anticollision and selection.
38
+ 4. The NTAG215 data is read.
39
+ 5. The amiibo ID is extracted from the tag data.
40
+ 6. The amiibo ID is looked up in the local amiibo database.
41
+ 7. The corresponding `Amiibo` is returned if it is known.
42
+ 8. Otherwise, an `UnknownTag` is returned.
43
+
44
+ If no tag is detected, `read_tag()` returns `None`.
45
+
46
+ ## Requirements
47
+
48
+ - Python 3.13+
49
+ - Raspberry Pi or another compatible Linux system
50
+ - MFRC522 RFID reader
51
+ - amiibo figures or compatible NTAG215 tags
52
+ - SPI interface enabled
53
+
54
+ The library uses the `mfrc522` Python package to communicate with the MFRC522 reader.
55
+
56
+ ## Installation
57
+
58
+ Clone the repository:
59
+
60
+ ```bash
61
+ git clone https://github.com/p-beger/amiibo-reader.git
62
+ cd amiibo-reader
63
+ ```
64
+
65
+ Create and activate a virtual environment:
66
+
67
+ ```bash
68
+ python3 -m venv venv
69
+ source venv/bin/activate
70
+ ```
71
+
72
+ If creating the virtual environment fails, make sure `python3-full` is installed:
73
+
74
+ ```bash
75
+ sudo apt-get install python3-full
76
+ ```
77
+
78
+ Install the library:
79
+
80
+ ```bash
81
+ python -m pip install .
82
+ ```
83
+
84
+ For development, install the development dependencies:
85
+
86
+ ```bash
87
+ python -m pip install --group dev
88
+ ```
89
+
90
+ ## MFRC522 configuration
91
+
92
+ The library currently uses the following MFRC522 configuration:
93
+ - SPI bus: 0
94
+ - SPI device: 0
95
+ - SPI speed: 1 MHz
96
+ - Reset pin: 22
97
+ SPI must be enabled on the Raspberry Pi.
98
+
99
+ The reader is automatically initialized when the library is imported.
100
+
101
+ ## Usage
102
+
103
+ The main API is exposed directly from the `amiibo_reader` package:
104
+
105
+ ```python
106
+ from amiibo_reader import close_reader, read_tag
107
+ ```
108
+
109
+ Read a tag:
110
+
111
+ ```python
112
+ tag = read_tag()
113
+
114
+ if tag is None:
115
+ print("No tag detected")
116
+ else:
117
+ print(tag)
118
+ ```
119
+
120
+ ## Recognized amiibo
121
+
122
+ When the tag is recognized, `read_tag()` returns an `Amiibo` object:
123
+
124
+ ```python
125
+ from amiibo_reader import Amiibo, read_tag
126
+
127
+ tag = read_tag()
128
+
129
+ if isinstance(tag, Amiibo):
130
+ print(f"ID: {tag.id}")
131
+ print(f"Series: {tag.series}")
132
+ print(f"Character: {tag.character}")
133
+ ```
134
+
135
+ For example:
136
+ ```
137
+ ID: 0000000000000002
138
+ Series: Super Smash Bros.
139
+ Character: Mario
140
+ ```
141
+
142
+ ## Unknown Tags
143
+
144
+ If a tag is detected but cannot be identified as a known amiibo, `read_tag()` returns an `UnknownTag` object:
145
+
146
+ ```python
147
+ from amiibo_reader import UnknownTag, read_tag
148
+
149
+ tag = read_tag()
150
+
151
+ if isinstance(tag, UnknownTag):
152
+ print(f"Unknown tag ID: {tag.id}")
153
+ ```
154
+
155
+ The `id` contains the most relevant identifier available:
156
+ - the tag UID if the amiibo data could not be read
157
+ - the amiibo ID if the tag was successfully read but is not present in the local database
158
+
159
+ ## No tag detected
160
+
161
+ If not tag is present:
162
+
163
+ ```python
164
+ tag = read_tag()
165
+
166
+ if tag is None:
167
+ print("No tag detected")
168
+ ```
169
+
170
+ ## Closing the reader
171
+
172
+ When the reader is no longer needed, close it with:
173
+
174
+ ```python
175
+ from amiibo_reader import close_reader
176
+
177
+ close_reader()
178
+ ```
179
+
180
+ ## amiibo database
181
+
182
+ The amiibo database is stored in `amiibos.py`.
183
+
184
+ Each entry associates an amiibo ID with an `Amiibo` object.
185
+
186
+ ```python
187
+ AMIIBOS: dict[str, Amiibo] = {
188
+ "0000000000000002": Amiibo(
189
+ "0000000000000002",
190
+ AmiiboSeries.SUPER_SMASH_BROS,
191
+ "Mario",
192
+ ),
193
+ }
194
+ ```
195
+
196
+ Additional amiibo can be added to the database by adding their amiibo ID, series and character.
197
+
198
+ ## Project structure
199
+
200
+ ```
201
+ amiibo-reader/
202
+ ├── src/
203
+ │ └── amiibo_reader/
204
+ │ ├── __init__.py
205
+ │ ├── amiibo.py
206
+ │ ├── amiibos.py
207
+ │ ├── read_tag.py
208
+ │ └── unknown_tag.py
209
+ ├── .gitignore
210
+ ├── pyproject.toml
211
+ └── README.md
212
+ ```
213
+
214
+ ## Development
215
+
216
+ The project uses:
217
+ - Ruff for formatting and linting
218
+ - mypy for static type checking
219
+
220
+ Format the code with Ruff:
221
+
222
+ ```bash
223
+ ruff format .
224
+ ```
225
+
226
+ Lint the code:
227
+
228
+ ```bash
229
+ ruff check .
230
+ ```
231
+
232
+ Run mypy:
233
+
234
+ ```bash
235
+ mypy src
236
+ ```
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/amiibo_reader/__init__.py
5
+ src/amiibo_reader/amiibo.py
6
+ src/amiibo_reader/amiibos.py
7
+ src/amiibo_reader/read_tag.py
8
+ src/amiibo_reader/unknown_tag.py
9
+ src/amiibo_reader.egg-info/PKG-INFO
10
+ src/amiibo_reader.egg-info/SOURCES.txt
11
+ src/amiibo_reader.egg-info/dependency_links.txt
12
+ src/amiibo_reader.egg-info/requires.txt
13
+ src/amiibo_reader.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ mfrc522-python==0.0.9
@@ -0,0 +1 @@
1
+ amiibo_reader