04 February 2014

BluetoothSPP - การเชื่อมต่ออุปกรณ์ผ่านบลูทูธแบบง่ายๆด้วย BluetoothSPP

Updated on
        บทความนี้เก่ามากแล้ว และหยุดพัฒนาต่อแล้ว

        จากที่เจ้าของบล็อกได้เกริ่นถึงคลาส BluetoothSPP ที่เจ้าของบล็อกลองนั่งเขียนเองแล้ว ทีนี้มาพูดถึงวิธีการใช้งานเบื้องต้นกันบ้าง

        สำหรับผู้ที่หลงเข้ามาอ่านคนใดที่ยังไม่ได้อ่านบทความแนะนำ อยากให้ลองเข้าไปอ่านคร่าวๆก่อนที่ [Android Code] เปลี่ยนเรื่องบลูทูธให้เป็นเรื่องง่ายด้วย BluetoothSPP Library


        สำหรับตัวอย่างนี้จะเป็นตัวอย่างการใช้งานเบื้องต้นเท่านั้น โดยจะมีตัวอย่างการทำงานหลักๆดังนี้
                • มีปุ่มกดเพื่อส่งข้อความตามที่กำหนดไว้ในคำสั่ง
                • เมื่อมีข้อมูลถูกส่งเข้ามาจะแสดงข้อมูลผ่าน Toast บนหน้าจอ
                • แสดง Toast เมื่อเชื่อมต่อกับอุปกรณ์ต่างๆได้ หรือไม่ได้ หรือหยุดการเชื่อมต่อ
                • ปุ่มเลือกอุปกรณ์ที่เชื่อมต่อ เมื่อกำลังเชื่อมต่ออยู่พอกดอีกครั้งจะหยุดเชื่อมต่อ


        ก่อนจะใช้งานก็อย่าลืมดาวน์โหลด BluetoothSPP มา Import ไว้ใน Eclipse กันเสียก่อน โดยดาวน์โหลดได้จาก

                BluetoothSPP [GitHub]

                BluetoothSPP [Google Drive]

                BluetoothSPP [Sleeping For Less]


        เมื่อดาวน์โหลดเสร็จแล้วก็นำมา Import ซะ แล้วสร้างโปรเจคของผู้ที่หลงเข้ามาอ่านขึ้นมา แล้วกำหนดให้ใช้งานไลบรารี BluetoothSPP ด้วย



        แล้วกำหนดในไฟล์ project.properties เพิ่มเข้าไปว่า

manifestmerger.enabled=true

        ซึ่งคำสั่งนี้จะทำให้ Android Manifest ของโปรเจคที่สร้างกับไลบรารีนั้นถูกรวมเข้าด้วยกัน



        และต้องประกาศ Permission ไว้ใน Android Manifest ดังนี้

<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />


        สำหรับหัวใจสำคัญหลักๆในการใช้งาน BluetoothSPP มีดังนี้

        ประกาศและกำหนดค่าตัวแปรของ BluetoothSPP (ไม่งั้นก็ใช้งานไม่ได้น่ะสิ....)

BluetoothSPP bt = new BluetoothSPP(this);


        หรือจะกำหนดแบบระบุชื่อ Activity ด้วยเลยก็ได้

BluetoothSPP bt = new BluetoothSPP(Main.this);


        โดยให้ประกาศชื่อตัวแปรเป็น Global ส่วนกำหนดค่าก็เอาไว้ใน onCreate ของ Activity ที่ต้องการใช้งานบลูทูธ

public class Main extends Activity {
    BluetoothSPP bt;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        bt = new BluetoothSPP(this);
    }
}

        เมื่อประกาศเสร็จแล้ว อย่างแรกสุดก็ควรจะเช็คก่อนว่าเครื่องรองรับบลูทูธหรือไม่ ถ้าไม่รองรับก็จะให้ปิดแอปพลิเคชันทันทีแล้วแสดงข้อความแจ้งผู้ใช้ผ่าน Toast

        เพิ่มเติม - จริงๆแล้วแอปพลิเคชันเวลาอยู่บน Google Play แล้ว ถ้ามีการใช้งานบลูทูธด้วย เครื่องที่ไม่มีบลูทูธในตัวจะไม่สามารถดาวน์โหลดได้ แต่ก็ต้องมีการเช็คเผื่อไว้ในกรณีที่ผู้ใช้ถือวิสาสะแอบเอาไปติดตั้งแบบไฟล์ APK หรือเครื่องบางรุ่นที่เป็นเครื่องจีนแล้วเฟิร์มแวร์ห่วยๆที่ผู้ผลิตใส่ไว้ว่ารองรับบลูทูธทั้งๆที่เครื่องนั้นไม่มีบลูทูธ ทำให้โหลดแอพบลูทูธจาก Google Play ได้ แต่ใช้งานไม่ได้

