目錄
- 前言
- 開發環境
- 1. 步驟展示
- 手機注冊藍牙通信Server端
- 藍牙設備注冊Client端
- Android手機端通過藍牙發送打開前門指令
- Android手機端通過藍牙發送打開后備箱指令
- 2. Android 藍牙通信的基本介紹和實作步驟
- 結語
前言
現在物聯網設備和車聯網設備中使用藍牙通信的場景越來越多,前幾年只有高端車才具備使用手機控制車輛的功能,現在已經越來越普及,包括自動解鎖、一鍵尋車、車輛控制、電量檢測,OTA無線升級等,還有現在很普及的共享單車,也是通過手機的藍牙無線控制實作的解鎖功能,
這幾天韌帶做手術比較有時間,又不能到處跑,不然有空肯定是出去嗨呀… 見下圖:

這個實體的 Android Studio 版本4.1.2 , TargetSdkVersion版本是 28,Gradle 版本: 6.5
本文主要包含下面幾個內容:
1. 首先展示了我做的一個使用Android控制藍牙設備的一個實體,模擬的是用戶使用手機打開車輛前門和打開后備箱,
2. Android 藍牙通信的基本介紹和實作步驟
Enjoy !

開發環境
Android Studio 版本4.1.2
TargetSdkVersion: 28
CompileSdkVersion 28
Gradle 版本: 6.5
1. 步驟展示
手機注冊藍牙通信Server端
首先,車主手機端進入Android 車控APP,在車控藍牙通信程序中,注冊為Server端,這樣車控APP就可以主動建立藍牙連接,
Android 藍牙通信中會會涉及SPP通用資料傳輸協議,這個協議是Android 2.0中引入的API,通過Socket的形式實作資料傳輸及互動,需要區分Server端和 Client 端,

藍牙設備注冊Client端
藍牙從設備就是通信中的Client端,我這里用一個有藍牙功能的Pad 模擬了車載藍牙模塊,這樣雙機通信的方法比較方(省)便(錢) , 現在市面上用得比較多的是HC-06 藍牙通信模塊,可以和單片機配合使用,有錢有時間的自己試一下,

Android手機端通過藍牙發送打開前門指令

Android手機端通過藍牙發送打開后備箱指令

