summaryrefslogtreecommitdiff
path: root/recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py
diff options
context:
space:
mode:
Diffstat (limited to 'recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py')
-rw-r--r--recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py447
1 files changed, 373 insertions, 74 deletions
diff --git a/recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py b/recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py
index 294268b..992de45 100644
--- a/recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py
+++ b/recipes-connectivity/bluez/bluez5/rfcomm/rfcomm.py
@@ -4,7 +4,7 @@ import os
import dbus
import dbus.service
import dbus.mainloop.glib
-from gi.repository import GObject
+from gi.repository import GObject, GLib
import sys
import time
import threading
@@ -14,23 +14,57 @@ import logging.handlers
import syslog
import grp
import stat
+import atexit
+import re
+import mmap
+import subprocess
+import threading
+import struct
+import fcntl
+import termios
+import signal
+import time
+
+# Do we need stty onlcr????
+
+#SerialPortProfile = '00001101-0000-1000-8000-00805f9b34fb'
global lg
global opts
global RFCOMMDIR
RFCOMMDIR = '/run/rfcomm'
-global TTY_GID # Group-ID number of the TTY group
-global doterm
-doterm = True
+SLAVEDIR='/dev/pts'
+BLUEZLIB='/var/lib/bluetooth'
+global TTY_GID # Group-ID number of the TTY group
+global doterm # If true, this python program is a terminal console.
+global needpseudot # Login option and pseudoterminal option
+needpseudot = False
+global terminatenow
+terminatenow = False
# 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__)
+class dopidfile(object):
+ pidPath = "/"
+
+ def writePidFile(self):
+ global pidPath
+ pid = str(os.getpid())
+ pidPath = '/run/rfcomm' + '.pid'
+ f = open(pidPath, 'w')
+ f.write(pid)
+ f.close()
+
+ def rmPidFile(self):
+ try:
+ os.remove(pidPath)
+ except OSError:
+ pass
-#SerialPortProfile = '00001101-0000-1000-8000-00805f9b34fb'
# Log formating class
class flog:
@@ -141,24 +175,121 @@ class flog:
# End of log handler
+
+# Thread to create login process, with
+# stdin, stdout, stderr matching the file descriptor
+# This is because NewConnection cannot create threads or
+# use a mutex. Workarounds are pipes, IPC semamphores,
+# IPC messaging
+class logins(object):
+ slavefd = -1
+
+ # Thread to wait on our children
+ def IgnoreWait(self,pid):
+ lg.debug("IgnoreWait: Waiting on process pid: %d" % (pid.pid))
+ pid.wait()
+ lg.debug("login terminated: %d" % (pid.pid))
+
+ def StartLogin(self,rpipe,mainloop):
+ datafd=''
+ masters = []
+ lg.debug("StartLogin enter: rpipe fd=%d" % (rpipe))
+ while 1:
+ try:
+ datafd=os.read(rpipe,8)
+ except Exception as e:
+ lg.error('os.read error: %s' % (e))
+ lg.debug('Done with StartLogin, calling quit')
+ for fd in masters:
+ os.close(fd)
+ os.kill(os.getpid(), signal.SIGINT)
+ thread.exit()
+ # Single integer.
+ (slavefd,masterfd) = struct.unpack("ii",bytearray(datafd))
+ if masterfd > 0:
+ masters.append(masterfd)
+ lg.debug("StartLogin: slavefd %d" % (slavefd))
+ if slavefd < 0:
+ lg.debug("Told to exit, so exiting StartLogin thread")
+ os.close(rpipe)
+ # We try to close all the masters, as it gets
+ # things wound down in a hurry.
+ for fd in masters:
+ lg.debug('StartLogin: Try to close fd %d' % (fd))
+ try:
+ os.close(fd)
+ except Exception as e:
+ lg.debug('StartLogin (ignore error): OK: Did not close fd: %d %s' % (fd,e))
+ sys.exc_clear()
+ os.kill(os.getpid(), signal.SIGINT)
+ thread.exit()
+ # Start login with fd, and close it.
+ Env = {'TERM': 'dumb'}
+ self.slavefd = slavefd
+ lg.debug('Popen slavefd: %d' % (slavefd))
+ try:
+ # Mar 16 14:23:35 mtcdt daemon.err ERROR rfcomm.py Popen login: global name 's' is not defined
+ pid = subprocess.Popen(['/bin/login','--'],env=Env,preexec_fn = lambda: ( os.setsid(),fcntl.ioctl(0, termios.TIOCSCTTY, 0) ),stdin=slavefd,stdout=slavefd,stderr=slavefd,close_fds=True,cwd='/')
+ lg.debug('Start IgnoreWait thread')
+ try:
+ IgnoreWaitThread = threading.Thread(target=self.IgnoreWait,args=[pid])
+ except Exception as e:
+ lg.error('IgnoreWaitThread: threading.Thread: %s' % (e))
+ try:
+ IgnoreWaitThread.start()
+ except Exception as e:
+ lg.error('IgnoreWaitThread: start: %s' % (e))
+ except Exception as e:
+ lg.error('Popen login: %s' % (e))
+ os.close(slavefd)
+
class Profile(dbus.service.Object):
fd = -1
readThread = None
path = None
io_id = -1
io_id2 = -1
+ hup_id = -1
+ hup_id2 = -1
io_pty_master = -1
io_pty_slave = -1
- myPath = None
-
+ slavePath = None
+ linkPath = None
+ w = -1
+ # True False pseudonyms for making code readable (or not!)
+ exiting = True
+ notexiting = False
+
@dbus.service.method('org.bluez.Profile1',
in_signature='',
out_signature='')
def Release(self):
- print('Release')
- log.info('Release')
+ lg.info('Release/quit')
mainloop.quit()
+ @dbus.service.method("org.bluez.Profile1",
+ in_signature="", out_signature="")
+ def Cancel(self):
+ lg.info("Cancel")
+
+ def removeLink(self,state):
+ lg.debug('removeLink: state: %r' % (state))
+ path = self.linkPath
+ lg.debug('removeLink: path %s' % (path))
+ if state == self.exiting:
+ lg.debug('Clearing out linkPath')
+ self.linkPath = None # Burn bridges, do it once.
+ if path and os.path.lexists(path):
+ try:
+ os.remove(path)
+ except Exception as e:
+ lg.error("os.remove(self.linkPath): Tried to remove %s" % (path))
+ lg.error('%s' % (e))
+
+
+
+ # New Connection is called when a new Bluetooth
+ # is established
@dbus.service.method('org.bluez.Profile1',
in_signature='oha{sv}',
out_signature='')
@@ -166,112 +297,220 @@ class Profile(dbus.service.Object):
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:
+
+ # Complete directory does not exist test
+ address = os.path.basename(self.path)
+
+ # Search for connected device by address
+ for localdir in os.listdir(BLUEZLIB):
+ fp_localdir = BLUEZLIB + '/' + localdir # Full path
+ lg.debug('Localdir: %s' % (fp_localdir))
+ if os.path.isdir(fp_localdir) and localdir.count(':') == 5:
+ lg.debug('Localdir is directory and 5 colons: %s' % (fp_localdir))
+ # We have MAC address
+ for clientdir in os.listdir(fp_localdir):
+ lg.debug('Clientdir is directory: %s' % (clientdir))
+ fp_clientdir = fp_localdir + '/' + clientdir
+ if os.path.isdir(fp_clientdir) and clientdir.count(':') == 5:
+ lg.debug('Clientdir is directory and 5 colons: %s' % (fp_clientdir))
+ infofile = fp_clientdir + '/' + 'info'
+ lg.debug('infofile: %s' % (infofile))
+ try:
+ file = open(infofile,'r')
+ lg.debug('Opened info file')
+ except:
+ continue
+ else:
+ continue
+ # Have an open file
+ s = mmap.mmap(file.fileno(),0,access=mmap.ACCESS_READ)
+ lg.debug('past mmap')
+ try:
+ cre = re.compile(br'[General][^[]*\nName=(?P<name>[^\n]*)')
+ except Exception as e:
+ lg.debug('Compile failed: %s' % (e))
+ lg.debug('past compile')
+ mname = cre.search(s)
+ lg.debug('past search')
+ try:
+ Name = mname.group('name')
+ break
+ except:
+ Name = 'unknown'
+
+ print('NewConnection(%s, %s, %s:%d)' % (path,Name,type(fd).__name__,self.fd))
+ lg.info('NewConnection(%s, %s, %s:%d)' % (path,Name,type(fd).__name__,self.fd))
+ atexit.register(self.RequestDisconnection,self.path)
+ lg.debug('Past atexit.register')
+
+ # Following code shows noting (why?)
+ lg.debug('Look for a key len(properties): %d',len(properties))
+ for key in properties.keys():
+ lg.debug('found a key!!!!!')
+ lg.debug(' %s = %s' % (key, properties[key]))
+ if key == 'Version' or key == 'Features':
+ lg.debug(' %s = 0x%04x' % (key, properties[key]))
+ lg.debug(' %s = 0x%04x' % (key, properties[key]))
+ else:
+ lg.debug(' %s = %s' % (key, properties[key]))
+ lg.info(' %s = %s' % (key, properties[key]))
+ lg.debug('Past keys, needpseudot: %s' % (needpseudot))
+
+ # Get a pseudoterminal to provide an I/O driver for
+ # a program that needs a TTY.
+ if needpseudot:
(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)))
+ self.minor = os.minor(slavestat.st_rdev)
+ lg.debug('pseudoterminal major and minor: (%d,%d)' % (os.major(slavestat.st_rdev),self.minor))
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))
+ self.linkPath = RFCOMMDIR + '/' + address + '_' + Name + '_pts' + str(self.minor)
+ self.slavePath = SLAVEDIR + '/' + str(self.minor)
+ lg.debug('termPath %s' % (self.linkPath))
+ self.removeLink(self.notexiting)
+ # linkPath was removed
+
+ lg.debug('os.symlink(%s,%s)' % (self.slavePath,self.linkPath))
old = os.umask(002)
+ lg.debug('past umask')
try:
- os.mknod(self.myPath,0660 | stat.S_IFCHR,slavestat.st_rdev)
+ os.symlink(self.slavePath,self.linkPath)
+ try:
+ atexit.register(self.removeLink,self.exiting);
+ except Exception as e:
+ lg.error('Register atexit: %s' % (e))
except Exception as e:
- print '%s' % (e)
- lg.error('%s' % (e))
+ lg.error('symlink failed: %s' % (e))
return False
+ lg.debug('Before umask')
os.umask(old)
- os.chown(self.myPath,0,TTY_GID)
- # Completed pseudoterminal case to create device and node
+ lg.debug('After umask')
+ # os.chown(self.myPath,0,TTY_GID)
- # 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]))
+ # Completed pseudoterminal case to create device and node
-
# + 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.
+ # Note that io_add_watch causes a poll to be done by the
+ # python GI library. The callback functions (io_term and io_cb)
+ # will be called when there is an event on the file descriptor
+ # being polled.
sys.stdout.flush()
if not opts.loopback:
- if opts.pseudoterminal:
+ if needpseudot:
local_fd = self.io_pty_master
+ lg.debug('NewConnection: master_fd: %d slave_fd: %d' % (local_fd,self.io_pty_slave)) #success to here.
else:
+ # stdin
local_fd = 0
- self.io_id2 = GObject.io_add_watch(local_fd,
+ lg.debug('Ready to do io_add_watch on local_fd: %d' % (local_fd))
+ try:
+ self.io_id2 = GObject.io_add_watch(local_fd,
GObject.PRIORITY_DEFAULT,
- GObject.IO_IN | GObject.IO_PRI,
+ GObject.IO_IN | GObject.IO_PRI | GObject.IO_HUP | GObject.IO_ERR,
self.io_term)
+ except Exception as e:
+ lg.error('io_addwatch failed for local_fd %d: IO_IN, IO_PRI %s' % (local_fd,e))
+
+ if opts.login:
+ # Writing the slave file descriptor causes the login process to start.
+ lg.debug('opts.login is true slave fd: %d pipe: %d' % (self.io_pty_slave,self.w))
+ try:
+ os.write(self.w,struct.pack('ii',self.io_pty_slave,self.io_pty_master))
+ except Exception as e:
+ lg.error('os.write of slave fd:%d master fd: %d failed: %s' % (self.io_pty_slave,self.io_pty_slave,e))
+ os.close(self.io_pty_slave)
+ self.io_pty_slave = -1
+ return False
+ self.io_pty_slave = -1
+
if doterm:
+ lg.debug('Profile: Write the prompt')
os.write(1,'TTY> ')
- elif opts.pseudoterminal:
- os.write(io_pty_master,'TTY> ')
+
+ lg.debug('NewConnection: doterm: %s, io_add_watch is next' % doterm)
self.io_id = GObject.io_add_watch(self.fd,
GObject.PRIORITY_DEFAULT,
- GObject.IO_IN | GObject.IO_PRI,
+ GObject.IO_IN | GObject.IO_PRI | GObject.IO_HUP | GObject.IO_ERR,
self.io_cb)
lg.debug('io_id(remote input) = %d io_id2(local input) = %d' % (self.io_id,self.io_id2))
+
+ # I/O read from Bluetooth remote to local application.
def io_cb(self, fd, conditions):
+ if terminatenow:
+ self.RequestDisconnection(self.path)
+
+ if (conditions & GObject.IO_HUP or conditions & GObject.IO_ERR):
+ lg.debug('Found HUP on fd: %d, so terminate' % (fd))
+ self.RequestDisconnection(self.path)
+ return False
# Read from remote
data = None
try:
data = os.read(fd, 1024)
except:
return True
+ lg.debug('io_cb: past read: doterm: %s' % doterm)
if opts.loopback:
+ toutput = fd # same as input
+ else:
+ toutput = self.io_pty_master
+
+ if opts.loopback or needpseudot or opts.login:
if data:
start = 0
remain = len(data)
- while remain:
+ result = 1
+ lg.debug('remain is %d entering the loop' % (remain))
+ while remain > 0 and result > 0:
try:
- result = os.write(fd,len(data))
- except:
- lg.debut
+ result = os.write(toutput,data)
+ except Exception as e:
+ lg.debug('os.write failed: %s' % (e))
return True
- if remain != result:
+ lg.debug('os.write returned %d for %s remain: %d' % (result,data,remain))
+ if remain != result and remain > 0:
remain -= result
- data = data[-result:]
+ lg.debug('remain is now %d result is %d' % (remain,result))
+ data = data[-remain:]
+ lg.debug('remain to print %s' % (data))
+ else:
+ remain = 0
+ lg.debug('returning true to end this routing')
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:
+ if doterm:
print('\n'+data.decode('ascii'))
os.write(1,'TTY> ')
+
return True
+ # I/O written to bluetooth from local slave (application write to remote)
def io_term(self, fd0, conditions):
+ if terminatenow:
+ self.RequestDisconnection(self.path)
+ if (conditions & GObject.IO_HUP or conditions & GObject.IO_ERR):
+ lg.debug('Found HUP on fd0: %d, so terminate' % (fd0))
+ self.RequestDisconnection(self.path)
+ return False
# Read from local (not used for loopback)
data = None
data = os.read(fd0, 1024)
+ lg.debug('io_term: fd0: %d len(data): %d' % (fd0,len(data)))
if not data:
# No Data == EOF
self.RequestDisconnection(self.path)
@@ -285,10 +524,8 @@ class Profile(dbus.service.Object):
lg.error('%s' % (e))
self.RequestDisconnection(self.path)
return True
- if opts.pseudoterminal:
+ if doterm:
os.write(fd0,'TTY> ')
- else:
- os.write(1,'TTY> ')
return True
@dbus.service.method('org.bluez.Profile1',
@@ -298,35 +535,56 @@ class Profile(dbus.service.Object):
print('RequestDisconnection(%s)' % (path))
lg.info('RequestDisconnection(%s)' % (path))
if self.fd != -1:
- lg.debug('closing fd: %s',self.fd)
+ 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)
+ 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)
+ 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)
+ 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)
+ 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)
+ lg.debug('removed id2: %s %s %s' % (self.io_id2,'result: ',rmv))
self.io_id2 = -1
- if opts.pseudoterminal:
+ if self.hup_id != -1:
+ lg.debug('closing hup_id: %s' % (self.hup_id))
+ rmv = GObject.source_remove(self.hup_id)
+ lg.debug('removed id2: %s %s %s' % (self.hup_id,'result: ',rmv))
+ self.hup_id = -1
+ if self.hup_id2 != -1:
+ lg.debug('closing hup_id2: %s' % (self.hup_id2))
+ rmv = GObject.source_remove(self.hup_id2)
+ lg.debug('removed id2: %s %s %s' % (self.hup_id2,'result: ',rmv))
+ self.hup_id2 = -1
+ if needpseudot:
if self.io_pty_slave != -1:
os.close(self.io_pty_slave)
+ self.io_pty_slave = -1
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
+ try:
+ savefd = self.io_pty_master
+ self.io_pty_master = -1
+ os.close(savefd)
+ except Exception as e:
+ lg.error("close(io_pty_master): Tried to close %d" % (savefd))
+ lg.error('%s' % (e))
+
+ self.removeLink(self.exiting)
+
+def terminationHandler(mainloop):
+ lg.debug('SIGTERM: terminationHandler was called')
+ mainloop.quit()
+
if __name__ == '__main__':
import argparse
+ doterm = False
TTY_GID = grp.getgrnam('tty').gr_gid
# Set up logging initially info and above
lg = flog(myscript,'daemon','info')
@@ -345,6 +603,8 @@ if __name__ == '__main__':
help='Echo data for testing (exclusive with pseudoterminal)')
parser.add_argument('--debug',
action='store_true', help='Verbose operation mode.')
+ parser.add_argument('--login',
+ action='store_true', help='Use RFCOMM to log into this device.')
opts = parser.parse_args()
if opts.debug:
@@ -355,10 +615,17 @@ if __name__ == '__main__':
print msg
lg.error(msg)
exit(1)
- if not opts.pseudoterminal and not opts.loopback:
+ if not opts.pseudoterminal and not opts.loopback and not opts.login:
doterm = True
+ print "main: doterm is %s" % (str(doterm))
+
+ if opts.pseudoterminal or opts.login:
+ needpseudot = True
+
+
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
+
bus = dbus.SystemBus()
manager = dbus.Interface(bus.get_object('org.bluez',
@@ -367,6 +634,26 @@ if __name__ == '__main__':
mainloop = GObject.MainLoop()
+ GLib.unix_signal_add(GLib.PRIORITY_HIGH,signal.SIGTERM,terminationHandler,mainloop)
+ mypidfile = dopidfile()
+ mypidfile.writePidFile()
+
+ if opts.login:
+ # Need to create thead to exec logins.
+ rpipe,wpipe = os.pipe()
+ loginProcess = logins()
+ Profile.w = wpipe
+ lg.debug('Call threading next')
+ try:
+ StartLoginThread = threading.Thread(target=loginProcess.StartLogin,args=[rpipe,mainloop])
+ except Exception as e:
+ lg.error('threading.Thread: StartLogin %s' % (e))
+ try:
+ StartLoginThread.start()
+ except Exception as e:
+ lg.error('StartLogin.start: %s' % (e))
+
+
profile_path = '/foo/bar/profile'
SPP_opts = {
@@ -389,12 +676,24 @@ if __name__ == '__main__':
print 'Try running as root'
exit(1)
+ lg.debug('Completed Register Profile...')
dbus.mainloop.glib.threads_init()
+ lg.debug('Completed threads init... Now mainloop.run')
try:
mainloop.run()
except KeyboardInterrupt:
pass
- finally:
- print '\nSerial Port Profile: Goodbye'
- lg.info('Serial Port Profile: Goodbye')
- exit
+ except Exception as e:
+ lg.error('mainloop exception: %s' % (e))
+ print '\nSerial Port Profile: ERROR Goodbye'
+ lg.error('Serial Port Profile: ERROR Goodbye')
+ data = struct.pack('i',-1)
+ os.write(wpipe,data)
+ mainloop.quit()
+
+ lg.info('Serial Port Profile: Goodbye')
+ if opts.login:
+ data = struct.pack('ii',-1,-1)
+ os.write(wpipe,data)
+ mypidfile.rmPidFile()
+ mainloop.quit()