忍者ブログ

Azukish

消えゆく世界と流れる未来に最後の灯を since 2006/4/3

2016/11/25

【備忘録】BluetoothでAndroidからLinuxにメッセージを送る


まず基本処理。検索して〜みたいな部分は前の説明に載ってるので省略。ManifestもLayoutも変えてないので省略。前のに載ってる。
具体的にはIntent移行処理部分が増えました。
ListViewのアイテムがクリックされたら次に飛ぶ。その時にクリックされたBluetoothのデバイスに関する情報を次のIntentに持って行かせる。
putExtra()は超便利

MainActivity.java
package com.example.azuki.myapplication;

import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.content.IntentFilter;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;

import static android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_FINISHED;
import static android.bluetooth.BluetoothAdapter.ACTION_DISCOVERY_STARTED;
import static android.bluetooth.BluetoothDevice.ACTION_FOUND;
import static android.bluetooth.BluetoothDevice.ACTION_NAME_CHANGED;
import static android.bluetooth.BluetoothDevice.EXTRA_DEVICE;

public class MainActivity extends Activity {
  public static final String app = "Bluetooth Sample";
  public static final int ACTION_REQUEST_ENABLE_BLUETOOTH = 10;
  public ListView listView;
  public ArrayAdapter<string> adapter = null;
  public BluetoothAdapter bt;
  public ArrayList<bluetoothdevice> foundDevices = new ArrayList<bluetoothdevice>();
  public boolean btFound = false;


  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    listView = (ListView) findViewById(R.id.listView);
    adapter = new ArrayAdapter<string>(getApplicationContext(), R.layout.activity_main_text_listview);

    bt = BluetoothAdapter.getDefaultAdapter();

    if (!bt.isEnabled()) {
      // もしもBluetoothがオフになっていたら、オンにするように促すダイアログを表示
      // ユーザの操作後onActivityResult()が呼ばれる
      startActivityForResult(new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE), ACTION_REQUEST_ENABLE_BLUETOOTH);
    } else
      searchDevice();
  }

  private void searchDevice() {
    // ブロードキャストを受信するにあたって、どんなものが受信したいか決めておく
    IntentFilter filter = new IntentFilter();
    filter.addAction(ACTION_DISCOVERY_STARTED);   // 検索開始
    filter.addAction(ACTION_FOUND);               // 何か見つかった
    filter.addAction(ACTION_NAME_CHANGED);        // デバイス名が変わった
    filter.addAction(ACTION_DISCOVERY_FINISHED);  // 検索が終わった
    registerReceiver(DeviceFoundReceiver, filter);
    bt.startDiscovery();
  }

  private BroadcastReceiver DeviceFoundReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
      String action = intent.getAction();
      final BluetoothDevice foundDevice;
      if (action.equals(null)) {  // ぬるぽ対策
        return;
      } else {
        switch (action) {
          // 検索開始時に行うこと(初期化)
          case ACTION_DISCOVERY_STARTED:
            Log.d(app, "start");
            if (foundDevices!=null && !foundDevices.isEmpty())
              foundDevices.clear();
            break;
          // デバイスが見つかったら配列に追加
          case ACTION_FOUND:
            foundDevice = intent.getParcelableExtra(EXTRA_DEVICE);
            foundDevices.add(foundDevice);
            Log.d(app, foundDevice.getName() + " " + foundDevice.getAddress());
            adapter.add(foundDevice.getName());
            btFound = true;
            break;
          // 検索終了して、デバイスが一つでも見つかっていればそれらをListViewに並べて表示
          case ACTION_DISCOVERY_FINISHED:
            Log.d(app,"finished");
            if (btFound) {
              listView.setAdapter(adapter);

              //---------
              // 具体的に増えたところ
              //---------
              // ListViewがクリックされたらSendMessageに飛ぶ
              listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                  Toast.makeText(getApplicationContext(), String.valueOf(position), Toast.LENGTH_SHORT).show();
                  Intent sendMessageIntent = new Intent(getApplicationContext(), SendMessage.class);
                  sendMessageIntent.putExtra("device", foundDevices.get(position));
                  startActivity(sendMessageIntent);
                }
              });
              //---------
              // 具体的に増えたところここまで
              //---------

            }
          default:
            break;
        }
      }
    }
  };

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
    // Bluetoothをオンにするように促すダイアログ
    if (requestCode == ACTION_REQUEST_ENABLE_BLUETOOTH) {
      if (resultCode == Activity.RESULT_OK) {
        Log.d(app, "Bluetooth on");
        searchDevice();
      } else {
        // オンにしてもらえなかったら死ぬ
        Log.d(app, "Bluetooth cannot be on. Abort.");
        finish();
      }
    }
  }
}




ここからが本格的に増やした部分。
まず、Layout。
EditTextとButtonだけですっげシンプル。

activity_send_message.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_send_message"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.azuki.myapplication.SendMessage">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="textPersonName"
        android:text="text"
        android:ems="10"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:id="@+id/editText"/>

    <Button
        android:text="Send"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/editText"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="30dp"
        android:id="@+id/button_send"/>
</RelativeLayout>



で、処理部分。 おおよそコメントに書いてあるから特筆するようなものもない・・・。
前のIntentのputExtraで投げたやつを受け取って
それを使ってソケットを作って
作ったソケットを開いて
文字列を用意して
送って
ソケットを閉じて終了。
Keep It Simple and Shortを体現したようなコード(ただし最適化なんてしてない)

