Raspberry Piをbluetooth使用ルータとして活用する (Step4 metricの設定)
metricの設定
bluetooth接続ができると、ネットワークインタフェースは
- 有線LAN(eth0)
- bluetooth PAN(bnep0)
の二つになります。 インターネットとつなぐためには有線ではなくてbluetooth PANを使ってほしいのでそのための設定を行います。 単にインターネットを見に行くインタフェースをbluetooth側にしたいだけならデフォルトゲートウェイを設定するだけですが、 今回はADSLが使えなくなった時の避難用なので、「bluetoothがあるときにはそちらを使う」「ない時には有線LAN側を使う」と切り替えたいため、 metricを使って対応したいと思います。
参考ページ
ifmetricパッケージを導入することで、各ネットワークインタフェースにmetric値を手動で設定できるようになります。
pi@raspberrypi:~ $ sudo apt-get install ifmetric
interfacesに次の行を追加します。eth0にmetric値を設定するとともに、bluetooth pan側もちゃんと設定を書きます。
auto lo iface lo inet loopback auto eth0 iface eth0 inet static address 192.168.11.100 netmask 255.255.255.0 gateway 192.168.11.1 metric 100 allow-hotplug bnep0 iface bnep0 inet dhcp
再起動して、PANを有効にしてからルーティングを確認します。デフォルトゲートウェイとしてMetric 0のbnep0が利用されることとなります。
pi@raspberrypi:~ $ route カーネルIP経路テーブル 受信先サイト ゲートウェイ ネットマスク フラグ Metric Ref 使用数 インタフェース default 192.168.44.1 0.0.0.0 UG 0 0 0 bnep0 default 192.168.11.1 0.0.0.0 UG 100 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
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) (以下略)
DHCPでIPアドレスがもらえているようです。androidにpingを打って通信できるか確認します。
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の確立は完了です。
Raspberry Piをbluetooth使用ルータとして活用する (Step2 bluetoothペアリング)
bluetoothペアリング
次はRaspberry Piからandroid携帯にテザリングするところまでです。 2015年9月になりRaspbian の新バージョン(Raspbian 8 "jessie")が公開されています。 以前のバージョン(Raspbian 7 "Wheezy"またはRaspbian "squeeze")ではbluez-utilsを使った方法がありましたが、 新バージョンではbluez-utilsが無くなってしまったようなので、別の方法で接続する必要があります。
まずはandroid間とペアリング設定を行います。Bluetoothサポートプログラムをインストール(もしかしたらいらないかも)
pi@raspberrypi:~ $ sudo apt-get install bluetooth
次にbluetoothctlにてandroidとの信頼関係を構築します。(参考 kakakikikekeのブログ: RaspberryPi の bluetoothctl で無線キーボードに接続)
pi@raspberrypi:~ $ sudo bluetoothctl [NEW] Controller XX:XX:XX:XX:XX:XX raspberrypi [default] [bluetooth]# power on Changing power on succeeded [bluetooth]# scan on Changing power on succeeded Discovery started [CHG] Controller XX:XX:XX:XX:XX:XX Discovering: yes [NEW] Device YY:YY:YY:YY:YY:YY ZTE Blade Vec 4G [bluetooth]# pair YY:YY:YY:YY:YY:YY Attempting to pair with YY:YY:YY:YY:YY:YY
これでandroid側にbluetoothのペアリング許可ダイアログが出てきます。
[CHG] Device YY:YY:YY:YY:YY:YY Paired: yes Pairing successful [CHG] Device YY:YY:YY:YY:YY:YY Connected: no [bluetooth]# paired-devices Device YY:YY:YY:YY:YY:YY ZTE Blade Vec 4G [bluetooth]# exit
これでペアリングは完了です。次回でPAN接続を行います。
Raspberry Piをbluetooth使用ルータとして活用する (Step1 IPアドレスを固定する)
Raspberry PiのIPアドレスを固定する
Raspberry Piのインストール直後はDHCPでIPアドレスを取得するようになっていて、再起動すると違うIPアドレスに変るかもしれません。サーバ用途で利用するには固定IPの方がなにかと便利。なので、ネットワークの設定を変更し、固定IPを持たせます。
ネットワークの設定には、/etc/network/interfacesを変更します。
iface eth0 inet dhcp
を
auto eth0 iface eth0 inet static address (割り当てるIPアドレス) netmask (サブネットマスク) gateway (デフォルトルータのIPアドレス)
とします。
今回の例では
auto eth0 iface eth0 inet static address 192.168.1.100 netmask 255.255.255.0 gateway 192.168.1.1
とします。設定完了したら、
pi@raspberrypi:~ $ sudo reboot
でRaspberry Piを再起動すれば完了です。
Raspberry Piをbluetooth使用ルータとして活用する(概要)
目的
我が家ではインターネットへの出口としてADSLを利用しているのですが、それが定期的に死にます。 経路上のどこかで盛大にノイズが乗っているのか、2、3日の間接続できないか、200kbpsぐらいで繋がる (それも頻繁に切れるずっと繋がらないのなら光回線等を検討するのですが、 普段は使えているだけに乗り換えは躊躇するところです。光だと今の倍近くの料金になるしね。
なので、その際だけ回避用の回線を利用したいと思います。ちょうど手元にMVMOでテザリンク可なandroid端末と 常時動いているraspberryPiがあるので、それでなんとかならないか試行錯誤してみます。
注釈
Androidがあるならそれがwifiでテザリングするのが簡単。なはずなのですが、 うちにはPS3や有線LANのデスクトップパソコン、chromecastなどあり、すべての設定を変えるのは面倒です (ADSLが使えないときぐらいchromecastを使わなくてもいいんだけどね
それと、お仕事でunixやネットワークのことも勉強する必要があるので勉強がてら。という面もあります。