BluetoothのプロトコルRFCOMM(RFCOMM ‐ 通信用語の基礎知識)での通信を試した。
- クライアント-サーバ
- PC-PC
- Android-PC
PC-PC間 通信
- サーバ
- Ubuntu 18.04
- Python 3.7.4
- クライアント
- Windows 10
- Python 3.6.8
導入(Ubuntu)
sudo apt install libbluetooth-dev
pip3 install pybluez
導入(Windows)
# ref: https://github.com/pybluez/pybluez/issues/279
pip3 install PyBluez-win10
ペアリング
ペアリングはOSのUIを使って手動で行った。
Ubuntu:Settings>Bluetooth
Windows:設定>デバイス>Bluetoothとその他のデバイス
Bluetoothデバイスのアドレス(MACアドレス)はbluetoothctl
(Ubuntu)かデバイスマネージャ(Windows)で分かる。
簡易コード
インタフェースはsocketとほぼ同じものらしい。
サーバサイド
import bluetooth
PORT = 1
server_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
server_sock.bind(('', PORT))
server_sock.listen(1) # backlog: 接続待ち受け数
client_sock, client_addrport = server_sock.accept() # blocking until connection
data = client_sock.recv(1024)
print(data) # bytes
# print(data.decode('ascii'))
クライアントサイド
import bluetooth
server_addr = '**:**:**:**:**:**' # replace here
server_port = 1
sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
sock.connect((server_addr, server_port))
sock.send(b'Hello World') # bytes
PC-Android間 通信
Bluetooth の概要 | Android デベロッパー | Android Developers
接続先を指定するときはポートではなくUUIDを使うらしい(直接ポートを使うインタフェースはなさそう)。
サーバサイド(PC)
pybluezのサンプル(サーバ、クライアント)でもポートを自動にしてUUIDで接続先を決めるようになっているので、Bluetooth的にもともとこれが推奨なのかな(ポートふさがってたら困るし)。
import uuid
print(uuid.uuid4()) # ef7ce24a-a1eb-45d4-9208-f896b0ae8336
import bluetooth
server_sock = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
server_sock.bind(('', bluetooth.PORT_ANY))
server_sock.listen(1) # backlog: 接続待ち受け数
# server_port = server_sock.getsockname()[1]
uuid = 'ef7ce24a-a1eb-45d4-9208-f896b0ae8336'
bluetooth.advertise_service(
server_sock, "MyServer", service_id=uuid,
service_classes=[ uuid, bluetooth.SERIAL_PORT_CLASS ],
profiles=[ bluetooth.SERIAL_PORT_PROFILE ],
)
client_sock, client_addrport = server_sock.accept() # blocking until connection
data = client_sock.recv(1024)
print(data) # bytes
# print(data.decode('ascii'))
適当なUUIDを生成して、サーバ側のコードを修正した。
***site-packages/bluetooth/bluez.py", line 275, in advertise_service
raise BluetoothError (*e.args)
bluetooth.btcommon.BluetoothError: [Errno 2] No such file or directory
- bluetooth.btcommon.BluetoothError: (2, 'No such file or directory') - Stack Overflow
- Bluetooth Python script. - Raspberry Pi Forums
pybluezのラップしてるコマンドラインインタフェースが非推奨になってるらしい。仕方ないので互換モードを有効にして、Serial Port Profileを追加する。
# vim /etc/systemd/system/dbus-org.bluez.service
# ExecStart=/usr/lib/bluetooth/bluetoothd
ExecStart=/usr/lib/bluetooth/bluetoothd -C
# sdptool add SP
# systemctl daemon-reload
# systemctl restart bluetooth
***site-packages/bluetooth/bluez.py", line 275, in advertise_service
raise BluetoothError (*e.args)
bluetooth.btcommon.BluetoothError: [Errno 13] Permission denied
実行にroot権限が必要になってしまったみたいなので、rootのPython環境(3.6.9)にpybluez
を入れて再実行したところ動いた。
クライアントサイド(Android/Java)
try-catchを分割したので長くなったが、createRfcommSocketToServiceRecord
でBluetoothSocket
のインスタンスを取得すればjava.net.Socket
とだいたい同じ。
Bluetoothの権限が必要なので、AndroidManifest.xml
に以下を追加。
<uses-permission android:name="android.permission.BLUETOOTH" />
以下のようなコードを実機で動かして(ペアリング済みの)PCと通信できることが確認できた。実際には、adapter.getBondedDevices()
でペアリング済みのデバイスリスト(List<BluetoothDevice>
)が手に入るのでそこから宛先を選択させる実装をするか、未ペアリングのアドレスにconnectしようとすれば自動でペアリング用のOSのUIが開くらしいので、アドレスをサーバ側に(テキストとかQRコードとかで)表示して入力させるか、みたいな感じなのかな。
final String SERVER_DEVICE_ADDRESS = "**:**:**:**:**:**";
final String SERVER_SERVICE_UUID = "ef7ce24a-a1eb-45d4-9208-f896b0ae8336";
final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
Log.d(TAG, "No bluetooth support.");
return;
}
if (! adapter.isEnabled()) {
Log.d(TAG, "Bluetooth is disabled.");
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enableBtIntent, REQUEST_CODE_ENABLE_BT);
return; // onActivityResult省略
}
new Thread(new Runnable() {
@Override
public void run() {
BluetoothDevice server = adapter.getRemoteDevice(SERVER_DEVICE_ADDRESS);
final BluetoothSocket socket;
try {
socket = server.createRfcommSocketToServiceRecord(UUID.fromString(SERVER_SERVICE_UUID));
} catch (IOException e) {
e.printStackTrace();
return;
}
try {
socket.connect();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "Unable to connect.");
try {
socket.close();
} catch (IOException ex) {
ex.printStackTrace();
Log.d(TAG, "Unable to close socket.");
return;
}
return;
}
final OutputStream os;
try {
os = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "Unable to create output stream.");
try {
socket.close();
} catch (IOException ex) {
ex.printStackTrace();
Log.d(TAG, "Unable to close socket.");
return;
}
return;
}
try {
os.write("Hello World from Android\n".getBytes(Charset.forName("ascii")));
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "Unable to write to output stream.");
}
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "Unable to close output stream.");
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
Log.d(TAG, "Unable to close socket.");
}
}
}).run();