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

Add NFCPy support #2190

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions documentation/developers/rfid/generic_nfcpy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Generic NFCPy Reader

This module is based on the user space NFC reader library [nfcpy](https://nfcpy.readthedocs.io/en/latest/overview.html) ([on github](https://github.com/nfcpy/nfcpy)).
The link above also contains a list of [supported devices](https://nfcpy.readthedocs.io/en/latest/overview.html#supported-devices).

The goal of this module is to handle USB NFC devices, that don't have a HID-keyboard
driver, and thus cannot be used with the [genericusb](genericusb.md) module.

> [!NOTE]
> Since nfcpy is a user-space library, it is required to supress the kernel from loading its driver.
> The setup will do this automatically, so make sure the device is connected
> before running the [RFID reader configuration tool](../coreapps.md#RFID-Reader).

# Configuration

The installation script will scan for compatible devices and will assist in configuration.
By setting `rfid > readers > generic_nfcpy > config > device_path` in `shared/settings/rfid.yaml` you can override the
device location. By specifying an explicit device location it is possible to use multiple readers compatible with NFCpy.

Example configuration for a usb-device with vendor ID 072f and product ID 2200:
```yaml
rfid:
readers:
read_00:
module: generic_nfcpy
config:
device_path: usb:072f:2200
same_id_delay: 1.0
log_ignored_cards: false
place_not_swipe:
enabled: false
card_removal_action:
alias: pause
```

For possible values see the `path` parameter in this [nfcpy documentation](https://nfcpy.readthedocs.io/en/latest/modules/clf.html#nfc.clf.ContactlessFrontend.open)
2 changes: 2 additions & 0 deletions src/jukebox/components/rfid/hardware/generic_nfcpy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@

For documentation see [documentation/developers/rfid/generic_nfcpy.md](../../../../../../documentation/developers/rfid/generic_nfcpy.md).
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
""" List of supported devices https://nfcpy.readthedocs.io/en/latest/overview.html"""
# 40 chars: '========================================'
DESCRIPTION = 'Generic NFCPY NFC Reader Module'
128 changes: 128 additions & 0 deletions src/jukebox/components/rfid/hardware/generic_nfcpy/generic_nfcpy.py
s-martin marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Standard imports from python packages
import logging

import nfc
import glob
from nfc.clf import RemoteTarget
import nfc.clf.device

# Import the ReaderBaseClass for common API. Leave as this line as it is!
from components.rfid import ReaderBaseClass
import jukebox.cfghandler
import misc.inputminus as pyil
from misc.simplecolors import Colors

# Also import the description string into this module, to make everything available in a single module w/o code duplication
# Leave this line as is!
from .description import DESCRIPTION

# Create logger.
logger = logging.getLogger('jb.rfid.nfcpy')
# Get the global handler to the RFID config
cfg = jukebox.cfghandler.get_handler('rfid')


def query_customization() -> dict:
# filter all log records from nfc.clf
loggerNfcClf = logging.getLogger('nfc.clf')
loggerNfcClf.filter = lambda record: 0

devices = []
clf = nfc.ContactlessFrontend()

# find usb devices
for vid_pid_pair in nfc.clf.device.usb_device_map.keys():
device_id = "usb:%04x:%04x" % vid_pid_pair
if clf.open(device_id):
AlvinSchiller marked this conversation as resolved.
Show resolved Hide resolved
devices.append({'id': device_id, 'vendor': clf.device.vendor_name, 'name': clf.device.product_name})
clf.close()

# find tty device
matching_files = glob.glob("/dev/ttyUSB[0-9]*")
matching_files += glob.glob("/dev/ttyAMA[0-9]*")
for file_path in matching_files:
for driver in nfc.clf.device.tty_driver_list:
device_id = f'{file_path}:{driver}'
if clf.open(device_id):
AlvinSchiller marked this conversation as resolved.
Show resolved Hide resolved
devices.append({'id': device_id, 'vendor': clf.device.vendor_name, 'name': clf.device.product_name})
clf.close()

print("\nChoose RFID device from USB device list:\n")
logger.debug(f"USB devices: {[x['name'] for x in devices]}")
if len(devices) == 0:
logger.error("USB device list is empty. Make sure USB RFID reader is connected. Then re-run reader registration")
return {'device_path': None}

for idx, dev in enumerate(devices):
print(f" {Colors.lightgreen}{idx:2d}{Colors.reset}:"
f"{Colors.lightcyan}{Colors.bold}{dev['vendor']} {dev['name']}{Colors.reset}")

dev_id = pyil.input_int("Device number?", min=0, max=len(devices) - 1, prompt_color=Colors.lightgreen, prompt_hint=True)
device_path = devices[dev_id]['id']

return {'device_path': device_path}


class ReaderClass(ReaderBaseClass):
"""
The reader class for nfcpy supported NFC card readers.
"""
def __init__(self, reader_cfg_key):
# Create a per-instance logger, just in case the reader will run multiple times in various threads
self._logger = logging.getLogger(f'jb.rfid.nfcpy({reader_cfg_key})')
# Initialize the super-class. Don't change anything here
super().__init__(reader_cfg_key=reader_cfg_key, description=DESCRIPTION, logger=self._logger)

# Get the configuration from the rfid.yaml:
# Lock config around the access
with cfg:
# Get a reference to the actual reader-specific config
config = cfg.getn('rfid', 'readers', reader_cfg_key, 'config', default=None)
AlvinSchiller marked this conversation as resolved.
Show resolved Hide resolved
if config is None:
self._logger.error("Configuration may not be empty!!")
raise KeyError("configuration may not be empty!!")

device_path = config.setdefault('device_path', None)
self.clf = nfc.ContactlessFrontend()
self.clf.open(device_path)

self._keep_running = True

def cleanup(self):
"""
The cleanup function: free and release all resources used by this card reader (if any).
"""
self.clf.close()

def stop(self):
"""
This function is called to tell the reader to exit its reading function.
"""
self._keep_running = False

def read_card(self) -> str:
"""
Blocking or non-blocking function that waits for a new card to appear and return the card's UID as string
"""
self._logger.debug("Wait for card")
while self._keep_running:
target = self.clf.sense(RemoteTarget('106A'),
RemoteTarget('106B'),
RemoteTarget('212F'),
interval=0.1,
iterations=1)
if not target:
continue

tag = nfc.tag.activate(self.clf, target)
if not tag:
continue

id = ''
for char in tag.identifier:
id += '%02X' % char

self._logger.debug(f'Found card with ID: "{id}"')
return id
self._logger.debug("NFC read stopped")
return ''
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# NFCPy related requirements
# You need to install these with `python -m pip install --upgrade --force-reinstall -q -r requirements.txt`

nfcpy
42 changes: 42 additions & 0 deletions src/jukebox/components/rfid/hardware/generic_nfcpy/setup.inc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env bash

CURRENT_USER="${SUDO_USER:-$(whoami)}"

modprobe_file="/etc/modprobe.d/disable_driver_jukebox_nfcpy.conf"

if [ -e "$modprobe_file" ]; then
sudo rm -f "$modprobe_file"
fi
if lsmod | grep "pn533_usb"; then
sudo rmmod pn533_usb 2>/dev/null
sudo sh -c "echo 'install pn533_usb /bin/true' >> $modprobe_file"
AlvinSchiller marked this conversation as resolved.
Show resolved Hide resolved
fi

if lsmod | grep "port100"; then
sudo rmmod port100 2>/dev/null
sudo sh -c "echo 'install port100 /bin/true' >> $modprobe_file"
fi

udev_file="/etc/udev/rules.d/50-usb-nfc-rule.rules"

usb_devices=$(lsusb | sed -e 's/.*ID \([a-f0-9]\+:[a-f0-9]\+\).*/\1/g')

valid_device_ids=($(python -c "import nfc.clf.device; [print('%04x:%04x' % x) for x in nfc.clf.device.usb_device_map.keys()]"))

if [ -e "$udev_file" ]; then
sudo rm -f "$udev_file"
fi
for dev in $usb_devices; do
if echo ${valid_device_ids[@]} | grep -woq $dev; then
#$dev is in valid id array
VID="${dev%%:*}"
PID="${dev#*:}"
sudo sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"$VID\", ATTRS{idProduct}==\"$PID\", GROUP=\"plugdev\"' >> $udev_file"
fi
done
sudo udevadm control --reload-rules
sudo udevadm trigger

sudo gpasswd -a $CURRENT_USER plugdev
sudo gpasswd -a $CURRENT_USER dialout
sudo gpasswd -a $CURRENT_USER tty
Loading