見出し画像

AndroidからBluetoothで通信

KotlinでBluetoothを使おう!

急にどうした?

後輩から、KotlinでAndroid開発はじめたけどBluetoothの使い方がわからないと言われたので、教えるついでに記事も書いちゃえと思ったげるすら。

ただそれだけの記事です。

大したことは書きませんし、深いところまでは突っ込みません。
とりあえずBluetoothでスマホ(実機)からPC(Pythonで受信)に適当に
なんか送って、「通信できたぜ!」ってするだけの内容です。


使用する設備

  • パソコン(Windos11)

  • AndroidStudio(KotlinでAndroidアプリを作るIDE)

  • Python3.7.9(PyBluezがPython3.7までしか対応してない)

  • PyCharm(Pythonを書くIDE)

  • Android端末(Bluetoothを送信させます)


事前準備

まず、Android端末とPCでペアリングしておきましょう。


Python側

ライブラリ

まず、必要なライブラリをインストールしましょう。

pip install PyBluez

以上です。


環境確認用コード

ペアリングできている状態で、PyBluezライブラリを使用してデバイスを認識できているか確認してみます。

import bluetooth


def main():
    for addr, name in bluetooth.discover_devices(lookup_names=True):
        print(f'{addr} - {name}')


if __name__ == '__main__':
    main()

取得できたデバイスのMACアドレスとデバイス名が出力されます。

AA:AA:AA:AA:AA:AA - WF-1000XM4
BB:BB:BB:BB:BB:BB - WH-1000XM3
CC:CC:CC:CC:CC:CC - Xperia 5 III
XX:XX:XX:XX:XX:XX - gerusuraimu  # これが今回使うおもちゃ用Android端末

目的のAndroid端末が正常に取得できていればひとまずOKです。

確認用コードはもう使わないので削除して大丈夫です!


受信用コード

Android端末から信号を受け取るコードを書きましょう。

import bluetooth


PORT = 5  # ネット記事だとPORT=1と記述していることが多いですが、OSErrorが出ます!


def main():
    global PORT

    server = bluetooth.BluetoothSocket(bluetooth.RFCOMM)
    server.bind(('', PORT))
    server.listen(1)

    uuid = "00001101-0000-1000-8000-00805F9B34FB"
    bluetooth.advertise_service(
        server,
        "SampleServer",
        service_id=uuid,
        service_classes=[uuid, bluetooth.SERIAL_PORT_CLASS],
        profiles=[bluetooth.SERIAL_PORT_PROFILE]
    )

    sock, addr = server.accept()
    print(f'Accepted connection from {addr}')

    try:
        while True:
            data = sock.recv(1024)
            if not data:
                break
            print(f'Received: {data}')

    finally:
        sock.close()
        server.close()


if __name__ == '__main__':
    main()

これでBluetooth経由でPORT5番に来た通信を受信して出力できます。
では、Kotlinで送信側を書いていきましょう。


Kotlin側

パーミッションの設定

まず、アプリがAndroidくんに対してBluetooth機能を使うアプリである事を
お知らせする必要があります。

AndroidStudioでプロジェクトを作成したら、
appフォルダ⇒manifestsフォルダの中にある
AndroidManifest.xml
にお知らせを追記しましょう。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <uses-feature android:name="android.hardware.bluetooth"/>
    <uses-permission android:name="android.permission.BLUETOOTH"/>
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyApplication"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

追記後のAndroidManifest.xmlの全体です。
追記したのは以下の項目です。

  • <uses-feature android:name="android.hardware.bluetooth"/>

  • <uses-permission android:name="android.permission.BLUETOOTH"/>

  • <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>

  • <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

これでパーミッションはOKです。
まぁ、現代のスマホでBluetoothに対して「uses-feature」が必要なのか
正直疑問ではありますけど、個人的に書いた方が気持ちいいので書きます。


送信用コード

Android端末から信号を送るコードです。