if(!bt.isBluetoothAvailable()) {
    Toast.makeText(getApplicationContext(), "Bluetooth is not available", Toast.LENGTH_SHORT).show();
    finish();
}


        สำหรับ onCreate โดยหลักๆจะมีเบื้องต้นแค่นั้น (แต่เดี๋ยวมีเพิ่มอีกทีหลัง) ทีนี้มาต่อกันที่ onStart โดยจะให้เรียกใช้งาน Service ของคลาส BluetoothSPP หลังจากที่ประกาศใช้งานใน onCreate

public void onStart() {
    super.onStart();
    if(!bt.isBluetoothEnabled()) {
        Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
        startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
    } else {
        bt.setupService();
        bt.startService(BluetoothState.DEVICE_ANDROID);
        setup();
    }
}

        จะทำการตรวจสอบก่อนว่าบลูทูธเปิดใช้งานอยู่หรือไม่ ถ้าไม่ก็จะให้ Intent เพื่อร้องขอเปิดใช้งานบลูทูธ


        สำหรับ Intent อันนี้เมื่อผู้ใช้เลือก Deny หรือ Allow แล้วจะ Return กลับมาที่ฟังก์ชัน onActivityResult อีกที ซึ่งเดี๋ยวค่อยว่ากันทีหลัง

        แต่ในกรณีที่บลูทูธเปิดใช้งานอยู่แล้ว ก็จะเช็คว่า Service พร้อมใช้งานหรือยัง ถ้าไม่พร้อมใช้งานก็จะทำการกำหนดค่าให้กับ Service แล้วเรียกใช้ฟังก์ชัน setup ซึ่งฟังก์ชันนี้จะให้สร้างขึ้นมาเอง เอาไว้กำหนดค่าต่างๆเมื่อบลูทูธพร้อมใช้งานแล้ว เช่น ให้ปุ่มส่งข้อความสามารถทำงานได้ เป็นต้น

        เพิ่มเติม - ถ้าเอาไปกำหนดไว้ใน onCreate แล้ว Service ไม่พร้อมใช้งาน พร้อมกับผู้ใช้ไปกดปุ่มส่งข้อความ ผลก็คือแอพเด้งเพราะเออเรอนั่นเอง


        จะสังเกตุเห็นว่าคำสั่ง startService มีการกำหนดค่าเป็น DEVICE_ANDROID ด้วย ทั้งนี้เพราะเป็นเรื่องของ UUID ที่ใช้เชื่อมต่อกับอุปกรณ์อื่นๆ ซึ่งมีให้กำหนดค่าสองแบบดังนี้

bt.startService(BluetoothState.DEVICE_ANDROID);

bt.startService(BluetoothState.DEVICE_OTHER);

        DEVICE_ANDROID จะเป็นการใช้ UUID ที่ใช้เชื่อมต่ออุปกรณ์แอนดรอยด์ด้วยกัน

        DEVICE_OTHER เป็นอุปกรณ์อื่นๆที่ไม่ใช่อุปกรณ์แอนดรอยด์ อย่างเช่น โมดูล BlueStick ของ INEX เพื่อเอาไปใช้สั่งงานกับอุปกรณ์ไมโครคอนโทรลเลอร์ผ่านบลูทูธ เป็นต้น


        ดังนั้นจะเชื่อมต่อกับอะไรก็กำหนดค่าไว้ให้ดี หรือจะทำเป็นเมนูเลือกก็ได้ว่าจะเชื่อมต่อแบบไหน


        ส่วนฟังก์ชัน setup ที่เรียกใช้งาน ในนี้ก็เอาไว้กำหนดค่าตามที่บอกนั่นแหละ

public void setup() {

}


        ทีนี้มาต่อกันที่ onDestroy เวลาที่ Activity ถูกปิดลงจะต้องทำการใช้คำสั่งปิด Service ของคลาส BluetoothSPP ด้วย

public void onDestroy() {
    super.onDestroy();
    bt.stopService();
}


        ต่อมาเป็นการไปยังหน้าเลือกอุปกรณ์ที่จะเชื่อมต่อด้วย ซึ่งอันนี้จะใช้วิธี Intent ไป Activity ที่เจ้าของบล็อกได้เขียนไว้ในคลาส BluetoothSPP แล้วนั่นเอง

