基于 L2CAP 协议的蓝牙 BLE 设备通信指南

蓝牙 BLE 是什么蓝牙BLE,即蓝牙低功耗 (Bluetooth Lower Energy)是一种蓝牙通信标准,设计用于短距离通信和低功耗应用。 相比经典蓝牙,BLE 更加节能,传输距离更远,连接更快。BLE 主要用于健身设备、医疗设备、家居自动化等场景。 蓝牙 BLE 设备的连接信道L2CAP的基本概念是信道(Signaling Channel)。信道是个抽象概念,表示两个设备某个协议层之间的通道。每个信道分配一个2字节的信道ID——CID(Channel ID),每个信道功用不同,比如CID=0x0004的信道表示属性协议(Attribute Protocol)专用信道。对于BLE协议,L2CAP共有三个信道ID: 0x0004 – 属性协议 0x0005 – 低功耗信令信道 0x0006 – 安全管理协议 其他信道则用于经典蓝牙。协议复用可以理解为,不同的协议走不同的信道,互不干扰。 代码#define ATT_CID 4; // 创建 L2CAP socket int s = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); // 绑定 L2CAP socket struct sockaddr_l2 bind_addr = {0}; bind_addr.l2_family = AF_BLUETOOTH; bind_addr.l2_cid = htobs(ATT_CID); // ATT 信道 CID bdaddr_t any_addr = {{0, 0, 0, 0, 0, 0}}; bacpy(&bind_addr.l2_bdaddr, &any_addr); bind_addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; int err = bind(s, (struct sockaddr *)&bind_addr, sizeof(bind_addr)); if (err) { return -1; } // 连接 L2CAP socket struct sockaddr_l2 conn_addr = {0}; conn_addr.l2_family = AF_BLUETOOTH; conn_addr.l2_cid = htobs(ATT_CID); // ATT CID str2ba(mac_address.c_str(), &conn_addr.l2_bdaddr); conn_addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; err = connect(s, (struct sockaddr *)&conn_addr, sizeof(conn_addr)); if (err) { exit(-1); } L2CAP 数据包解析L2CAP(Logical Linked Control and Adaptation Protocol 逻辑链路控制与适配协议)工作在链路层,为上层协议提供数据通道。它支持数据分片与重组,确保数据完整可靠地传输。 ...

2024年06月16日 · 3 min · Cassius0924

解决VScode远程开发C++项目时无代码提示问题

简单粗暴,给本地主机和远程主机都安装上C/C++插件即可。

2024年06月16日 · 1 min · Cassius0924

三维重建笔记——从变换矩阵中提取并分析平移量

在编写点云变换和姿态估计代码时,我们经常需要从结果的变换矩阵中提取平移信息,并分析平移量的大小,帮助判断结果的准确性。 本文将介绍如何从变换矩阵中提取平移向量,并计算其欧几里得长度。 提取平移向量假设我们通过某种姿态估计算法得到了一个4x4的变换矩阵 result.transformation_ ,其中包含了旋转和平移信息。 我们可以通过以下代码提取出平移部分:(以Open3D为例) core::Tensor translation = result.transformation_.Slice(0, 0, 3).Slice(1, 3, 4); Slice(0,0,3);表示从矩阵的第0维(行),第0行开始,取3行,即前3行;Slice(1,3,4);表示从矩阵的第1维(列),第3列开始,取1列,即第4列。 最后获得一个3x1的平移向量,包含x、y、z三个方向的平移量。 计算欧几里得长度有了平移向量后,我们可以计算其欧几里得长度,表示平移量的大小: double translation_norm = std::sqrt((translation * translation).Sum({0, 1}).Item<double>()); std::sqrt计算平方根,translation * translation 计算向量每个元素的平方,Sum 聚合,最终得到平移向量的L2范数,即欧几里得长度。 分析平移量通过提取并计算平移向量的模长,我们可以分析相机或物体的移动距离,以及姿态估计结果的准确性。 如果 translation_norm 的值过大,可能表示估计存在误差,需要反复优化算法。 设置不同的阈值可以根据实际场景需求,判断平移量是否在允许的范围内。 以上是从变换矩阵中提取和利用平移信息的简单示例,可以提供点云变换和姿态估计任务有用的辅助分析。

