Android から Bluetooth+RFCOMM を利用してモーター制御をする

Arduino で Bluetooth シリアル変換モジュール(HC-05)を使う | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/6819

の続きです。
ストアアプリで作ると、ノートPCでちまちま(Surfaceでもいいけど)やらないちけないので、スマートフォンから動かせるようにします。と言いますか、せっかく Xamarin.Android があるんだから、それで RFCOMM してしまおうという訳です。

最近、中古で購入した Galaxy S3 は、Android 4.1.2 までしか上がらないので BLE は使えないのですが、従来の Bluetooth は使えます。まあ、接続先が Bluetooth 2.0 でシリアル通信なのでこれでok。

内容的には、以下を参考にして作っています。

Connect to a Bluetooth Serial Device with Xamarin.Android
http://brianpeek.com/post/Connect-to-a-Bluetooth-Device-with-XamarinAndroid

Android アプリの画面はこんな感じ。

Android で RFCOMM を使う

Xamarin.Forms を使うと、何故か Android.Bluetooth 名前空間が参照できないので、ノーマルな Xamarin.Forms で作っています。
BluetoothAdapter.DefaultAdapter でデフォルトの Bluetooth を取ってきて、CreateRfcommSocketToServiceRecord メソッドで RFCOMM 用のソケットを作ります。データの送受信はこれに対して、OutputStream と InputStream を使えば ok です。

[Activity(Label = "AndroidBluetooth", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : Activity
{
	TextView text1;
	EditText edit1;
	protected override void OnCreate(Bundle bundle)
	{
		base.OnCreate(bundle);

		// Set our view from the "main" layout resource
		SetContentView(Resource.Layout.Main);

		// Get our button from the layout resource,
		// and attach an event to it
		Button button = FindViewById<Button>(Resource.Id.MyButton);
		button.Click += button_Click;
		Button btnSend = FindViewById<Button>(Resource.Id.button1);
		btnSend.Click += btnSend_Click;
		text1 = FindViewById<TextView>(Resource.Id.textView1);
		edit1 = FindViewById<EditText>(Resource.Id.editText1);
		Button btn2 = FindViewById<Button>(Resource.Id.button2);
		btn2.Click += btn2_Click;
		FindViewById<Button>(Resource.Id.button3).Click += clickMotorOn;
		FindViewById<Button>(Resource.Id.button4).Click += clickMotorOff;
	}

	BluetoothSocket _socket;
	/// <summary>
	/// 接続
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	async void button_Click(object sender, EventArgs e)
	{
		try
		{

			BluetoothAdapter adapter = BluetoothAdapter.DefaultAdapter;
			if (adapter == null)
				throw new Exception("No Bluetooth adapter found.");

			if (!adapter.IsEnabled)
				throw new Exception("Bluetooth adapter is not enabled.");

			BluetoothDevice device = (from bd in adapter.BondedDevices
										where bd.Name == "HC-05"
										select bd).FirstOrDefault();
			if (device == null)
				throw new Exception("Named device not found.");

			_socket = device.CreateRfcommSocketToServiceRecord(UUID.FromString("00001101-0000-1000-8000-00805f9b34fb"));
			await _socket.ConnectAsync();
			text1.Text = "接続しました";
		}
		catch (Exception ex)
		{
			text1.Text = ex.Message;
		}
	}

	async void SendCommand(string text)
	{
		// 8文字にして送る
		if (text.Length < 8)
		{
			text = text.PadRight(8, '*');
		}
		else
		{
			text = text.Substring(0, 8);
		}
		var buffer = System.Text.Encoding.UTF8.GetBytes(text);
		// 送信
		await _socket.OutputStream.WriteAsync(buffer, 0, buffer.Length);
		// 受信待ち
		var buffer2 = new byte[8];
		for (int i = 0; i < buffer2.Length; i++)
		{
			int n = _socket.InputStream.ReadByte();
			buffer2[i] = (byte)n;
		}
		string str = System.Text.Encoding.UTF8.GetString(buffer2);
		text1.Text = str;
	}

	/// <summary>
	/// 送信
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	void btnSend_Click(object sender, EventArgs e)
	{
		string text = edit1.Text ;
		SendCommand(text);
	}
	void clickMotorOn(object sender, EventArgs e)
	{
		string text = edit1.Text;
		SendCommand("m1on");
	}
	void clickMotorOff(object sender, EventArgs e)
	{
		string text = edit1.Text;
		SendCommand("m1off");
	}
	/// <summary>
	/// 切断
	/// </summary>
	/// <param name="sender"></param>
	/// <param name="e"></param>
	void btn2_Click(object sender, EventArgs e)
	{
		_socket.Close();
		_socket = null;
		text1.Text = "切断しました";
	}
}

コマンド自体は、Arduino が受けやすいように8バイト固定にしています。
実験して分かったのですが、Arduino からは1バイトずつ送っているので、受信する Android で ReadAsync を使うと最初の1バイトだけ先に受信してしまいます。このあたりはバッファを先読みして8バイト溜まったら読み込めばいいのですが、面倒ので1バイトずつ読み込んでいます。

		// 受信待ち
		var buffer2 = new byte[8];
		for (int i = 0; i < buffer2.Length; i++)
		{
			int n = _socket.InputStream.ReadByte();
			buffer2[i] = (byte)n;
		}

あと、プロジェクトの Android Manifest を開いて BLUETOOTH にチェックを入れます。

20150217_05

 

これがうまくいくと、Android スマートフォンからモーター制御ができるようになります。

ちなみに、上の方に写っている白いボードは「Freaduino UNO Rev1.8」です。Arduino Uno にサーボ用のシールドを作るのが面倒で買ってしまいました。手元の meArm を制御している

Assembly | Adafruit 16-Channel Servo Driver with Arduino | Adafruit Learning System
https://learn.adafruit.com/16-channel-pwm-servo-driver/assembly

とは違うけどデジタルピンに1対1で対応しているので、Arudino IDE で Servo ライブラリがそのまま使えます。

カテゴリー: 開発, Android, Arduino, Xamarin パーマリンク