#!/usr/bin/env python2 import thread import os import dbus import dbus.service import dbus.mainloop.glib from gi.repository import GObject import sys import time import threading import socket import logging import logging.handlers import syslog import grp import stat global lg global opts global RFCOMMDIR RFCOMMDIR = '/run/rfcomm' global TTY_GID # Group-ID number of the TTY group global doterm doterm = True # 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__) #SerialPortProfile = '00001101-0000-1000-8000-00805f9b34fb' # 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 class Profile(dbus.service.Object): fd = -1 readThread = None path = None io_id = -1 io_id2 = -1 io_pty_master = -1 io_pty_slave = -1 myPath = None @dbus.service.method('org.bluez.Profile1', in_signature='', out_signature='') def Release(self): print('Release') log.info('Release') mainloop.quit() @dbus.service.method('org.bluez.Profile1', in_signature='oha{sv}', out_signature='') def NewConnection(self, path, fd, properties): dbus.mainloop.glib.threads_init() self.fd = fd.take() # Extract File Descriptor from dbus UnixFD class. self.path = path print('NewConnection(%s, %s:%d)' % (path,type(fd).__name__,self.fd)) lg.info('NewConnection(%s, %d)' % (path, self.fd)) if opts.pseudoterminal: (self.io_pty_master,self.io_pty_slave) = os.openpty() print('Acquired pseudoterminal master and slave fd: (%d %d)' % (self.io_pty_master,self.io_pty_slave)) slavestat = os.fstat(self.io_pty_slave) lg.debug('pseudoterminal major and minor: (%d,%d)' % (os.major(slavestat.st_rdev),os.minor(slavestat.st_rdev))) if not os.path.isdir(RFCOMMDIR): lg.debug('Before mkdir: RFCOMMDIR %s' % (RFCOMMDIR)) os.mkdir(RFCOMMDIR,0755) # Complete directory does not exist test address = os.path.basename(path) lg.debug('Address %s' % (address)) self.myPath = RFCOMMDIR + '/' + address lg.debug('termPath %s' % (self.myPath)) if os.path.lexists(self.myPath): lg.debug('termPath %s already exists so remove it' % (self.myPath)) os.remove(self.myPath) print('os.mknod(%s,0%o,0%o)' % (self.myPath,0660,slavestat.st_rdev)) old = os.umask(002) try: os.mknod(self.myPath,0660 | stat.S_IFCHR,slavestat.st_rdev) except Exception as e: print '%s' % (e) lg.error('%s' % (e)) return False os.umask(old) os.chown(self.myPath,0,TTY_GID) # Completed pseudoterminal case to create device and node # Following code shows noting (why?) for key in properties.keys(): if key == 'Version' or key == 'Features': print(' %s = 0x%04x' % (key, properties[key])) log.debug(' %s = 0x%04x' % (key, properties[key])) else: print(' %s = %s' % (key, properties[key])) log.info(' %s = %s' % (key, properties[key])) # + For loopback, we only monitor the RFCOMM line (self.fd). # + For interactive (not pseudoterminal or loopback option, # we monitor the stdin (0) and the RFCOMM line for input # + For pseudoterminal, we monitor input on the RFCOMM line, # and the slave pseudoterminal. sys.stdout.flush() if not opts.loopback: if opts.pseudoterminal: local_fd = self.io_pty_master else: local_fd = 0 self.io_id2 = GObject.io_add_watch(local_fd, GObject.PRIORITY_DEFAULT, GObject.IO_IN | GObject.IO_PRI, self.io_term) if doterm: os.write(1,'TTY> ') elif opts.pseudoterminal: os.write(io_pty_master,'TTY> ') self.io_id = GObject.io_add_watch(self.fd, GObject.PRIORITY_DEFAULT, GObject.IO_IN | GObject.IO_PRI, self.io_cb) lg.debug('io_id(remote input) = %d io_id2(local input) = %d' % (self.io_id,self.io_id2)) def io_cb(self, fd, conditions): # Read from remote data = None try: data = os.read(fd, 1024) except: return True if opts.loopback: if data: start = 0 remain = len(data) while remain: try: result = os.write(fd,len(data)) except: lg.debut return True if remain != result: remain -= result data = data[-result:] return True if data and len(data) > 0: final = data[-1] if data[-1] == '\n': date = data[:-1] if opts.pseudoterminal: # Write to master os.write(io_pty_master,'\r\n'+data.decode('ascii')+'\r\nTTY>') else: print('\n'+data.decode('ascii')) os.write(1,'TTY> ') return True def io_term(self, fd0, conditions): # Read from local (not used for loopback) data = None data = os.read(fd0, 1024) if not data: # No Data == EOF self.RequestDisconnection(self.path) return True #for character in data: # print character, character.encode('hex') try: os.write(self.fd,data) except Exception as e: print '%s' % (e) lg.error('%s' % (e)) self.RequestDisconnection(self.path) return True if opts.pseudoterminal: os.write(fd0,'TTY> ') else: os.write(1,'TTY> ') return True @dbus.service.method('org.bluez.Profile1', in_signature='o', out_signature='') def RequestDisconnection(self, path): print('RequestDisconnection(%s)' % (path)) lg.info('RequestDisconnection(%s)' % (path)) if self.fd != -1: lg.debug('closing fd: %s',self.fd) s = socket.fromfd(self.fd,socket.AF_INET,socket.SOCK_STREAM) result = s.shutdown(socket.SHUT_RDWR) lg.debug('After shutdown fd: %s %s %s',self.fd,' result:',result) result = os.close(self.fd) lg.debug('After closing fd: %s %s %s',self.fd,' result:',result) self.fd = -1 if self.io_id != -1: lg.debug('remove id: %s',self.io_id) rmv = GObject.source_remove(self.io_id) # lg.debug('removed id: %s %s %s',self.io_id,'result: ',rmv) self.io_id = -1 if self.io_id2 != -1: lg.debug('closing id2: %s',self.io_id2) rmv = GObject.source_remove(self.io_id2) lg.debug('removed id2: %s %s %s',self.io_id2,'result: ',rmv) self.io_id2 = -1 if opts.pseudoterminal: if self.io_pty_slave != -1: os.close(self.io_pty_slave) if self.io_pty_master != -1: os.close(self.io_pty_master) if self.myPath and os.path.lexists(self.myPath): os.remove(self.myPath) self.myPath = None if __name__ == '__main__': import argparse TTY_GID = grp.getgrnam('tty').gr_gid # Set up logging initially info and above lg = flog(myscript,'daemon','info') parser = argparse.ArgumentParser( description='BlueZ RFCOMM server.') parser.add_argument('-u', '--uuid', metavar='uuid_or_shortcut', default='spp', help='Service UUID to use. Can be either full UUID' ' or one of the shortcuts: gn, panu, nap. Default: %(default)s.') parser.add_argument('--pseudoterminal', action='store_true', help='Create a pseudoterminal and put slave in /run/rfcomm' ' Suitable for background operation.') parser.add_argument('--loopback', action='store_true', help='Echo data for testing (exclusive with pseudoterminal)') parser.add_argument('--debug', action='store_true', help='Verbose operation mode.') opts = parser.parse_args() if opts.debug: lg.setThreshold('debug') if opts.pseudoterminal and opts.loopback: msg = 'Cannot have both pseudoterminal and loopback option' print msg lg.error(msg) exit(1) if not opts.pseudoterminal and not opts.loopback: doterm = True dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) bus = dbus.SystemBus() manager = dbus.Interface(bus.get_object('org.bluez', '/org/bluez'), 'org.bluez.ProfileManager1') mainloop = GObject.MainLoop() profile_path = '/foo/bar/profile' SPP_opts = { 'AutoConnect': True, 'Role': 'server', 'Name': 'SerialPort' } print('Starting Serial Port Profile...') lg.info('Starting Serial Port Profile...') profile = Profile(bus, profile_path) try: manager.RegisterProfile(profile_path, opts.uuid, SPP_opts) except dbus.exceptions.DBusException as inst: print 'dbus exception:',inst._dbus_error_name lg.error('dbus exception: %s',inst._dbus_error_name) if inst._dbus_error_name == 'org.freedesktop.DBus.Error.AccessDenied': print 'Try running as root' exit(1) dbus.mainloop.glib.threads_init() try: mainloop.run() except KeyboardInterrupt: pass finally: print '\nSerial Port Profile: Goodbye' lg.info('Serial Port Profile: Goodbye') exit