2024年06月16日 · 1 min · Cassius0924

通过 frp 内网穿透实现异地 SSH 连接(反向代理)

起因是我放假回家,想在家里通过 SSH 连接放在学校的无显示器的 Linux,但是学校的 Linux 是内网,无法直接连接,且无显示器无法使用向日葵等远程桌面软件,所以想到了使用 frp 的反向代理功能实现内网穿透,进而实现异地 SSH 连接。 前提 一台具有公网 IP 的服务器(阿里云、腾讯云等) 配置远程主机我们需要有三台主机,分别是:自己的电脑、远程 Linux 主机和具有公网 IP 的服务器。 只需要在远程 Linux 和具有公网 IP 的服务器上配置 frp 即可。 首先在远程 Linux 上下载 frp,Github 下载地址。下载远程主机对应的版本,我这里是 ARM64 架构的 Linux,所以下载 frp_0.51.2_linux_arm64.tar.gz。 下载完毕后解压: tar -xvf frp_0.51.2_linux_arm64.tar.gz cd frp_0.51.2_linux_arm64 远程 Linux 为客户端,所以只需要保留 frpc* 文件即可,frps可以删除。 rm frps* 修改frpc.ini,只需要将server_addr修改为服务器的 IP 地址即可,local_ip不变。server_port和remote_port一般不变,若与其他服务冲突了可以修改。 vim frpc.ini #:wq 退出 配置服务器同样下载好对应系统版本的 frp,解压后删除frpc*文件。 rm frpc* 修改frpc.ini,确保bind_port与 frpc 客户端,即远程主机的server_port一致。 再前往阿里云或腾讯云官网配置服务器防火墙规则,开放服务器的 6000 和 7000 端口(若修改了则开放修改后的端口)。 ...

2024年06月16日 · 1 min · Cassius0924

C++ Copy&Swap 惯用法指南

Copy&Swap 是什么Copy&Swap 是一种 C++ 中常用的编程技巧,用于实现类的赋值运算符(operator=)。 实现传统写法先看看未使用 Copy&Swap 的赋值运算符写法: #include <iostream> #include <vector> class OldAClass { private: int _count; std::string _str; std::vector<int> _vec; public: OldAClass() : _count(0), _vec(10) {} // 拷贝构造函数 和 拷贝赋值运算符 OldAClass(OldAClass &a) : _count(a._count), _str(a._str), _vec(a._vec) { std::cout << "Copy constructor called\n"; } OldAClass &operator=(OldAClass &a) { std::cout << "Copy Assignment operator called\n"; if (this != &a) { //判断传入的 a 是否是自己 _count = a._count; _str = a._str; _vec = a._vec; } return *this; } // 移动构造函数 和 移动赋值运算符 OldAClass(OldAClass &&a) noexcept : _count(a._count), _str(std::move(a._str)), _vec(std::move(a._vec)) { std::cout << "Move constructor called\n"; } OldAClass &operator=(OldAClass &&a) noexcept { std::cout << "Move Assignment operator called\n"; if (this != &a) { _count = a._count; _str = std::move(a._str); _vec = std::move(a._vec); } return *this; } }; 可以看到,这种写法需要重复写两次赋值运算符,并且每次都需要判断传入的参数是否是自己,而且代码重复度高。 Copy&Swap 写法class AClass { private: int _count; std::string _str; std::vector<int> _vec; public: AClass() : _count(0), _vec(10) {} static void swap(AClass &a, AClass &b) { std::swap(a._count, b._count); std::swap(a._str, b._str); std::swap(a._vec, b._vec); } // 拷贝构造函数 AClass(AClass &a) : _count(a._count), _str(a._str), _vec(a._vec) { std::cout << "Copy constructor called\n"; } // 移动构造函数 AClass(AClass &&a) noexcept { std::cout << "Move constructor called\n"; swap(*this, a); } // 赋值运算符 AClass &operator=(AClass a) { // 注意这里的参数是值传递,会调用拷贝构造函数 std::cout << "Assignment operator called\n"; swap(*this, a); return *this; } }; 这种写法只需要写一次赋值运算符,代码更简洁,而且不需要判断传入的参数是否是自己。 ...

