在嵌入式设备运行Rust/bluer蓝牙简单应用

基于bluez/DBus的蓝牙库

在上一篇博客文章中,笔者演示了在嵌入式设备上运行Rust/DBus进程间通信组件的简单应用,主要聚焦解决dbus/Rust模块dbus-rs的编译问题。工作中接触了蓝牙相关的开发内容(仅限于C语言),笔者希望更进一步,在嵌入式设备上使用Rust编程语言来实现基本的蓝牙操作(需使用DBus等通信组件),这样就能实现在蓝牙相关软件开发方面,Rust编程语言对C语言的完全替代。这些探索性的开发内容有一定的参考价值,在此分享给希望使用Rust编程语言来做嵌入式软件开发的小伙伴们。

笔者并不执着于使用新的编程语言,不过绝大多数嵌入式软件开发团队的技术能力,以及所处的公司环境决定了,使用传统的C/C++编程语言来开发,会引入比较多的软件缺陷;而且时常软件代码不具备可扩展性、可维护性,同时稳定性低、执行效率差,当然还有开发效率低等问题。尽管笔者已在工作中使用Rust编程语言做了一些嵌入式软件的开发,但若有朝一日笔者能有带团队的工作角色,笔者更倾向于使用C语言来做嵌入式软件开发。

开源社区提供了Rust语言版本的蓝牙库,bluer;该库仅依赖DBus C/C++库,而不依赖bluez库。它通过DBus IPC组件与bluez的守护进程进行通信,从而实现了对蓝牙设备的操作。这一点也很好地补充了上一篇博客的内容:Rust/DBus在嵌入式设备上,确实有用武之地。

为嵌入式ARM设备编译Rust/bluer

正如笔者在上一篇博客中提到的,交叉编译bluer库之前,需要事先编译好的DBus库,这一点可以通过openwrt自动化编译构建得到。相关代码的编译构建的配置大致与上一篇博客相同,这里简单重复一下。

修改libdbus-sys-0.2.2

libdbus-sys-0.2.2源码如何获取?一种可行的方案是:第一步,在电脑上安装dbus库:sudo apt install libdbus-1-dev;第二步,在电脑上编译bluercargo build --release;编译完成bluer后,就可以在电脑上找到libdbus-sys-0.2.2以及dbus-0.9.6的源码了。根据Rust编译器的不同安装方式,其源码可能在$HOME/.cargo目录下找到,也可能在Rust的安装路径下找到。笔者在以下路径找到了二者的源码:

cp -r -p /opt/rust-lang/cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd/dbus-0.9.6 ~/
cp -r -p /opt/rust-lang/cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd/libdbus-sys-0.2.2 ~/

libdbus-sys-0.2.2的修改是为了在交叉编译dbus-0.9.6库时,解决找不到DBus库的链接问题:

diff --git a/build.rs b/build.rs
index 5e2d1cd..303fa03 100644
--- a/build.rs
+++ b/build.rs
@@ -1,18 +1,3 @@
-extern crate pkg_config;
-
 fn main() {
-    // See https://github.com/joshtriplett/metadeps/issues/9 for why we don't use
-    // metadeps here, but instead keep this manually in sync with Cargo.toml.
-    if let Err(e) = pkg_config::Config::new().atleast_version("1.6").probe("dbus-1") {
-        eprintln!("pkg_config failed: {}", e);
-        eprintln!(
-            "One possible solution is to check whether packages\n\
-            'libdbus-1-dev' and 'pkg-config' are installed:\n\
-            On Ubuntu:\n\
-            sudo apt install libdbus-1-dev pkg-config\n\
-            On Fedora:\n\
-            sudo dnf install dbus-devel pkgconf-pkg-config\n"
-        );
-        panic!();
-    }
+    println!("cargo:rustc-link-lib=dbus-1");
 }
修改dbus-0.9.6
diff --git a/Cargo.toml b/Cargo.toml
index 5a32ddb..6481f80 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -48,6 +48,9 @@ default-features = false
 [dependencies.libc]
 version = "0.2.66"
 
+[patch.crates-io]
+libdbus-sys = { git = "file:///home/yejq/libdbus-sys-0.2.2/.git" }
+
 [dependencies.libdbus-sys]
 version = "0.2.2"
修改bluer

bluer的修改,也限于对libdbus-sys/dbus两个依赖库使用自定义的patch