ソケットのwriteやreadには専用のスレッドを作ったほうがいい(Bluetooth を使うの「接続の管理」参照)みたいだけど、練習用だし、これから先に何か処理をつなげて書くわけでもないので、他の処理はブロックさせとく。
Serial Port Profile (SPP)を使うのでUUIDは"00001101-0000-1000-8000-00805F9B34FB"にしておかないといけないっぽい(2011-10-25 - ここのことはなかったことにするかも [Android] Bluetooth でパソコンと通信BluetoothChatでシリアル接続(SPP)を行う | dietposter's blog)。

SendMessage.java
package com.example.azuki.myapplication;

import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.UUID;

public class SendMessage extends AppCompatActivity {
  public static final String app = "Bluetooth Sample";
  private final UUID uuid = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB");  // UUIDは必ずコレ
  BluetoothSocket mmSocket;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // 初期化プロセス
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_send_message);
    Button button = (Button)findViewById(R.id.button_send);
    final EditText editText = (EditText)findViewById(R.id.editText);

    // 前のIntentからの変数受け取り
    final BluetoothDevice bluetoothDevice = getIntent().getParcelableExtra("device");
    editText.setText(bluetoothDevice.getName()+"%sに送る文字を入力");

    // ボタンクリック時に以下を実行
    button.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        // ソケット作る
        BluetoothSocket tmpBTSocket = null;
        try {
          tmpBTSocket = bluetoothDevice.createRfcommSocketToServiceRecord(uuid);
        } catch (IOException e) {
          Log.e(app, "create() failed", e);
          return;
        }
        // ソケット開く
        mmSocket = tmpBTSocket;
        try {
          mmSocket.connect();
        } catch (IOException e) {
          try {
            Log.e(app, "cannot connect", e);
            mmSocket.close();
            return;
          } catch (IOException e2) {
            Log.e(app, "unable to close", e2);
            return;
          }
        }
        // 送るための文字列を用意する
        //InputStream inputStream = null; // ←文字列を受け取りたい場合は次を使う
        OutputStream outputStream = null;
        try {
          //inputStream = mmSocket.getInputStream(); // ←文字列を受け取りたい場合は(ry
          outputStream = mmSocket.getOutputStream();
        } catch (IOException e) {
          Log.e(app, "sockets not created", e);
          return;
        }
        // 文字列を送る
        try {
          outputStream.write(editText.getText().toString().getBytes(Charset.forName("UTF-8")));
        } catch (Exception e) {
          Log.e(app, "cannot be translated to UTF-8", e);
          return;
        }
        //ソケットを閉じて終了
        try {
          mmSocket.close();
        } catch (IOException e) {
          Log.e(app, "cannot be closed", e);
          return;
        }
        Log.d(app,"done!");
        return;
      }
    });
  }
}



次はLinux。BlueZとその周辺部が入ってることが前提。
C言語を使う。中身はおおよそRFCOMM socketsのrfcomm-server.cと同じ。
通信を受け付けて送信されたものを受信するだけ。

bluetooth.c
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>

int main(int argc, char **argv)
{
    struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 };
    char buf[1024] = { 0 };
    int s, client, bytes_read;
    socklen_t opt = sizeof(rem_addr);

    // allocate socket
    s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

    // bind socket to port 1 of the first available 
    // local bluetooth adapter
    loc_addr.rc_family = AF_BLUETOOTH;
    loc_addr.rc_bdaddr = *BDADDR_ANY;
    loc_addr.rc_channel = (uint8_t) 22;
    bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));

    // put socket into listening mode
    listen(s, 1);

    // accept one connection
    client = accept(s, (struct sockaddr *)&rem_addr, &opt);

    ba2str( &rem_addr.rc_bdaddr, buf );
    printf("accepted connection from %s\n", buf);
    memset(buf, 0, sizeof(buf));

    // read data from the client
    bytes_read = read(client, buf, sizeof(buf));
    if( bytes_read > 0 ) {
        printf("received [%s]\n", buf);
    }

    // close connection
    close(client);
    close(s);
    return 0;
}



で、Makefile
#include <stdio.h>
CC = g++
INCDIR = -I/usr/include/bluetooth
LIBS   = -lbluetooth

all: bluetooth

bluetooth: bluetooth.c
	$(CC) -o bluetooth bluetooth.c $(INCDIR) $(LIBS) -fpermissive

clean:
	rm bluetooth
あとはBluetoothの設定をして(Fedoraの場合は下のPSを参照)、そのフォルダに行ってmakeして実行すればおk。
# hciconfig hci0 piscan
# sdptool add --channel=22 SP
$ make
$ ./bluetooth
以下がAndroid側から"hello"と送った時の画像:
たぶんもうひと工夫しないと日本語は送れない・・・



PS:
Fedoraの場合は、そのままだとsdptoolに登録できないため、
"/etc/systemd/system/dbus-org.bluez.service"の"ExecStart"から始まる行の尻に"--compat"をつければいいらしい。
ExecStart=/usr/lib/bluetooth/bluetoothd --compat
↑こんな感じ
できたら
# systemctl daemon-reload
# systemctl restart bluetooth
# sudo chmod 777 /var/run/sdp
で、読み直して終わり。
参考:raspbian - Failed to connect to SDP server on FF:FF:FF:00:00:00: No such file or directory - Raspberry Pi Stack Exchange

拍手

コメント













カウンター

カレンダー

04 2017/05 06
S M T W T F S
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31

アーカイブ

AD

Azkishはamazon.co.jpを宣伝しリンクすることによって サイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイト宣伝プログラムである、 Amazonアソシエイト・プログラムの参加者です。