2024年01月15日 · 2 min

Jetson (Ubuntu Arm64) 安装 Bazel

简介本文旨在帮助用户在 Jetson 上的 Ubuntu Arm64 系统上安装 Bazel。Bazel 是一个开源的构建工具,它专注于构建和测试大型软件项目,并且被广泛应用于机器学习和深度学习领域。通过使用 Bazel,您可以更高效地管理和构建您的项目。 步骤 1:安装OpenJDK在开始安装 Bazel 之前,我们需要安装 OpenJDK。在终端中执行以下命令: sudo add-apt-repository ppa:webupd8team/java sudo apt-get update sudo apt-get install openjdk-11-jdk 步骤 2:下载 Bazel 安装包在安装 OpenJDK 之后,我们需要下载 Bazel 的安装包。在终端中执行以下命令: wget https://github.com/bazelbuild/bazel/releases/download/6.2.1/bazel-6.2.1-dist.zip 或者,您也可以从 Bazel 的 Github 仓库下载最新版本的安装包。(必须下载dist.zip文件) 步骤 3:安装 Bazel下载完成后,我们可以使用以下命令来安装 Bazel: unzip bazel-6.2.1-dist.zip -d bazel-6.2.1 bash./compile.sh sudo cp output/bazel /usr/local/bin 步骤 4:验证安装安装完成后,我们可以验证 Bazel 是否成功安装。在终端中执行以下命令: bazel version 如果一切正常,您应该能够看到如下输出:

2023年06月14日 · 1 min · Cassius0924

Ubuntu 安装 Protobuf 指南

Protobuf(Protocol Buffers)是一种轻量级的数据交换格式,常用于高效地序列化结构化数据。本指南将介绍如何在 Ubuntu 上安装 Protobuf。 步骤 1:更新系统在安装 Protobuf 之前,我们首先需要确保系统已经更新到最新版本。打开终端并执行以下命令: sudo apt update sudo apt upgrade 这将更新系统的软件包并安装最新的安全补丁。 步骤 2:安装编译工具和依赖项在安装 Protobuf 之前,我们需要安装一些编译工具和依赖项。执行以下命令来安装它们: sudo apt install build-essential autoconf libtool 这些工具和依赖项将帮助我们编译和构建 Protobuf。 步骤 3:下载和编译 Protobuf 首先,我们需要下载 Protobuf 的源代码。这里选择下载v3.20.3版本的Protobuf源码压缩包。(必须下载-all压缩包) 解压压缩包 tar -zxvf protobuf-all-3.20.3.tar.gz 这将克隆 Protobuf 的源代码到当前目录。 进入克隆下来的 Protobuf 目录: cd protobuf-all-3.20.3 在源代码目录中,运行以下命令来生成配置文件和构建系统: ./autogen.sh 接下来,我们需要运行 configure 脚本来配置编译选项。可以使用以下命令进行配置: ./configure 配置完成后,我们可以使用以下命令编译和安装 Protobuf: sudo make sudo make check #这一步可能会报错,解决方法见下文 sudo make install sudo ldconfig 编译过程可能需要一些时间,请耐心等待。 步骤 4:验证安装安装完成后,我们可以验证 Protobuf 是否成功安装。执行以下命令来检查 Protobuf 的版本信息: ...

2023年06月12日 · 1 min · Cassius0924

解决 Open3D 同时链接其他库时的 Undefined Reference 错误

