platypush/platypush/backend/nfc.py
2020-08-17 17:16:53 +02:00

204 lines
7.3 KiB
Python

import base64
import json
from platypush.backend import Backend
from platypush.message.event.nfc import NFCTagDetectedEvent, NFCTagRemovedEvent, NFCDeviceConnectedEvent, \
NFCDeviceDisconnectedEvent
class NfcBackend(Backend):
"""
Backend to detect NFC card events from a compatible reader.
Triggers:
* :class:`platypush.message.event.nfc.NFCDeviceConnectedEvent` when an NFC reader/writer is connected
* :class:`platypush.message.event.nfc.NFCDeviceDisconnectedEvent` when an NFC reader/writer is disconnected
* :class:`platypush.message.event.nfc.NFCTagDetectedEvent` when an NFC tag is detected
* :class:`platypush.message.event.nfc.NFCTagRemovedEvent` when an NFC tag is removed
Requires:
* **nfcpy** >= 1.0 (``pip install 'nfcpy>=1.0'``)
* **ndef** (``pip install ndef``)
Run the following to check if your device is compatible with nfcpy and the right permissions are set::
python -m nfc
"""
def __init__(self, device='usb', *args, **kwargs):
"""
:param device: Address or ID of the device to be opened. Examples:
* `'usb:003:009'` opens device 9 on bus 3
* `'usb:003'` opens the first available device on bus 3
* `'usb'` opens the first available USB device (default)
"""
super().__init__(*args, **kwargs)
self.device_id = device
self._clf = None
def _get_clf(self):
import nfc
if not self._clf:
self._clf = nfc.ContactlessFrontend()
self._clf.open(self.device_id)
self.bus.post(NFCDeviceConnectedEvent(reader=self._get_device_str()))
self.logger.info('Initialized NFC reader backend on device {}'.format(self._get_device_str()))
return self._clf
def _get_device_str(self):
return str(self._clf.device)
def close(self):
if self._clf:
self._clf.close()
self._clf = None
self.bus.post(NFCDeviceDisconnectedEvent(reader=self._get_device_str()))
@staticmethod
def _parse_records(tag):
from ndef.text import TextRecord
from ndef.uri import UriRecord
from ndef.smartposter import SmartposterRecord
from ndef.deviceinfo import DeviceInformationRecord
from ndef.wifi import WifiSimpleConfigRecord, WifiPeerToPeerRecord
from ndef.bluetooth import BluetoothLowEnergyRecord, BluetoothEasyPairingRecord
from ndef.signature import SignatureRecord
records = []
if not tag.ndef:
return records
for record in tag.ndef.records:
r = {
'record_type': record.type,
'record_name': record.name,
}
if isinstance(record, TextRecord):
try:
r = {
**r,
'type': 'json',
'value': json.loads(record.text),
}
except ValueError:
r = {
**r,
'type': 'text',
'text': record.text,
}
elif isinstance(record, UriRecord):
r = {
**r,
'type': 'uri',
'uri': record.uri,
'iri': record.iri,
}
elif isinstance(record, SmartposterRecord):
r = {
**r,
'type': 'smartposter',
**{attr: getattr(record, attr) for attr in ['resource', 'titles', 'title', 'action', 'icon',
'icons', 'resource_size', 'resource_type']},
}
elif isinstance(record, DeviceInformationRecord):
r = {
**r,
'type': 'device_info',
**{attr: getattr(record, attr) for attr in ['vendor_name', 'model_name', 'unique_name',
'uuid_string', 'version_string']},
}
elif isinstance(record, WifiSimpleConfigRecord):
r = {
**r,
'type': 'wifi_simple_config',
**{attr: record[attr] for attr in record.attribute_names()}
}
elif isinstance(record, WifiPeerToPeerRecord):
r = {
**r,
'type': 'wifi_peer_to_peer',
**{attr: record[attr] for attr in record.attribute_names()}
}
elif isinstance(record, BluetoothEasyPairingRecord):
r = {
**r,
'type': 'bluetooth_easy_pairing',
**{attr: getattr(record, attr) for attr in ['device_address', 'device_name', 'device_class']},
}
elif isinstance(record, BluetoothLowEnergyRecord):
r = {
**r,
'type': 'bluetooth_low_energy',
**{attr: getattr(record, attr) for attr in ['device_address', 'device_name', 'role_capabilities',
'appearance', 'flags', 'security_manager_tk_value',
'secure_connections_confirmation_value',
'secure_connections_random_value']},
}
elif isinstance(record, SignatureRecord):
r = {
**r,
'type': 'signature',
**{attr: getattr(record, attr) for attr in ['version', 'signature_type', 'hash_type', 'signature',
'signature_uri', 'certificate_format',
'certificate_store', 'certificate_uri',
'secure_connections_random_value']},
}
else:
r = {
**r,
'type': 'binary',
'data': base64.encodebytes(record.data).decode(),
}
records.append(r)
return records
@staticmethod
def _parse_id(tag):
return ''.join([('%02X' % c) for c in tag.identifier])
def _on_connect(self):
def callback(tag):
if not tag:
return False
tag_id = self._parse_id(tag)
records = self._parse_records(tag)
self.bus.post(NFCTagDetectedEvent(reader=self._get_device_str(), tag_id=tag_id, records=records))
return True
return callback
def _on_release(self):
def callback(tag):
tag_id = self._parse_id(tag)
self.bus.post(NFCTagRemovedEvent(reader=self._get_device_str(), tag_id=tag_id))
return callback
def run(self):
super().run()
while not self.should_stop():
try:
clf = self._get_clf()
clf.connect(rdwr={
'on-connect': self._on_connect(),
'on-release': self._on_release(),
})
finally:
self.close()
# vim:sw=4:ts=4:et: