amiibo-reader 0.1.0__py3-none-any.whl
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.
- amiibo_reader/__init__.py +11 -0
- amiibo_reader/amiibo.py +36 -0
- amiibo_reader/amiibos.py +119 -0
- amiibo_reader/read_tag.py +176 -0
- amiibo_reader/unknown_tag.py +6 -0
- amiibo_reader-0.1.0.dist-info/METADATA +236 -0
- amiibo_reader-0.1.0.dist-info/RECORD +10 -0
- amiibo_reader-0.1.0.dist-info/WHEEL +5 -0
- amiibo_reader-0.1.0.dist-info/licenses/LICENSE +19 -0
- amiibo_reader-0.1.0.dist-info/top_level.txt +1 -0
amiibo_reader/amiibo.py
ADDED
|
@@ -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
|
amiibo_reader/amiibos.py
ADDED
|
@@ -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,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,10 @@
|
|
|
1
|
+
amiibo_reader/__init__.py,sha256=rY_RHD52cVfVl4cTiGVTlyoK7e0s6N6dr_cX17c8XxQ,225
|
|
2
|
+
amiibo_reader/amiibo.py,sha256=SFzUdw_Pa7e-LiwMRNAANyaYqMCoouOQVJ6WMdy7XIM,1220
|
|
3
|
+
amiibo_reader/amiibos.py,sha256=ovBWci2xXw212oSScj_rRk2GbsZ2vVlv8AmEymgFVJo,5498
|
|
4
|
+
amiibo_reader/read_tag.py,sha256=zBYFM9lRIXS4wVfOudilEXL3637V_2uqnqOV1OJ13sE,3823
|
|
5
|
+
amiibo_reader/unknown_tag.py,sha256=1KSeXqlKCiZj4XejJCqEfxQ6GhSwpmAq9Hb4X2d7Eys,90
|
|
6
|
+
amiibo_reader-0.1.0.dist-info/licenses/LICENSE,sha256=RQm_nUhBRXwOJHHnJObpKH3CYiK7Li-rZ1lfQazu3t8,1056
|
|
7
|
+
amiibo_reader-0.1.0.dist-info/METADATA,sha256=tP6sKuyPm9fk78jU2PlsubXrdp5BZpkeNW0o5XRHSQA,4868
|
|
8
|
+
amiibo_reader-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
9
|
+
amiibo_reader-0.1.0.dist-info/top_level.txt,sha256=MbW0CUU5HU-ypArty50r5lAC8APl4zTtnm8xmkDjE5E,14
|
|
10
|
+
amiibo_reader-0.1.0.dist-info/RECORD,,
|
|
@@ -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 @@
|
|
|
1
|
+
amiibo_reader
|