從上面的畫面大家可以看到,用戶的手機和藍牙設備是沒有通過資料線直接連接的,手機通過藍牙向Client設備發送了指令,Client設備接收到指令后,執行了打開車門和打開后備箱的動作,將模型中的相應部位變成了紅色,
2. Android 藍牙通信的基本介紹和實作步驟
- #在Manifest 里申請藍牙權限
首先,要在新建專案中的AndroidManifest.xml中宣告兩個權限:BLUETOOTH權限用于請求連接和傳送資料;BLUETOOTH_ADMIN權限用于啟動設備、發現或進行藍牙設定,如果要擁有該權限,必須先擁有BLUETOOTH權限,
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
注意:這里有一個坑,如果你的TargetVersion大于22,就意味著你需要讓用戶動態申請權限,這個時候需要增加下面的ACCESS_FINE_LOCATION和 ACCESS_COARSE_LOCATION權限, 否則車主的手機會無法搜索到藍牙設備,我就遇到了這個問題,
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission-sdk-23 android:name="android.permission.ACCESS_COARSE_LOCATION"/>
- SSP通用資料傳輸協議中,Android 藍牙通信用到的四個類:
-
BluetoothAdapter:代表本地藍牙配接器,是所有藍牙互動的入口,使用這個你可以發現其他藍牙設備,查詢已配對的設備串列,使用一個已知的 MAC 地址來實體化一個 BluetoothDevice,以及 創建一個BluetoothServerSocket 來為監聽與其他設備的通信
-
BluetoothDevice:代表一個遠程藍牙設備,使用這個來請求一個與遠程設備的 BluetoothSocket連接,或者查詢關于設備名稱、地址、類和連接狀態等設備資訊 ,
-
BluetoothSocket:代表一個藍牙 socket 的介面(和 TCP Socket 類似),這是一個連接點,它允許一個應用與其他藍牙設備通過 InputStream 和 OutputStream 交換資料,
-
BluetoothServerSocket:代表一個開 放的服務 器 socket,它 監聽接受 的請求( 與 TCP ServerSocket 類似),為了連接兩臺 Android 設備,一個設備必須使用這個類開啟一個服務器 socket,當一個遠程藍牙設備開始一個和該設備的連接請求,BluetoothServerSocket 將會回傳一 個已連接的BluetoothSocket,接受該連接,
- 在藍牙通信中,無論在Server端,還是在Client端,都需要使用BluetoothAdapter配接器啟動藍牙功能
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
iv_server_original!!.setImageResource(R.drawable.server_background_original)
if (mBluetoothAdapter == null) {
Toast.makeText(applicationContext, "bluetooth is no available", Toast.LENGTH_LONG).show()
return@OnClickListener
}
mBluetoothAdapter!!.enable()
if (!mBluetoothAdapter!!.isEnabled) {
Toast.makeText(applicationContext, "bluetooth function is no available", Toast.LENGTH_LONG).show()
finish()
return@OnClickListener
}
try {
serverSocket = mBluetoothAdapter!!.listenUsingRfcommWithServiceRecord("aaa", uuid)
btListen_Thread = btListenThread(serverSocket, bluetoothServerMessageHandle)
btListen_Thread!!.start()
Toast.makeText(applicationContext, "服務器端已經開始運行", Toast.LENGTH_SHORT).show()
} catch (e: IOException) {
e.printStackTrace()
}
})
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter() : 用于獲取藍牙配接器
mBluetoothAdapter!!.enable() : 允許藍牙配接器,如果enable 失敗,表示當前藍牙設備不可用
if (mBluetoothAdapter == null) //表示當前手機不支持藍牙
- 使用SSP協議中的uuid作為引數,使用
mBluetoothAdapter!!.listenUsingRfcommWithServiceRecord("aaa", uuid)方法新建ServerSocket物件,并且通過btListenThread(serverSocket, bluetoothServerMessageHandle)建構式創建 藍牙監聽執行緒,
下面的bluetoothServerMessageHandle物件用戶接受藍牙指令后,在界面的展示,
try {
serverSocket = mBluetoothAdapter!!.listenUsingRfcommWithServiceRecord("aaa", uuid)
btListen_Thread = btListenThread(serverSocket, bluetoothServerMessageHandle)
btListen_Thread!!.start()
Toast.makeText(applicationContext, "服務器端已經開始運行", Toast.LENGTH_SHORT).show()
} catch (e: IOException) {
e.printStackTrace()
}
- 創建藍牙監聽執行緒:
下面的serverSocket!!.accept()函式用于接受客戶端的連接,并且產生與此藍牙連接對應的Socket,如果沒有連接成功的話,執行緒就會停在這里,這樣就可以獲取藍牙通信的輸出流和輸入流DataOutputStream(BufferedOutputStream(clientSocket.getOutputStream()))和
DataInputStream(BufferedInputStream(clientSocket.getInputStream())) - blue_tooth_msg_server_thread 是藍牙訊息服務器執行緒,用于給界面發送異步訊息,這個下面會講
blue_tooth_msg_server_thread = bluetoothMsgServerThread(mmInStream!!, msgHandler)
blue_tooth_msg_server_thread!!.start()
class btListenThread(serverSocket: BluetoothServerSocket?, msgHandler: Handler?) : Thread() {
var serverSocket: BluetoothServerSocket? = null //藍牙socket物件
var clientSocket: BluetoothSocket? = null //藍牙socket物件
private var mmInStream: DataInputStream? = null //輸入資料流
var outStream: DataOutputStream? = null //輸入資料流
private set
var blue_tooth_msg_server_thread: bluetoothMsgServerThread? = null //藍牙執行緒
private val msgHandler: Handler? = null //Handler物件
override fun run() {
while (!interrupted()) {
try {
clientSocket = serverSocket!!.accept()
outStream = DataOutputStream(BufferedOutputStream(clientSocket.getOutputStream())) //獲取out資料量物件
mmInStream = DataInputStream(BufferedInputStream(clientSocket.getInputStream()))
//創建執行緒接收
blue_tooth_msg_server_thread = bluetoothMsgServerThread(mmInStream!!, msgHandler)
blue_tooth_msg_server_thread!!.start()
val msg = Message() //創建message物件
msg.what = 0x1235
msgHandler!!.sendMessage(msg) //通過msgHandler將訊息發送到界面
} catch (e: IOException) {
e.printStackTrace()
}
}
val msg = Message() //創建message物件
msg.what = 0x2000
msgHandler!!.sendMessage(msg) //通過msgHandler將訊息發送到界面
blue_tooth_msg_server_thread!!.interrupt()
try {
outStream!!.close()
mmInStream!!.close()
clientSocket!!.close()
} catch (ex: Exception) {
}
}
init { //建構式
this.msgHandler = msgHandler
this.serverSocket = serverSocket
}
}
- 藍牙訊息服務器執行緒
bluetoothMsgServerThread:
internal class bluetoothMsgServerThread //建構式
(//輸入資料流
private val mmInStream: DataInputStream, //Handler物件
private val msgHandler: Handler?) : Thread() {
override fun run() {
val InBuffer = ByteArray(64) //定義緩沖區
while (!interrupted()) {
try {
val readed = mmInStream.read(InBuffer, 0, 64) //讀取輸入資料
val msg = Message() //創建message物件
msg.what = 0x1234
msg.obj = InBuffer
msg.arg1 = readed
msgHandler!!.sendMessage(msg) //通過msgHandler將訊息發送到界面
} catch (e: IOException) {
e.printStackTrace()
}
}
val msg = Message() //創建message物件
msg.what = 0x2001
msgHandler!!.sendMessage(msg) //通過msgHandler將訊息發送到界面
}
}
-
發送藍牙指令
這一部分直接看備注就好了這里有一個坑,藍牙指令發送完畢以后,一定要記得將藍牙輸出流 flush mmOutStream.flush掉,這個坑花掉了我一個小時的時間,
buttonServerSend = findViewById<View>(R.id.buttonServerSend) as Button
buttonServerSend!!.setOnClickListener(View.OnClickListener {
iv_server_original!!.setImageResource(R.drawable.server_status_frontopened)
val mmOutStream = btListen_Thread!!.outStream
var buffer: ByteArray? = null
try {
buffer = editTextServerSend!!.text.toString().toByteArray(charset("UTF-8"))
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
}
//byte[] buffer = editTextServerSend.getText().toString().getBytes(); //定義 要發送的資料內容
if (buffer!!.size < 1) {
Toast.makeText(applicationContext, "請輸入要發送的內容", Toast.LENGTH_SHORT).show()
return@OnClickListener
}
try {
buffer = "Car_Control_OpenRearDoor".toByteArray(charset("UTF-8"))
mmOutStream!!.write(buffer) //發送buffer的內容給藍牙
mmOutStream.flush()
vib!!.vibrate(100) //震動
} catch (e: Exception) {
e.printStackTrace()
}
})
stopServerBtn = findViewById<View>(R.id.stopServerBtn) as Button
stopServerBtn!!.setOnClickListener {
iv_server_original!!.setImageResource(R.drawable.server_status_rearopened)
if (btListen_Thread != null) {
btListen_Thread!!.interrupt()
try {
serverSocket!!.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
}
- Client端搜索附件的藍牙設備
- 在Client搜索藍牙的方法里面,首先是獲取藍牙配接器
BluetoothAdapter.getDefaultAdapter(),
通過藍牙設備器獲取系結的藍牙設備,并將其保存在 Set 內,如果沒有配對的藍牙設備,則彈出提示即可,
- 在Client搜索藍牙的方法里面,首先是獲取藍牙配接器
- NameList用于保存可以配對的藍牙
- spinner.adapter 用戶下拉展示配對的藍牙設備
-
iv_car_status!!.setImageResource(R.drawable.car_status_original)
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter() //獲取藍牙配接器
if (mBluetoothAdapter == null) {
Toast.makeText(applicationContext, "bluetooth is no available", Toast.LENGTH_LONG).show()
return@OnClickListener
}
mBluetoothAdapter!!.enable() //允許藍牙配接器
if (!mBluetoothAdapter!!.isEnabled) {
Toast.makeText(applicationContext, "bluetooth function is no available", Toast.LENGTH_LONG).show()
finish()
return@OnClickListener
}
val pairedDevices = mBluetoothAdapter!!.bondedDevices //獲取安卓系統已經配對的藍牙設備
if (pairedDevices.size < 1) {
Toast.makeText(applicationContext, "沒有配對的藍牙設備,請先配對再使用", Toast.LENGTH_LONG).show()
return@OnClickListener
}
val spinner = findViewById<View>(R.id.spinner1) as Spinner //下拉框控制元件定義
for (device in pairedDevices) {
Namelist.add(device.name)
Addresslist.add(device.address) //將mac地址加入到下拉框中
}
//通過配接器物件 顯示下拉框中的內容
val adapter = ArrayAdapter(applicationContext, android.R.layout.simple_spinner_item, Namelist)
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter
- Client 端連接服務器
mBluetoothAdapter!!.getRemoteDevice(address)可以用戶根據藍牙地址獲取到藍牙設備mBluetoothAdapter!!.getRemoteDevice(address)方法用于根據UUID 連接到ClientSocketblue_tooth_msg_thread = bluetoothMsgThread(mmInStream!!, bluetoothMessageHandle)用于//創建執行緒接收 單片機發送給安卓 藍牙的資料
iv_car_status!!.setImageResource(R.drawable.car_status_original)
editTextAll!!.setText("") //清空接收區
try {
val spinner = findViewById<View>(R.id.spinner1) as Spinner
address = Addresslist[spinner.selectedItemPosition]
device = mBluetoothAdapter!!.getRemoteDevice(address)
clientSocket = device.createRfcommSocketToServiceRecord(uuid)
clientSocket.connect() //連接藍牙設備
mmOutStream = clientSocket.getOutputStream() //獲取out資料量物件
mmInStream = DataInputStream(BufferedInputStream(clientSocket.getInputStream()))
Toast.makeText(applicationContext, "藍牙設備連接成功", Toast.LENGTH_SHORT).show()
vib!!.vibrate(100) //震動
//set_btn_status(true); //設定 繼電器、led按鈕 允許操作
//創建執行緒接收 單片機發送給安卓 藍牙的資料
blue_tooth_msg_thread = bluetoothMsgThread(mmInStream!!, bluetoothMessageHandle)
blue_tooth_msg_thread!!.start()
} catch (e: Exception) {
//set_btn_status(false); //不運行界面操作
Toast.makeText(applicationContext, "連接藍牙設備失敗:$e", Toast.LENGTH_SHORT).show()
e.printStackTrace()
}
- 好了,這樣Client藍牙設備就可以通過下面的方法獲取Server發送過來的藍牙車控指令,
mmInStream.read(InBuffer, 0, 64)用于讀取輸入資料msgHandler.sendMessage(msg)用于通過msgHandler將訊息發送到界面msgHandler.sendMessage(msg)用于通過msgHandler將訊息發送到界面show_result(msg.obj as ByteArray, msg.arg1)用于顯示內容到界面上
lass bluetoothMsgThread //建構式
(//輸入資料流
private val mmInStream: DataInputStream, //Handler物件
private val msgHandler: Handler) : Thread() {
override fun run() {
val InBuffer = ByteArray(64) //定義緩沖區
while (!interrupted()) {
try {
//mmInStream.readFully(InBuffer, 0, 64); //讀取輸入資料
val readed = mmInStream.read(InBuffer, 0, 64)
val msg = Message() //創建message物件
msg.what = 0x1234
msg.obj = InBuffer
msg.arg1 = readed
msgHandler.sendMessage(msg)
} catch (e: IOException) {
e.printStackTrace()
val msg = Message() //創建message物件
msg.what = 0x3000
msgHandler.sendMessage(msg)
break
}
}
}
var bluetoothMessageHandle: Handler = object : Handler() {
//定義handler物件
override fun handleMessage(msg: Message) {
if (msg.what == 0x1234) { //接收what = 0x1234的自定義資料
show_result(msg.obj as ByteArray, msg.arg1)
iv_car_status!!.setImageResource(R.drawable.car_status_frontopen)
}
if (msg.what == 0x3000) { //接收what = 0x1234的自定義資料
Toast.makeText(applicationContext, "藍牙連接斷開", Toast.LENGTH_SHORT).show()
iv_car_status!!.setImageResource(R.drawable.car_status_rearopen)
}
}
結語
好了,以上就是Android 手機通過藍牙通信給車輛網設備發送車控指令的流程,

再上三天班,就是2021年的春節,
放假啦.
Bye!
我的GitHub地址:https://github.com/18601949127
CSDN博客:https://blog.csdn.net/weixin_37734988

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/257838.html
標籤:其他
