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の表示を次に示します。