Intent intent = new Intent(getApplicationContext(), DeviceList.class);
startActivityForResult(intent, BluetoothState.REQUEST_CONNECT_DEVICE); 


        ในกรณีที่อยากให้เปิดหน้านั้นๆขึ้นมาแล้วไปยังหน้าเลือกอุปกรณ์ทันที ก็ให้ใส่คำสั่งนี้ไว้ในท้ายสุดของฟังก์ชัน setup ได้เลย

public void setup() {
    ...
    ...
    ... 
    
    Intent intent = new Intent(Main.this, DeviceList.class);
    startActivityForResult(intent, BluetoothState.REQUEST_CONNECT_DEVICE);
}


        แต่ถ้าอยากให้กดปุ่มแล้วค่อยไปหน้าเชื่อมต่อก็เอาไปใส่ไว้ใน onClickListener ของปุ่มนั้นๆ

Button btnConnect = (Button)findViewById(R.id.btnConnect);
btnConnect.setOnClickListener(new OnClickListener(){
    public void onClick(View v){
        Intent intent = new Intent(Main.this, DeviceList.class);
        startActivityForResult(intent, BluetoothState.REQUEST_CONNECT_DEVICE); 
    }
});


        สุดท้ายคือ onActivityResult ที่พูดค้างไว้ตอน Intent นั่นเอง

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if(requestCode == BluetoothState.REQUEST_CONNECT_DEVICE) {
        if(resultCode == Activity.RESULT_OK)
            bt.connect(data);
    } else if(requestCode == BluetoothState.REQUEST_ENABLE_BT) {
        if(resultCode == Activity.RESULT_OK) {
            bt.setupService();
            bt.startService(BluetoothState.DEVICE_ANDROID);
            setup();
        } else {
            Toast.makeText(getApplicationContext(), "Bluetooth was not enabled."
                    , Toast.LENGTH_SHORT).show();
            finish();
        }
    }
}

        ใน onActivityResult นี้จะทำหน้าที่รอ Return กลับมาจากการ Intent ไปที่อื่น ซึ่งในตัวอย่างของเจ้าของบล็อกคือ Intent ไปเพื่อเปิดใช้งานบลูทูธในกรณีที่ยังไม่ได้เปิด (REQUEST_ENABLE_BT) และ Intent เพื่อไปยังหน้าเลือกอุปกรณ์เพื่อเชื่อมต่อ (REQUEST_CONNECT_DEVICE)

        ถ้า Return กลับมาเป้นของ REQUEST_ENABLE_BT ก็จะเช็คว่าผลลัพธ์เป็น RESULT_OK หรือไม่ ถ้าใช่ก็จะหมายถึงผู้ใช้กดเปิดบลูทูธนั่นเอง ก็จะให้ทำการกำหนดค่า Service และสั่งให้เริ่มทำงานโดยกำหนดอุปกรณ์เป็น DEVICE_ANDROID (สามารถเปลี่ยนได้ตามที่พูดไว้ก่อนหน้านี้) แต่ถ้าผลลัพธ์ไม่ใช่ RESULT_OK ก็จะทำการปิดแอปพลิเคชันทันทีพร้อมกับแสดงข้อความแจ้งผู้ใช้ผ่าน Toast เพราะผู้ใช้กดยกเลิกการเปิดใช้งานบลูทูธนั่นเอง

        ถ้า Return กลับมาเป็นของหน้า REQUEST_CONNECT_DEVICE ก็จะเช็คว่าผลลัพธ์เป็น RESULT_OK หรือป่าว ถ้าใช่ก็จะทำการเชื่อมต่อบลูทูธกับอุปกรณ์ที่เลือก


        เท่านี้ก็เสร็จแล้วกับคำสั่งหลักๆ

        แต่ว่านั่นก็ยังไม่พอ เพราะต้องมีคำสั่งเสริมเพื่อทำให้แอปพลิเคชันสมบูรณ์มากขึ้น เช่น

        ในฟังก์ชัน setup ที่เจ้าของบล็อกพูดไว้ แต่ในนั้นกลับไม่ได้ใส่คำสั่งอะไรเลย ทั้งนี้ก็เพราะว่าขึ้นอยู่กับผู้ใช้นั่นเอง ว่าจะมีอะไรบ้างที่สั่งงานบลูทูธ

        สมมติว่าเจ้าของบล็อกสร้างปุ่ม Button ขึ้นมาตัวนึง เอาไว้ส่งข้อความว่า Text  ก็กำหนดในนี้เลย

