summaryrefslogtreecommitdiff
path: root/recipes-connectivity/bluez
diff options
context:
space:
mode:
Diffstat (limited to 'recipes-connectivity/bluez')
-rw-r--r--recipes-connectivity/bluez/bluez5.inc8
-rw-r--r--recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py400
2 files changed, 406 insertions, 2 deletions
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