用 Androidx + 蓝牙HC05 点灯(2): Handler 掌舵人 和 Thread 线人,逮着蓝牙老大说黑话:此灯为我开,101011...

本文介绍了使用Androidx通过Handler和Thread处理蓝牙HC05的连接,修复了密码输入漏洞,详细阐述了蓝牙的捆绑、连接、点灯操作,并探讨了异步点灯方法及UUID开关的实验,最终实现稳定的蓝牙控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

🐉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。我还加了显示倒数功能。
countdown
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,上传。开始测。
test
不错不错,能够显示 FAIL 部分。


👨‍❤️‍👨2. 创建两兄弟:Handler 掌舵人 和 Thread 线人

Message .
留言
Looper
Thread .
线人
Handler .
掌舵 .
Message
Queue .
留言栏 .
留言C .
留言B .
留言A .
  • Thread 线人是主线之外的主要劳动力,是为 支线 线人也。它帮助 APP 躲开 Main 主线交通的阻塞,跑小路计算。

  • Handler 掌舵人。功能如:

  • handleMessage() 接报,内容是 Message 留言,然后决定干什么。

  • sendMessage() 发报。

  • obtainMessage(what, arg1, arg2, obj) 包装留言。

  • Message 留言:它的 sendToTarget()==> Handler 掌舵人的 getTarget()交接。


武侠版块:
掌舵😌(望着蓝牙的闪闪红灯):我掌江山一遍红,如此风光。
线人😮:老板,你有信来了。

蓝牙线人

【 ➰ 】 bondThread 捆绑线: 用socket

  1. 申请插座:(一定要有 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)
            }
        }
    }

打开蓝牙,跑起来:
try2
跑了两次,时间太短。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); 
  }
}

开灯:
ON
关灯:
off
你不用管 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。
fail1
测试2:正常 ON;蓝牙拔电插回;按OFF;没结果;再按OFF。
fail2
重启成功。有个小问题,停电回来后,灯会关着,不会亮。

🔄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. 英文连接

英文版
帮帮忙啦,在那里拍拍手👏。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值