当你的 Open3D 项目同时使用了 OpenCV 或 Protobuf 等其他库时,在链接库时可能会出现 Undefined Reference 的错误。这是因为 Open3D 默认使用的 C++ ABI 版本与其他库不一致导致的。 为了解决这个问题,可以在重新编译安装 Open3D 时打开 -DGLIBCXX_USE_CXX11_ABI=ON 选项,即使用 C++11 ABI 版本。以下是具体的步骤: 解决方法找到 Open3D 源码cd open3d 找不到请在 Github 上重新下载。 重新编译安装 Open3D进入 Open3D 的源代码目录的 build 子目录进行编译安装。 cd build 在 build 子目录中执行 CMake 命令生成 Makefile。在命令行中添加 -DGLIBCXX_USE_CXX11_ABI=ON 选项。 cmake .. -DBUILD_SHARED_LIBS=ON -DGLIBCXX_USE_CXX11_ABI=ON -DCMAKE_BUILD_TYPE=Release 最后,执行 make 命令编译并安装 Open3D。 make -j6 sudo make install 使用 Open3D重新编译安装后的 Open3D 就能够正常链接其他库了。

2023年06月09日 · 1 min · Cassius0924

Unity 脚本使用 NuGet 安装解决方案包

本文旨在教大家如何在 Unity 中使用 NuGet 安装解决方案包。 Visual Studio 集成了 NuGet 管理器,可以方便的安装和卸载解决方案包。但在使用 Viusal Studio 开发 Unity C# 脚本时,不能直接在内置的 NuGet 管理器安装解决方案包。因为 Unity 工程打开或运行时会刷新工程文件,导致我们在 Visual Studio 内置 NuGet 管理器安装的解决方案包失效。所以 Unity 使用 NuGet 安装解决方案包需要特殊步骤。 步骤下载解决方案包在NuGet官网下载你需要解决方案包,例如Google.Protobuf。 直接下载会下载最新预览版,稳定版需要点击右上角的 Full stats,查看所有版本。 找到稳定版 3.22.4,点击即可下载。 解压包下载后会得到拓展名为.nupkg的文件,先将文件拓展名改为.zip,接着解压即可。 导入dll文件解压后可以得到 dll 文件,位于 google.protobuf.3.22.4/lib/**/Google.Protobuf.dll ,将dll文件复制到 Unity 工程文件夹的 Assets/Plugins 文件夹下,若没有这个文件夹自己手动新建一个。 配置Unity项目接着需要将 Unity 项目的 Api Compatibility Level 更改为与 dll 文件对应的版本。打开 Unity 的 Project Settings,找到 Player > Other Settings > Api Compatibility Level。 ...

2023年06月01日 · 1 min · Cassius0924

垃圾代码书写准则

这是一个你的项目应该遵循的垃圾代码书写准则的列表,把称为适当的垃圾代码。 来自GitHub开源项目State-of-the-art。 准则💩 以一种代码已经被混淆的方式命名变量如果我们键入的东西越少,那么就有越多的时间去思考代码逻辑等问题。 Good 👍🏻 let a = 42; Bad 👎🏻 let age = 42; 💩 多用拼音命名变量和函数写拼音多方便,我们要推崇。 Good 👍🏻 function hqtpgd(tp) { //获取图片高度 return tp.height; //图片 } Bad 👎🏻 function getImageHeight(image) { return image.height; } 💩 变量/函数混合命名风格为不同庆祝一下。 Good 👍🏻 let wWidth = 640; let w_height = 480; Bad 👎🏻 let windowWidth = 640; let windowHeight = 480; 💩 不要写注释反正没人会读你的代码。 Good 👍🏻 const cdr = 700; Bad 👎🏻 更多时候,评论应该包含一些“为什么”,而不是一些“是什么”。如果“什么”在代码中不清楚,那么代码可能太混乱了。 // 700ms的数量是根据UX A/B测试结果进行经验计算的。 // @查看: <详细解释700的一个链接> const callbackDebounceRate = 700; 💩 使用母语写注释如果您违反了“无注释”原则,那么至少尝试用一种不同于您用来编写代码的语言来编写注释。如果你的母语是英语,你可能会违反这个原则。 Good 👍🏻 // Закриваємо модальне віконечко при виникненні помилки. toggleModal(false); Bad 👎🏻 // 隐藏错误弹窗 toggleModal(false); 💩 尽可能混合不同的格式为不同庆祝一下。 Good 👍🏻 ...

2023年04月21日 · 4 min · Cassius0924