15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

スマホ/PCからラズパイ4にbluetooth 接続する時のメモ

Last updated at Posted at 2019-12-30

スマホ/PCからラズパイにbluetooth 接続し、ラズパイ側でpython を使った制御を行う時の一通りの手順をまとめてみました。
(外のサイトを参照していて嵌ったところもあったので、改めて。。。)

動作環境

  • Raspberry PI 4 & Rasubian
  • Windows 10

環境のインストール

参考:
https://qiita.com/shippokun/items/0953160607833077163f

# pyBluez の依存パッケージをインストール
$ sudo apt-get install -y python-dev libbluetooth3-dev

# pyBluez のインストール
$ sudo pip3 install pybluez

# sudo apt-get install bluetooth blueman -y # bluez-tool

$ sudo apt install libusb-dev
$ sudo apt install libdbus-1-dev
$ sudo apt install libglib2.0-dev
$ sudo apt install libudev-dev -y
$ sudo apt install libical-dev -y
$ sudo apt install libreadline-dev -y
$ sudo apt install libdbus-glib-1-dev -y
$ sudo apt install libbluetooth-dev

ラズパイのBluetooth macアドレスを確認する(必要に応じて)

$ hciconfig
hci0:   Type: Primary  Bus: UART
        BD Address: DC:A6:32:37:3D:60  ACL MTU: 1021:8  SCO MTU: 64:1
        UP RUNNING PSCAN
        RX bytes:3163 acl:22 sco:0 events:92 errors:0
        TX bytes:3627 acl:21 sco:0 commands:64 errors:0

未使用のチャンネルを調べる(必要に応じて)

$ sudo sdptool browse local | grep Channel
    Channel: 17
    Channel: 16
    Channel: 15
    Channel: 14
    Channel: 10
    Channel: 9
    Channel: 24
    Channel: 12
    Channel: 3

sudo sdptool browse local だけ実行すると何にどのチャンネルが使われているのがわかると思います。

$ sudo sdptool browse local
...
Service Name: Headset Voice gateway
Service RecHandle: 0x10005
Service Class ID List:
  "Headset Audio Gateway" (0x1112)
  "Generic Audio" (0x1203)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 12
Profile Descriptor List:
  "Headset" (0x1108)
    Version: 0x0102

...

未使用のチャンネルを指定してシリアルポートサービスを追加。
(当然のことながら、使用済みのチャンネルを指定するとこの後のステップで失敗します。これに気づかずに最初嵌りました…)

sudo sdptool add --channel=22 SP

チャンネルを指定せずにシリアルポートサービスを追加する場合は、上記の代わりに以下を実行する。

sudo sdptool add SP

シリアルポートサービスを追加できたかどうか確認する。

sudo sdptool browse local

上記を実行すると以下のような
Service Name: Serial Port
という出力が得られるはず。ここに
Channel: 1
のようにチャンネル番号が表示されているので確認しておくこと。

Service Name: Serial Port
Service Description: COM Port
Service Provider: BlueZ
Service RecHandle: 0x10001
Service Class ID List:
  "Serial Port" (0x1101)
Protocol Descriptor List:
  "L2CAP" (0x0100)
  "RFCOMM" (0x0003)
    Channel: 1
Language Base Attr List:
  code_ISO639: 0x656e
  encoding:    0x6a
  base_offset: 0x100
Profile Descriptor List:
  "Serial Port" (0x1101)
    Version: 0x0100

設定ファイルを編集して、起動直後にBluetoothでシリアル通信ができるようにする。

sudo nano /etc/systemd/system/dbus-org.bluez.service

ExecStart ... の行に --compat を追記する(互換モードで動作するようにする)。
また、

ExecStartPost=/usr/bin/sdptool add SP
# チャンネルを指定する場合は上記の代わりに以下を記述する。
# 使用済のチャンネルを指定しないように注意。
# ExecStartPost=/usr/bin/sdptool add --channel=22 SP

を追記してシリアル通信プロトコル(SPP)が起動時に追加されるようにしておく。
チャンネル番号の指定は任意で。

[Unit]
Description=Bluetooth service
Documentation=man:bluetoothd(8)
ConditionPathIsDirectory=/sys/class/bluetooth

[Service]
Type=dbus
BusName=org.bluez
ExecStart=/usr/lib/bluetooth/bluetoothd --compat
ExecStartPost=/usr/bin/sdptool add SP
# チャンネルを指定する場合は上記の代わりに以下を記述する。
# 使用済のチャンネルを指定しないように注意。
# ExecStartPost=/usr/bin/sdptool add --channel=22 SP

NotifyAccess=main
#WatchdogSec=10
#Restart=on-failure
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
LimitNPROC=1
ProtectHome=true
ProtectSystem=full


ラズパイを再起動する。

$ sudo reboot -h

ペアリングする

ラズパイのBluetoothをONし、server側でBluetooth が検索されるのを許可する。

sudo bluetoothctl
[bluetooth] power on
[bluetooth] discoverable on
[bluetooth] agent on
[bluetooth] default-agent

この状態で接続しようとすると以下のようなパスキー確認やサービス認証の確認を求められるので、yes/no を入力する。

参考:
https://qiita.com/oko1977/items/9f53f3b11a1b033219ea

