יום שבת, 15 ביוני 2013

Android BlueTooth Guide



במאמר האחרון בנושא ראינו כיצד ניתן להשתמש ב GPS שבמכשיר לצרכי האפליקציה שלנו אבל קיימים עוד רכיבים מגניבים שצריך לעבור עליהם, אחד הדברים שסקרנו אותי הוא כיצד אני יכול לשלוט בעזרת הסמארטפון או הטאבלט על מכשירים אחרים והחלטתי להתמקד ב BlueTooth כי כמעט כל מכשיר תומך בתקשורת הזאת והיא נחשבת לזולה ואמינה, ה"שן הכחולה" מבוססת על גלי רדיו קצרים בתדר 2.4 Mhz שעד טווח של מאה מטר ובשנת 1999 הוגדרה כתקן וקיבלה המון שיפורים עם השנים.


android.bluetooth

המפתחים של Android כתבו מרחב שמות של מחלקות שמאפשר לנו להשתמש ב Bluetooth ולבצע פעולות רבות כמו חיפוש מכשירים, אירוח משתמשים או התחברות למארח, ההתממשקות מבוססת על כתובת ה MAC במכשיר ועל פרמטר ייחודי של האפליקציה, על מנת שנוכל לתקשר ב BlueTooth יש לעבוד ב Thread נפרד ולתת מספר הרשאות בקובץ Manifest  כפי שניתן לראות בדוגמה:

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

המחלקות העיקריות הן:

רכיב ה BlueTooth שעל המכשיר

מכשיר חיצוני 

אירוח מכשירים חיצוניים

הקישור בין המכשירים


Android

הדוגמה הראשונה הופכת את המכשיר שלנו לשרת שממתין למכשירים חיצוניים שהתחברו אליו ובעצם אנחנו במצב האזנה אינסופי בעזרת AsyncTask שמאפשר לנו לעשות את התהליך ב Thread נפרד מבלי לחסום את האפליקציה.

private class BTListener extends AsyncTask<String, String, String> {

private BluetoothServerSocket mmServerSocket;
private OutputStream mmOutStream;
private InputStream mmInStream;
private final String NAME = "BlueTooth Test Application";
private UUID MY_UUID;
public String message = "";

//getting application uid and start to listen
public BTListener(String CODE) {
MY_UUID = UUID.fromString(CODE);

//getting device adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter
.getDefaultAdapter();
if (mBluetoothAdapter == null) {
// no bluetooth adapter found.
return;
}

BluetoothServerSocket tmp = null;
try {
tmp = mBluetoothAdapter.listenUsingRfcommWithServiceRecord(
NAME, MY_UUID);
} catch (IOException e) {
}

mmServerSocket = tmp;
}

// create listener to start serving the client.
private void executeListener() {

BluetoothSocket socket = null;
message = "";

while (true) {

try {
// enter to blocking mode until connection create, or timeout
socket = mmServerSocket.accept();

// getting the sockets
mmInStream = socket.getInputStream();
mmOutStream = socket.getOutputStream();

int bytesRead = 0;

int bufferSize = 1024;
byte[] buffer = new byte[1024];

message = "";
// get the message from client and update the gui.
while (true) {

bytesRead = mmInStream.read(buffer);

if (bytesRead != -1) {
while ((bytesRead == bufferSize)
&& (buffer[bufferSize] != 0)) {
message = message
+ new String(buffer, 0, bytesRead);
bytesRead = mmInStream.read(buffer);
}
// update the gui from another thread.
publishProgress(new String(buffer, 0, bytesRead));

}
}
} catch (IOException e) {
break;
} finally {
if (socket != null) {
try {
mmServerSocket.close();
} catch (IOException e) {
break;
}
}
}
}

}

// write message to the outputstream
public void write(String msg) {

byte[] buffer = msg.getBytes();
try {
mmOutStream.write(buffer);

} catch (IOException e) {
Log.e(TAG, "Exception during write", e);
}
}

@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);

}

@Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
// sending information to function in the activity thread
setLblMessage(values[0]);
}

@Override
protected String doInBackground(String... params) {
executeListener();
return null;
}

}
}

על מנת להשלים את התמונה אנחנו צריכים מכשיר שיתחבר ל Android בהתחלה חשבתי להתחבר עם מכשיר Android נוסף, האמת שזו הזדמנות לשלב פלטפורמה נוספת כמו מחשב פיסי רגיל ובעזרת .Net לכתוב את הקוד , אבל העסק לא בא בחבילה אחת כמו ב Android וצריך להשתמש בספריות צד שלישי, החלטתי להשתמש עבור הדוגמה ב 32Feet.net שהיא מאוד נוחה וכמובן חינמית, המחלקה הזו יוצרת חיבור עם המכשיר שלנו ובעצם פותחת את ה Socket שמאפשר למכשירים לתקשר אחד עם השני, בדומה ל Android גם פה ניכנס למצב האזנה אינסופי ובעזרת BackgroundWorker נעשה זאת ב Thread חיצוני. 



