エンジニア

AndroidでBluetooth通信を行おう

投稿日:2014年8月12日 更新日:

今回のエンジニアブログを担当する大原です。

以前のブログ「AndroidでBluetooth対応デバイスを検索しよう」でデバイスを検索するところまで書きました。
今回はその続きで、Bluetoothのサーバーとクライアントで実際の通信を行うところを書いていきたいと思います。

まずは、メインのActivityのjavaファイルに通信するためのクラスとコールバックを追加していきます。

public class blog_BT extends Cocos2dxActivity{
    /**  
    * ブルートゥースを扱うためのアダプタークラス  
    */
    private final BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    protected BluetoothSocket mSocket;
    private static final int REQUEST_DISCOVERABLE_BT = 5000;
    private static final int WAIT_TIME = 120;
    private static ArrayList<String> mCandidateServers;
//---------------- (ここから) -----------------  
    //ターミナルでuuidgenを叩いてUUIDを作成してね  
    private final UUID mUuid = UUID.fromString("C150069C-E494-4E4A-AF6E-A2B3EF36BA4C");
    private ServerThread mServerThread;    //サーバー用のスレッド  
    private ClientThread mClientThread;    //クライアント用のスレッド  
//---------------- (ここまで追加) -----------------  

    //cocos2dにコールバック  
    //ペアリング待ち  
    public native static void callBackPairing();
    //デバイス検索結果  
    public native static void callBackEndSearch(String[] devices);
//---------------- (ここから) -----------------  
    //通信接続コールバック  
    public native static void callbackConnect();
    //受信メッセージ  
    public native static void callBackReceiveMessage(String mes);
//---------------- (ここまで追加) -----------------

規定されているUUIDを指定することにより、既存のプロトコルと通信することができますが、
今回は、独自のプロトコルとして通信を行いため、自分でUUIDを生成します。

今回はサーバーにも、クライアントにもなれるようにソースを書くため、
ServerThread、ClientThreadを宣言します。
通信接続成功を知らせるためのcallbackConnect()と
受信メッセージを受取るためのcallBackReceiveMessage()を追加します。

クライアントの接続をするために、以下のコードを書いていきます。

   //============================  
    //クライアント側の処理  
    //============================  
    /**  
    * クライアントのスレッド  
    */
    private class ClientThread extends ReceiverThread  {
        private final BluetoothDevice mServer;

