Android携帯を使って、BLEモジュールをスキャンし、CC2541 SensorTagから温度データを取得します。

  • Android携帯:京セラ android one s2
  • Androidバージョン:7.1.2
  • BLEモジュール:CC2541 SensorTag
  • Android開発環境:Android Studio Flamingo 2022.2.1

新規プロジェクト作成時の変更

以前のバージョンでは「Empty Activity」となっていましたが、新バージョン(Android Studio Flamingo 2022.2.1)では「Empty Views Activity」に名称が変わりました。

Android携帯を使ってBLEモジュールのスキャン(その2)

Android携帯を使ってBLEモジュールをスキャンするために作成した、「Android携帯を使ってBLEモジュールのスキャン」のjavaコードを最新のAndroid開発環境で開発しました。

\BleScan1\app\src\main\java\com\tomosoft\blescan1\MainActivity.java

package com.tomosoft.blescan1;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.Manifest;

import android.bluetooth.BluetoothAdapter;
//import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;

import androidx.core.content.PermissionChecker;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.widget.Toast;
import android.widget.TextView;

import android.util.Log;

public class MainActivity extends AppCompatActivity {
    private BluetoothLeScanner scanner;
    private MyScancallback scancallback;

    //private final int PERMISSION_REQUEST = 100;

    //private BluetoothDevice device;
    private TextView textView;
    private android.app.Activity SaveActivity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d("onCreate", "start");
        textView = findViewById(R.id.textView);
        SaveActivity = this;

        // テキストを設定
        textView.setText("");

        //BLE対応端末かどうかを調べる。対応していない場合はメッセージを出して終了
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
            finish();
        }
        Log.d("onCreate", "10");
        //Bluetoothアダプターを初期化する
        BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        BluetoothAdapter adapter = manager.getAdapter();


        //bluetoothの使用が許可されていない場合は許可を求める。
        if (adapter == null || !adapter.isEnabled()) {
            Log.d("onCreate", "20");
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            //startActivityForResult(intent, PERMISSION_REQUEST);

            activityResultLauncher.launch(intent);
            finish();

        } else {
            Log.d("onCreate", "30");

            if (PermissionChecker.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
                    == PermissionChecker.PERMISSION_GRANTED) {
                Toast.makeText(this, "許可されています。", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "許可されていません。", Toast.LENGTH_SHORT).show();
            }

            scanner = adapter.getBluetoothLeScanner();
            scancallback = new MyScancallback();

            //スキャニングを10秒後に停止
            Handler handler = new Handler();
            int SCAN_PERIOD = 10000;
            handler.postDelayed(() -> {
                Log.d("onCreate", "40");
                scanner.stopScan(scancallback);
                finish();
            }, SCAN_PERIOD);
            Log.d("onCreate", "50");
            //スキャンの開始
            scanner.startScan(scancallback);
        }
    }

    private final ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
            result -> {
                Log.d("ActivityResultLauncher", "start");
                if (result.getResultCode() == RESULT_OK) {
                    if (result.getData() != null) {
                        //結果を受け取った後の処理
                        Log.d("ActivityResultLauncher", "10");
                    }
                }
            });

    /*
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d("onActivityResult", "start");
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PERMISSION_REQUEST) {
            Log.d("onActivityResult", "permission");
        }
    }
    */
    class MyScancallback extends ScanCallback {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            Log.d("scanResult", "start");


            if (result.getDevice() == null) return;

            if (PermissionChecker.checkSelfPermission(SaveActivity, Manifest.permission.ACCESS_COARSE_LOCATION)
                    == PermissionChecker.PERMISSION_GRANTED) {
                Toast.makeText(SaveActivity, "許可されています。", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(SaveActivity, "許可されていません。", Toast.LENGTH_SHORT).show();
            }

            textView.append(result.getDevice().getAddress() + " - " + result.getDevice().getName() + "\n");
        }
    }
}

マニフェストファイル「AndroidManifest.xml」は「Bluetooth の権限」を参考にして作成しました。Android 12(API レベル 31)以降をターゲットとするアプリの場合は、アプリのマニフェスト ファイルで次の権限を宣言します。

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

    <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.BleSensortag1"
        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>

    <!-- Request legacy Bluetooth permissions on older devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH"
        android:maxSdkVersion="30" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
        android:maxSdkVersion="30" />

    <!-- Needed only if your app looks for Bluetooth devices.
         You must add an attribute to this permission, or declare the
         ACCESS_FINE_LOCATION permission, depending on the results when you
         check location usage in your app. -->
    <uses-permission android:name="android.permission.BLUETOOTH_SCAN" />

    <!-- Needed only if your app makes the device discoverable to Bluetooth
         devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

    <!-- Needed only if your app communicates with already-paired Bluetooth
         devices. -->
    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