diff --git a/Cargo.toml b/Cargo.toml
index 021a101..067ef4c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,4 +2,8 @@
 members = [
     "bluer",
     "bluer-tools",
-]
\ No newline at end of file
+]
+
+[patch.crates-io]
+libdbus-sys = { git = "file:///home/yejq/libdbus-sys-0.2.2/.git" }
+dbus = { git = "file:///home/yejq/dbus-0.9.6/.git" }
为嵌入式ARM设备编译bluer

笔者使用树莓派做为演示的嵌入式设备,它自带了蓝牙芯片,同时openwrt系统也默认支持树莓派设备。首先,配置Rust交叉编译环境:

#!/bin/sh    

export TARGET_CC=arm-openwrt-linux-gnueabi-gcc
OPENWRT_DIR='/home/yejq/program/openwrt'

which -a ${TARGET_CC} >/dev/null 2>/dev/null
if [ $? -ne 0 ] ; then
    export STAGING_DIR="${OPENWRT_DIR}/staging_dir/target-arm_cortex-a7+neon-vfpv4_glibc_eabi"
    export PATH="${OPENWRT_DIR}/staging_dir/toolchain-arm_cortex-a7+neon-vfpv4_gcc-11.2.0_glibc_eabi/bin:${PATH}"
    export TARGET_CFLAGS='-march=armv7-a -Os -fPIC -D_GNU_SOURCE -ggdb'
    export CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER=${TARGET_CC}
fi
unset OPENWRT_DIR

之后,在bluer目录下依次执行:

cargo build --release --target armv7-unknown-linux-gnueabihf
cargo build --release --target armv7-unknown-linux-gnueabihf --examples

最后,编译得到的示例如下:

yejq@ubuntu:~/bluer/target/armv7-unknown-linux-gnueabihf/release/examples$ file discover_devices
discover_devices: ELF 32-bit LSB shared object, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/Linux 5.4.0, with debug_info, not stripped
yejq@ubuntu:~/bluer/target/armv7-unknown-linux-gnueabihf/release/examples$ ls -lh discover_devices
-rwxrwxr-x 2 yejq yejq 7.6M Jan  8 12:12 discover_devices
yejq@ubuntu:~/bluer/target/armv7-unknown-linux-gnueabihf/release/examples$ patchelf --print-needed discover_devices
libdbus-1.so.3
libgcc_s.so.1
libm.so.6
libc.so.6
ld-linux-armhf.so.3

获取原始BLE蓝牙广播包

在为树莓派设备编译openwrt系统时,需要使能bluez软件包。笔者希望通过bluer获取原始的BLE蓝牙广播包,虽然bluer提供了相关的接口,但仍需要对相关的示例(discover_devices.rs)做必要的修改(注意,以下补丁不完整,笔者省略了的dump_vecu8函数的实现):

diff --git a/bluer/examples/discover_devices.rs b/bluer/examples/discover_devices.rs
index 1a52b9c..3d2bf7c 100644
--- a/bluer/examples/discover_devices.rs
+++ b/bluer/examples/discover_devices.rs
@@ -4,6 +4,54 @@ use bluer::{Adapter, AdapterEvent, Address, DeviceEvent};
 use futures::{pin_mut, stream::SelectAll, StreamExt};
 use std::{collections::HashSet, env};
 
