Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚀 | Support ntag NFC Chips with rc522 #2373

Open
lukruh opened this issue May 15, 2024 · 30 comments
Open

🚀 | Support ntag NFC Chips with rc522 #2373

lukruh opened this issue May 15, 2024 · 30 comments
Labels
enhancement future3 Relates to future3 development help wanted legacy_v2 Issues, discussions and PRs related to Version 2.x

Comments

@lukruh
Copy link

lukruh commented May 15, 2024

Feature

Extend the Mfrc522Reader class to support cheap NFC chips like ntag215. This might require switching to another library. The currently used pi-rc522 seems not to support the newer ntag chips. There are several other libraries, that might support the ntag type chips as well as the currently supported s50/s70 types.

I don't have experience with RFID/NFC or the phoniebox it self. But I'm happy to share my current workaround or help implementing a more general solution. Maybe some more experienced users/devs want to jump in for discussion.
(I will link related Issues later).

User perspective

From reading the docs (before #2372) here in the repo and/or the description of the RC522 users (like me) might expect all NFC tags are supported as long as the frequency matches. The ntag type chips seem to be very common on the market. For users who got some of these it should be possible to use them without touching the code.

Further information

I managed to use the phoniebox with ntag type chips by adding a reader based on the mrfc522 library with some copy pasted code from their examples and other github issues to work around Auth errors. This could be at least a preliminary solution for users who only want to use this type of chips:

class Mfrc522NtagReader(object):
    def __init__(self):
        from mfrc522 import MFRC522
        self.device = MFRC522()
    
    def readCard(self):
        while True:
            reader = self.device
            status, _ = reader.MFRC522_Request(reader.PICC_REQIDL)
            if status != reader.MI_OK:
                sleep(0.1)
                continue
            status, backData = reader.MFRC522_Anticoll()
            buf = reader.MFRC522_Read(0)
            reader.MFRC522_Request(reader.PICC_HALT)
            if buf:
                card_id = "".join([str(i) for i in buf])
                logger.info(card_id)
                return card_id

    @staticmethod
    def cleanup():
        GPIO.cleanup()

I have adjusted the Reader class and setup script accordingly to switch between the current reader and this workaround.

@lukruh lukruh added enhancement future3 Relates to future3 development labels May 15, 2024
@s-martin
Copy link
Collaborator

s-martin commented May 15, 2024

Thanks for your feature request.

The current Phoniebox implementation for rc522 readers seems to have issues with tags, which require(?) authentication.

The GitHub repo of the used rc-522 library seems to have recent activity (again).

If I understand the readme example correctly https://github.com/ondryaso/pi-rc522?tab=readme-ov-file#usage the RFID class seems to have support for auth, which might also fix the original issue.

@s-martin s-martin added the legacy_v2 Issues, discussions and PRs related to Version 2.x label May 15, 2024
@s-martin
Copy link
Collaborator

Looking at our code in V2 the implementation might be too simple, just ignoring auth? @AlvinSchiller

@s-martin s-martin removed the future3 Relates to future3 development label May 15, 2024
@s-martin
Copy link
Collaborator

@lukruh I think you are currently referring to V2 implementation (using Reader.experimental.py), so I tagged the issue accordingly.

Nevertheless we need to check V3 as well.

@s-martin
Copy link
Collaborator

@lukruh I think you are currently referring to V2 implementation (using Reader.experimental.py), so I tagged the issue accordingly.

Nevertheless we need to check V3 as well.

Looking at https://github.com/MiczFlor/RPi-Jukebox-RFID/blob/future3%2Fdevelop/src%2Fjukebox%2Fcomponents%2Frfid%2Fhardware%2Frc522_spi%2Frc522_spi.py#L100-L131 the V3 has the same (maybe too simple?) implementation.

@s-martin s-martin added the future3 Relates to future3 development label May 15, 2024
@s-martin
Copy link
Collaborator

s-martin commented May 18, 2024

Documentation has been updated (see #2372 and #2374) that ntags are currently not supported. Nevertheless we should try to fix this.

@s-martin
Copy link
Collaborator

s-martin commented May 18, 2024

Documentation has been updated (see #2372 and #2374) that ntags are currently not supported. Nevertheless we should try to fix this.

@s-martin
Copy link
Collaborator

s-martin commented May 18, 2024

If pi-rc522 library supports ntags and we need to improve our implementation this should be the way to go from my point of view.

The mfrc522 library seems to support it, but there is no activity for almost 4 years, so I'm not sure, if this helps us in the future.

We also need to consider the RPi.GPIO issue for Raspberry Pi OS Bookworm (at least for V3), see #2313 (comment)

@AlvinSchiller
Copy link
Collaborator

AlvinSchiller commented May 18, 2024

Looking at our code in V2 the implementation might be too simple, just ignoring auth? @AlvinSchiller

Yes, it looks like the case is just not covered, also in v3.
I don't have this kind of tags around to test though.

@AlvinSchiller
Copy link
Collaborator

If pi-rc522 library supports ntags and we need to improve our implementation this should be the way to go from my point of view.

The mfrc522 library seems to support it, but there is no activity for almost 4 years, so I'm not sure, if this helps us in the future.

We also need to consider the RPi.GPIO issue for Raspberry Pi OS Bookworm (at least for V3), see #2313 (comment)

Agree in all points 👍

@lukruh
Copy link
Author

lukruh commented May 23, 2024

I got a couple of the ntag215 ones and can try to get to ignore auth in pi-rc522.
So the preferred order of solving this would be:

  1. get it running within Reader class using current pi-rc522 library
  2. if required request changes for pir-rc522 upstream
  3. if nothing of the above works fall back to mfrc522 (at least as a temporary workaround?)

@spelech
Copy link

spelech commented Jun 9, 2024

I have a working update for pi-rc522 that does work for ntag215 and the classic cards. Ntag cards tend to cause more read errors (could also be cheap tags being crappy). I modified it based on the pattern from this other repo. I'd prefer to find the official docs laying out the sequence of events but this works. I'll put together an MR for pi-rc522 and see how that goes, that repo is pretty dead aside from a readme change

@spelech
Copy link

spelech commented Jun 9, 2024

Would anyone be willing to take a look at something? I am running into some weird behavior and I'm not sure what is going on.
When I run daemon_rfid_reader.py, I can see my new class getting loaded correctly and it picks up the ntag215 chips (less reliably than the mifare 1k card but that could be an antenna size/quality problem on this tags). Since the debug version updates the settings files the same way, I can actually see the ntag215 uids showing up on the web page for registering new cards.

HOWEVER, the service does not seem to be having the same luck - I do not see the load at the end of my custom class's constructor hitting the log. Is this just an issue with relative paths for the new class?

@spelech
Copy link

spelech commented Jun 10, 2024

I've hit a wall. I can't understand why the wait_for_tag() method works correctly in debug but not when I run the script normally. Could be a timing issue that needs to be resolved between the two. Here is the doc that outlines the comms, and honestly it looks like the existing code should have handled 7 byte (but not 10) UIDs. I ordered a stack of classic cards in the mean time

@s-martin
Copy link
Collaborator

Do you have a branch or a PR yet?

It would probably help, if people could look at the code to help.

@spelech
Copy link

spelech commented Jun 12, 2024

Yeah let me clean things up. I am going to separate my changes, revert the repo and diff things. It's just bizarre. I think most of the code to properly grab NTag 21x UIDs is there (maybe minus another select command). I got frustrated with whatever was happening with Python debug vs normal and decided to look into .NET IoT libraries.

I am writing this now looking at my cheap MRFC522 spitting out NTag215 UIDs like crazy, and handling the classics just fine using the .NET libraries. This works for me for now because I can just mimic the script behavior with the dll, but I'll take another look at the python piece since I don't want to leave everyone hanging

@spelech
Copy link

spelech commented Jun 12, 2024

Diffing the current pi rfc522 library against the working c# libraries, a few things stand out

The libraries diverge quick from there. The C# library handles cascading internal to the Select command, and again the registers/values look. I'm a few years removed from working with hardware and rarely use python, but I can't imagine there differences aren't leading to problems. If you check out the class ESPHome uses alongside it's python, the c class write similar values to the c# source, and I haven't seen threads for Home Assistant complaining about it

My apologies but for now this is the most I can do - it's a large rewrite from initial glance but someone more versed in python should double check

@flesser
Copy link

flesser commented Jun 22, 2024

To clarify: NTAG213 tags (13, not 15) seem to work in current master.

(after applying the workaround from #2387, wich was also required for me to read MIFARE Classic tags)

@damaev
Copy link
Contributor

damaev commented Jun 22, 2024

Here the part of the nxp mfrc522 documentation corresponding to the interrupts

8.4 Interrupt request system (3) (4) The MFRC522 indicates certain events by setting the Status1Reg register’s IRq bit and, if activated, by pin IRQ. The signal on pin IRQ can be used to interrupt the host using its interrupt handling capabilities. This allows the implementation of efficient host software. 8.4.1 Interrupt sources overview Table 18 shows the available interrupt bits, the corresponding source and the condition for its activation. The ComIrqReg register’s TimerIRq interrupt bit indicates an interrupt set by the timer unit which is set when the timer decrements from 1 to 0. The ComIrqReg register’s TxIRq bit indicates that the transmitter has finished. If the state changes from sending data to transmitting the end of the frame pattern, the transmitter unit automatically sets the interrupt bit. The CRC coprocessor sets the DivIrqReg register’s CRCIRq bit after processing all the FIFO buffer data which is indicated by CRCReady bit = 1. ). The ComIrqReg register’s RxIRq bit indicates an interrupt when the end of the received data is detected. The ComIrqReg register’s IdleIRq bit is set if a command finishes and the Command[3:0] value in the CommandReg register changes to idle (see Table 149 on page 70 The ComIrqReg register’s HiAlertIRq bit is set to logic 1 when the Status1Reg register’s HiAlert bit is set to logic 1 which means that the FIFO buffer has reached the level indicated by the WaterLevel[5:0] bits. The ComIrqReg register’s LoAlertIRq bit is set to logic 1 when the Status1Reg register’s LoAlert bit is set to logic 1 which means that the FIFO buffer has reached the level indicated by the WaterLevel[5:0] bits.
The ComIrqReg register’s ErrIRq bit indicates an error detected by the contactless UART during send or receive. This is indicated when any bit is set to logic 1 in register ErrorReg

Table 18. Interrupt sources
List of register bits indicating the cause for the interrupt.

@damaev
Copy link
Contributor

damaev commented Jun 22, 2024

From my point of view the first thing that needs to be done, is checking if the irq system needs to be activated.
The next question is, which kind of interrupt is send, when an interrupt occurs.
Hence the status register’s would need to be read out.
However the table 8 doesn't hint that there actually is an interrupt that indicates a card nearby.
However, I probably won't find the time to dig into this one.

As for the authentication, I think both libraries are supporting it. For a better understanding you need to dig into the protocol of the rfid Chips. In the end it comes down to sending som hex code via the rc522, like when getting the last 4 bytes of the uid of a 7 byte uid

@damaev
Copy link
Contributor

damaev commented Jun 22, 2024

For just making the Reader read cards the IRQ is NOT needed with any library, see #2387

@flesser
Copy link

flesser commented Jun 23, 2024

Ah, now I see how these two issues are related. I'm just building my first Phoniebox and just ran into the #2387 issue with my existing Tonuino tags (small paper stickers underneath wooden tokens) which report as ISO 14443-3A NXP - NTAG213 in NFC Tools.

I then tried a random NFC card from my wallet (my company id card actually) which reports as ISO 14443-4 NXP - Mifare Classic 1k and it also did not work, despite it being a Mifare Classic tag.

Now I also found the plain white card that came shipped with my RC522 reader and this works even with IRQ! It seems to be a different variant of Mifare Classic: ISO 14443-3A NXP - Mifare Classic 1k.

Maybe I will find some time this week to dig into the RC522 specs and the python library code, but no promises...

@flesser
Copy link

flesser commented Jun 24, 2024

After adding

self.clear_bitmask(0x13, 0x04)

at https://github.com/ondryaso/pi-rc522/blob/master/pirc522/rfid.py#L499 (/usr/local/lib/python3.9/dist-packages/pirc522/rfid.py on the phoniebox) I am getting IRQs from the reader with my empty NTAG213 tags 🎉

This will clear the RxNoErr bit in the RxModeReg register which defaults to 1 = "an invalid received data stream (less than 4 bits received) will be ignored and the receiver remains active"

That will have an effect on ComIrqReg register's RxIRq bit ("receiver has detected the end of a valid data stream. If the RxModeReg register’s RxNoErr bit is set to logic 1, the RxIRq bit is only set to logic 1 when data bytes are available in the FIFO"), which in turn is one of the interrupt sources.

@s-martin
Copy link
Collaborator

Do you want to open a PR at https://github.com/ondryaso/pi-rc522 to fix this upstream?

This would be the easiest solution, we would avoid to fork the upstream repo.

@spelech
Copy link

spelech commented Jun 25, 2024

After adding

self.clear_bitmask(0x13, 0x04)

at https://github.com/ondryaso/pi-rc522/blob/master/pirc522/rfid.py#L499 (/usr/local/lib/python3.9/dist-packages/pirc522/rfid.py on the phoniebox) I am getting IRQs from the reader with my empty NTAG213 tags 🎉

I'll give it a whirl. I have a pack of NTAG215 little disc tags I bought for some esphome projects so I'd like those to work but I think from what I've seen 213/215 are similar (7 byte UID) just the 215 has more storage

@spelech
Copy link

spelech commented Jun 25, 2024

Unfortunately, the bitmask change was not enough to get NTAG215 UIDs to be read correctly. I'm willing to offer up the .NET code I have that is working if others need it. The other thing I considered was leveraging the c code that's out there for rc522 to put together a library Reader.py can just call directly and skip pirc522

@damaev
Copy link
Contributor

damaev commented Jun 25, 2024

Unfortunately, the bitmask change was not enough to get NTAG215 UIDs to be read correctly. I'm willing to offer up the .NET code I have that is working if others need it. The other thing I considered was leveraging the c code that's out there for rc522 to put together a library Reader.py can just call directly and skip pirc522

Is your .Net code using the interrupt pin?
Because I'm currently asking myself whether we should spend time on an interrupt system, which we most probably do not necessarily need.

@flesser
Copy link

flesser commented Jun 25, 2024

Do you want to open a PR ... to fix this upstream?

Definitely, but only after testing a bit more to be sure that this actually fixes the issue.
I'm still not entirely sure what the heck the library is doing there in wait_for_tag().

Because I'm currently asking myself whether we should spend time on an interrupt system, which we most probably do not necessarily need.
I think that would be the sane way to go.

My hope for an interrupt-based solution was to save CPU cycles (the workaround with commented-out wait_for_tag() currently uses ~25% CPU on my Raspi 3B+ according to htop) and act only if the reader triggers a GPIO event via the IRQ pin.

But according to the datasheet (and https://arduino.stackexchange.com/a/76285) the rc522 actually does not seem to support a direct "interrupt when tag detected", only interrupts for e.g. the TX queue reaching a certain length.

The pi-rc522 library implementation of wait_for_tag() also does not seem to passively wait for a GPIO event but uses some active polling mechanism: There is an infinite loop that every 100 ms completely reinitializes the reader and transmits some radio data until either a "receiver interrupt request (RxIRq bit)" is received (I guess this means that a tag was present and has answered) or an optional timeout is reached (currently not used by Reader.py) .

So I guess it should be safe to just ignore wait_for_tag() and just directly poll for tag IDs in Reader.py.

@damaev
Copy link
Contributor

damaev commented Jun 25, 2024

But according to the datasheet (and https://arduino.stackexchange.com/a/76285) the rc522 actually does not seem to support a direct "interrupt when tag detected", only interrupts for e.g. the TX queue reaching a certain length.

I got the same understanding, so I was already wondering how the wait_for_tag() is implemented and then, why your solution works. Now that i took a look in the implementation, the first thing done seems to be a complete reset of the reader (self.init()) and at the end, it waits for an event waiting = not self.irq.wait(0.1); according to the threading-doc

Wait until notified or until a timeout occurs. If the calling thread has not acquired the lock when this method is called, a RuntimeError is raised.

Hence, includeing the wait(0.1) may be the reason for the reduced load. This could of course be also implemented in the reader.read()-phase

@damaev
Copy link
Contributor

damaev commented Jun 26, 2024

After adding

self.clear_bitmask(0x13, 0x04)

at https://github.com/ondryaso/pi-rc522/blob/master/pirc522/rfid.py#L499 (/usr/local/lib/python3.9/dist-packages/pirc522/rfid.py on the phoniebox) I am getting IRQs from the reader with my empty NTAG213 tags 🎉

I tested the code as well. With my NTAG215 it's not working.
maybe at this point it's wise to bring up an old issue i had.

with my first setup the uids could not be read completely. the issue was, that i only got a complete answer from the tag from time to time. Other people with a similar issue suggested that the main issue was the lack of power. The rfid reader was just not capable of providing enough energy to get a first and then in almost no time the second answer from the tag, providing the full uid. After buying a new rfid reader, the issue was gone.
Keeping that in mind, the previously working setup may be a result of voltage drops. If I'm not mistaken, the irq-pin is pulled down to indicate an interrupt. Hence, it may be possible that issue now appears, because I have a better power source keeping the irq stable while it wasn't stable beforehand resulting in a working setup (even my old rfid-reader is working with my workaround) ¯\(ツ)/¯ .

@spelech
Copy link

spelech commented Jun 29, 2024

Unfortunately, the bitmask change was not enough to get NTAG215 UIDs to be read correctly. I'm willing to offer up the .NET code I have that is working if others need it. The other thing I considered was leveraging the c code that's out there for rc522 to put together a library Reader.py can just call directly and skip pirc522

Is your .Net code using the interrupt pin? Because I'm currently asking myself whether we should spend time on an interrupt system, which we most probably do not necessarily need.

No, the .NET MFRC522 implementation does not use the interrupt pin, nor is it even something you can pass/set via a constructor or property. Here is the source.

I questioned the need for the IRQ pin in my post as well after getting all my tags read fine through the .NET code. At the same time I was doing this, I was looking at languages for a Pi Pico project and came across the differences in MicroPython and CircuitPython - one main one being a lack of true interrupt support in CircuitPython. I started wondering if the interrupt was never being caught or something

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement future3 Relates to future3 development help wanted legacy_v2 Issues, discussions and PRs related to Version 2.x
Projects
None yet
Development

No branches or pull requests

6 participants