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




