🐉0、简介
- 更新填补上一章的漏洞(😎:咱有始有终。)
- 雇佣哥俩好:大哥是 Handler 掌舵人,(😃:瞧这名字开真威风!久仰久仰。)二哥是 Thread 线人。(😑:那里来的二五仔?)
- 联络蓝牙大哥,因为还没有开灯嘛!(蓝牙🦄:叽里咕噜…😅:尽是黑话,跑龙套的初哥那去了?没办法,我来翻译翻译。)
🐰1、更新:密码无人输入的漏洞
有的朋友可能会发现,在发现蓝牙后,新蓝牙机要输入密码。但是无人输入密码的话,30 至 40 秒之间,页面就会自动关闭。这个 App 还会停留在 Bonding 阶段。
BleHc05Observer:
ACTION_BOND_STATE_CHANGED -> {
lgd(tag + "Checking Bonded State...")
val device: BluetoothDevice =
intent.getParcelableExtra<Parcelable>(
EXTRA_DEVICE
) as BluetoothDevice
when (device.bondState) {
BOND_BONDED -> {
lgd("$tag Bonded to device")
devStatus.postValue(BONDED)
}
BOND_BONDING -> {
lgd("$tag Bonding to device")
// 什么都没有,卡!
}
BOND_NONE -> {
lge("$tag Nothing has bonded.")
devStatus.postValue(FAIL)
}
}
}
结果:
🙄:在 bonding 加料。
BOND_BONDING -> {
lgd("$tag Bonding to device")
devStatus.postValue(BONDING)
}
设定是新机就加个时限,没人输入密码就跳到 FAIL。我还加了显示倒数功能。
MainActivity:瞧瞧,咱这个MVVM 好啊!现在指令一目了然,逻辑的事 ViewModel 都帮我干了。😭老子在 MVC 的那里可是乱啊,又当爹又当妈的、、、
//region vars:
...
private val counterTV: TextView by
lazy { findViewById(R.id.counterTV) }
...
private var newDevice = false
private var counter = 0
private var bonded = false
//endregion
...
》》》在 **onCreate()** 里面:
mainVM.deviceStatus.observe(
this,
{ status ->
when (status) {
...
PAIRING -> {
val info = "Searching Bonded List..."
infoTV.text = info
progressBar.visibility = View.VISIBLE
mainVM.checkBondedList()
}
DISCOVERING -> {
val info = "Discovering device in range..."
infoTV.text = info
progressBar.visibility = View.VISIBLE
newDevice = true
mainVM.discovering()
}
BONDING -> {
val info = "Device Found in Record!\nBonding..."
infoTV.text = info
if (newDevice)
mainVM.checkNewDevice()
else
mainVM.bonding()
}
DISCOVERED -> {
val pass = "Your Pass: ${ConfigHelper.getPass()}"
discoveryTV.text = pass
discoveryTV.visibility = View.VISIBLE
}
BONDED -> {
lgd("MainAct: Bonded successful!")
counterTV.visibility = View.GONE
counter = 0
bonded = true
discoveryTV.text = ""
discoveryTV.visibility = View.GONE
progressBar.visibility = View.GONE
val info = "Bonded to $DEVICE_NAME."
infoTV.text = info
msg(this, info, 1)
mainVM.connected()
}
CONNECTED -> {
val info = "Device connected..."
infoTV.text = info
onBT.visibility = View.VISIBLE
progressBar.visibility = View.GONE
}
FAIL -> {
counter = 0
counterTV.visibility = View.GONE
val info = "FAIL to create connection!" +
"\nOr\nPassword is incorrect!"
infoTV.text = info
discoveryTV.visibility = View.GONE
progressBar.visibility = View.GONE
tryAgainBT.visibility = View.VISIBLE
}
NOT_FOUND -> {
lgd("MainAct: Device Not Found.")
progressBar.visibility = View.GONE
val info = "Device NOT Found!"
infoTV.setTextColor(Color.RED)
infoTV.text = info
msg(this, info, 1)
}
COUNT_DOWN -> {
if (!bonded) {
counterTV.visibility = View.VISIBLE
val down = 35 - counter
counter++
val timer = "35s to Enter Password: $down"
counterTV.text = timer
}
}
...
➕ COUNT_DOWN 进 DeviceStatus:
enum class DeviceStatus {
PAIRING, BONDING, DISCOVERING, SWITCHING,
DISCOVERED, BONDED, CONNECTED,
FAIL, DISCONNECT, NOT_FOUND, COUNT_DOWN
}
那个 MainViewModel 当然要改啦:
fun bonding() {
val result = bleHelper.checkDeviceBonding()
lgd("$tag Bonding...$result")
if (result) {
deviceStatus.postValue(CONNECTED)
//bleHelper.discoverService()
} else {
deviceStatus.postValue(FAIL)
}
}
...
fun checkNewDevice() {
// new password entry timeout is 40s
viewModelScope.launch {
repeat(40) {
deviceStatus.postValue(COUNT_DOWN)
delay(SEC)
}
if (!bleHelper.checkBondedList()) {
lgd("Check Bonded List.")
deviceStatus.postValue(FAIL)
}
}
}
这里 SEC = 1000L;方程用的是 Coroutines,异步程序。我设定 40秒 内去 COUNT_DOWN 跳数字;40 秒后,蓝牙没有上榜的话就跳去 FAIL。写完,Ctrl+K,上传。开始测。
不错不错,能够显示 FAIL 部分。
👨❤️👨2. 创建两兄弟:Handler 掌舵人 和 Thread 线人
-
Thread 线人是主线之外的主要劳动力,是为 支线 线人也。它帮助 APP 躲开 Main 主线交通的阻塞,跑小路计算。
-
Handler 掌舵人。功能如:
-
handleMessage() 接报,内容是 Message 留言,然后决定干什么。
-
sendMessage() 发报。
-
obtainMessage(what, arg1, arg2, obj) 包装留言。
-
Message 留言:它的 sendToTarget()==> Handler 掌舵人的 getTarget()交接。
…
武侠版块:
掌舵😌(望着蓝牙的闪闪红灯):我掌江山一遍红,如此风光。
线人😮:老板,你有信来了。
蓝牙线人:
【 ➰ 】 bondThread 捆绑线: 用socket
- 申请插座:(一定要有 UUID。😳:那里来的?😁:系统给的。)
private val bondThread = object : Thread() {
override fun run() {
var fail = false
try {
// 插座
mBTSocket = createBluetoothSocket(serviceUuid)
} catch (e: IOException) {
fail = true
lge("Socket creation failed")
}
这是插座来源:
@Throws(IOException::class)
private fun createBluetoothSocket(uuid: UUID): BluetoothSocket? {
return mBleDevice.createRfcommSocketToServiceRecord(uuid)
}
…
2. 接着上面的 run() :
// 连接蓝牙
try {
mBTSocket?.connect() // 连接
} catch (e: IOException) {
try {
fail = true
mBTSocket?.close()
// 留言打包:通知3号,arg1:没有,arg2:没有
mHandler?.obtainMessage(CONNECTING_STATUS, -1, -1)
?.sendToTarget() // 报告老大
} catch (e2: IOException) {
lge("Socket creation failed")
}
}
CONNECTING_STATUS 是公用的常量,放在
companion object {
..
private const val MESSAGE_READ = 4
private const val CONNECTING_STATUS = 3
现在用的是 3 号,和 4 号。其实就是个目录。
Kotlin 就这样,喜欢把东西放在一堆。不像 Java,甩着 static 满天飞。
…
3. 插座没问题,就接蓝牙的 ConnectedThread 连接线。
if (!fail) {
mConnectedThread = mBTSocket?.let { ConnectedThread(it) }
mConnectedThread?.setHandler(mHandler!!) // 提供掌舵人
// 一生一次 start()
if (mConnectedThread?.state != State.NEW) mConnectedThread?.start()
// 留言打包:尝试连接
mHandler?.obtainMessage(CONNECTING_STATUS, 1, -1, name)
?.sendToTarget() // send message
}
}
}
…
【 ➰ 】ConnectedThread 连接线
private class ConnectedThread(socket: BluetoothSocket) : Thread() {
private var mmSocket: BluetoothSocket = socket
private val mmInStream: InputStream? = mmSocket.inputStream // 进口
private val mmOutStream: OutputStream? = mmSocket.outputStream // 出口
private lateinit var mHandler: Handler
找老板:
fun setHandler(handler: Handler) { mHandler = handler }
run():收信
override fun run() {
val buffer = ByteArray(1024) // buffer store for the stream
var bytes = 0 // bytes returned from read()
// 跑啊跑,永无止境。
while (true) {
if (mmInStream != null) {
try {
// 读取 InputStream
bytes = mmInStream.read(buffer)
lgd("+++++ ConnectedThread: bytes size = $bytes")
// 打包:告知 4 号,arg1=多少,arg2=没有,资料
mHandler.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
.sendToTarget() // 发送
} catch (e: IOException) {
lge("+++++ ConnectedThread: Error on read: ${e.message}")
break
}
}
}
}
…
🐷:上面的意思是我收到蓝牙发来的信息,打包转发给师傅。
🙄:八戒都出来了,猴哥呢?
🐷:去去去,我也是主角。
…
有接当然有发:
// 给蓝牙写信
fun write(input: String) {
val bytes = input.toByteArray() //converts entered String into bytes
try {
mmOutStream?.write(bytes)
} catch (e: IOException) {
lge("+++++ ConnectedThread: Error on write: ${e.message}")
}
}
直发就可以,不用打包。还有个取消程序:
// 停摆
fun cancel() {
try {
mmInStream?.close()
mmOutStream?.close()
mmSocket.close()
} catch (e: IOException) {
}
}
}
把插头拔掉。(😯:停电了?)
…
掌舵人 mHandler, 老大当然要发号施令了:
private val newHandler = object : Handler() {
override fun handleMessage(msg: Message) {
lgd ("Handler: what = ${msg.what}")
// 选择
when (msg.what) {
// 阅读留言
MESSAGE_READ -> {
lgd("msg = MESSAGE READ")
var readMessage: String? = null
try {
readMessage = String((msg.obj as ByteArray), StandardCharsets.UTF_8)
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
}
}
// 连接状态
CONNECTING_STATUS -> {
lgd("msg = CONNECTION STATUS")
if (msg.arg1 == CONNECTED)
lgd("Connected to Device: " + msg.obj as String)
else
lge("Connection Failed")
}
}
}
}
现在只是读收条,看状态。有需要的话,就要增加公用常量,几家一起用。
🖐🏻3. 第四步:捆绑,连接测试。
第四步:系统捆绑能找到蓝牙吗?
上一篇,第三步 Discovery 发现 到 Bonding 捆绑 是成功的, Logcat 中 Bonded 有记录。现在关机重来,走到第二步查表,当然有记录啦,跳到第四步。
👯(高音重合音):MVVM 就是玩跳棋,啦啦、啦、、、
🙄:Opera
MainActivity:
PAIRING -> {
val info = "Searching Bonded List..."
infoTV.text = info
progressBar.visibility = View.VISIBLE
mainVM.checkBondedList()
}
BONDING -> {
val info = "Device Found in Record!\nBonding..."
infoTV.text = info
if (newDevice)
mainVM.checkNewDevice()
else
mainVM.bonding()
}
MainViewModel:
fun checkBondedList() {
if (bleHelper.checkBondedList()) {
lgd("$tag Found. Move to Step 4, Bonding.")
deviceStatus.postValue(BONDING)
} else {
lgd("$tag Not Found. Move to Step 3, Discovering.")
deviceStatus.postValue(DISCOVERING)
}
}
fun bonding() {
val result = bleHelper.checkDeviceBonding()
lgd("$tag Bonding...$result")
if (result) {
deviceStatus.postValue(CONNECTED)
} else {
deviceStatus.postValue(FAIL)
}
}
BluetoothHelper:
// Step 4
fun checkDeviceBonding(): Boolean {
mBleDevice.createBond()
val uuids = mBleDevice.uuids
serviceUuid = uuids[0].uuid // 蓝牙哥的 UUID 身份
val bonded = mBleDevice.bondState == BOND_BONDED
if (bonded) {
lgd("$tag state: bonded")
} else {
lge("$tag state: bonded fail")
}
return bonded
}
🤓:把蓝牙关掉,看看 App 什么反应。请看 Logcat:
D: MainVM: Found. Move to Step 4, Bonding.
D: BlueHelper: Service UUID: 00001101-0000-1000-8000-00805f9b34fb
D: BlueHelper: Characteristic UUID: 00000000-0000-1000-8000-00805f9b34fb
D: BlueHelper: state: bonded
D: MainVM: Bonding...true
D: Handler: what = 3
D: msg = CONNECTION STATUS
E: Connection Failed
这证明 bonded 捆绑 和 connect 连接 是两回事。bonded 不可能帮你找附近的蓝牙设备。
捆绑哥😑:不关我的事?
掌舵人😎:我自己来啦。
…
连线找蓝牙
BluetoothHelper:加个变量
// check connection
private var connected = false
在 mHandler 老大那里拿答案:
private val newHandler = object : Handler() {
override fun handleMessage(msg: Message) {
...
CONNECTING_STATUS -> {
lgd("msg = CONNECTION STATUS")
if (msg.arg1 == CONNECTED) {
lgd("Connected to Device: " + msg.obj as String)
connected = true // <<<< 报告
}
else {
lge("Connection Failed")
connected = false // <<<< 报告
}
}
checkDeviceBonding() 改线人捆绑:
suspend fun checkDeviceBonding(): Boolean {
...
// handler:
if (mHandler == null) mHandler = newHandler
// 线人是一生一次 start(),要不死机。浪漫不?
if (bondThread.state == State.NEW) bondThread.start()
// 通讯停顿
delay(COM_DELAY)
lgd("$tag connected? $connected")
return connected
}
因为是异步,这次用 Kotlin Coroutines, 加 suspend 暂停。
➕个常量,COM_DELAY 通讯等待:
companion object {
private const val COM_DELAY = 500L
}
在 MainViewModel 那里加 scope 区域:
fun bonding() {
viewModelScope.launch {
val result = bleHelper.checkDeviceBonding()
lgd("$tag Bonding...$result")
if (result) {
deviceStatus.postValue(CONNECTED)
} else {
deviceStatus.postValue(FAIL)
}
}
}
打开蓝牙,跑起来:
跑了两次,时间太短。COM_DELAY = 3000L,加到 3 秒看看。
🤩:接机成功,直接就跳到 ON 的画面,不用重来。
🌟4. 点灯
👧🏻:我们来这点天灯,你既要写 C,又要写手机。
😊:小朋友说话押韵啊。叔叔给颗糖。
👧🏻:不要,我会胖的。
😅:…
VS Code:main.cpp :
- [ 🐣 ] 开头
#include <Arduino.h>
#include <SoftwareSerial.h>
// LED switch
#define ledPin 13
#define rxPin 2 // => HC05: rx
#define txPin 3 // => HC05: tx
#define LED_ON "101"
#define LED_OFF "011"
int codeLen = 3;
// Bluetooth port
SoftwareSerial BTserial(rxPin, txPin); // RX | TX of Arduino
const long interval = 50; // interval at which to delay
char reading = ' ';
String pass = ""; // received from Bluetooth
void decodePass();
开头基本都是老样子,加了 decodePass() 解码。
- [ 🐥 ] setup() 设定:
void setup() {
// set led pin
pinMode( ledPin, OUTPUT );
digitalWrite( ledPin, HIGH );
Serial.begin(9600);
Serial.println("Arduino to Host is 9600 Baud");
BTserial.begin(9600);
Serial.println("Bluetooth started at 9600 Baud");
}
- [ 🐍 ] loop() 绕圈:
void loop() {
// digitalWrite(ledPin, LOW); // set the LED off
// delay(interval); // wait for a second
// digitalWrite(ledPin, HIGH); // set the LED on
// delay(interval);
if( BTserial.available() )
{
reading = BTserial.read();
Serial.print("Bluetooth Received: ");
Serial.println(reading);
switch (reading) {
case '1':
pass.concat('1');
break;
case '2':
pass.concat('2');
break;
case '3':
pass.concat('3');
break;
case '4':
pass.concat('4');
break;
case '5':
pass.concat('5');
break;
case '6':
pass.concat('6');
break;
case '7':
pass.concat('7');
break;
case '8':
pass.concat('8');
break;
case '9':
pass.concat('9');
break;
case '0':
pass.concat('0');
break;
case 'a':
pass.concat('a');
break;
case 'b':
pass.concat('b');
break;
case 'c':
pass.concat('c');
break;
case 'd':
pass.concat('d');
break;
case 'e':
pass.concat('e');
break;
case 'f':
pass.concat('f');
break;
case '-':
pass.concat('-');
break;
default:
Serial.println("Junk...");
}
// read input
if (pass.length() >= codeLen) {
Serial.print("pass =");
Serial.println(pass);
decodePass();
pass = "";
delay(interval);
}
}
}
它接收 Hex 码,国际惯例 UUID 一样。其实你想让它干嘛就干嘛,UNO 是 小白一个,说黑话都可以,黑话可以说是历史最早的密语通讯。
- [ 🎭 ] decodePass() 解码:
void decodePass()
{
// connect?
if (pass.equals(LED_ON))
{
Serial.print("LED ON Code: ");
Serial.println(pass);
}
else if (pass.equals(LED_ON))
{
Serial.print("LED OFF Code: ");
Serial.println(pass);
}
}
…
Android Studio
【👆🏻】ON 键 和 OFF 键
MainActivity 加 setOnClickListener() 给 ON 键 和 OFF 键。
onCreate 里面:
onBT.visibility = View.GONE
onBT.setOnClickListener {
offBT.visibility = View.VISIBLE
onBT.visibility = View.GONE
mainVM.turnOn()
}
offBT.visibility = View.GONE
offBT.setOnClickListener {
offBT.visibility = View.GONE
onBT.visibility = View.VISIBLE
mainVM.turnOff()
}
MainViewModel 加:
fun turnOn() {
bleHelper.ledSwitch("101") // 增加破译难度
}
fun turnOff() {
bleHelper.ledSwitch("011")
}
BluetoothHelper:
fun ledSwitch(signal: String) {
mConnectedThread?.write(signal)
}
简单,给什么就送什么。跑起来,看看监视屏:
Arduino to Host is 9600 Baud
Bluetooth started at 9600 Baud
Bluetooth Received: 1
Bluetooth Received: 0
Bluetooth Received: 1
pass =101
Bluetooth Received: 0
Bluetooth Received: 1
Bluetooth Received: 1
pass =011
…
【👆🏻】开灯的成功与失败
在 VS Code,加开灯过过瘾。
void decodePass()
{
if (pass.equals(LED_ON))
{
Serial.print("LED ON Code: ");
Serial.println(pass);
digitalWrite(ledPin, LOW); // 开
delay(interval); // 等,急不得。
} else if (pass.equals(LED_ON))
{
Serial.print("LED OFF Code: ");
Serial.println(pass);
digitalWrite(ledPin, HIGH); // 关
delay(interval);
}
}
开灯:
关灯:
你不用管 RFID 阅读器,这个以后讲。
初哥😆:啊,LED 亮了!
🙄:这龙套那跑出来的?导演、导演、、、
👧🏻:我把蓝牙断电再插上,APP 失灵了。没用!叔叔没用、、、
看看 Logcat:
E: +++++ ConnectedThread: Error on write: Broken pipe
😅:小朋友,怎么说话的。净捣乱啊?灯不是亮了吗?(⊙o⊙)…Socket 失联了,Socket 失联了。
🔀5. 异步点灯法
不是直联,遥控肯定会出错。所以要增加蓝牙返回信息。还有线人死了,要修。
增加常量,增加异步方程
- [ ✔️ ] DeviceStatus
enum class DeviceStatus {
PAIRING, BONDING, DISCOVERING, SWITCHING,
DISCOVERED, BONDED, CONNECTED,
FAIL, DISCONNECTED, NOT_FOUND, COUNT_DOWN,
LED_ON, LED_OFF, LED_FAIL_ON, LED_FAIL_OFF,
RESTART
}
加了
- LED_ON 正常开启,
- LED_OFF 正常关闭,
- LED_FAIL_ON 开启失败,
- LED_FAIL_OFF 关闭失败,
- RESTART 重启。
工作量真大,一个改,个个改。
- [ ✔️ ] MainActivity
onCreate():
mainVM.deviceStatus.observe(
this,
{ status ->
when (status) {
...
LED_ON -> {
offBT.visibility = View.VISIBLE
onBT.visibility = View.GONE
val info = "LED is ON."
infoTV.setTextColor(Color.BLUE)
infoTV.text = info
}
LED_FAIL_ON -> {
val info = "LED: Fail to turn ON!\n" +
"Please check your distance!"
infoTV.setTextColor(Color.DKGRAY)
infoTV.text = info
}
LED_OFF -> {
offBT.visibility = View.GONE
onBT.visibility = View.VISIBLE
val info = "LED is OFF."
infoTV.setTextColor(Color.BLACK)
infoTV.text = info
}
LED_FAIL_OFF -> {
val info = "LED: Fail to turn OFF!\n" +
"Please check your distance!"
infoTV.setTextColor(Color.DKGRAY)
infoTV.text = info
}
RESTART -> {
lgd("MainAct: Restart the App")
val info = "Broken Connection\n" +
"Restarting the App!"
infoTV.setTextColor(Color.RED)
infoTV.text = info
progressBar.visibility = View.VISIBLE
val packageManager: PackageManager = this.packageManager
val intent = packageManager.getLaunchIntentForPackage(this.packageName)
val componentName = intent!!.component
val mainIntent = Intent.makeRestartActivityTask(componentName)
this.startActivity(mainIntent)
Runtime.getRuntime().exit(0)
}
else -> {
val info = "Illegal Process Error..."
infoTV.text = info
}
}
}
为什么要重启呢?
👻线人:见死不救啊!谷歌,还我命来!
😑:死线人,死 socket 是不安全的。只有重启了。88
…
- [ ✔️ ] ConfigHelper
ConfigHelper:
const val ERR_BROKEN_PIPE = "Socket: Broken pipe"
👲🏻 :我是下水道灵灵通,通渠请找我。电话是 XXXXXXXXXXX。
😳:咦,怎么有插播广告?
- [ ✔️ ] MainViewMode
fun turnOn() {
viewModelScope.launch {
if (bleHelper.bleMsg != ERR_BROKEN_PIPE) {
var bleMsg = ""
for (i in 1..3) {
bleMsg = bleHelper.ledSwitch("101")
lgd("$tag Retry $i; received Bluetooth message: $bleMsg")
if (bleMsg.contains(ON)) break
}
if (bleMsg.contains(ON)) {
deviceStatus.postValue(LED_ON)
} else {
deviceStatus.postValue(LED_FAIL_ON)
}
} else {
lgd("Call Restart!")
deviceStatus.postValue(RESTART)
}
}
}
fun turnOff() {
viewModelScope.launch {
if (bleHelper.bleMsg != ERR_BROKEN_PIPE) {
var bleMsg = ""
for (i in 1..3) {
bleMsg = bleHelper.ledSwitch("011")
lgd("$tag Retry $i; received Bluetooth message: $bleMsg")
if (bleMsg.contains(OFF)) break
}
if (bleMsg.contains(OFF)) {
deviceStatus.postValue(LED_OFF)
} else {
deviceStatus.postValue(LED_FAIL_OFF)
}
} else {
lgd("Call Restart!")
deviceStatus.postValue(RESTART)
}
}
}
还是用 Coroutines 做异步,反正就加两行,特简单。不停地呼叫 三次,没有就拉倒,上 FAIL 选项。还增加了测 死 socket。
- [ ✔️ ] BluetoothHelper:
var bleMsg = ""
suspend fun ledSwitch(signal: String): String {
bleMsg = ""
mConnectedThread?.write(signal)
delay(1000)
return bleMsg
}
一秒一次,应该够用了。
还有 ConnectedThread 连接线 的错误返汇。
override fun run() {
...
while (true) {
if (mmInStream != null) {
try {
...
} catch (e: IOException) {
lge("+++++ ConnectedThread: Error on read: ${e.message}")
mHandler.obtainMessage(BROKEN_PIPE, -1, -1)
.sendToTarget()
break
}
}
}
}
/* Call this from the main activity to send data to the remote device */
fun write(input: String) {
...
try {
...
} catch (e: IOException) {
lge("+++++ ConnectedThread: Error on write: ${e.message}")
mHandler.obtainMessage(BROKEN_PIPE, -1, -1)
.sendToTarget()
}
}
直接发给 掌舵人 Handler。用 bleMsg 提取蓝牙的返汇 或者 ERR_BROKEN_PIPE。
private val newHandler = object : Handler() {
override fun handleMessage(msg: Message) {
...
when (msg.what) {
MESSAGE_READ -> {
try {
val readBuffer = msg.obj as ByteArray
bleMsg += String(readBuffer, 0, msg.arg1)
} catch (e: UnsupportedEncodingException) {
e.printStackTrace()
}
}
...
BROKEN_PIPE -> {
bleMsg = ERR_BROKEN_PIPE
}
}
}
}
➕ BROKEN_PIPE 常量:
companion object {
private const val tag = "BlueHelper: "
private const val MESSAGE_READ = 4
private const val CONNECTING_STATUS = 3
private const val BROKEN_PIPE = 5
VS Code:main.cpp
一只手当然拍不响,要 UNO 配合。
- [ 👷🏼 ] decodePass:
void decodePass()
{
// connect?
Serial.print("Decode Pass = ");
if (pass.equals(LED_ON))
{
Serial.println("LED ON");
digitalWrite(ledPin, LOW); // 开
BTserial.println("ON"); // 手机兄:开了
delay(interval);
}
else if (pass.equals(LED_OFF))
{
Serial.println("LED OFF");
digitalWrite(ledPin, HIGH); // 关
BTserial.println("OFF"); // 手机兄:关了
delay(interval);
}
}
👧🏻:叔叔,特简单,println,我都会。
😅:是啊,是啊!现在的小朋友真聪明。
…
测试结果:
测试1:蓝牙拔电插回;按 ON;没结果;再按 ON。
测试2:正常 ON;蓝牙拔电插回;按OFF;没结果;再按OFF。
重启成功。有个小问题,停电回来后,灯会关着,不会亮。
…
🔄6. 尝试用 UUID 开关。
刚刚用的短语开关是: 101 和 011。现在试试 36 字母的 UUID 做开关。
ConfigHelper:
// BLE code
const val UUID_LED_ON = "538688b3-54ee-36ed-a830-f7ec4b0f24bb"
const val UUID_LED_OFF = "031048cc-2fad-3d80-bc69-a35119a12f49"
MainViewModel:
fun turnOn() {
viewModelScope.launch {
if (bleHelper.bleMsg != ERR_BROKEN_PIPE) {
var bleMsg = ""
for (i in 1..3) {
bleMsg = bleHelper.ledSwitch(UUID_LED_ON)
...
fun turnOff() {
viewModelScope.launch {
if (bleHelper.bleMsg != ERR_BROKEN_PIPE) {
var bleMsg = ""
for (i in 1..3) {
bleMsg = bleHelper.ledSwitch(UUID_LED_OFF)
UNO,Arduino C:
// LED 123456789a123456789b123456789c123456
#define LED_ON "538688b3-54ee-36ed-a830-f7ec4b0f24bb"
#define LED_OFF "031048cc-2fad-3d80-bc69-a35119a12f49"
unsigned int codeLen = 36;
再接再厉,跑起来:
灯是亮了,但是 App 显示失败。看看 Logcat:
D: MainVM: Bonding...true
D: MainVM: Retry 1; received Bluetooth message:
D: MainVM: Retry 2; received Bluetooth message: NN
D: MainVM: Retry 3; received Bluetooth message: NN
ON 读成了 NN。UNO 尝试改成重复两次。
if (pass.equals(LED_ON))
{
...
BTserial.println("ONON");
delay(interval);
}
else if (pass.equals(LED_OFF))
{
...
BTserial.println("OFFOFF");
delay(interval);
}
因为,对比的时候是用 contains 包含,不是 == 。
再试:
D: MainVM: Retry 1; received Bluetooth message:
D: MainVM: Retry 2; received Bluetooth message: ONON
第二秒成功。
D: MainVM: Retry 1; received Bluetooth message:
D: MainVM: Retry 2; received Bluetooth message: OFFOFF
第二秒成功。
D: MainVM: Retry 1; received Bluetooth message: NNON
第一秒成功:虽然是有两个乱码。
…
😊:真累!来来,小朋友帮我按这些按钮,很好玩的。
👦🏻👧🏻:好啊!踏踏踏、、、
他们尝试了100来次,成功率高达100%。
实验结果
发送长字母没有问题。但是由 蓝牙HC05 发回的信息会失真。双答案是个很好的选择。
🎁 7. 英文连接
英文版
帮帮忙啦,在那里拍拍手👏。