+fn dump_vecu8(vec: &[u8], sep: char) {
+    // Add dump code here
+}
+
 async fn query_device(adapter: &Adapter, addr: Address) -> bluer::Result<()> {
     let device = adapter.device(addr)?;
     println!("    Address type:       {}", device.address_type().await?);
@@ -18,6 +66,15 @@ async fn query_device(adapter: &Adapter, addr: Address) -> bluer::Result<()> {
     println!("    RSSI:               {:?}", device.rssi().await?);
     println!("    TX power:           {:?}", device.tx_power().await?);
     println!("    Manufacturer data:  {:?}", device.manufacturer_data().await?);
+    let adv = device.advertising_data().await?;
+    if adv.is_some() {
+        let adv = adv.unwrap();
+        for (adv_key, adv_dat) in &adv {
+            print!("    AdvertisingData:    {} -> ", adv_key);
+            dump_vecu8(&adv_dat, ' ');
+            println!();
+        }
+    }
     println!("    Service data:       {:?}", device.service_data().await?);
     Ok(())
 }

笔者在树莓派设备上的运行discover_devices示例的结果如下:

root@OpenWrt:/tmp# ./discover_devices 
Discovering devices using Bluetooth adapater hci0

Device added: 4A:04:E4:99:17:93
    Address type:       random
    Name:               None
    Icon:               None
    Class:              None
    UUIDs:              {}
    Paired:             false
    Connected:          false
    Trusted:            false
    Modalias:           None
    RSSI:               Some(-96)
    TX power:           Some(7)
    Manufacturer data:  Some({76: [16, 5, 38, 24, 44, 211, 109]})
    AdvertisingData:    38 -> 02 01 1a 02 0a 07 0a ff 4c 00 10 05 26 18 2c d3 6d 
    Service data:       None

Device added: D4:8A:3E:0F:32:86
    Address type:       public
    Name:               Some("客厅的小米电视")
    Icon:               None
    Class:              None
    UUIDs:              {0000fdaa-0000-1000-8000-00805f9b34fb}
    Paired:             false
    Connected:          false
    Trusted:            false
    Modalias:           None
    RSSI:               Some(-96)
    TX power:           None
    Manufacturer data:  Some({911: []})
    AdvertisingData:    38 -> 03 ff 8f 03 03 03 aa fd 
    Service data:       None

不过,bluez默认不会发送原始的BLE广播包数据,需要为bluez库加入一个补丁,补丁在openwrt工程的存放路径为./feeds/packages/utils/bluez/patches/207-bluez-enable-advertising-data-unconditionally.patch

From: yejq 
Date: Sun, 27 Nov 2022 15:11:00 +0800
Subject: [PATCH] Enable bluetooth advertising data unconditionally

---
 src/adapter.c   | 20 ++++++++++++++++----
 src/device.c    |  9 ++++-----
 src/eir.c       | 16 +++++++++++++---
 src/eir.h       |  2 +-
 src/shared/ad.c | 17 ++++++++++++++++-
 5 files changed, 50 insertions(+), 14 deletions(-)

diff --git a/src/adapter.c b/src/adapter.c
index 9971187..969d906 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -162,6 +162,8 @@ static GSList *adapter_drivers = NULL;
 static GSList *disconnect_list = NULL;
 static GSList *conn_fail_list = NULL;
 
+static int force_disc;
+
 struct link_key_info {
 	bdaddr_t bdaddr;
 	unsigned char key[16];
@@ -6909,12 +6911,12 @@ static void update_found_devices(struct btd_adapter *adapter,
 		return;
 
 	memset(&eir_data, 0, sizeof(eir_data));
-	eir_parse(&eir_data, data, data_len);
+	eir_parse(&eir_data, data, data_len, force_disc);
 
 	ba2str(bdaddr, addr);
 
-	discoverable = device_is_discoverable(adapter, &eir_data, addr,
-							bdaddr_type);
+	discoverable = (force_disc != 0) || \
+		device_is_discoverable(adapter, &eir_data, addr, bdaddr_type);
 
 	dev = btd_adapter_find_device(adapter, bdaddr, bdaddr_type);
 	if (!dev) {
@@ -9090,7 +9092,7 @@ static void connected_callback(uint16_t index, uint16_t length,
 
 	memset(&eir_data, 0, sizeof(eir_data));
 	if (eir_len > 0)
-		eir_parse(&eir_data, ev->eir, eir_len);
+		eir_parse(&eir_data, ev->eir, eir_len, force_disc);
 
 	if (eir_data.class != 0)
 		device_set_class(device, eir_data.class);
@@ -10250,8 +10252,18 @@ static void mgmt_debug(const char *str, void *user_data)
 
 int adapter_init(void)
 {
+	const char * fdisc;
 	dbus_conn = btd_get_dbus_connection();
 
+	force_disc = 0;
+	fdisc = getenv("FORCE_DISCOVERABLE");
+	if (fdisc && (force_disc = (int) strtol(fdisc, NULL, 0)) < 0) {
+		force_disc = 0;
+		fprintf(stderr, "Error, invalid FORCE_DISCOVERABLE: %s\n", fdisc);
+		fflush(stderr);
+	}
+	DBG("Note that force_disc = %d", force_disc);
+
 	mgmt_primary = mgmt_new_default();
 	if (!mgmt_primary) {
 		error("Failed to access management interface");
diff --git a/src/device.c b/src/device.c
index ac83cdc..fdc2bd0 100644
--- a/src/device.c
+++ b/src/device.c
@@ -3019,14 +3019,13 @@ static const GDBusPropertyTable device_properties[] = {
 					dev_property_exists_tx_power },
 	{ "ServicesResolved", "b", dev_property_get_svc_resolved, NULL, NULL },
 	{ "AdvertisingFlags", "ay", dev_property_get_flags, NULL,
-					dev_property_flags_exist,
-					G_DBUS_PROPERTY_FLAG_EXPERIMENTAL},
+					dev_property_flags_exist },
 	{ "AdvertisingData", "a{yv}", dev_property_get_advertising_data,
-				NULL, dev_property_advertising_data_exist,
-				G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
+				NULL, dev_property_advertising_data_exist },
 	{ "WakeAllowed", "b", dev_property_get_wake_allowed,
 				dev_property_set_wake_allowed,
-				dev_property_wake_allowed_exist },
+				dev_property_wake_allowed_exist,
+				G_DBUS_PROPERTY_FLAG_EXPERIMENTAL },
 	{ }
 };
 
diff --git a/src/eir.c b/src/eir.c
index 0f5d14f..37a88b0 100644
--- a/src/eir.c
+++ b/src/eir.c
@@ -238,12 +238,16 @@ static void eir_parse_data(struct eir_data *eir, uint8_t type,
 	eir->data_list = g_slist_append(eir->data_list, ad);
 }
 
-void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len)
+void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len, int forced)
 {
 	uint16_t len = 0;
+	uint8_t oldlen;
+	const uint8_t * olddata;
 
 	eir->flags = 0;
 	eir->tx_power = 127;
+	oldlen = eir_len;
+	olddata = eir_data;
 
 	/* No EIR data to parse */
 	if (eir_data == NULL)
@@ -359,12 +363,18 @@ void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len)
 			break;
 
 		default:
-			eir_parse_data(eir, eir_data[1], data, data_len);
+			if (forced == 0)
+				eir_parse_data(eir, eir_data[1], data, data_len);
 			break;
 		}
 
 		eir_data += field_len + 1;
 	}
