summaryrefslogtreecommitdiff
path: root/recipes-connectivity/bluez/bluez5/bt-pan/bt-pan
diff options
context:
space:
mode:
authorJohn Klug <john.klug@multitech.com>2017-10-18 17:46:54 -0500
committerJohn Klug <john.klug@multitech.com>2017-10-18 17:46:54 -0500
commit84591cf1f33e695add589a882653b86504f2d1e0 (patch)
tree028a820ff621c03f952c855ea32d7808b630d9fc /recipes-connectivity/bluez/bluez5/bt-pan/bt-pan
parent603994e38d6ff3a319fcc269c1b235fcfd53e14e (diff)
downloadmeta-mlinux-84591cf1f33e695add589a882653b86504f2d1e0.tar.gz
meta-mlinux-84591cf1f33e695add589a882653b86504f2d1e0.tar.bz2
meta-mlinux-84591cf1f33e695add589a882653b86504f2d1e0.zip
bt-pan daemon for bluetooth pan
Diffstat (limited to 'recipes-connectivity/bluez/bluez5/bt-pan/bt-pan')
-rwxr-xr-xrecipes-connectivity/bluez/bluez5/bt-pan/bt-pan425
1 files changed, 425 insertions, 0 deletions
diff --git a/recipes-connectivity/bluez/bluez5/bt-pan/bt-pan b/recipes-connectivity/bluez/bluez5/bt-pan/bt-pan
new file mode 100755
index 0000000..987ecaa
--- /dev/null
+++ b/recipes-connectivity/bluez/bluez5/bt-pan/bt-pan
@@ -0,0 +1,425 @@
+#!/usr/bin/env python2
+from __future__ import absolute_import, print_function
+
+import os, sys, time, types, subprocess, signal
+
+import dbus
+import logging
+import logging.handlers
+
+# cgitb is in python-misc and requires python-pkgutil and python-pydoc
+# It is very usefull for analyzing exceptions.
+# import cgitb
+
+# who am i
+myscript = os.path.basename(__file__)
+
+# Log formating class
+class flog:
+ # priority strings to be used
+ # with the __init__ function
+ priorities = {
+ 'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'warn': logging.WARNING,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ 'critical': logging.CRITICAL,
+ }
+
+ def __init__(self,myscript,facility,priority):
+ """
+ Initialize for logging
+
+ :param myscript: The name of the python program script
+ :param facility: The syslog facility, such as daemon or user
+ :param priority: The minimum priority to be printed to the log
+ :returns: Nothing
+ :raises TBD: logging class errors.
+ """
+ name_len = str(len(myscript))
+ self.myscript = myscript
+ self.log = logging.getLogger(myscript)
+ self.handler = logging.handlers.SysLogHandler(address=('/dev/log'),facility=facility)
+ self.default_fmt = ' %(levelname)-9s %(name)-' + name_len + 's %(message)s'
+ self.verbose_fmt1 = ' %(levelname)-9s %(name)-' + name_len + 's %(threadName)-14s '
+ self.verbose_fmt2 = ' %(message)s'
+ formatter = logging.Formatter(self.default_fmt)
+ self.handler.setFormatter(formatter)
+ self.log.setLevel(self.priorities[priority]) # Minimum infolevel to log
+ self.log.addHandler(self.handler)
+ self.handler.createLock()
+
+ def __default(self,func,*args):
+ self.handler.acquire()
+ formatter = logging.Formatter(self.default_fmt)
+ self.handler.setFormatter(formatter)
+ func(*args)
+ self.handler.release()
+
+ def setThreshold(self,threshold):
+ """
+ Change the syslog priority threshold
+
+ :param priority: Character string corresponding to the threshold
+ """
+ self.handler.acquire()
+ self.log.setLevel(self.priorities[threshold]) # Minimum infolevel to log
+ self.handler.release()
+
+ def critical(self,*args):
+ """
+ Prints a variable argument list at critical priority
+
+ :returns: logging result
+ """
+ self.__default(self.log.critical,*args)
+
+ def error(self,*args):
+ """
+ Prints a variable argument list at error priority
+
+ :returns: logging result
+ """
+ self.__default(self.log.error,*args)
+
+ def warning(self,*args):
+ """
+ Prints a variable argument list at warning priority
+
+ :returns: logging result
+ """
+ self.__default(self.log.warning,*args)
+
+ # Python has no notice level!
+
+ def info(self,*args):
+ """
+ Prints a variable argument list at info priority
+
+ :returns: logging result
+ """
+ self.__default(self.log.info,*args)
+
+ def debug(self,*args):
+ """
+ Prints a variable argument list at debug priority
+
+ Printing debug includes function name and line
+ number.
+
+ :returns: logging result
+ """
+ caller_frame = sys._getframe().f_back
+ callerfunc = caller_frame.f_code.co_name + '@' + str(caller_frame.f_lineno);
+ callerfunc = callerfunc.ljust(16)
+ self.handler.acquire()
+ log = logging.getLogger(self.myscript)
+ formatter = logging.Formatter(self.verbose_fmt1+callerfunc+self.verbose_fmt2)
+ self.handler.setFormatter(formatter)
+ log.debug(*args)
+ self.handler.release()
+
+# End of log handler
+
+### ~bluezutils.py
+
+iface_base = 'org.bluez'
+iface_dev = '{}.Device1'.format(iface_base)
+iface_adapter = '{}.Adapter1'.format(iface_base)
+iface_props = 'org.freedesktop.DBus.Properties'
+global lg
+
+# Will this write to syslog?????
+class BTError(Exception): pass
+
+def get_bus():
+ bus = getattr(get_bus, 'cached_obj', None)
+ if not bus: bus = get_bus.cached_obj = dbus.SystemBus()
+ return bus
+
+def get_manager():
+ manager = getattr(get_manager, 'cached_obj', None)
+ if not manager:
+ try:
+ manager = get_manager.cached_obj = dbus.Interface(
+ get_bus().get_object(iface_base, '/'),
+ 'org.freedesktop.DBus.ObjectManager' )
+ except dbus.exceptions.DBusException as e:
+ s=""
+ try:
+ s = e.__dict__["_dbus_error_name"]
+ except KeyError:
+ pass
+ unk = 'org.freedesktop.DBus.Error.ServiceUnknown'
+ cexit = 'org.freedesktop.DBus.Error.Spawn.ChildExited'
+ if (s == unk) or (s == cexit):
+ msg = 'Is bluetoothd running? Bluetooth tree missing from DBUS'
+ lg.error(msg)
+ print(msg)
+ exit(1)
+ else:
+ raise
+ return manager
+
+def prop_get(obj, k, iface=None):
+ if iface is None: iface = obj.dbus_interface
+ return obj.Get(iface, k, dbus_interface=iface_props)
+def prop_set(obj, k, v, iface=None):
+ if iface is None: iface = obj.dbus_interface
+ return obj.Set(iface, k, v, dbus_interface=iface_props)
+
+msg=''
+def find_adapter(pattern=None):
+ try:
+ adapter = find_adapter_in_objects(get_manager().GetManagedObjects(), pattern)
+
+ # DBusException
+ # Original code:
+ # template = "An exception of type {0} occurred. Arguments:\n{1!r}"
+ # message = template.format(type(ex).__name__, ex.args)
+ # print message
+ # dbus.exceptions.DBusException:
+ # org.freedesktop.DBus.Error.AccessDenied:
+ except dbus.exceptions.DBusException as e:
+ s=""
+ try:
+ s = e.__dict__["_dbus_error_name"]
+ except KeyError:
+ pass
+
+ if "org.freedesktop.DBus.Error.AccessDenied" == s:
+ rot="You do not have sufficient privilege to run bt-pan"
+ print(rot)
+ lg.error(rot)
+ exit(1)
+ else:
+ raise
+ return adapter
+
+def find_adapter_in_objects(objects, pattern=None):
+ bus, obj = get_bus(), None
+ for path, ifaces in objects.iteritems():
+ adapter = ifaces.get(iface_adapter)
+ if adapter is None: continue
+ if not pattern or pattern == adapter['Address'] or path.endswith(pattern):
+ obj = bus.get_object(iface_base, path)
+ yield dbus.Interface(obj, iface_adapter)
+ if obj is None:
+ msg = 'Bluetooth adapter not found'
+ lg.error(msg)
+ raise BTError(msg)
+
+def find_device(device_address, adapter_pattern=None):
+ return find_device_in_objects(get_manager().GetManagedObjects(), device_address, adapter_pattern)
+
+def find_device_in_objects(objects, device_address, adapter_pattern=None):
+ bus = get_bus()
+ path_prefix = ''
+ if adapter_pattern:
+ if not isinstance(adapter_pattern, types.StringTypes): adapter = adapter_pattern
+ else: adapter = find_adapter_in_objects(objects, adapter_pattern)
+ path_prefix = adapter.object_path
+ for path, ifaces in objects.iteritems():
+ device = ifaces.get(iface_dev)
+ if device is None: continue
+ if device['Address'] == device_address and path.startswith(path_prefix):
+ obj = bus.get_object(iface_base, path)
+ return dbus.Interface(obj, iface_dev)
+ msg = 'Bluetooth device not found'
+ lg.error(msg)
+ raise BTError(msg)
+
+pidPath = ""
+
+def writePidFile(device):
+ global pidPath
+ pid = str(os.getpid())
+ pidPath = '/run/bt-pan.' + device + '.pid'
+ f = open(pidPath, 'w')
+ f.write(pid)
+ f.close()
+
+def hexdump(string):
+ return ":".join("{:02x}".format(ord(c)) for c in string)
+
+### bt-pan
+
+def main(args=None):
+ import argparse
+ global lg
+
+ # Set up logging initially info and above
+ lg = flog(myscript,'daemon','info')
+
+ # cgitb.enable(format='text')
+
+ parser = argparse.ArgumentParser(
+ description='BlueZ bluetooth PAN network server/client.')
+
+ parser.add_argument('-i', '--device', metavar='local-addr/pattern',
+ help='Local device address/pattern to use (if not default).')
+ parser.add_argument('-a', '--device-all', action='store_true',
+ help='Use all local hci devices, not just default one.'
+ ' Only valid with "server" mode, mutually exclusive with --device option.')
+ parser.add_argument('-u', '--uuid',
+ metavar='uuid_or_shortcut', default='nap',
+ help='Service UUID to use. Can be either full UUID'
+ ' or one of the shortcuts: gn, panu, nap. Default: %(default)s.')
+ parser.add_argument('--systemd', action='store_true',
+ help='Use systemd service'
+ ' notification/watchdog mechanisms in daemon modes, if available.')
+ parser.add_argument('--debug',
+ action='store_true', help='Verbose operation mode.')
+
+ cmds = parser.add_subparsers( dest='call',
+ title='Supported operations (have their own suboptions as well)' )
+
+ cmd = cmds.add_parser('server', help='Run infinitely as a NAP network server.')
+ cmd.add_argument('iface_name',
+ help='Bridge interface name to which each link will be added by bluez.'
+ ' It must be created and configured before starting the server.')
+
+ cmd = cmds.add_parser('client', help='Connect to a PAN network.')
+ cmd.add_argument('remote_addr', help='Remote device address to connect to.')
+ cmd.add_argument('-d', '--disconnect', action='store_true',
+ help='Instead of connecting (default action), disconnect'
+ ' (if connected) and exit. Overrides all other options for this command.')
+ cmd.add_argument('-w', '--wait', action='store_true',
+ help='Go into an endless wait-loop after connection, terminating it on exit.')
+ cmd.add_argument('-c', '--if-not-connected', action='store_true',
+ help='Dont raise error if connection is already established.')
+ cmd.add_argument('-r', '--reconnect', action='store_true',
+ help='Force reconnection if some connection is already established.')
+
+ opts = parser.parse_args()
+
+ if opts.debug:
+ lg.setThreshold('debug')
+
+ if not opts.device_all: devs = [next(iter(find_adapter(opts.device)))]
+ else:
+ if opts.call != 'server':
+ parser.error('--device-all option is only valid with "server" mode.')
+ devs = list(find_adapter())
+ devs = dict((prop_get(dev, 'Address'), dev) for dev in devs)
+ for dev_addr, dev in devs.viewitems():
+ prop_set(dev, 'Powered', True)
+ lg.debug('Using local device (addr: %s): %s', dev_addr, dev.object_path)
+
+ wait_iter_noop = 3600
+ if opts.systemd:
+ from systemd import daemon
+ def wait_iter():
+ if not wait_iter.sd_ready:
+ daemon.notify('READY=1')
+ daemon.notify('STATUS=Running in {} mode...'.format(opts.call))
+ wait_iter.sd_ready = True
+ time.sleep(wait_iter.timeout)
+ if wait_iter.sd_wdt: daemon.notify('WATCHDOG=1')
+ wd_pid, wd_usec = (os.environ.get(k) for k in ['WATCHDOG_PID', 'WATCHDOG_USEC'])
+ if wd_pid and wd_pid.isdigit() and int(wd_pid) == os.getpid():
+ wd_interval = float(wd_usec) / 2e6 # half of interval in seconds
+ assert wd_interval > 0, wd_interval
+ else: wd_interval = None
+ if wd_interval:
+ lg.debug('Initializing systemd watchdog pinger with interval: %ss', wd_interval)
+ wait_iter.sd_wdt, wait_iter.timeout = True, min(wd_interval, wait_iter_noop)
+ else: wait_iter.sd_wdt, wait_iter.timeout = False, wait_iter_noop
+ wait_iter.sd_ready = False
+ else: wait_iter = lambda: time.sleep(wait_iter_noop)
+ signal.signal(signal.SIGTERM, lambda sig,frm: sys.exit(0))
+
+
+ if opts.call == 'server':
+ brctl = subprocess.Popen(
+ ['brctl', 'show', opts.iface_name],
+ stdout=open(os.devnull, 'wb'), stderr=subprocess.PIPE )
+ brctl_stderr = brctl.stderr.read()
+ p = lambda fmt='',*a,**k: print(fmt.format(*a,**k), file=sys.stderr)
+ writePidFile(opts.iface_name)
+ if brctl.wait() or brctl_stderr:
+ p = lambda fmt='',*a,**k: print(fmt.format(*a,**k), file=sys.stderr)
+ p('brctl check failed for interface: {}', opts.iface_name)
+ lg.error('brctl check failed for interface (missing?): {}', opts.iface_name)
+ p()
+ p('Bridge interface must be added and configured before starting server, e.g. with:')
+ p(' brctl addbr bnep-bridge')
+ p(' brctl setfd bnep-bridge 0')
+ p(' brctl stp bnep-bridge off')
+ p(' ip addr add 10.101.225.84/24 dev bnep-bridge')
+ p(' ip link set bnep-bridge up')
+ return 1
+
+ servers = list()
+ try:
+ for dev_addr, dev in devs.viewitems():
+ server = dbus.Interface(dev, 'org.bluez.NetworkServer1')
+ server.Unregister(opts.uuid) # in case already registered
+ server.Register(opts.uuid, opts.iface_name)
+ servers.append(server)
+ lg.debug( 'Registered uuid %r with'
+ ' bridge/dev: %s / %s', opts.uuid, opts.iface_name, dev_addr )
+ while True: wait_iter()
+ except KeyboardInterrupt: pass
+ finally:
+ if servers:
+ for server in servers: server.Unregister(opts.uuid)
+ lg.debug('Unregistered server uuids')
+
+
+ elif opts.call == 'client':
+ dev_remote = find_device(opts.remote_addr, devs.values()[0])
+ lg.debug( 'Using remote device (addr: %s): %s',
+ prop_get(dev_remote, 'Address'), dev_remote.object_path )
+ try: dev_remote.ConnectProfile(opts.uuid)
+ except: pass # no idea why it fails sometimes, but still creates dbus interface
+ net = dbus.Interface(dev_remote, 'org.bluez.Network1')
+
+ if opts.disconnect:
+ try: net.Disconnect()
+ except dbus.exceptions.DBusException as err:
+ if err.get_dbus_name() != 'org.bluez.Error.Failed': raise
+ connected = prop_get(net, 'Connected')
+ if connected: raise
+ lg.debug(
+ 'Disconnected from network'
+ ' (dev_remote: %s, addr: %s) uuid %r, by explicit command',
+ dev_remote.object_path, prop_get(dev_remote, 'Address'), opts.uuid )
+ return
+
+ for n in xrange(2):
+ try: iface = net.Connect(opts.uuid)
+ except dbus.exceptions.DBusException as err:
+ if err.get_dbus_name() != 'org.bluez.Error.Failed': raise
+ connected = prop_get(net, 'Connected')
+ if not connected: raise
+ if opts.reconnect:
+ lg.debug( 'Detected pre-established connection'
+ ' (iface: %s), reconnecting', prop_get(net, 'Interface') )
+ net.Disconnect()
+ continue
+ if not opts.if_not_connected: raise
+ else: break
+ lg.debug(
+ 'Connected to network (dev_remote: %s, addr: %s) uuid %r with iface: %s',
+ dev_remote.object_path, prop_get(dev_remote, 'Address'), opts.uuid, iface )
+
+ if opts.wait:
+ try:
+ while True: wait_iter()
+ except KeyboardInterrupt: pass
+ finally:
+ net.Disconnect()
+ lg.debug('Disconnected from network')
+
+
+ else: raise ValueError(opts.call)
+ global pidPath
+ try:
+ os.remove(pidPath)
+ except OSError:
+ pass
+ lg.debug('Finished')
+
+if __name__ == '__main__': sys.exit(main())