.Net

מקבלים את רשימת המכשירים שמארחים בעזרת הפונקציה getAdapter ,יוצרים אובייקט חדש מסוג BTDesktopCLient ומעמיסים עליו את המכשיר המתאים יחד עם המפתח היחודי של האפליקציה.

using InTheHand.Net.Sockets;

 public class BTDesktopCLient
    {
        //return message to gui.
        public delegate void _delegateReadHandler(string msg);
        public event _delegateReadHandler readHandler;

        private const int MSGSIZE = 1024;

        BluetoothClient _client;
        BluetoothDeviceInfo _peer;
        BackgroundWorker _worker;
        Stream _peerStream;
        Guid _uid;
        private BluetoothAddress bluetoothAddress;
        private Guid _gu;

        public static BluetoothDeviceInfo[] getAdapter()
        {
            BluetoothClient tmp = new BluetoothClient();
            return tmp.DiscoverDevices();
        }

        //getting the device with application unique key
        public BTDesktopCLient(BluetoothDeviceInfo peer, Guid uid)
        {
            _uid = uid;
            _peer = peer;
            _worker = new BackgroundWorker();
            _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
         
            connect();
            _worker.RunWorkerAsync();
        }

        //make the first connection with the device 
        //sending the unique application UID.
        public void connect()
        {
            try
            {
                _client = new BluetoothClient();
                BluetoothEndPoint ep = new BluetoothEndPoint(_peer.DeviceAddress, _uid);
                _client.Connect(ep);
                _peerStream = _client.GetStream();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        //write to the socket
        public void Write(string msg)
        {
            if (!_client.Connected)
                return;
            try
            {

                byte[] msgBuff = Encoding.ASCII.GetBytes(msg);
                _peerStream.Write(msgBuff, 0, msgBuff.Length);
                _peerStream.Flush();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

        void _worker_DoWork(object sender, DoWorkEventArgs e)
        {
            //entering infinity loop
            looper();
        }

        void looper()
        {
            //starting listening to the connected socket for incoming stream
            while (true)
            {
                byte[] buff = new byte[MSGSIZE];
                int bytesRead = 0;

                bytesRead = _peerStream.Read(buff, 0, buff.Length);
                while (bytesRead != 0)
                {
                    readHandler(Encoding.ASCII.GetString(buff).Trim());
                    buff = new byte[MSGSIZE];
                    bytesRead = _peerStream.Read(buff, 0, buff.Length);
                }
             
            }
        }
    }

יופי, אז עד עכשיו ראינו כיצד Android מארח חיבורים חיצוניים כמו מחשב הפיסי הרגיל שיש לנו בבית השלב הבא הוא להפוך את מחשב הפיסי למארח של משתמש Android  אם נשים לב לקוד נראה ש BTListener מקבל רק את המפתח היחודי של האפליקציה ולעומת זאת ה BTDesktopCLient מקבל מפתח ו Device שאליו הוא מעוניין להתחבר נהפוך את התהליך בעזרת מחלקות נוספות.

Android

עכשיו על ה Android לחפש מכשירים וליזום את החיבור, ההבדל העיקרי הוא שיש לבצע Discovering בדומה ל BTDesktopClient ב .Net, על מנת שנוכל למצוא את המכשיר המארח ואת זה עושים בעזרת פונקציה פשוטה כמו בדוגמה הבאה:

private void discovery() {
mBluetoothAdapter.startDiscovery();
Set<BluetoothDevice> pairedDevices = mBluetoothAdapter
.getBondedDevices();

if (pairedDevices.size() > 0) {
for (BluetoothDevice device : pairedDevices) {
Log.d(TAG, device.getName());
//send the wanted device to BTClient
}
}
}


לאחר שמצאנו את המכשיר  נייצר אובייקט מסוג BTClient ונעביר אליו את ה Device ואת המפתח הייחודי של האפליקציה גם הפעם נצטרך להעזר ב AsyncTask על מנת לרוץ ברקע.

private class BTClient extends AsyncTask<String, String, String> {
                private final String TAG = null;
private BluetoothSocket mmSocket;
private BluetoothDevice mmDevice;
private BluetoothAdapter mBluetoothAdapter;
private InputStream mmInStream;
private OutputStream mmOutStream;
private UUID mmUUID;

//getting the device with application unique key
public BTClient(BluetoothDevice device, String CODE) {
mmUUID = UUID.fromString(CODE);
mmDevice = device;
BluetoothSocket tmp = null;

//getting the your own device adpater
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();

//try to create a socket
try {
tmp = device.createRfcommSocketToServiceRecord(mmUUID);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

mmSocket = tmp;

}

@Override
protected String doInBackground(String... params) {

//if our adapter on discovering, cancel it
if(mBluetoothAdapter.isDiscovering())
mBluetoothAdapter.cancelDiscovery();
try {
//make the connection and collect the streams
mmSocket.connect();
mmInStream = mmSocket.getInputStream();
mmOutStream = mmSocket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}

byte[] buffer = new byte[1024];
int bytes;
String message = "";

// keep listening to the InputStream while connected
while (true) {
try {
// read from the InputStream
bytes = mmInStream.read(buffer);
message = message + new String(buffer, 0, bytes);

publishProgress(new String(buffer, 0, bytes));
mmSocket.getInputStream();

} catch (IOException e) {
Log.e(TAG, "disconnected", e);
break;
}
}

return null;
}

//write message to the outputstream
public void write(String msg) {

byte[] buffer = msg.getBytes();
try {
mmOutStream.write(buffer);
} catch (IOException e) {
Log.e(TAG, "Exception during write", e);
}
}

@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
}

@Override
protected void onProgressUpdate(String... values) {
super.onProgressUpdate(values);
//sending information to function in the activity thread
setLblMessage(values[0]);
}

}


.Net

בדומה ל Android גם פה נאזין במצב אינסופי על המפתח הייחודי ובעזרת BackgroundWorker ,נמתין עד שמשתמש יבקש להתחבר ונפתח Socket מולו.

using InTheHand.Net.Sockets;

public class BTDesktopListener
    {
        // return message to gui.
        public delegate void _delegateReadHandler(string msg);
        public event _delegateReadHandler readHandler;

        private const int MSGSIZE = 1024;

        Guid _uid;
        BluetoothListener _listener;
        BluetoothClient _client;
        Stream _peerStream;

        BackgroundWorker _worker;

        // getting application uid and start to listen
        public BTDesktopListener(Guid uid)
        {
            _uid = uid;
            _worker = new BackgroundWorker();
            _worker.DoWork += new DoWorkEventHandler(_worker_DoWork);
            _worker.RunWorkerAsync();

        }


        // create listener to start serving the client.
        void _worker_DoWork(object sender, DoWorkEventArgs e)
        {
           //entering infinity loop
            looper();
        }

        void looper()
        {
            _listener = new BluetoothListener(_uid);
            _listener.Start();

            while (true)
            {
                // enter to blocking mode until connection create, or timeout
                _client = _listener.AcceptBluetoothClient();
                _peerStream = _client.GetStream();
                byte[] buff = new byte[MSGSIZE];
                int bytesRead = 0;

                // getting the socket
                bytesRead = _peerStream.Read(buff, 0, buff.Length);
                while (bytesRead != 0)
                {
                    // get the message from client and update the gui.
                    readHandler(Encoding.ASCII.GetString(buff).Trim());
                    buff = new byte[MSGSIZE];
                    bytesRead = _peerStream.Read(buff, 0, buff.Length);
                }
            }
        }

        //write to the socket
        public void Write(string msg)
        {
            if (!_client.Connected)
                return;

            try
            {
                byte[] msgBuff = Encoding.ASCII.GetBytes(msg);
                _peerStream.Write(msgBuff, 0, msgBuff.Length);
                _peerStream.Flush();
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }

    }

כיצד זה נראה ב Android:


כיצד זה נראה ב PC:


סיכום:

ראינו את התהליך משני צדדי המטבע ועכשיו ניתן לשלב בין הפלטפורמות או לעבוד על כל פלטפורמה בנפרד, המאמר הזה הוא נקודת ציון להמשך שבו נשלב את ה Android עם מערכות משובצות מחשב אחרות ונוכל לשלוט בהן מרחוק.

קבצי מקור:

טיפולי שיניים משהו?

3 תגובות:

  1. it has been bugging me for a while, so here are two links of interest:
    http://en.support.wordpress.com/code/posting-source-code/

    http://www.hanselman.com/blog/HowToPostCodeToYourBlogAndOtherReligiousArguments.aspx

    השבמחק
  2. נראה שאתה מאוד אוהב את זה.
    אצלינו זקוקים לעזרה ב Bluetooth.
    אולי תהיה מעוניין בעבודה לא גדולה, משתלמת וערכית?
    בבקשה צור קשר dudieng@gmail.com

    השבמחק