Arduinoの基礎 - Bluetooth
概要
Bluetoothモジュール HC-06
このセクションでは、Bluetoothの使用前に実施するBluetoothモジュールHC-06の設定方法について記載する。
HC-06は、Arduinoとシリアルインターフェイスで接続する。
ホストPCとHC-06の接続にはFT232RLを使用して、ATコマンドを送信することにより、デバイス名やボーレート等の設定ができる。
WindowsでHC-06の設定を行う場合は、RealTermを使用する。
LinuxでHC-06の設定を行う場合は、GNOME Bluetooth、Bluedevil(KDE)、Blueberry、Bluemanを使用する。
まず、HC-06のジャンパを3.3[V]にする。
COMポートの設定は、RealTermの[Port]タブ -[Port]プルダウンから、<数字> = \VCP0
を選択する。(Linuxの場合、/dev/ttyUSB0等)
ボーレートの設定は、RealTermの[Port]タブ -[Baud]プルダウンから選択する。
ここでは、ボーレートを38400[bps]に設定しているが、既定の設定では9600[bps]である。
下図左のように、AT
という文字を送信した後、HC-06からOK
を受信すれば正常に接続されている。
下図右のように、HC-06のバージョンを確認する場合は、AT+VERSION
を送信する。
このように、AT+<コマンド>
という形式のデータを送信することで、モジュールの設定を行うことができる。
まず、デバイス名をMYBTに設定するため、以下のコマンドを送信する。
HC-06からOKsetnameを受信すれば正常に設定されている。
AT+NAMEMYBT
次に、ボーレートを38400[bps]に変更するため、以下のコマンドを送信する。(6は38400を表す)
AT+BAUD6
これらの設定は、HC-06の再起動後に有効になる。
また、Bluetoothの既定の接続PINは1234である。
Bluetoothモジュール HC-06と温度センサ
このセクションでは、Arduino、BluetoothモジュールHC-06、温度センサTMP36を使用して、温度データをBluetoothで送信する。
TMP36は、アナログ出力温度測定ICである。
詳細は、Arduinoの基礎 - 温度センサを参照すること。
ここでは、HC-06のデバイス名をMYBT、ボーレートを38400[bps]に設定している。
HC-06の設定については、上記のセクションを参照すること。
下図に、回路図および配線図を示す。
下図右では、ArduinoからのTXを分圧している抵抗器が接続されている。
以下に、Arduinoのサンプルコードを記述する。
HC-06がGET:TEMPという文字を受信した時、温度データを送信している。
ここでは簡易的な通信のみを行っているが、クライアント側とサーバ側でデータフォーマットの取り決めをする方がよい。
このままでは、データを受信するクライアントが無いため、
次のセクションでは、スマートフォンとHC-06を接続して、遠隔通信で温度データを受信する。
#include <SoftwareSerial.h>
const int PIN_TMP36 = 1;
SoftwareSerial bt = SoftwareSerial(2,3);
boolean bt_found = false;
void setup()
{
Serial.begin(9600);
bt.begin(38400);
delay(3000);
String s = bt_sendCommand("AT");
Serial.println("AT --> " + s);
if( s == "OK" )
{
bt_found = true;
}
else
{
return;
}
}
void loop()
{
if(!bt_found)
{
return;
}
if(bt.available() == 0)
{
return;
}
String r = "";
while(bt.available() > 0)
{
char ch = bt.read();
r += ch;
}
Serial.println("App Command Received: " + r);
process_command( r );
}
float get_temperature()
{
int i = analogRead( PIN_TMP36 );
float f = i * 5.0 / 1023.0;
// TMP36
// C = 100V - 50
return 100 * f - 50;
}
void process_command(String r)
{
if( r == "GET:TEMP" )
{
float temp = get_temperature();
String s = String(temp, 1);
bt.print(s);
bt.flush();
}
else
{
Serial.println("Unknown command.");
}
}
String bt_sendCommand(String cmd)
{
bt.print(cmd);
bt.flush();
Serial.println("Waiting [" + cmd + "]");
while(bt.available() == 0)
{
delay(300);
}
Serial.println("OK [" + cmd + "]");
String r = "";
while(bt.available() > 0)
{
char ch = bt.read();
r += ch;
}
Serial.println("Response [" + r + "]");
return r;
}
HC-06とクライアント
上記のセクションに記載したArduinoで温度データを送信するデバイス・ソフトウェアにおいて、
Bluetoothを使用してPCまたはスマートフォンと接続して、温度データを受信するPCまたはスマートフォンのソフトウェアの作成手順を記載する。
PCのソフトウェア
ソフトウェアの開発は、Visual Studio 2019のコンソールを用いている。
予め、HC-06とペアリングする必要がある。PINは、0000または1234のいずれかである。
標準ライブラリにあるBluetoothのAPIはUWP専用であるため、NuGetからコンソールでも使用できるUwpDesktop
をインストールする。
Visual Studioの[プロジェクト]メニューバー - [NuGet パッケージの管理]を選択して、UwpDesktopと入力してインストールする。
以下に、Bluetooth通信の手順を大まかに記載する。
- 周囲のBluetoothデバイスをスキャンする。
- 見つかったデバイスから、接続・通信を行う端末かどうかを確認して、データの受信を行う。
まず、周囲のBluetoothデバイスをスキャンする。
Bluetoothデバイスは、アドバタイズパケットという自身の場所を伝えるためのデータを発信するため、
そのパケットをスキャンすることでBluetoothデバイスを認識して接続することができる。
以下の例では、BluetoothLEAdvertisementWatcher
クラスのプロパティの値を設定した後、Startメソッドを実行してスキャンしている。
using System;
using System.Threading;
using Windows.Devices.Bluetooth.Advertisement;
class Program
{
private static BluetoothLEAdvertisementWatcher advWatcher;
static async void Main(string[] args)
{
Console.WriteLine("Start");
advWatcher = new BluetoothLEAdvertisementWatcher();
advWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(1000);
advWatcher.ScanningMode = BluetoothLEScanningMode.Passive;
advWatcher.Received += WatcherReceived;
advWatcher.Start();
await Task.Delay(60000);
advWatcher.Stop();
Console.WriteLine("Stop");
}
}
次に、スキャンして見つかったデバイスにおいて、接続・通信を行うデバイスかどうか確認する。
WatcherReceived
メソッドからスキャンされたデバイスの情報を受信するので、その情報を元に確認する。
- スキャンして見つかったデバイスが持つUUIDと、接続・通信を行うデバイスのUUIDを比較する。
- 同じなら、スキャンを停止してそのデバイスと接続する。
- 接続したデバイスから、サービスのUUIDとキャラクタリスティックのUUIDを用いて目的のデータにアクセスする。
- アクセスに成功した後、コールバックを設定して、目的のデータの受信する。
以下のサンプルコードにおいて、CharacteristicChangedVroomController
メソッド(ユーザ自身が作成したメソッド)は、
受信したデータ(バイト列)に対して、任意の処理を行うメソッドである。
UUIDを確認する理由は、Bluetooth通信で使用されるデータフォーマットGATTがあるためである。
GATTは、サービスとキャラクタリスティックの2つから構成されており、各々がUUIDで設定されている。
サービスがフォルダ、キャラクタリスティックがファイルに相当する。
GATTに則っているBluetoothデバイスは、必ずこの形式でデータを送受信するため、UUIDを確認することで目的のデバイスを判別できる。
UUIDを知る方法は、販売元が公開している場合や自身で全パケットを確認する方法がある。
private GattDeviceService gattService;
private GattCharacteristic vroomService;
private readonly String serviceGuid = "00001101-0000-1000-8000-00805f9b34fb";
private async Task WatcherReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
{
bool find = false;
var bleServiceUUIDs = args.Advertisement.ServiceUuids;
foreach (var uuidone in bleServiceUUIDs)
{
// serviceGuid : デバイス固有のGUID
if (uuidone == serviceGuid)
{
// 発見
find = true;
break;
}
}
if (find)
{
try
{
// スキャンの停止
advWatcher.Stop();
// デバイスと接続
BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
// UUIDを用いて目的のデバイスを取得
gattService = device.GetGattService(serviceGuid);
// キャラクタリスティックUUIDを用いて目的のキャラクタリスティックを取得
var characteristics = gattService.GetCharacteristics(new Guid("キャラクタリスティックのGuid"));
// 戻り値の配列が空かどうか確認
if (characteristics.Count > 0)
{
vroomService = characteristics.First();
if (vroomService == null)
{
throw new Exception("Not Exist Service");
}
// 読み取り可能な場合、キャラクタリスティックにアクセスしてコールバックを設定
if (vroomService.CharacteristicProperties.HasFlag(GattCharacteristicProperties.Read))
{
vroomService.ValueChanged += CharacteristicChangedVroomController;
Console.WriteLine("Connect");
await vroomService.ReadClientCharacteristicConfigurationDescriptorAsync();
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception...{ex.Message})");
}
}
}
スマートフォン(Android)のソフトウェア
クライアントからサーバに接続する時、RFCOMMチャネルを作成してBluetoothSocketを作成する必要があるが、
この時に指定するUUIDは、SPP(シリアルポートプロファイル)のUUIDである00001101-0000-1000-8000-00805f9b34fbを指定する。
また、デバイス名はMYBTとしているため、BondedリストからBluetoothDeviceを探す際の名前も変更する。
Bluetooth通信を使用するパーミッションを設定する。
ディスカバリは行わないため、LOCATIONのパーミッションは設定しない。
<!-- AndroidManifest.xmlファイル -->
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.keicode.android.testapps.bttest1c">
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
>
<activity android:name=".MainActivity" android:configChanges="orientation">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
状態を表示するためのTextViewとArduinoおよびHC-06から送信される文字を表示するためのTextViewの計2つのTextViewを並べている。
<!-- res/layout/activity_main.xmlファイル -->
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.keicode.android.testapps.bttest1c.MainActivity">
<TextView
android:id="@+id/btStatusTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Not connected" />
<TextView
android:id="@+id/tempTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="(unknown)"
android:layout_below="@id/btStatusTextView"
android:textSize="24sp"/>
</RelativeLayout>
以下のサンプルコードでは、ワーカースレッド用に1つのクラスを定義しているだけである。
例外が発生した場合、その状況をHandlerを介してUI側にフィードバックしている。
// MainActivity.java
package com.keicode.android.testapps.bttest1c;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Set;
public class MainActivity extends AppCompatActivity
{
static final String TAG = "BTTEST1";
BluetoothAdapter bluetoothAdapter;
TextView btStatusTextView;
TextView tempTextView;
BTClientThread btClientThread;
final Handler handler = new Handler()
{
@Override
public void handleMessage(Message msg)
{
String s;
switch(msg.what)
{
case Constants.MESSAGE_BT:
s = (String) msg.obj;
if(s != null)
{
btStatusTextView.setText(s);
}
break;
case Constants.MESSAGE_TEMP:
s = (String) msg.obj;
if(s != null)
{
tempTextView.setText(s);
}
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState)
{
Log.d(TAG, "onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Find Views
btStatusTextView = (TextView) findViewById(R.id.btStatusTextView);
tempTextView = (TextView) findViewById(R.id.tempTextView);
if(savedInstanceState != null)
{
String temp = savedInstanceState.getString(Constants.STATE_TEMP);
tempTextView.setText(temp);
}
// Initialize Bluetooth
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if( bluetoothAdapter == null )
{
Log.d(TAG, "This device doesn't support Bluetooth.");
}
}
@Override
protected void onResume()
{
super.onResume();
btClientThread = new BTClientThread();
btClientThread.start();
}
@Override
protected void onPause()
{
super.onPause();
if(btClientThread != null)
{
btClientThread.interrupt();
btClientThread = null;
}
}
@Override
protected void onSaveInstanceState(Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putString(Constants.STATE_TEMP, tempTextView.getText().toString());
}
public class BTClientThread extends Thread
{
InputStream inputStream;
OutputStream outputStrem;
BluetoothSocket bluetoothSocket;
public void run()
{
byte[] incomingBuff = new byte[64];
BluetoothDevice bluetoothDevice = null;
Set<BluetoothDevice> devices = bluetoothAdapter.getBondedDevices();
for(BluetoothDevice device : devices)
{
if(device.getName().equals(Constants.BT_DEVICE))
{
bluetoothDevice = device;
break;
}
}
if(bluetoothDevice == null)
{
Log.d(TAG, "No device found.");
return;
}
try
{
bluetoothSocket = bluetoothDevice.createRfcommSocketToServiceRecord(Constants.BT_UUID);
while(true)
{
if(Thread.interrupted())
{
break;
}
try
{
bluetoothSocket.connect();
handler.obtainMessage(Constants.MESSAGE_BT, "CONNECTED " + bluetoothDevice.getName()).sendToTarget();
inputStream = bluetoothSocket.getInputStream();
outputStrem = bluetoothSocket.getOutputStream();
while(true)
{
if(Thread.interrupted())
{
break;
}
// Send Command
String command = "GET:TEMP";
outputStrem.write(command.getBytes());
// Read Response
int incomingBytes = inputStream.read(incomingBuff);
byte[] buff = new byte[incomingBytes];
System.arraycopy(incomingBuff, 0, buff, 0, incomingBytes);
String s = new String(buff, StandardCharsets.UTF_8);
// Show Result to UI
handler.obtainMessage(Constants.MESSAGE_TEMP, s).sendToTarget();
// Update again in a few seconds
Thread.sleep(3000);
}
}
catch(IOException e)
{
// connect will throw IOException immediately
// when it's disconnected.
Log.d(TAG, e.getMessage());
}
handler.obtainMessage(Constants.MESSAGE_BT, "DISCONNECTED").sendToTarget();
// Re-try after 3 sec
Thread.sleep(3 * 1000);
}
}
catch(InterruptedException e)
{
e.printStackTrace();
}
catch(IOException e)
{
e.printStackTrace();
}
if(bluetoothSocket != null)
{
try
{
bluetoothSocket.close();
}
catch(IOException e)
{
}
bluetoothSocket = null;
}
handler.obtainMessage(Constants.MESSAGE_BT, "DISCONNECTED - Exit BTClientThread").sendToTarget();
}
}
}
// Constants.java
package com.keicode.android.testapps.bttest1c;
import java.util.UUID;
public interface Constants
{
public static final String BT_DEVICE = "MYBT";
public static final UUID BT_UUID = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
public static final String STATE_TEMP = "STATE_TEMP";
public static final int MESSAGE_BT = 0;
public static final int MESSAGE_TEMP = 2;
}
次に、環境設定を行う。
ここでは、デバイス名はMYBTとしているが、これをあらかじめペアリングする。
以上の変更を行い、Arduinoとスマートフォンの両方を起動することで自動的にBluetooth接続して、スマートフォンの画面に温度が表示される。