package com.example.myapplication

import android.Manifest
import java.util.*
import java.io.IOException
import android.os.Bundle
import android.bluetooth.BluetoothSocket
import android.bluetooth.BluetoothDevice
import android.bluetooth.BluetoothAdapter
import android.content.pm.PackageManager
import android.os.Build
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat

class MainActivity : AppCompatActivity() {
    companion object {
        private const val REQUEST_BLUETOOTH_CONNECT = 100
        private val MY_UUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
        private const val DEVICE_ADDRESS = "XX:XX:XX:XX:XX:XX"  // Python側(PC)のMACアドレスを記入しましょう!
    }

    private var bluetoothAdapter: BluetoothAdapter? = null
    private var bluetoothSocket: BluetoothSocket? = null
    private var isConnected: Boolean = false
    private var timerTask: TimerTask? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
        if (bluetoothAdapter == null) {
            Toast.makeText(this, "Bluetooth is not available", Toast.LENGTH_LONG).show()
            finish()
            return
        }

        requestBluetoothPermissions()
    }

    private fun requestBluetoothPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.BLUETOOTH_CONNECT
            ) != PackageManager.PERMISSION_GRANTED
        ) {
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.BLUETOOTH_CONNECT),
                REQUEST_BLUETOOTH_CONNECT
            )
        } else {
            setupBluetoothConnection()
        }
    }

    private fun setupBluetoothConnection() {
        if (ContextCompat.checkSelfPermission(
                this,
                Manifest.permission.BLUETOOTH_CONNECT
            ) == PackageManager.PERMISSION_GRANTED
        ) {
            try {
                val pairedDevices: Set<BluetoothDevice>? = bluetoothAdapter?.bondedDevices
                val device = pairedDevices?.firstOrNull { it.address == DEVICE_ADDRESS }
                device?.let {
                    bluetoothSocket = it.createRfcommSocketToServiceRecord(MY_UUID)
                    timerTask = object : TimerTask() {
                        override fun run() {
                            try {
                                if (bluetoothSocket?.isConnected == false) {
                                    bluetoothSocket?.connect()
                                    isConnected = true
                                }
                                bluetoothSocket?.outputStream?.write("1".toByteArray())
                            } catch (e: IOException) {
                                isConnected = false
                                cancel()
                            }
                        }
                    }
                    Timer().schedule(timerTask, 0, 1000)
                }
            } catch (e: IOException) {
                Toast.makeText(
                    this,
                    "Could not create Bluetooth socket",
                    Toast.LENGTH_SHORT
                ).show()
            } catch (e: SecurityException) {
                Toast.makeText(
                    this,
                    "Bluetooth permission is required",
                    Toast.LENGTH_SHORT
                ).show()
            }
        } else {
            Toast.makeText(
                this,
                "Bluetooth permission is required",
                Toast.LENGTH_SHORT
            ).show()
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        timerTask?.cancel()
        try {
            bluetoothSocket?.close()
        } catch (e: IOException) {
            //
        }
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<String>,
        grantResults: IntArray
    ) {
        when (requestCode) {
            REQUEST_BLUETOOTH_CONNECT -> {
                if ((grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED)) {
                    setupBluetoothConnection()
                } else {
                    Toast.makeText(
                        this,
                        "Bluetooth permission is required",
                        Toast.LENGTH_SHORT
                    ).show()
                }
            }
            else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        }
    }
}

上記のコードは、Android端末側から一秒に一回「1」というデータを送信するように書かれています。

送受信が成功していれば、Python側で一秒ごとに
Received: b'1'
と表示されます。


最後に

解説記事というより、個人的な備忘録に近いですし、事細かに解説できるほどKotlinを理解してませんので、げるすらに聞いても答えられるか正直自信ありません笑

ただ、聞いていただければなるべくお答えしますので、もし聞きたいことがあれば遠慮なくどうぞ。

この記事が気に入ったらサポートをしてみませんか?