ぱっそ あ ぱっそ

個人的なメモをだらだらと。無保証。記事は随時書き直しています。

Raspberry Piをbluetooth使用ルータとして活用する (Step3 bluetooth PAN)

bluetooth PAN

STEPその2でbluetoothのペアリングが出来ましたので、androidとのPAN(Personal Area Network)を構築します。 以前のバージョン(Raspbian 7 "Wheezy"またはRaspbian "squeeze")ではbluez-utilsのpandを使った方法がありましたが 新バージョン(Raspbian 8 "jessie")ではbluez-utilsが無くなってしまったようなので、スプリクトを用意してPANの構築を行います。

参考:My blog_title_here · Bluetooth PAN Network Setup with BlueZ 5.X

pi@raspberrypi:~ $ nano bt-pan

として、以下の内容をbt-panに記載します。(https://raw.githubusercontent.com/mk-fg/fgtk/master/bt-panから転記しています。)


#!/usr/bin/env python2
from __future__ import absolute_import, print_function

import os, sys, time, types, subprocess

import dbus, signal

### ~bluezutils.py

iface_base = 'org.bluez'
iface_dev = '{}.Device1'.format(iface_base)
iface_adapter = '{}.Adapter1'.format(iface_base)
iface_props = 'org.freedesktop.DBus.Properties'

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:
		manager = get_manager.cached_obj = dbus.Interface(
			get_bus().get_object(iface_base, '/'),
			'org.freedesktop.DBus.ObjectManager' )
	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)

def find_adapter(pattern=None):
	return find_adapter_in_objects(get_manager().GetManagedObjects(), pattern)

def find_adapter_in_objects(objects, pattern=None):
	bus = get_bus()
	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)
			return dbus.Interface(obj, iface_adapter)
	raise BTError('Bluetooth adapter not found')

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)
	raise BTError('Bluetooth device not found')

### bt-pan

def main(args=None):
	import argparse
	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('-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('-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()

	global log
	import logging
	logging.basicConfig(level=logging.DEBUG if opts.debug else logging.WARNING)
	log = logging.getLogger()

	dev_local = find_adapter(opts.device)
	prop_set(dev_local, 'Powered', True)
	log.debug( 'Using local device (addr: %s): %s',
		prop_get(dev_local, 'Address'), dev_local.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:
			log.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()
		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)
			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

		server = dbus.Interface(dev_local, 'org.bluez.NetworkServer1')
		server.Register(opts.uuid, opts.iface_name)
		log.debug('Registered uuid %r with bridge: %s', opts.uuid, opts.iface_name)

		try:
			while True: wait_iter()
		except KeyboardInterrupt: pass
		finally:
			server.Unregister(opts.uuid)
			log.debug('Unregistered server uuids')


	elif opts.call == 'client':
		dev_remote = find_device(opts.remote_addr, dev_local)
		log.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')
		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:
					log.debug( 'Detected pre-established connection'
						' (iface: %s), reconnecting', prop_get(net, 'Interface') )
					net.Disconnect()
					continue
				if not opts.if_not_connected: raise
			else: break
		log.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()
				log.debug('Disconnected from network')


	else: raise ValueError(opts.call)
	log.debug('Finished')

if __name__ == '__main__': sys.exit(main())

出来上がったら

pi@raspberrypi:~ $ sudo chmod 777 bt-pan

として、ファイルに実行権限をつけます。ここまで出来ればいよいよPANの構築

pi@raspberrypi:~ $ sudo ./bt-pan client YY:YY:YY:YY:YY:YY

で接続完了です。確認で

pi@raspberrypi:~ $ ifconfig
bnep0     Link encap:イーサネット  ハードウェアアドレス **:**:**:**:**:**
          inet6アドレス: ****:****:****:****:****:****/64 範囲:リンク
          inetアドレス:192.168.44.244 ブロードキャスト:192.168.44.255  マスク:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  メトリック:1
          RXパケット:2 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:6 エラー:0 損失:0 オーバラン:0 キャリア:0
      衝突(Collisions):0 TXキュー長:1000
          RXバイト:410 (410.0 B)  TXバイト:596 (596.0 B)
(以下略)

DHCPIPアドレスがもらえているようです。androidpingを打って通信できるか確認します。

pi@raspberrypi:~ $ route
カーネルIP経路テーブル
受信先サイト    ゲートウェイ    ネットマスク   フラグ Metric Ref 使用数 インタフェース
default         192.168.11.1    0.0.0.0         UG    0      0        0 eth0
default         192.168.11.1    0.0.0.0         UG    202    0        0 eth0
default         192.168.44.1    0.0.0.0         UG    203    0        0 bnep0
192.168.11.0    *               255.255.255.0   U     202    0        0 eth0
192.168.44.0    *               255.255.255.0   U     203    0        0 bnep0
pi@raspberrypi:~ $ sudo ping 192.168.44.1
PING 192.168.44.1 (192.168.44.1) 56(84) bytes of data.
64 bytes from 192.168.44.1: icmp_seq=1 ttl=64 time=449 ms
64 bytes from 192.168.44.1: icmp_seq=2 ttl=64 time=29.7 ms

ping に返事があればPANの確立は完了です。