記事の詳細
周波数レスポンスアナライザ特集 第4章 タブレットのアプリケーションの製作
最後はタブレット側のアプリケーションの製作です。この製作はAndroid OSのもとで動作するアプリを製作することになりますので、Android Studio+Android SDKという環境でJava言語を使って製作することになります。
4-1 全体構成
製作するアプリは図1-3のような流れで制御します。このアプリを実現するためにはBluetoothの送受信機能が必須になります。この通信機能はGoogleの開発者用サイトにある例題を参考にして製作することとし、全体を図4-1のような構成にしました。
全体を3つのJava実行ファイルで構成していて、Bluetoothの端末探索(DeviceListActivity.java)と送受信を制御する部分(BluetoothClient.java)は独立ファイル構成としています。この部分はGoogleの例題を基本にしていますので他のBluetoothを使うアプリにも共通で使えます。
この他にリソースがありますが、レイアウトや文字列は直接プログラムですべて記述していますので基本のレイアウトはリソースにはなく、デバイスリスト用のブランクレイアウトだけとなっています。文字列のリソースもアプリ名称だけとなっています。
全体としてはマニフェストがアプリの起動やBluetoothの許可に重要な働きをしています。
Androidタブレットには標準でBluetoothが実装されていて、それを駆動するためのAPIも標準で用意されていますから、これを使って制御します。
4-2 画面構成と機能
アプリケーションの機能としてはボタンに対応させていますので、画面構成から説明します。画面は図4-2のようにしました。左側がアプリケーション用のボタンとメッセージ領域で、右側はグラフ表示領域です。機能はすべてボタンタップから開始され、ボタンやテキスト領域には図4-2に示したようなフィールド変数の名称を付加しています。
ボタンと画面に対応する機能は表4-1のようにしました。これらの機能はBluetoothの制御以外すべてアプリケーション本体で実行しています。
4-3 Bluetoothの制御
Bluetoothの制御はGoogleのAndroid Developpersに詳しく解説されていますので、これに沿って作成します。その他にネットでサーチすればいくつか製作例が紹介されていますので参考にできます。
Android OS内のandroid.bluetoothパッケージでBluetooth APIがサポートされており、多くのクラスが提供されています。今回の製作例では次のようなクラスを使っています。
この製作例ではタブレット側が要求を出して機能を実現しますので、タブレットをBluetoothクライアントとして構成します。
① BluetoothAdapter
タブレット自身のBluetoothデバイスを表し、すべての機能をこの中で実現します。
② BluetoothDevice
相手となるPIC側のBluetoothデバイスを表し、MACアドレスを元にインスタンス化されます。
これで相手デバイスの名前、アドレス、クラス、結合状態などの情報の問い合わせをします。
③ BluetoothSocket
BluetoothDeviceとして生成されたリモートとのBluetooth通信用ソケット、つまりインターフェースとして生成され、このソケットを使って実際の通信がInputStreamとOutputStreamで行われます。
Bluetoothの制御は基本的 に次の4つのステップで行います。以下でこの手順の詳細を説明します。
- Bluetoothの許可と有効化
- ペアデバイスまたは利用可能デバイスの発見(スキャン)
- デバイスとの接続(ペアリング)
- データ転送
4-3-1 Bluetoothの許可と有効化
最初に使用するタブレットで自分のBluetoothデバイスを許可するため、マニフェストファイルに次の2行を追加します。これでBluetoothが使えるようになります。
<uses-permission android:name=“android.permission.BLUETOOTH_ADMIN”/>
<uses-permission android:name=“android.permission.BLUETOOTH”/>
次にアプリケーション内ではBluetoothデバイスが存在するかどうかの判定が必要になります。これはBluetoothAdapterオブジェクトが取得できるかどうかで判定ができますので次の記述で判定しています。つまりgetDefaultAdapter()メソッドがnullを返した場合は使用不可です。これをアプリケーションの最初に実行するonCreateメソッドで実行して判定します。
// Bluetoothが有効な端末か確認
BTadapter = BluetoothAdapter.getDefaultAdapter();
if (BTadapter == null) {
text0.setTextColor(Color.YELLOW);
text0.setText(“Bluetooth未サポート”);
}
BluetoothAdapterが取得できたら次はBluetoothを有効化します。これはもともとタブレット自身でBluetoothをOnとすればできますが、アプリケーションから呼び出してダイアログを表示させて行えるようにしておきます。この手順は次のようになり、onStart()メソッド内で行います。まず、isEnabled()メソッドで有効かどうか判定します。falseが返されたときは無効になっていますので、インテントでBluetoothAdapterを起動し「ACTION_REQUEST_ENABLE」という有効化ダイアログを「ENABLEBLUETOOTH」という戻り値として起動要求します。
trueを返してきた場合は、すでに有効ですからクライアントとして動作開始させるため別ファイルとなっているBluetoothClientクラスを新たに生成し、このクラスからの応答処理用のハンドラを生成しておきます。
if (BTadapter.isEnabled() == false) { // Bluetoorhが有効でない場合
// Bluetoothを有効にするダイアログ画面に遷移し有効化要求
Intent BTenable = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
// 有効化されて戻ったらENABLEパラメータ発行
startActivityForResult(BTenable, ENABLEBLUETOOTH);
}
else {
if (BTclient == null) {
// BluetoothClientをハンドラで生成
BTclient = new BluetoothClient(this, handler);
}
}
有効化ダイアログに遷移すると図4-3のようなダイアログが表示されます。
ここで「許可」をクリックすればメインアプリケーションに戻り、onActivityResultに対し「ENABLEBLUETOOTH」のイベントを発生します。このイベントに対応する処理では、前述と同じようにしてBluetoothClientクラスを新たに生成し、そのクラスからの応答処理用ハンドラを用意しています。それ以外の要求の場合はBluetoothサポートなしとしてメッセージだけ出力しています。
/ ************ 遷移ダイアログからの戻り処理 *********************/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
// 端末選択ダイアログからの戻り処理
case CONNECTDEVICE: // 端末が選択された場合
(一部省略)
break;
// 有効化ダイアログからの戻り処理
case ENABLEBLUETOOTH: // Bluetooth有効化の場合
if (resultCode == Activity.RESULT_OK) {
// 正常に有効化できたらクライアントクラスを生成しハンドラを生成する
BTclient = new BluetoothClient(this, handler);
}
else {
Toast.makeText(this, “Bluetoothのサポートなし”, Toast.LENGTH_SHORT).show();
finish();
}
}
}
4-3-2 ペアデバイスの発見
BluetoothAdpterクラスを使うことで、デバイス発見機能かペアデバイスのリストを取得するかのいずかでリモート端末を見つけることができます。
本製作例では、「端末接続」ボタンのクリックでこの機能を開始することにしています。この発見機能そのものをDeviceListActivity.javaという独立ファイルとして構成しています。
まず、メインアプリのボタンの処理部は次のようにしています。「CONNECTDEVICE」を戻り値としてインテントでDeviceListActivityクラスを起動しています。
/********* 接続ボタンイベントクラス ****************/
class SelectExe implements OnClickListener{
public void onClick(View v){
// デバイス検索と選択ダイアログへ移行
Intent Intent = new Intent(BT_FreqAnalyzer.this, DeviceListActivity.class);
// 端末が選択されて戻ったら接続要求するようにする
startActivityForResult(Intent, CONNECTDEVICE);
}
}
DeviceListActivityクラスでは、既にペアリング済みのデバイスのみのリストを図4-4のようなListViewで表示し選択を待ちます。デバイスが選択されたらリモート端末のアドレスを取得して「RESULT_OK」コードを戻り値として、「CONNECTDEVICE」のonActivityResultイベントを発生します。
4-3-3 選択端末との接続
デバイスが選択された「CONNECTDEVICE」のonActivityResultイベントでメインアプリケーションに戻ったら、次のようにして、接続したリモートのアドレスを取得し、そのアドレスでBluetoothDeviceクラスを生成し、connect()メソッドを呼びだして、そのリモートとの接続処理を開始します。
/************ 遷移ダイアログからの戻り処理 *********************/
public void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
// 端末選択ダイアログからの戻り処理
case CONNECTDEVICE: // 端末が選択された場合
if (resultCode == Activity.RESULT_OK) {
String address = data.getExtras().getString(DeviceListActivity.DEVICEADDRESS);
// 生成した端末に接続要求
BluetoothDevice device = BTadapter.getRemoteDevice(address);
BTclient.connect(device); // 端末へ接続
}
break;
connect()メソッドを含む接続処理のメソッドはBluetoothClientクラスとして独立ファイルとしています。
その中のconnect()メソッドでは、スレッドを起動し、次のようにしてUUIDを使ってSPPプロファイルのBluetoothSocketを生成しリモート端末との接続を開始します。
// UUIDでリモート端末との接続用ソケットの生成
public ConnectThread(BluetoothDevice device) {
try {
this.BTdevice = device;
BTsocket = device.createRfcommSocketToServiceRecord(MY_UUID);
}
catch (IOException e) {
}
}
接続処理では、先の探索処理が継続中であれば探索を強制終了させます。また、すでに接続中であったり、転送中であったりした場合にはいったんそれらを中止し、改めて接続を開始するようにして同期化しています。この間の接続状態を呼び出し元アプリケーションに返すことで接続状態を表示できるようにしています。この接続状態表示は、メインアプリのハンドラで実行し、「接続待ち」→「接続中」→「接続完了」または「接続失敗」という流れで表示するようにしています。
接続が正常に完了したら、connected()メソッドを呼び出して今度はConnectedThreadのスレッドを起動して実際の送受信実行を行います。
4-3-4 送受信の実行
送受信は、生成したソケットをインターフェースとして、InputStreamとOutputStreamで行いますので、まずこれらのインスタンスを生成します。
送信は単純に次の手順で行いますが、常にバッファサイズの64バイト単位で送信しています。
/******** 送信処理 *******/
public void write(byte[] buf) {
try {
OutputStream output = BTsocket.getOutputStream();
output.write(buf); // 送信実行
}
catch (IOException e) {
}
}
受信は、次のようにしてスレッドで常時受信を継続するようにし、64バイト受信完了したら、呼び出し元に受信データを返すようにしています。つまり、送信も受信も常に64バイト単位で通信を行うようにしています。
この受信データはメインアプリのハンドラで受け取って受信データ処理を開始します。
/******** 受信を待ち、バッファからデータ取り出し処理 *********/
public void run() {
byte[] buf = new byte[64]; // 受信バッファの用意
byte[] Rcv = new byte[64]; // 取り出しバッファの用意
int bytes, Index, i; // 受信バイト数他
Index = 0; // バイトカウンタリセット
/***** 受信繰り返しループ *****/
while (true) {
try {
// 64バイト受信完了まで受信繰り返し
while(Index < 64){
InputStream input = BTsocket.getInputStream();
bytes = input.read(buf); // 受信実行
for(i=0; i<bytes; i++){ // 受信バイト数だけ繰り返し
Rcv[Index]=buf[i]; // バッファコピー
if(Index < 64)
Index++; // バイトカウンタ更新
}
}
Index = 0; // バイトカウンタリセット
/**** 受信したデータを返す Rcvバッファに格納 ****/
handler.obtainMessage(MESSAGE_READ, 64, -1, Rcv).sendToTarget();
}
catch (IOException e) {
setState(STATE_NONE);
break;
}
}
}
以上がBluetoothの制御部の全体となります。次は、これらを実際に使って機能を実行するアプリケーション本体部になります。
4-4 アプリケーション本体部詳細
続いてアプリケーションの本体部(MainActivity.java)の詳細を説明します。最初の部分がアクティビティのフィールド変数の宣言部となります。ここではBluetooth用の送受信バッファと、GUI用のクラス変数を定義しているだけなので詳細は省略します。
次がアクティビティが呼び出されたとき最初に実行するメソッドであるonCreate()メソッドでリスト4-1となります。
onCreate()メソッドでは、まず画面の使い方をフルスクリーンでタイトルなしとして使うとしてから、表示するボタンなどのコンポーネントの生成と描画をしています。横幅と縦間隔の微調整が必要になります。最後にグラフ部の表示領域を確保しています。
このアプリケーションでは、グラフ表示とコンポーネント表示を同じ画面で行うようにしましたので、Android Studioのグラフィックツールを使わず、直接プログラムで記述して画面を構成しています。
続いてボタンのイベント定義とイベント処理メソッドを生成し、Bluetoothの実装を確認してから、データバッファを0でクリアしています。
最後に現状のファイルリストを表示しています。
次が、アクティビティの状態遷移に伴う処理部でリスト4-2となります。ここではスタート時の処理が重要で、onStartイベント処理で、前節で説明したBluetoothが有効化されているかどうかのチェック処理を行っています。
次が接続ボタンタップのイベント処理でリスト4-3となります。
ここでBluetoothとの接続処理を開始します。接続ボタンの処理では単純に接続相手の探索と接続のアクティビティを起動しているだけです。
その接続アクティビティからの通知に応じて接続状態を表示処理するハンドラ部がリスト4-3の後半となります。ハンドラ部では、接続中の場合はその進捗状態をメッセージで表示し、受信完了の場合は受信したデータの処理をするProcess()メソッドを呼び出します。
次がスイープボタンと固定出力ボタンのイベント処理部で、リスト4-4となります
ここではBluetoothの送信と受信が衝突しないよう、ステートが0の時のみ送信を実行し、それ以外のステートの場合は何もしません。
またスイープ中にスイープがタップされた場合は現在のスイープを中止しステートを0に戻します。
スイープボタンの場合は開始コマンドだけ送信し、ステートを2に進めるだけとし、後は応答受信ごとにProcess()メソッドで進めることになります。
固定周波数の出力ボタンの場合には、周波数設定データを取り込んでそれを直接設定コマンドとして送信し、ステートを1にして応答を待ちます。
次が受信データ処理メソッドのProcess()メソッドでリスト4-5となります。ここですべての受信データ処理を実行しています。
最初に受信データの先頭文字がMである場合のみ処理をします。
次にState変数で処理を分岐しています。Stateが0の場合は何も受信データ待ちをしていない状態で、ボタンのイベント待ち状態となります。
Stateが1の場合は、固定出力がタップされた場合の受信応答待ちの場合ですので、受信データをデシベルの値に変換してからテキストで表示しています。
Stateが2の場合は、スイープボタンがタップされた最初の応答待ちの場合ですので、OK応答の受信を確認できたら最初の周波数を送信バッファにセットして周波数設定コマンドを送信しStateを3に進めています。
Stateが3の場合がスイープの場合の応答受信待ち状態の場合ですので、グラフ表示用バッファに受信データを格納し、さらにデシベルの値に変換してテキストで表示しています。
その後、次のX軸の値を対数計算で求め右端まで行っていなければ、次の周波数を送信バッファにセットして周波数設定コマンドを送信しています。
その前にスイープ中に再度スイープボタンがタップされたかどうかをチェックし、もしタップされていれば、Sweepフラグがセットされていますので、スイープ動作を最初からやり直します。
スイープボタンがタップされていなければこれまでの格納データでグラフを再描画します。
次が描画データの保存、読出し、削除のボタンのイベント処理部でリスト4-6となります。
保存ボタンの処理ではまずテキストボックスに入力されたファイル名を取り出しその名前でファイルをオープンします。
正常にオープンできたらデータ数とレベル値と横軸の周波数値をバイトに変換してバッファに保存します。最後に一括でファイルを書き込んで終了です。
正常に書き込みが終了したら完了メッセージを表示しファイルリスト表示を更新します。
異常の場合は保存失敗としてメッセージ表示をします。
読出しの場合は、ファイル名を取得してからオープンし、正常にオープンできたら完了メッセージを表示しさらにファイルリスト表示を更新しています。
続いて読み出したデータをバイトからINT型に変換してからグラフとして表示します。
次が削除ボタンとデータ転送ボタンの処理部でリスト4-7となります。
削除処理ではファイル名を元に削除し、正常に削除できたらその旨のメッセージを表示します。指定ファイルが無い場合にはエラーメッセージを表示します。
データ転送ボタンのイベント処理では、まずパソコンとBluetoothで接続することが必要であるメッセージを表示します。
接続していない場合でも送信処理を実行しますが、特にエラーにはなりません。
送信データは、現在グラフとして表示されているものを対象として送信します。
1回ごとの計測データを「番号,周波数,レベルCRLF」の構成の文字列として送信し、これをデータ数だけ送信します。
周波数はHz単位の整数で、レベルはxx.xx形式の浮動小数で送信します。
最後がグラフを実際に表示するクラス部でリスト4-8となります。
最初に全体の背景色を黒とし、グラフ目盛線はグレイで描画します。まずX軸は周波数ですので対数目盛として描画し、Y軸はデシベルですので等間隔で描画します。
その後白色線で外枠を描画し、シアンで0dBのラインを描画します。
次にX軸の目盛を周波数値の文字で描画し、Y軸のdB値も描画します。描画位置は微調整が必要です。
最後に実際のデータを黄線で直線を使って描画します。
以上でタブレット側のアプリケーションの全体詳細を説明しました。
あとは、実際に動作させてみるだけです。
著者略歴:後閑哲也
1947年 愛知県名古屋市で生まれる。
1971年 東北大学工学部応用物理学科卒業。大手通信機器メーカに勤務
2003年 有限会社マイクロチップ・デザインラボ設立
計測制御システムの開発コンサルタント
2012年 神奈川工科大学 客員教授