blob: f844abe69572755bc5f0eebbd825189fcaa4dedb [file] [log] [blame]
#!/usr/bin/env python
# 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()