基于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
;第二步,在电脑上编译bluer
:cargo 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 <yejq.jiaqiang@gmail.com>
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蓝牙广播的原始数据了。