From 1f96fde1e2003eacef6c04c80ce29f6b41b5de4c Mon Sep 17 00:00:00 2001 From: John Klug Date: Tue, 9 Jan 2018 10:55:32 -0600 Subject: Start of rfcomm python solution --- recipes-connectivity/bluez/bluez5.inc | 8 +- recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py | 400 +++++++++++++++++++++ 2 files changed, 406 insertions(+), 2 deletions(-) create mode 100644 recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py (limited to 'recipes-connectivity/bluez') diff --git a/recipes-connectivity/bluez/bluez5.inc b/recipes-connectivity/bluez/bluez5.inc index b7ca007..844f38b 100644 --- a/recipes-connectivity/bluez/bluez5.inc +++ b/recipes-connectivity/bluez/bluez5.inc @@ -10,6 +10,7 @@ LIC_FILES_CHKSUM = "file://COPYING;md5=12f884d2ae1ff87c09e5b7ccc2c4ca7e \ DEPENDS = "udev libusb dbus-glib glib-2.0 libcheck readline" RDEPENDS_${PN}-pand += "bash" DEPENDS_${PN}-pand += "python-dbus python-logging python-syslog" +DEPENDS_${PN}-rfcomm += "python-dbus python-pygobject" PROVIDES += "bluez-hcidump" RPROVIDES_${PN} += "bluez-hcidump" @@ -31,6 +32,7 @@ SRC_URI = "\ file://bt-pan/init \ file://bt-pan/default \ file://bt-pan/bt-pan \ + file://rfcomm/rfcomm.py \ " S = "${WORKDIR}/bluez-${PV}" @@ -64,6 +66,7 @@ do_install_append() { install -m 0755 ${WORKDIR}/bt-pan/init ${D}${INIT_D_DIR}/bt-pan install -d ${DBTEXEC} install -m 0755 ${WORKDIR}/bt-pan/bt-pan ${DBTEXEC} + install -m 0755 ${WORKDIR}/rfcomm/rfcomm.py ${DBTEXEC}/rfcomm install -d ${D}${sysconfdir}/bluetooth/ if [ -f ${S}/profiles/audio/audio.conf ]; then @@ -96,7 +99,7 @@ do_install_append() { } ALLOW_EMPTY_libasound-module-bluez = "1" -PACKAGES =+ "libasound-module-bluez ${PN}-testtools ${PN}-obex ${PN}-noinst-tools ${PN}-pand" +PACKAGES =+ "libasound-module-bluez ${PN}-testtools ${PN}-obex ${PN}-noinst-tools ${PN}-pand ${PN}-rfcomm" FILES_libasound-module-bluez = "${libdir}/alsa-lib/lib*.so ${datadir}/alsa" FILES_${PN} += "${libdir}/bluetooth/plugins/*.so ${systemd_unitdir}/ ${datadir}/dbus-1" @@ -111,7 +114,8 @@ FILES_${PN}-obex = "${libexecdir}/bluetooth/obexd \ ${datadir}/dbus-1/services/org.bluez.obex.service \ " -FILES_${PN}-pand = "${libexecdir}/bluetooth/bluetooth/bt-pan ${sysconfdir}/default/bt-pan ${sysconfdir}/init.d/bt-pan" +FILES_${PN}-pand = "${libexecdir}/bluetooth/bt-pan ${sysconfdir}/default/bt-pan ${sysconfdir}/init.d/bt-pan" +FILES_${PN}-rfcomm = "${libexecdir}/bluetooth/rfcomm" CONFFILES_${PN}-pand = "${sysconfdir}/default/bt-pan" SYSTEMD_SERVICE_${PN}-obex = "obex.service" diff --git a/recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py b/recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py new file mode 100644 index 0000000..294268b --- /dev/null +++ b/recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py @@ -0,0 +1,400 @@ +#!/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 -- cgit v1.2.3