public void setup() {
    Button btnSend = (Button)findViewById(R.id.btnSend);
    btnSend.setOnClickListener(new OnClickListener(){
        public void onClick(View v){
            bt.send("Text", true);
        }
    });
}

        จะมีสิบปุ่มก็ได้ตามใจชอบ เวลากำหนดก็มากำหนดไว้ในฟังก์ชัน setup ละกัน

        ต่อมาก็คือ Listener สำหรับ Event การเชื่อมต่อบลูทูธ เพื่อให้สามารถรู้สถานะการเชื่อมต่อบลูทูธได้ โดยจะมี 3 Event ด้วยกัน คือ

                • onDeviceConnected เมื่อเชื่อมต่อกับอุปกรณ์นั้นๆได้

                • onDeviceDisconnected เมื่อหยุดทำการเชื่อมต่อ

                • onDeviceConnectionFailed เมื่อไม่สามารถเชื่อมต่อได้


        โดยจะให้แสดงสถานะการเชื่อมต่อให้ผู้ใช้เห็นผ่าน Toast

bt.setBluetoothConnectionListener(new BluetoothConnectionListener() {
    public void onDeviceConnected(String name, String address) {
        Toast.makeText(getApplicationContext(), "Connected to " + name + "\n" + address
                , Toast.LENGTH_SHORT).show();
    }

    public void onDeviceDisconnected() {
        Toast.makeText(getApplicationContext(), "Connection lost", Toast.LENGTH_SHORT).show();
    }

    public void onDeviceConnectionFailed() {
        Toast.makeText(getApplicationContext(), "Unable to connect", Toast.LENGTH_SHORT).show();
    }
});

        ถ้าแอปพลิเคชันที่สร้างมีการรับข้อมูลจากอุปกรณ์ที่เชื่อมต่อด้วย ก็ควรมีการประกาศใช้ Listener สำหรับ Event การรับข้อมูลทางบลูทูธ เพื่อให้สามารถรับข้อมูลที่ส่งเข้ามาได้ โดยจะมี 1 Event เท่านั้น คือ

                • onDataReceived เมื่อมีข้อมูลส่งเข้ามา

        โดยจะให้ทำการแสดงข้อความที่รับเข้ามาให้ผู้ใช้งานเห็นผ่าน Toast

bt.setOnDataReceivedListener(new OnDataReceivedListener() {
    public void onDataReceived(byte[] data, String message) {
        Toast.makeText(Main.this, message, Toast.LENGTH_SHORT).show();
    }
});

        สำหรับ Listener สามารถประกาศไว้ใน onCreate ได้เลย ต่อท้ายจากการประกาศใช้งานคลาส BluetoothSPP และตรวจสอบว่าเครื่องมีบลูทูธแล้ว แต่มีหนึ่งสิ่งที่สำคัญคือจะทำงานก็ต่อเมื่อรับข้อมูลที่มี LF(0x0A) กับ CR(0x0D) ต่อท้ายเท่านั้น ถ้าส่งมาแบบไม่มีตัวปิดก็จะไม่แสดง และเก็บไว้เรื่อยๆจนกว่าจะส่งตัวปิดมา เพื่อป้องกันความไม่ต่อเนื่องของข้อมูล


        ดังนั้นใน Activity ของเจ้าของบล็อกก็จะมีคำสั่งทั้งหมดดังนี้

Main.java
package app.akexorcist.bluetoothsppsimple;

import android.os.Bundle;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.content.Intent;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
import app.akexorcist.bluetoothspp.BluetoothSPP;
import app.akexorcist.bluetoothspp.BluetoothSPP.BluetoothConnectionListener;
import app.akexorcist.bluetoothspp.BluetoothSPP.OnDataReceivedListener;
import app.akexorcist.bluetoothspp.BluetoothState;
import app.akexorcist.bluetoothspp.DeviceList;

public class Main extends Activity {
    BluetoothSPP bt;
    
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        bt = new BluetoothSPP(this);

        if(!bt.isBluetoothAvailable()) {
            Toast.makeText(getApplicationContext(), "Bluetooth is not available"
                    , Toast.LENGTH_SHORT).show();
            finish();
        }
        
        bt.setOnDataReceivedListener(new OnDataReceivedListener() {
            public void onDataReceived(byte[] data, String message) {
                Toast.makeText(getApplicationContext(), message, Toast.LENGTH_SHORT).show();
            }
        });
        
        bt.setBluetoothConnectionListener(new BluetoothConnectionListener() {
            public void onDeviceConnected(String name, String address) {
                Toast.makeText(getApplicationContext(), "Connected to " + name + "\n" + address
                        , Toast.LENGTH_SHORT).show();
            }

            public void onDeviceDisconnected() {
                Toast.makeText(getApplicationContext(), "Connection lost"
                        , Toast.LENGTH_SHORT).show();
            }

            public void onDeviceConnectionFailed() {
                Toast.makeText(getApplicationContext(), "Unable to connect"
                        , Toast.LENGTH_SHORT).show();
            }
        });
        