</manifest>

作成したJavaコードを実行すると、Android携帯にスキャンしたBLEモジュールが次のように表示されます。

Logcatの表示を次に示します。

最新のAndroid開発環境で次のエラーが発生しました。

1.「Call requires permission 」エラー

Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`

次のJavaコードを埋め込みました。

if (PermissionChecker.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
        == PermissionChecker.PERMISSION_GRANTED) {
    Toast.makeText(this, "許可されています。", Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(this, "許可されていません。", Toast.LENGTH_SHORT).show();
}

2.startActivityForResultが非推奨
従来は呼び元側でIntentに呼び出すアクティビティのクラスをセットして、startActivityForResultを呼び出して、呼び先の結果をonActivityResultで受け取る実装でしたが、onActivityResultによるアクティビティの結果取得は非推奨となったため、ActivityResultLauncherを使用した実装に変更する必要があります。

つぎのActivityResultLauncherコードをonActivityResultの代わりに使用します。

    private final ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
            result -> {
                Log.d("ActivityResultLauncher", "start");
                if (result.getResultCode() == RESULT_OK) {
                    if (result.getData() != null) {
                        //結果を受け取った後の処理
                        Log.d("ActivityResultLauncher", "10");
                    }
                }
            });
 

Android携帯を使ってCC2541 SensorTagから温度データの取得(その2)

Android携帯を使ってCC2541 SensorTagから温度データを取得するために、「Android携帯を使ってCC2541 SensorTagから温度データの取得」のjavaコードを最新のAndroid開発環境で開発しました。

\BleSensortag1\app\src\main\java\com\tomosoft\blesensortag1\MainActivity.java

package com.tomosoft.blesensortag1;

import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.Handler;
import android.Manifest;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.le.BluetoothLeScanner;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;

import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;

import androidx.core.content.PermissionChecker;

import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.widget.Toast;
import android.widget.TextView;

import android.util.Log;

import java.util.List;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {
    private BluetoothLeScanner scanner;
    private MyScancallback scancallback;

    private BluetoothGatt mGatt;
    private BluetoothGattService mService;

    private final String TEMP_DATA = "f000aa01-0451-4000-b000-000000000000";


    private boolean mScanned = false;
    //private final int PERMISSION_REQUEST = 100;


    //private Handler handler;
    //private final int SCAN_PERIOD = 10000;

    private TextView textView;
    private android.app.Activity SaveActivity;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // text_view: activity_main.xml の TextView の id
        textView = findViewById(R.id.textView);
        SaveActivity = this;

        // テキストを設定
        textView.setText("");

        //BLE対応端末かどうかを調べる。対応していない場合はメッセージを出して終了
        if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
            Toast.makeText(this, R.string.ble_not_supported, Toast.LENGTH_SHORT).show();
            finish();
        }
        //Bluetoothアダプターを初期化する
        BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        BluetoothAdapter adapter = manager.getAdapter();

        //bluetoothの使用が許可されていない場合は許可を求める。
        if (adapter == null || !adapter.isEnabled()) {
            Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            //startActivityForResult(intent, PERMISSION_REQUEST);
            activityResultLauncher.launch(intent);
            finish();

        } else {
            if (PermissionChecker.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
                    == PermissionChecker.PERMISSION_GRANTED) {
                Toast.makeText(this, "許可されています。", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "許可されていません。", Toast.LENGTH_SHORT).show();
            }
            scanner = adapter.getBluetoothLeScanner();
            scancallback = new MyScancallback();

            //スキャニングを10秒後に停止
            Handler handler = new Handler();
            int SCAN_PERIOD = 10000;
            handler.postDelayed(() -> {
                Log.d("onCreate", "40");
                scanner.stopScan(scancallback);
                finish();
            }, SCAN_PERIOD);
            //スキャンの開始
            scanner.startScan(scancallback);

        }
    }

    private final ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
            result -> {
                Log.d("ActivityResultLauncher", "start");
                if (result.getResultCode() == RESULT_OK) {
                    if (result.getData() != null) {
                        //結果を受け取った後の処理
                        Log.d("ActivityResultLauncher", "10");
                    }
                }
            });

    /*
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.d("onActivityResult","start");
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == PERMISSION_REQUEST) {
            Log.d("onActivityResult","permission");
        }
    }
    */


    class MyScancallback extends ScanCallback {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            Log.d("scanResult", "start");
            if (mScanned ) return;
            if (result.getDevice() == null) return;
            if (PermissionChecker.checkSelfPermission(SaveActivity, Manifest.permission.ACCESS_COARSE_LOCATION)
                    == PermissionChecker.PERMISSION_GRANTED) {
                Toast.makeText(SaveActivity, "許可されています。", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(SaveActivity, "許可されていません。", Toast.LENGTH_SHORT).show();
            }

            textView.append(result.getDevice().getAddress() + " - " + result.getDevice().getName() + "\n");
            if (result.getDevice().getName() == null) return;
            Log.d("onScanResult", result.getDevice().getName());
            if (result.getDevice().getName().contains("SensorTag")) {
                Log.d("onScanResult", "10");
                //BLE端末情報の保持
                BluetoothDevice device = result.getDevice();
                mScanned = true;
                BluetoothGattCallback gattCallback = new MyGattcallback();
                device.connectGatt(getApplicationContext(), false, gattCallback);
                //スキャン停止
                scanner.stopScan(scancallback);
                Log.d("onScanResult", "20");
            }
        }
    }

    class MyGattcallback extends BluetoothGattCallback {
        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            Log.d("onConnect", "change");
            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.d("onConnect", "10");
                if (PermissionChecker.checkSelfPermission(MainActivity.this.SaveActivity, Manifest.permission.ACCESS_COARSE_LOCATION)
                        == PermissionChecker.PERMISSION_GRANTED) {
                    //Toast.makeText(SaveActivity, "許可されています。", Toast.LENGTH_SHORT).show();
                } else {
                    //Toast.makeText(SaveActivity, "許可されていません。", Toast.LENGTH_SHORT).show();
                }
                Log.d("onConnect", "20");
                gatt.discoverServices();
            }
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            mGatt = gatt;
            if (status == BluetoothGatt.GATT_SUCCESS) {
                Log.d("onServicesDiscovered", "success");
                List<BluetoothGattService> list = gatt.getServices();
                for (BluetoothGattService service : list) {
                    Log.d("onServicesDiscovered", service.getUuid().toString());

                    String TEMP_SERVICE = "f000aa00-0451-4000-b000-000000000000";
                    if (service.getUuid().toString().equals(TEMP_SERVICE)) {
                        Log.d("onServicesDiscovered", "success1");
                        if (PermissionChecker.checkSelfPermission(SaveActivity, Manifest.permission.ACCESS_COARSE_LOCATION)
                                == PermissionChecker.PERMISSION_GRANTED) {
                            //Toast.makeText(SaveActivity, "許可されています。", Toast.LENGTH_SHORT).show();
                        } else {
                            //Toast.makeText(SaveActivity, "許可されていません。", Toast.LENGTH_SHORT).show();
                        }
                        mService = service;

                        byte[] bytes = {1}; // 書き込むバイト列

                        String TEMP_REQ = "f000aa02-0451-4000-b000-000000000000";
                        BluetoothGattCharacteristic characteristic1 = service.getCharacteristic(UUID.fromString(TEMP_REQ));
                        characteristic1.setValue(bytes);
                        mGatt.writeCharacteristic(characteristic1);
                    }
                }
            }
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                // 成功
                Log.d("onCharacteristicWrite", "start");
                if (PermissionChecker.checkSelfPermission(SaveActivity, Manifest.permission.ACCESS_COARSE_LOCATION)
                        == PermissionChecker.PERMISSION_GRANTED) {
                    //Toast.makeText(SaveActivity, "許可されています。", Toast.LENGTH_SHORT).show();
                } else {
                    //Toast.makeText(SaveActivity, "許可されていません。", Toast.LENGTH_SHORT).show();
                }

                //Descriptorの記述
                BluetoothGattCharacteristic characteristicdata = mService.getCharacteristic(UUID.fromString(TEMP_DATA));
                mGatt.setCharacteristicNotification(characteristicdata, true);
                String NOTIFY_DESCRIPTOR = "00002902-0000-1000-8000-00805f9b34fb";
                BluetoothGattDescriptor descriptor = characteristicdata.getDescriptor(UUID.fromString(NOTIFY_DESCRIPTOR));
                descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                mGatt.writeDescriptor(descriptor);
            }
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            Log.d("onCharacteristic", "change");
            if (characteristic.getUuid().toString().equals(TEMP_DATA)) {
                Log.d("onCharacteristic", characteristic.getUuid().toString());
                final byte[] t = characteristic.getValue();
                Log.d("length", ":" + t.length);
                Log.d("value", String.format("%x %x %x %x", t[0], t[1], t[2], t[3]));

                runOnUiThread(() -> textView.append(String.format("%x %x %x %x\n", t[0], t[1], t[2], t[3])));
            }
        }
    }
}

作成したJavaコードを実行すると、Android携帯にスキャンしたBLEモジュールが次のように表示されます。

Logcatの表示を次に示します。