+
+	if (forced) {
+		/* force to deliver full broadcast data */
+		eir_parse_data(eir, EIR_TRANSPORT_DISCOVERY, olddata, oldlen);
+	}
 }
 
 int eir_parse_oob(struct eir_data *eir, uint8_t *eir_data, uint16_t eir_len)
@@ -385,7 +395,7 @@ int eir_parse_oob(struct eir_data *eir, uint8_t *eir_data, uint16_t eir_len)
 
 	/* optional OOB EIR data */
 	if (eir_len > 0)
-		eir_parse(eir, eir_data, eir_len);
+		eir_parse(eir, eir_data, eir_len, 0);
 
 	return 0;
 }
diff --git a/src/eir.h b/src/eir.h
index 6154e23..bfa205d 100644
--- a/src/eir.h
+++ b/src/eir.h
@@ -90,7 +90,7 @@ struct eir_data {
 };
 
 void eir_data_free(struct eir_data *eir);
-void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len);
+void eir_parse(struct eir_data *eir, const uint8_t *eir_data, uint8_t eir_len, int forced);
 int eir_parse_oob(struct eir_data *eir, uint8_t *eir_data, uint16_t eir_len);
 int eir_create_oob(const bdaddr_t *addr, const char *name, uint32_t cod,
 			const uint8_t *hash, const uint8_t *randomizer,
diff --git a/src/shared/ad.c b/src/shared/ad.c
index 27b76dc..b35db57 100644
--- a/src/shared/ad.c
+++ b/src/shared/ad.c
@@ -1005,11 +1005,26 @@ static uint8_t type_reject_list[] = {
 bool bt_ad_add_data(struct bt_ad *ad, uint8_t type, void *data, size_t len)
 {
 	size_t i;
+	int maxlen;
+	static int force_disc = -1;
 
 	if (!ad)
 		return false;
 
-	if (len > (BT_AD_MAX_DATA_LEN - 2))
+	maxlen = force_disc;
+	if (maxlen < 0) {
+		const char * fdisc;
+		maxlen = 0;
+		fdisc = getenv("FORCE_DISCOVERABLE");
+		if (fdisc && (maxlen = (int) strtol(fdisc, NULL, 0)) < 0)
+			maxlen = 0;
+		force_disc = maxlen;
+	}
+
+	if (maxlen > 0) {
+		if (len > (size_t) maxlen)
+			return false;
+	} else if (len > (BT_AD_MAX_DATA_LEN - 2))
 		return false;
 
 	for (i = 0; i < sizeof(type_reject_list); i++) {
-- 
2.34.1

加入该补本之后,仍不能解决discover_devices读不到原始广播数据的问题,还需要修改bluetoothd守护进程的启动脚本,加入FORCE_DISCOVERABLE=42环境变量;之后就可以获得BLE蓝牙广播的原始数据了。

你可能感兴趣的:(杂谈,rust,开发语言,后端)