[CHG] Device 80:19:34:31:CD:1E Connected: yes
Request confirmation
[agent] Confirm passkey 291086 (yes/no): yes
Authorize service
[agent] Authorize service 0000110e-0000-1000-8000-00805f9b34fb (yes/no): yes
Authorize service
[agent] Authorize service 0000110d-0000-1000-8000-00805f9b34fb (yes/no): yes
[CHG] Device 80:19:34:31:CD:1E UUIDs: 00001000-0000-1000-8000-00805f9b34fb
...
[CHG] Device 80:19:34:31:CD:1E UUIDs: c7f94713-891e-496a-a0e7-983a0946126e
[CHG] Device 80:19:34:31:CD:1E Connected: no
[CHG] Device 80:19:34:31:CD:1E Connected: yes
[CHG] Device 80:19:34:31:CD:1E Connected: no
[bluetooth]#

ここで、

image.png

上記画像のように、サウンドデバイスとして認識される場合、デバイスとプリンターからraspberrypi を選んで、

  • シリアルポート(SPP)'SerialPort'
  • リモートで制御可能なデバイス
  • リモート制御

以外の項目のチェックを外しておく。
(外しておかないと、サウンドデバイスとして扱われる。筆者の環境では、元々使っていたサウンドデバイスが不活性化して音が鳴らなくなった。。。)

image.png

image.png

ちなみに、「シリアルポート(SPP) 'SerialPort'」が出てこない場合は、序盤の手順で
sdptool add SP
を実行してシリアル通信サービスを有効いるかどうかを再度確認すること。

受信してみる

以下を実行する。

sudo rfcomm listen /dev/rfcomm0

チャンネル番号を指定する場合は以下のように実行する(チャンネル番号22の場合)。

sudo rfcomm listen /dev/rfcomm0 22

別コンソールを立ち上げて、Raspberry Pi側でメッセージを確認するために、/dev/rfcomm0をcatする。

$ sudo cat /dev/rfcomm0

また、さらに別コンソールを立ち上げて
Raspberry Pi上で/dev/rfcomm0デバイスにメッセージをechoしてみる。

$ sudo echo abcd > /dev/rfcomm0

python & bluez で受信してみる

  1 # -*- coding: utf-8 -*-
  2 # Author: Shinsuke Ogata
  3
  4 import sys
  5 import traceback
  6 import time
  7 import bluetooth
  8 import threading
  9
 10 class SocketThread(threading.Thread):
 11     '''
 12     @param client_socket accept の結果返ってきたクライアントソケット.
 13     @param notify_receive シリアル通信で受信したデータを処理する関数・メソッド.
 14     @param notify_error エラー時の処理を実行する関数・メソッド
 15     '''
 16     def __init__(self, server_socket, client_socket, notify_receive, notify_error, debug):
 17         super(SocketThread, self).__init__()
 18         self._server_socket = server_socket
 19         self._client_socket = client_socket
 20         self._receive = notify_receive
 21         self._error = notify_error
 22         self._debug = debug
 23
 24     def run(self):
 25         while True:
 26             try:
 27                 data = self._client_socket.recv(1024)
 28                 if self._receive != None:
 29                     self._receive(data)
 30             except KeyboardInterrupt:
 31                 self._client_socket.close()
 32                 self._server_socket.close()
 33                 break
 34             except bluetooth.btcommon.BluetoothError:
 35                 self._client_socket.close()
 36                 self._server_socket.close()
 37                 if self._debug:
 38                     print('>>>> bluetooth.btcommon.BluetoothError >>>>')
 39                     traceback.print_exc()
 40                     print('<<<< bluetooth.btcommon.BluetoothError <<<<')
 41                 break
 42             except:
 43                 self._client_socket.close()
 44                 self._server_socket.close()
 45                 if self._debug:
 46                     print('>>>> Unknown Error >>>>')
 47                     traceback.print_exc()
 48                     print('<<<< Unknown Error <<<<')
 49                 break
 50
 51 class BluetoothServer(threading.Thread):
 52
 53     '''
 54     @param notify_receive シリアル通信で受信したデータを処理する関数・メソッド.
 55     @param notify_error エラー時の処理を実行する関数・メソッド
 56     @param debug デバッグメッセージを出すときTrue をセット
 57     '''
 58     def __init__(self, notify_receive, notify_error=None, debug=False):
 59         super(BluetoothServer, self).__init__()
 60         self._port =1
 61         self._receive = notify_receive
 62         self._error = notify_error
 63         self._server_socket = None
 64         self._debug = debug
 65
 66     def run(self):
 67         try:
 68             self._server_socket=bluetooth.BluetoothSocket( bluetooth.RFCOMM )
 69
 70             if self._debug:
 71                 print("BluetoothServer: binding...")
 72
 73             self._server_socket.bind( ("",self._port ))
 74
 75             if self._debug:
 76                 print("BluetoothServer: listening...")
 77
 78             self._server_socket.listen(1)
 79
 80             client_socket,address = self._server_socket.accept()
 81
 82             if self._debug:
 83                 print("BluetoothServer: accept!!")
 84             task = SocketThread(self._server_socket, client_socket, self._receive, self._error, self._debug)
 85             task.start()
 86         except KeyboardInterrupt:
 87             if self._debug:
 88                 print("BluetoothServer: KeyboardInterrupt")
 89         except:
 90             if self._debug:
 91                 print('>>>> Unknown Error >>>>')
 92                 traceback.print_exc()
 93                 print('<<<< Unknown Error <<<<')
 94
 95
 96 def receive(data):
 97     print("receive [%s]" % data)
 98
 99 def error(data):
100     print("error")
101
102 if __name__ == '__main__':
103     task = BluetoothServer(receive, error, True)
104     task.start()
15
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?