一、前言
宿舍关灯神器应该是一个已经被玩烂了的项目了,我也废话不多说。本篇文章就如标题所说,教大家如何开发一个鸿蒙NEXT版的APP来对单片机进行蓝牙通信。本文的关灯器将采用ESP32作为控制器。
二、材料准备
- ESP32
- SG90舵机
- 杜邦线
- 5v锂电池
设备组装
舵机上红色的线是接vin(也就是esp32上的5v引脚,不同的板子叫法不同),黑色的接GND,另外一个是信号线,随便接一个GPIO引脚即可,我这里接的是12,这个要记住后面要用。
三、单片机代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| #include <BluetoothSerial.h> #include <string> #include <ESP32Servo.h> using namespace std; BluetoothSerial SerialBT; Servo myservo;
int servoPin = 12; int ini_pos = 90; bool is_on = false;
void turn_mid_light() { myservo.write(90); }
void turn_light() { myservo.write(90 + 45); delay(500); turn_mid_light(); } void turn_off_light() { myservo.write(90 - 45); delay(500); turn_mid_light(); } void setup() { myservo.attach(servoPin); Serial.begin(115200); SerialBT.begin("ESP32_Server"); int init_angle = myservo.read(); if (init_angle != ini_pos) { myservo.write(ini_pos); }; } void loop() { if(SerialBT.available()){ String cmd = SerialBT.readString(); Serial.print(cmd); if(cmd == "switch"){ if(is_on){ turn_off_light(); is_on = false; }else{ turn_light(); is_on = true; } } } }
|
下面我来解释一下这段代码:
首先引入了三个头文件<BluetoothSerial.h>(蓝牙库)、和<ESP32Servo.h>(控制舵机的库)。定义了蓝牙串口对象SerialBT和舵机对象myservo,以及servoPin(舵机引脚,就是组装时信号线接的位置)、ini_pos(初始位置角度)、is_on(表示灯是否开启的标志)。
接着定义了三个函数turn_mid_light(将舵机转到中间位置,避免影响人手正常开关灯)、turn_light(将舵机转到特定角度开启灯光,然后再转到中间位置)、turn_off_light(将舵机转到特定角度关闭灯光,然后再转到中间位置)。
在setup函数中,将舵机连接到指定引脚,初始化串口通信,设置蓝牙服务器名称,读取舵机初始角度,如果初始角度不是设定的初始位置角度,则将舵机转到初始位置。
在loop函数中,如果蓝牙串口有数据可读,就读取数据并存储在cmd字符串中。如果cmd等于 “switch”,根据is_on的状态来决定是开启灯光还是关闭灯光。
四、鸿蒙APP代码
1.蓝牙接口
我先不直接放代码,这里要先着重说一下鸿蒙蓝牙开发的方式,因为授之以鱼不如授之以渔,我解释了这一点以后,你在开发其他蓝牙应用的时候你就知道该怎么做了。
由于鸿蒙版本迭代快,api之间差异也不小,你在网上随便搜的教程指不定是哪个api的,并且写教程的人也没说,这就会造成许多误解。所以我会告诉大家我用的api版本,首先Dev eco studio版本是5.0.9.300,compatibleSdkVersion是 5.0.0(12)。
(由于这个项目是24年写的,现在整理的时候发现当前最新的api版本是16,但是主要的用法没有什么大变化。)
在华为官方文档中搜索的时候注意要选上你现在用的api版本
如果直接搜索蓝牙,会找到一些过时的文档,请注意看上边标注了从某某api后不再支持,如果用错了版本就会影响后面的开发。

经过我的查找,我发现了这个接口@ohos.bluetooth.connection (蓝牙connection模块),这个就是标准蓝牙通信的接口,当然你在左侧的栏目中也会发现有蓝牙ble模块,这个对应的是低功耗蓝牙,不过我这个项目用的是经典蓝牙,如果想试试ble肯定也可以,只不过固件代码也得相应的更改,有兴趣的读者可以研究一下。

2.部分代码
先引入一些必要的库,其中connection和socket是蓝牙连接和通信必要的组件。
1 2 3 4 5 6
| import { router } from '@kit.ArkUI'; import { connection } from '@kit.ConnectivityKit'; import { socket } from '@kit.ConnectivityKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { buffer } from '@kit.ArkTS'; import { promptAction } from '@kit.ArkUI';
|
下面来讲几个重要的函数:
-
connection.getPairedDevices()
这个是用于获取已配对设备的mac地址,返回是Array
-
connection.getRemoteDeviceName()
当我们拿到了mac地址后,我们想知道设备名称就要用这个函数,这个函数对于已配对设备和未配对都是一样的,只要传入地址就能返回设备名称。在api16中好像又新加进去了一个参数alias,不过是可选,我们不用管这个。
-
connection.on(‘bluetoothDeviceFind’, Callback<Array>)
这里可以看到有几个不同的on函数,其中第一个参数传入不同的字符串表示订阅不同的消息,这里我们当然要订阅发现设备的消息,也就是开启蓝牙扫描后只要发现了设备,就执行回调函数。
-
connection.startBluetoothDiscovery();
这个就不用多说了,就是开启扫描。
-
socket.sppConnect(deviceId: string, options: SppOptions, callback: AsyncCallback): void
连接指定地址的设备
-
socket.sppWrite(clientSocket: number, data: ArrayBuffer): void
发送字节数据
所以有了上面的代码,我们就可以分别写出以下功能:
检查已配对设备有无关灯器
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| getDevice = (): boolean=>{ try{ let devices: Array<string> = connection.getPairedDevices() for(let i=0; i<devices.length; i++){ if(connection.getRemoteDeviceName(devices[i])=="ESP32_Server"){ this.address = devices[i] return true } } } catch (err) { console.error("errCode:" + err.code + ",errMessage:" + err.message) } return false }
|
这里的设备名称是你自己设定的,根据你的固件代码进行实际调整。
扫描并配对
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| scanAndPair = ()=>{ try { connection.on('bluetoothDeviceFind', (data)=>{ for(let i=0;i<data.length;i++){ let adrr = data[i] if(adrr!=""){ let name = connection.getRemoteDeviceName(adrr) if(name == "ESP32_Server"){ connection.pairDevice(adrr) } } } }); connection.startBluetoothDiscovery(); } catch (err) { console.error("errCode:" + err.code + ",errMessage:" + err.message); } }
|
连接设备
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| connect = ()=>{ try{ socket.sppConnect(this.address, {uuid: '00001101-0000-1000-8000-00805f9b34fb', secure: false, type: 0}, (code: BusinessError, number: number)=>{ if (code) { console.error('sppListen error, code is ' + code) promptAction.showToast({message: '连接失败', duration: 2000}) return; } this.client_num = number promptAction.showToast({message: '连接成功', duration: 2000}) this.state = "在线" this.color = Color.Green }) } catch (err) { console.error("errCode:" + err.code + ",errMessage:" + err.message); promptAction.showToast({message: '连接失败', duration: 2000}) } }
|
发送消息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| sendCmd = ()=>{ try{ socket.sppWrite(this.client_num, this.strToBuffer("switch")) this.light_color==Color.Gray ? this.light_color = Color.Green : this.light_color = Color.Gray socket.sppCloseClientSocket(this.client_num) } catch (err) { console.error("errCode:" + err.code + ",errMessage:" + err.message); if(err.code == 2901054){ promptAction.showToast({message: '连接已丢失请重试', duration: 2000}) this.client_num = -1 this.state = "离线" this.color = Color.Red } } }
|
完整代码在此
https://github.com/1wpc/autolight_HMOS.git