672 lines
22 KiB
Python
Executable File
672 lines
22 KiB
Python
Executable File
#!/usr/bin/env python2
|
|
|
|
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import cmd
|
|
import dbus
|
|
import dbus.exceptions
|
|
import dbus.mainloop.glib
|
|
import gobject
|
|
import threading
|
|
|
|
from functools import wraps
|
|
|
|
|
|
DBUS_ERROR = 'org.freedesktop.DBus.Error'
|
|
NEARD_PATH = '/org/neard/'
|
|
PROMPT = 'NFC> '
|
|
|
|
class NfcClientException(Exception):
|
|
"""Exception class for exceptions thrown by NfcClient."""
|
|
|
|
|
|
def print_message(message, newlines=2):
|
|
"""
|
|
Prints the given message with extra wrapping newline characters.
|
|
|
|
@param message: Message to print.
|
|
@param newlines: Integer, specifying the number of '\n' characters that
|
|
should be padded at the beginning and end of |message| before
|
|
being passed to "print".
|
|
|
|
"""
|
|
padding = newlines * '\n'
|
|
message = padding + message + padding
|
|
print message
|
|
|
|
|
|
def handle_errors(func):
|
|
"""
|
|
Decorator for handling exceptions that are commonly raised by many of the
|
|
methods in NfcClient.
|
|
|
|
@param func: The function this decorator is wrapping.
|
|
|
|
"""
|
|
@wraps(func)
|
|
def _error_handler(*args):
|
|
try:
|
|
return func(*args)
|
|
except dbus.exceptions.DBusException as e:
|
|
if e.get_dbus_name() == DBUS_ERROR + '.ServiceUnknown':
|
|
print_message('neard may have crashed or disappeared. '
|
|
'Check if neard is running and run "initialize" '
|
|
'from this shell.')
|
|
return
|
|
if e.get_dbus_name() == DBUS_ERROR + '.UnknownObject':
|
|
print_message('Could not find object.')
|
|
return
|
|
print_message(str(e))
|
|
except Exception as e:
|
|
print_message(str(e))
|
|
return _error_handler
|
|
|
|
|
|
class NfcClient(object):
|
|
"""
|
|
neard D-Bus client
|
|
|
|
"""
|
|
NEARD_SERVICE_NAME = 'org.neard'
|
|
IMANAGER = NEARD_SERVICE_NAME + '.Manager'
|
|
IADAPTER = NEARD_SERVICE_NAME + '.Adapter'
|
|
ITAG = NEARD_SERVICE_NAME + '.Tag'
|
|
IRECORD = NEARD_SERVICE_NAME + '.Record'
|
|
IDEVICE = NEARD_SERVICE_NAME + '.Device'
|
|
|
|
def __init__(self):
|
|
self._mainloop = None
|
|
self._mainloop_thread = None
|
|
self._adapters = {}
|
|
self._adapter_property_handler_matches = {}
|
|
|
|
def begin(self):
|
|
"""
|
|
Starts the D-Bus client.
|
|
|
|
"""
|
|
# Here we run a GLib MainLoop in its own thread, so that the client can
|
|
# listen to D-Bus signals while keeping the console interactive.
|
|
self._dbusmainloop = dbus.mainloop.glib.DBusGMainLoop(
|
|
set_as_default=True)
|
|
dbus.mainloop.glib.threads_init()
|
|
gobject.threads_init()
|
|
|
|
def _mainloop_thread_func():
|
|
self._mainloop = gobject.MainLoop()
|
|
context = self._mainloop.get_context()
|
|
self._run_loop = True
|
|
while self._run_loop:
|
|
context.iteration(True)
|
|
self._mainloop_thread = threading.Thread(None, _mainloop_thread_func)
|
|
self._mainloop_thread.start()
|
|
|
|
self._bus = dbus.SystemBus()
|
|
self.setup_manager()
|
|
|
|
def end(self):
|
|
"""
|
|
Stops the D-Bus client.
|
|
|
|
"""
|
|
self._run_loop = False
|
|
self._mainloop.quit()
|
|
self._mainloop_thread.join()
|
|
|
|
def restart(self):
|
|
"""Reinitializes the NFC client."""
|
|
self.setup_manager()
|
|
|
|
@handle_errors
|
|
def _get_manager_proxy(self):
|
|
return dbus.Interface(
|
|
self._bus.get_object(self.NEARD_SERVICE_NAME, '/'),
|
|
self.IMANAGER)
|
|
|
|
@handle_errors
|
|
def _get_adapter_proxy(self, adapter):
|
|
return dbus.Interface(
|
|
self._bus.get_object(self.NEARD_SERVICE_NAME, adapter),
|
|
self.IADAPTER)
|
|
|
|
def _get_cached_adapter_proxy(self, adapter):
|
|
adapter_proxy = self._adapters.get(adapter, None)
|
|
if not adapter_proxy:
|
|
raise NfcClientException('Adapter "' + adapter + '" not found.')
|
|
return adapter_proxy
|
|
|
|
|
|
@handle_errors
|
|
def _get_tag_proxy(self, tag):
|
|
return dbus.Interface(
|
|
self._bus.get_object(self.NEARD_SERVICE_NAME, tag),
|
|
self.ITAG)
|
|
|
|
@handle_errors
|
|
def _get_device_proxy(self, device):
|
|
return dbus.Interface(
|
|
self._bus.get_object(self.NEARD_SERVICE_NAME, device),
|
|
self.IDEVICE)
|
|
|
|
@handle_errors
|
|
def _get_record_proxy(self, record):
|
|
return dbus.Interface(
|
|
self._bus.get_object(self.NEARD_SERVICE_NAME, record),
|
|
self.IRECORD)
|
|
|
|
@handle_errors
|
|
def _get_adapter_properties(self, adapter):
|
|
adapter_proxy = self._get_cached_adapter_proxy(adapter)
|
|
return adapter_proxy.GetProperties()
|
|
|
|
def _get_adapters(self):
|
|
props = self._manager.GetProperties()
|
|
return props.get('Adapters', None)
|
|
|
|
def setup_manager(self):
|
|
"""
|
|
Creates a manager proxy and subscribes to adapter signals. This method
|
|
will also initialize proxies for adapters if any are available.
|
|
|
|
"""
|
|
# Create the manager proxy.
|
|
self._adapters.clear()
|
|
self._manager = self._get_manager_proxy()
|
|
if not self._manager:
|
|
print_message('Failed to create a proxy to the Manager interface.')
|
|
return
|
|
|
|
# Listen to the adapter added and removed signals.
|
|
self._manager.connect_to_signal(
|
|
'AdapterAdded',
|
|
lambda adapter: self.register_adapter(str(adapter)))
|
|
self._manager.connect_to_signal(
|
|
'AdapterRemoved',
|
|
lambda adapter: self.unregister_adapter(str(adapter)))
|
|
|
|
# See if there are any adapters and create proxies for each.
|
|
adapters = self._get_adapters()
|
|
if adapters:
|
|
for adapter in adapters:
|
|
self.register_adapter(adapter)
|
|
|
|
def register_adapter(self, adapter):
|
|
"""
|
|
Registers an adapter proxy with the given object path and subscribes to
|
|
adapter signals.
|
|
|
|
@param adapter: string, containing the adapter's D-Bus object path.
|
|
|
|
"""
|
|
print_message('Added adapter: ' + adapter)
|
|
adapter_proxy = self._get_adapter_proxy(adapter)
|
|
self._adapters[adapter] = adapter_proxy
|
|
|
|
# Tag found/lost currently don't get fired. Monitor property changes
|
|
# instead.
|
|
if self._adapter_property_handler_matches.get(adapter, None) is None:
|
|
self._adapter_property_handler_matches[adapter] = (
|
|
adapter_proxy.connect_to_signal(
|
|
'PropertyChanged',
|
|
(lambda name, value:
|
|
self._adapter_property_changed_signal(
|
|
adapter, name, value))))
|
|
|
|
def unregister_adapter(self, adapter):
|
|
"""
|
|
Removes the adapter proxy for the given object path from the internal
|
|
cache of adapters.
|
|
|
|
@param adapter: string, containing the adapter's D-Bus object path.
|
|
|
|
"""
|
|
print_message('Removed adapter: ' + adapter)
|
|
match = self._adapter_property_handler_matches.get(adapter, None)
|
|
if match is not None:
|
|
match.remove()
|
|
self._adapter_property_handler_matches.pop(adapter)
|
|
self._adapters.pop(adapter)
|
|
|
|
def _adapter_property_changed_signal(self, adapter, name, value):
|
|
if name == 'Tags' or name == 'Devices':
|
|
print_message('Found ' + name + ': ' +
|
|
self._dbus_array_to_string(value))
|
|
|
|
@handle_errors
|
|
def show_adapters(self):
|
|
"""
|
|
Prints the D-Bus object paths of all adapters that are available.
|
|
|
|
"""
|
|
adapters = self._get_adapters()
|
|
if not adapters:
|
|
print_message('No adapters found.')
|
|
return
|
|
for adapter in adapters:
|
|
print_message(' ' + str(adapter), newlines=0)
|
|
print
|
|
|
|
def _dbus_array_to_string(self, array):
|
|
string = '[ '
|
|
for value in array:
|
|
string += ' ' + str(value) + ', '
|
|
string += ' ]'
|
|
return string
|
|
|
|
def print_adapter_status(self, adapter):
|
|
"""
|
|
Prints the properties of the given adapter.
|
|
|
|
@param adapter: string, containing the adapter's D-Bus object path.
|
|
|
|
"""
|
|
props = self._get_adapter_properties(adapter)
|
|
if not props:
|
|
return
|
|
print_message('Status ' + adapter + ': ', newlines=0)
|
|
for key, value in props.iteritems():
|
|
if type(value) == dbus.Array:
|
|
value = self._dbus_array_to_string(value)
|
|
else:
|
|
value = str(value)
|
|
print_message(' ' + key + ' = ' + value, newlines=0)
|
|
print
|
|
|
|
@handle_errors
|
|
def set_powered(self, adapter, powered):
|
|
"""
|
|
Enables or disables the adapter.
|
|
|
|
@param adapter: string, containing the adapter's D-Bus object path.
|
|
@param powered: boolean that dictates whether the adapter will be
|
|
enabled or disabled.
|
|
|
|
"""
|
|
adapter_proxy = self._get_cached_adapter_proxy(adapter)
|
|
if not adapter_proxy:
|
|
return
|
|
adapter_proxy.SetProperty('Powered', powered)
|
|
|
|
@handle_errors
|
|
def start_polling(self, adapter):
|
|
"""
|
|
Starts polling for nearby tags and devices in "Initiator" mode.
|
|
|
|
@param adapter: string, containing the adapter's D-Bus object path.
|
|
|
|
"""
|
|
adapter_proxy = self._get_cached_adapter_proxy(adapter)
|
|
adapter_proxy.StartPollLoop('Initiator')
|
|
print_message('Started polling.')
|
|
|
|
@handle_errors
|
|
def stop_polling(self, adapter):
|
|
"""
|
|
Stops polling for nearby tags and devices.
|
|
|
|
@param adapter: string, containing the adapter's D-Bus object path.
|
|
|
|
"""
|
|
adapter_proxy = self._get_cached_adapter_proxy(adapter)
|
|
adapter_proxy.StopPollLoop()
|
|
self._polling_stopped = True
|
|
print_message('Stopped polling.')
|
|
|
|
@handle_errors
|
|
def show_tag_data(self, tag):
|
|
"""
|
|
Prints the properties of the given tag, as well as the contents of any
|
|
records associated with it.
|
|
|
|
@param tag: string, containing the tag's D-Bus object path.
|
|
|
|
"""
|
|
tag_proxy = self._get_tag_proxy(tag)
|
|
if not tag_proxy:
|
|
print_message('Tag "' + tag + '" not found.')
|
|
return
|
|
props = tag_proxy.GetProperties()
|
|
print_message('Tag ' + tag + ': ', newlines=1)
|
|
for key, value in props.iteritems():
|
|
if key != 'Records':
|
|
print_message(' ' + key + ' = ' + str(value), newlines=0)
|
|
records = props['Records']
|
|
if not records:
|
|
return
|
|
print_message('Records: ', newlines=1)
|
|
for record in records:
|
|
self.show_record_data(str(record))
|
|
print
|
|
|
|
@handle_errors
|
|
def show_device_data(self, device):
|
|
"""
|
|
Prints the properties of the given device, as well as the contents of
|
|
any records associated with it.
|
|
|
|
@param device: string, containing the device's D-Bus object path.
|
|
|
|
"""
|
|
device_proxy = self._get_device_proxy(device)
|
|
if not device_proxy:
|
|
print_message('Device "' + device + '" not found.')
|
|
return
|
|
records = device_proxy.GetProperties()['Records']
|
|
if not records:
|
|
print_message('No records on device.')
|
|
return
|
|
print_message('Records: ', newlines=1)
|
|
for record in records:
|
|
self.show_record_data(str(record))
|
|
print
|
|
|
|
@handle_errors
|
|
def show_record_data(self, record):
|
|
"""
|
|
Prints the contents of the given record.
|
|
|
|
@param record: string, containing the record's D-Bus object path.
|
|
|
|
"""
|
|
record_proxy = self._get_record_proxy(record)
|
|
if not record_proxy:
|
|
print_message('Record "' + record + '" not found.')
|
|
return
|
|
props = record_proxy.GetProperties()
|
|
print_message('Record ' + record + ': ', newlines=1)
|
|
for key, value in props.iteritems():
|
|
print ' ' + key + ' = ' + value
|
|
print
|
|
|
|
def _create_record_data(self, record_type, params):
|
|
if record_type == 'Text':
|
|
possible_keys = [ 'Encoding', 'Language', 'Representation' ]
|
|
tag_data = { 'Type': 'Text' }
|
|
elif record_type == 'URI':
|
|
possible_keys = [ 'URI' ]
|
|
tag_data = { 'Type': 'URI' }
|
|
else:
|
|
print_message('Writing record type "' + record_type +
|
|
'" currently not supported.')
|
|
return None
|
|
for key, value in params.iteritems():
|
|
if key in possible_keys:
|
|
tag_data[key] = value
|
|
return tag_data
|
|
|
|
@handle_errors
|
|
def write_tag(self, tag, record_type, params):
|
|
"""
|
|
Writes an NDEF record to the given tag.
|
|
|
|
@param tag: string, containing the tag's D-Bus object path.
|
|
@param record_type: The type of the record, e.g. Text or URI.
|
|
@param params: dictionary, containing the parameters of the NDEF.
|
|
|
|
"""
|
|
tag_data = self._create_record_data(record_type, params)
|
|
if not tag_data:
|
|
return
|
|
tag_proxy = self._get_tag_proxy(tag)
|
|
if not tag_proxy:
|
|
print_message('Tag "' + tag + '" not found.')
|
|
return
|
|
tag_proxy.Write(tag_data)
|
|
print_message('Tag written!')
|
|
|
|
@handle_errors
|
|
def push_to_device(self, device, record_type, params):
|
|
"""
|
|
Pushes an NDEF record to the given device.
|
|
|
|
@param device: string, containing the device's D-Bus object path.
|
|
@param record_type: The type of the record, e.g. Text or URI.
|
|
@param params: dictionary, containing the parameters of the NDEF.
|
|
|
|
"""
|
|
record_data = self._create_record_data(record_type, params)
|
|
if not record_data:
|
|
return
|
|
device_proxy = self._get_device_proxy(device)
|
|
if not device_proxy:
|
|
print_message('Device "' + device + '" not found.')
|
|
return
|
|
device_proxy.Push(record_data)
|
|
print_message('NDEF pushed to device!')
|
|
|
|
|
|
class NfcConsole(cmd.Cmd):
|
|
"""
|
|
Interactive console to interact with the NFC daemon.
|
|
|
|
"""
|
|
def __init__(self):
|
|
cmd.Cmd.__init__(self)
|
|
self.prompt = PROMPT
|
|
|
|
def begin(self):
|
|
"""
|
|
Starts the interactive shell.
|
|
|
|
"""
|
|
print_message('NFC console! Run "help" for a list of commands.',
|
|
newlines=1)
|
|
self._nfc_client = NfcClient()
|
|
self._nfc_client.begin()
|
|
self.cmdloop()
|
|
|
|
def can_exit(self):
|
|
"""Override"""
|
|
return True
|
|
|
|
def do_initialize(self, args):
|
|
"""Handles "initialize"."""
|
|
if args:
|
|
print_message('Command "initialize" expects no arguments.')
|
|
return
|
|
self._nfc_client.restart()
|
|
|
|
def help_initialize(self):
|
|
"""Prints the help message for "initialize"."""
|
|
print_message('Initializes the neard D-Bus client. This can be '
|
|
'run many times to restart the client in case of '
|
|
'neard failures or crashes.')
|
|
|
|
def do_adapters(self, args):
|
|
"""Handles "adapters"."""
|
|
if args:
|
|
print_message('Command "adapters" expects no arguments.')
|
|
return
|
|
self._nfc_client.show_adapters()
|
|
|
|
def help_adapters(self):
|
|
"""Prints the help message for "adapters"."""
|
|
print_message('Displays the D-Bus object paths of the available '
|
|
'adapter objects.')
|
|
|
|
def do_adapter_status(self, args):
|
|
"""Handles "adapter_status"."""
|
|
args = args.strip().split(' ')
|
|
if len(args) != 1 or not args[0]:
|
|
print_message('Usage: adapter_status <adapter>')
|
|
return
|
|
self._nfc_client.print_adapter_status(NEARD_PATH + args[0])
|
|
|
|
def help_adapter_status(self):
|
|
"""Prints the help message for "adapter_status"."""
|
|
print_message('Returns the properties of the given NFC adapter.\n\n'
|
|
' Ex: "adapter_status nfc0"')
|
|
|
|
def do_enable_adapter(self, args):
|
|
"""Handles "enable_adapter"."""
|
|
args = args.strip().split(' ')
|
|
if len(args) != 1 or not args[0]:
|
|
print_message('Usage: enable_adapter <adapter>')
|
|
return
|
|
self._nfc_client.set_powered(NEARD_PATH + args[0], True)
|
|
|
|
def help_enable_adapter(self):
|
|
"""Prints the help message for "enable_adapter"."""
|
|
print_message('Powers up the adapter. Ex: "enable_adapter nfc0"')
|
|
|
|
def do_disable_adapter(self, args):
|
|
"""Handles "disable_adapter"."""
|
|
args = args.strip().split(' ')
|
|
if len(args) != 1 or not args[0]:
|
|
print_message('Usage: disable_adapter <adapter>')
|
|
return
|
|
self._nfc_client.set_powered(NEARD_PATH + args[0], False)
|
|
|
|
def help_disable_adapter(self):
|
|
"""Prints the help message for "disable_adapter"."""
|
|
print_message('Powers down the adapter. Ex: "disable_adapter nfc0"')
|
|
|
|
def do_start_poll(self, args):
|
|
"""Handles "start_poll"."""
|
|
args = args.strip().split(' ')
|
|
if len(args) != 1 or not args[0]:
|
|
print_message('Usage: start_poll <adapter>')
|
|
return
|
|
self._nfc_client.start_polling(NEARD_PATH + args[0])
|
|
|
|
def help_start_poll(self):
|
|
"""Prints the help message for "start_poll"."""
|
|
print_message('Initiates a poll loop.\n\n Ex: "start_poll nfc0"')
|
|
|
|
def do_stop_poll(self, args):
|
|
"""Handles "stop_poll"."""
|
|
args = args.split(' ')
|
|
if len(args) != 1 or not args[0]:
|
|
print_message('Usage: stop_poll <adapter>')
|
|
return
|
|
self._nfc_client.stop_polling(NEARD_PATH + args[0])
|
|
|
|
def help_stop_poll(self):
|
|
"""Prints the help message for "stop_poll"."""
|
|
print_message('Stops a poll loop.\n\n Ex: "stop_poll nfc0"')
|
|
|
|
def do_read_tag(self, args):
|
|
"""Handles "read_tag"."""
|
|
args = args.strip().split(' ')
|
|
if len(args) != 1 or not args[0]:
|
|
print_message('Usage read_tag <tag>')
|
|
return
|
|
self._nfc_client.show_tag_data(NEARD_PATH + args[0])
|
|
|
|
def help_read_tag(self):
|
|
"""Prints the help message for "read_tag"."""
|
|
print_message('Reads the contents of a tag. Ex: read_tag nfc0/tag0')
|
|
|
|
def _parse_record_args(self, record_type, args):
|
|
if record_type == 'Text':
|
|
if len(args) < 5:
|
|
print_message('Usage: write_tag <tag> Text <encoding> '
|
|
'<language> <representation>')
|
|
return None
|
|
if args[2] not in [ 'UTF-8', 'UTF-16' ]:
|
|
print_message('Encoding must be one of "UTF-8" or "UTF-16".')
|
|
return None
|
|
return {
|
|
'Encoding': args[2],
|
|
'Language': args[3],
|
|
'Representation': ' '.join(args[4:])
|
|
}
|
|
if record_type == 'URI':
|
|
if len(args) != 3:
|
|
print_message('Usage: write_tag <tag> URI <uri>')
|
|
return None
|
|
return {
|
|
'URI': args[2]
|
|
}
|
|
print_message('Only types "Text" and "URI" are supported by this '
|
|
'script.')
|
|
return None
|
|
|
|
def do_write_tag(self, args):
|
|
"""Handles "write_tag"."""
|
|
args = args.strip().split(' ')
|
|
if len(args) < 3:
|
|
print_message('Usage: write_tag <tag> [params]')
|
|
return
|
|
record_type = args[1]
|
|
params = self._parse_record_args(record_type, args)
|
|
if not params:
|
|
return
|
|
self._nfc_client.write_tag(NEARD_PATH + args[0],
|
|
record_type, params)
|
|
|
|
def help_write_tag(self):
|
|
"""Prints the help message for "write_tag"."""
|
|
print_message('Writes the given data to a tag. Usage:\n'
|
|
' write_tag <tag> Text <encoding> <language> '
|
|
'<representation>\n write_tag <tag> URI <uri>')
|
|
|
|
def do_read_device(self, args):
|
|
"""Handles "read_device"."""
|
|
args = args.strip().split(' ')
|
|
if len(args) != 1 or not args[0]:
|
|
print_message('Usage read_device <device>')
|
|
return
|
|
self._nfc_client.show_device_data(NEARD_PATH + args[0])
|
|
|
|
def help_read_device(self):
|
|
"""Prints the help message for "read_device"."""
|
|
print_message('Reads the contents of a device. Ex: read_device '
|
|
'nfc0/device0')
|
|
|
|
def do_push_to_device(self, args):
|
|
"""Handles "push_to_device"."""
|
|
args = args.strip().split(' ')
|
|
if len(args) < 3:
|
|
print_message('Usage: push_to_device <device> [params]')
|
|
return
|
|
record_type = args[1]
|
|
params = self._parse_record_args(record_type, args)
|
|
if not params:
|
|
return
|
|
self._nfc_client.push_to_device(NEARD_PATH + args[0],
|
|
record_type, params)
|
|
|
|
def help_push_to_device(self):
|
|
"""Prints the help message for "push_to_device"."""
|
|
print_message('Pushes the given data to a device. Usage:\n'
|
|
' push_to_device <device> Text <encoding> <language> '
|
|
'<representation>\n push_to_device <device> URI <uri>')
|
|
|
|
def do_exit(self, args):
|
|
"""
|
|
Handles the 'exit' command.
|
|
|
|
@param args: Arguments to the command. Unused.
|
|
|
|
"""
|
|
if args:
|
|
print_message('Command "exit" expects no arguments.')
|
|
return
|
|
resp = raw_input('Are you sure? (yes/no): ')
|
|
if resp == 'yes':
|
|
print_message('Goodbye!')
|
|
self._nfc_client.end()
|
|
return True
|
|
if resp != 'no':
|
|
print_message('Did not understand: ' + resp)
|
|
return False
|
|
|
|
def help_exit(self):
|
|
"""Handles the 'help exit' command."""
|
|
print_message('Exits the console.')
|
|
|
|
do_EOF = do_exit
|
|
help_EOF = help_exit
|
|
|
|
|
|
def main():
|
|
"""Main function."""
|
|
NfcConsole().begin()
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|