        private ClientThread(String address) {
            mServer = mBluetoothAdapter.getRemoteDevice(address);
            try {
                mSocket = mServer.createRfcommSocketToServiceRecord(mUuid);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            // connect() の前にデバイス検出をやめる必要がある  
            mBluetoothAdapter.cancelDiscovery();
            try {
                // サーバに接続する  
                mSocket.connect();
                callbackConnect();
                loop();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                cancel();
            }

        }

        private void cancel() {
            try {
                mSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    //接続  
    public static void connect(String address)
    {
        getActivity().onConnect(address);
    }
    public void onConnect(String address) {
        int index;
        if ((index = address.indexOf("\n")) != -1) {
            address = address.substring(index + 1);
        }
        // クライアント用のスレッドを生成  
        mClientThread = new ClientThread(address);
        mClientThread.start();
    }

後からReceiverThreadについては書いていきますが、受信スレッドReceiverThreadを継承し、クライアントスレッドを実装していきます。

デバイス接続先を見つけるにはmBluetoothAdapter.getRemoteDevice()に前回のブログで書いたコードを使って、検出できたデバイスのMACアドレスを渡します。
接続したデバイスクラスのcreateRfcommSocketToServiceRecord()にUUIDを渡すことで、独自プロトコルによる通信ソケットが生成されます。
通信ソケットクラスのconnect()を呼び出すことにより、クライアントの接続待ちになります。

次にサーバー待ちをするために、以下のコードを書いていきます。

   //============================  
    //サーバー側の処理  
    //============================  
    /**  
    * サーバーのスレッド  
    *  
    */
    private class ServerThread extends ReceiverThread  {
        private BluetoothServerSocket mServerSocket;

        private ServerThread() {
            try {
                mServerSocket = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(
                        getActivity().getPackageName(), mUuid);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            try {
                mSocket = mServerSocket.accept();
                callbackConnect();
                loop();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                cancel();
            }
        }

        private void cancel() {
            try {
                mServerSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //サーバー接続待ち開始  
    public static void startServer()
    {
        getActivity().onStartServer();
    }
    public void onStartServer() {
        if (mServerThread != null) {
            mServerThread.cancel();
        }
        mServerThread = new ServerThread();
        mServerThread.start();
    }

    public void cancel() {
        if (mServerThread != null) {
            mServerThread.cancel();
            mServerThread = null;
        }
        if (mClientThread != null) {
            mClientThread.cancel();
            mClientThread = null;
        }
    }

サーバーソケットもクライアントと同じようにmBluetoothAdapter.listenUsingRfcommWithServiceRecord()へUUIDを指定して
ソケットのインスタンスを生成します。
サーバーソケットのaccept()を呼び出すことにより、受付待ちになります。

以下にメッセージの送信と受信スレッドについてを書いていきます。

   /**  
    * cocos2dからメッセージの送信  
    */
    public static void sendMessage(String message)
    {
        getActivity().onSendMessage(message);
    }
    /**  
    * メッセージの送信  
    */
    public void onSendMessage(String message) {
        try {
            if (mServerThread != null) {
                mServerThread.sendMessage(message);
            }
            if (mClientThread != null) {
                mClientThread.sendMessage(message);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**  
    * 受信スレッド  
    */
    private abstract class ReceiverThread extends Thread {
        protected BluetoothSocket mSocket;

        /**  
        * 送信  
        */
        protected void sendMessage(String message) throws IOException {
            OutputStream os = mSocket.getOutputStream();
            os.write(message.getBytes());
            os.write("\n".getBytes());
        }

        protected void loop() throws IOException {
            BufferedReader br = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
            String message;
            while ((message = br.readLine()) != null) {
                callBackReceiveMessage(message);
            }
        }
    }

メッセージの送信をする場合、BluetoothSocketのgetOutputStream()やgetInputStream()を使うことで、簡単にメッセージのやり取りを行うことができます。

あとは、いつもの様にJNI経由でcocos2d-xに戻してあげます。

BluetoothJni.h

#ifndef __BLUETOOTH_JNI_H__  
#define __BLUETOOTH_JNI_H__  

#include "BluetoothWrapper.h"
#include <jni.h>

#ifdef __cplusplus  
extern "C" {
#endif

    extern void activeBluetoothJni();
    extern void pairingBluetoothJni(BluetoothWrapper* instance);
    extern void searchDeviceJni(BluetoothWrapper* instance);

//---------------------(ここから)-------------------------  
    extern void sendMessageJni(const char* message);
    extern void connectJni(BluetoothWrapper* instance,const char* address);
    extern void startServerJni(BluetoothWrapper* instance);
//---------------------(ここまで追加)-------------------------  

    // androidから呼ばれるコールバック関数  
    JNIEXPORT void JNICALL Java_jp_wonderplanet_blog_blog_1BT_callBackPairing(JNIEnv* env, jobject thiz);
    JNIEXPORT void JNICALL Java_jp_wonderplanet_blog_blog_1BT_callBackEndSearch(JNIEnv* env, jobject thiz,jobjectArray inJNIArray);
//---------------------(ここから)-------------------------  
    JNIEXPORT void JNICALL Java_jp_wonderplanet_blog_blog_1BT_callBackConnect(JNIEnv* env, jobject thiz);
    JNIEXPORT void JNICALL Java_jp_wonderplanet_blog_blog_1BT_callBackReceiveMessag(JNIEnv* env, jstring srcj);
//---------------------(ここまで追加)-------------------------  

#ifdef __cplusplus  
}
#endif

#endif

BluetoothJni.cpp

//---------------------(ここから)-------------------------  
    /**  
    * メッセージの送信  
    */
    void sendMessageJni(const char* message)
    {
        JniMethodInfo methodInfo;

        if (! JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "sendMessage", "(Ljava/lang/String;)V"))
        {
            return;
        }
        jstring strMsg = methodInfo.env->NewStringUTF(message);

        methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, strMsg);
        methodInfo.env->DeleteLocalRef(strMsg);
        methodInfo.env->DeleteLocalRef(methodInfo.classID);
    }

    /**  
    * サーバーに接続  
    */
    void connectJni(BluetoothWrapper* instance,const char* address)
    {
        JniMethodInfo methodInfo;

        __instance = instance;
        __instance->retain();

        if (! JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "connect", "(Ljava/lang/String;)V"))
        {
            return;
        }
        jstring strMsg = methodInfo.env->NewStringUTF(address);

        methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, strMsg);
        methodInfo.env->DeleteLocalRef(strMsg);
        methodInfo.env->DeleteLocalRef(methodInfo.classID);
    }

    /**  
    * サーバーの開始  
    */
     void startServerJni(BluetoothWrapper* instance)
    {
        JniMethodInfo methodInfo;

        __instance = instance;
        __instance->retain();

        if (! JniHelper::getStaticMethodInfo(methodInfo, CLASS_NAME, "startServer", "()V"))
        {
            return;
        }
        methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID);
        methodInfo.env->DeleteLocalRef(methodInfo.classID);
    }

    // androidから呼ばれるコールバック関数  
    JNIEXPORT void JNICALL Java_jp_wonderplanet_blog_blog_1BT_callBackPairing(JNIEnv* env, jobject thiz)
    {
        __instance->callBackPairing();
        __instance->release();
    }
//---------------------(ここまで追加)-------------------------  

    //検索終了で呼ばれるコールバック  
    JNIEXPORT void JNICALL Java_jp_wonderplanet_blog_blog_1BT_callBackEndSearch(JNIEnv* env, jobject thiz,jobjectArray inJNIArray)
    {
        std::vector<std::string> ret;

        jsize length = env->GetArrayLength(inJNIArray);
        for (int i = 0; i < length; i++) {
            jstring jstr = (jstring)env->GetObjectArrayElement( inJNIArray, i);
            const char *inCStr = env->GetStringUTFChars( jstr, NULL);
            if (NULL == inCStr) return;

            ret.push_back(std::string(inCStr));
        }

        __instance->callBackEndSearch(ret);
        __instance->release();
    }

//---------------------(ここから)-------------------------  
    //接続完了した時に呼ばれるコールバック  
    JNIEXPORT void JNICALL Java_jp_wonderplanet_blog_blog_1BT_callBackConnect(JNIEnv* env, jobject thiz)
    {
        __instance->callBackConnect();
        __instance->release();
    }
    //受信メッセージ  
    JNIEXPORT void JNICALL Java_jp_wonderplanet_blog_blog_1BT_callBackReceiveMessag(JNIEnv* env, jstring srcj)
    {
        const char* message = env->GetStringUTFChars(srcj, NULL);

        __instance->receiveMessag(message);
        __instance->release();

        env->ReleaseStringUTFChars(srcj, message);
    }
//---------------------(ここまで追加)-------------------------  

BluetoothWrapper.h

typedef void (CCObject::*SEL_ReceiveMessage)(const char*);

class BluetoothWrapper : public CCObject
{
private:
    CCObject* m_pListener;
    SEL_CallFunc m_callbackPairing;
    SEL_DeviceList m_callbackDevice;
    SEL_CallFunc m_callbackConnect;
    SEL_ReceiveMessage m_callbackReceive;

public:
    BluetoothWrapper();
    ~BluetoothWrapper();

    /**  
     * インスタンスを取得  
     * @return 本クラスの唯一のインスタンス  
     */
    static BluetoothWrapper* sharedInstance();

    /**  
     * Bluetooth ON  
     */
    void activeBluetooth();
    /**  
     * ペアリング  
     */
    void pairingBluetooth(CCObject* target, SEL_CallFunc callback);
    void callBackPairing();
    /**  
    * デバイスSearch  
    */
    void searchDevice(CCObject* target, SEL_DeviceList callback);
    void callBackEndSearch( std::vector< std::string > list );

//---------------------(ここから)-------------------------  
    /**  
     * サーバー待ち開始  
     */
    void startServerBT(CCObject* target, SEL_CallFunc callback);
    /**  
     * クライアント接続開始  
     */
    void connectBT(CCObject* target, SEL_CallFunc callback,const char* address);

    /**  
     * 接続確認  
     */
    void callBackConnect();

    /**  
     * メッセージの送信  
     */
    void sendMessage(const char* message);

    /**  
     * メッセージ受信のコールバック  
     */
    void setReceive(CCObject* target, SEL_ReceiveMessage callback);
    /**  
     * メッセージの受信  
     */
    void receiveMessag(const char* message);
//---------------------(ここまで追加)-------------------------  
};

BluetoothWrappar.cpp

//---------------------(ここから)-------------------------  
    //メッセージの送信  
    void BluetoothWrapper::sendMessage(const char* message)
    {
        sendMessageJni(message);
    }

    void BluetoothWrapper::setReceive(CCObject* target, SEL_ReceiveMessage callback)
    {
        m_pListener = target;
        m_pListener->retain();

        m_callbackReceive = callback;
    }
    //メッセージの受信  
    void BluetoothWrapper::receiveMessag(const char* message)
    {
        if (m_pListener)
        {
            (m_pListener->*m_callbackReceive)(message);
            m_pListener->release();
        }
    }

    /**  
    * サーバー待機  
    */
    void BluetoothWrapper::startServerBT(CCObject* target, SEL_CallFunc callback)
    {
        m_pListener = target;
        m_pListener->retain();

        m_callbackConnect = callback;

        startServerJni(this);
    }
    /*  
     * サーバーに接続  
     */
    void BluetoothWrapper::connectBT(CCObject* target, SEL_CallFunc callback,const char* address)
    {
        m_pListener = target;
        m_pListener->retain();

        m_callbackConnect = callback;

        connectJni(this,address);
    }

    void BluetoothWrapper::callBackConnect()
    {
        if (m_pListener)
        {
            (m_pListener->*m_callbackConnect)();
            m_pListener->release();
        }
    }
//---------------------(ここまで追加)-------------------------  

以上のコードを実装することで、Bluetoothでの通信によるメッセージのやり取りが出来るようになります。

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-,

© WonderPlanet Inc.