        Button btnConnect = (Button)findViewById(R.id.btnConnect);
        btnConnect.setOnClickListener(new OnClickListener(){
            public void onClick(View v){
                if(bt.getServiceState() == BluetoothState.STATE_CONNECTED) {
                    bt.disconnected();
                } else {
                    Intent intent = new Intent(getApplicationContext(), DeviceList.class);
                    startActivityForResult(intent, BluetoothState.REQUEST_CONNECT_DEVICE); 
                }
            }
        }); 
    }
    
    public void onDestroy() {
        super.onDestroy();
        bt.stopService();
    }
    
    public void onStart() {
        super.onStart();
        if(!bt.isBluetoothEnabled()) {
            Intent intent= new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
            startActivityForResult(intent, BluetoothState.REQUEST_ENABLE_BT);
        } else {
            if(!bt.isServiceAvailable()) { 
                bt.setupService();
                bt.startService(BluetoothState.DEVICE_ANDROID);
                setup();
            }
        }
    }
    
    public void setup() {
        Button btnSend = (Button)findViewById(R.id.btnSend);
        btnSend.setOnClickListener(new OnClickListener(){
            public void onClick(View v){
                bt.send("Text", true);
            }
        });
    }
    
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if(requestCode == BluetoothState.REQUEST_CONNECT_DEVICE) {
            if(resultCode == Activity.RESULT_OK)
                bt.connect(data);
        } else if(requestCode == BluetoothState.REQUEST_ENABLE_BT) {
            if(resultCode == Activity.RESULT_OK) {
                bt.setupService();
                bt.startService(BluetoothState.DEVICE_ANDROID);
                setup();
            } else {
                Toast.makeText(getApplicationContext()
                        , "Bluetooth was not enabled."
                        , Toast.LENGTH_SHORT).show();
                finish();
            }
        }
    }
}


        สำหรับ Layout ที่เจ้าของบล็อกใช้ในตัวอย่างนี้ จะไม่มีอะไรมาก มีแค่ Button 2 อัน อันนึงกดเพื่อไปหน้าเลือกอุปกรณ์แอนดรอยด์ และอีกปุ่มสำหรับส่งข้อความไปทางบลูทูธ

main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/btnSend"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Send" />

    <Button
        android:id="@+id/btnConnect"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="Connect" />

</RelativeLayout>



        และ Android Manifest ของบทความนี้จะไม่ต้องกำหนดอะไร เพราะว่าได้ทำการรวม Android Manifest เข้ากับของไลบรารีแล้ว ทำให้ไม่ต้องประกาศอะไรเพิ่ม เนื่องจากในไลบรารีได้ประกาศไว้แล้ว


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.akexorcist.bluetoothsppsimple"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="19" />

    <uses-permission android:name="android.permission.BLUETOOTH" />
    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="app.akexorcist.bluetoothsppsimple.Main"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

        เมื่อเสร็จแล้วก็ลองทดสอบแอปพลิเคชันได้เลย ให้ลองกดเชื่อมต่อกับอุปกรณ์อื่นๆดู จะหาอุปกรณ์แอนดรอยด์มาสองเครื่อง แล้วลงแอปพลิเคชันนี้ไว้ทั้งคู่ แล้วเปิดขึ้นมาทำการเชื่อมต่อกันก็ได้ จากนั้นก็ลองส่งข้อความดู ลองกดยกเลิกการเชื่อมต่อดู แล้วจะเห็นการทำงานต่างๆที่ได้เขียนไว้ในโปรแกรม


        สำหรับผู้ที่หลงเข้ามาอ่านคนใดที่ต้องการไฟล์ตัวอย่างสามารถดาวน์โหลดได้จาก

                BluetoothSPP Simple [Google Drive]

                BluetoothSPP Simple [Github]

                BluetoothSPP Simple [Sleeping For Less]



บทความที่เกี่ยวข้อง

        • เปลี่ยนเรื่องบลูทูธให้เป็นเรื่องง่ายด้วย BluetoothSPP Library
        • การใช้งาน Listener สำหรับบลูทูธใน BluetoothSPP
        • การใช้งานการเชื่อมต่ออัตโนมัติในคลาส BluetoothSPP
        • การสร้างหน้าเลือกอุปกรณ์ที่จะเชื่อมต่อสำหรับ BluetoothSPP