【Linux C】基于树莓派/香橙派的蓝牙服务端——支持多蓝牙设备接入

news2024/12/26 21:42:46

一、需求

在树莓派/香橙派上利用开发板自带的蓝牙作为一个蓝牙服务端(主机),允许外来设备(从机)通过蓝牙接入进行通信,通信格式为透传方式;采用的编程语言为Linux C

二、环境准备

bluez安装

linux C在终端中输入以下命令,安装BlueZ库:

sudo apt-get update
sudo apt-get install bluez
sudo apt-get install libbluetooth-dev
修改配置文件

修改 /etc/systemd/system/dbus-org.bluez.service

在ExecStart =/usr/lib/Bluetooth/bluetoothd 后面添加-C
紧接着添加一行:ExecStartPost=/usr/bin/sdptool add SP

其中修改系统中蓝牙服务的启动选项,-C的意思就是compat,兼容性模式运行蓝牙服务;sdptool add SP是为了开机自启动SPP服务,默认是把这个服务放到channel =1的通道中,这个通道类似于socket的端口号。

在这里插入图片描述

再reboot重启跟新配置

检查蓝牙设备是否加载成功

hciconfig检查蓝牙加载情况,正常启动显示如下:

root@orangepizero2:/home/orangepi# hciconfig
hci0:   Type: Primary  Bus: UART
        BD Address: 63:E8:09:BF:10:A5  ACL MTU: 1021:8  SCO MTU: 240:3
        UP RUNNING
        RX bytes:744 acl:0 sco:0 events:51 errors:0
        TX bytes:5366 acl:0 sco:0 commands:51 errors:0
蓝牙命令行操作(非必须)

如果想改变蓝牙的配置或查询状态等,可以通过bluetoothctl的命令行进行操作,具体可以参考这篇博文:

https://blog.csdn.net/lxyoucan/article/details/124705648

三、服务端程序

代码思路

在主函数中创建一个用于广播信息的线程sendmsg_func;广播时,往在线的客户端发送相同消息

然后主函数处于监听状态,等待外来蓝牙客户端的接入,为每一个接入的客户端生成对应的recv_func线程,同时允许最多20个蓝牙客户端接入(其实蓝牙即使开了主从模式也接受不了这么多从机接入,容易出现不稳定的情况,所以这里设定20个客户端已经很大);

其中,客户端套接字数组c_fd[ClientMax]都会初始化为-1,当客户端套接字被使用后离线,程序会将该套接字的值重新置为-1,表明该套接字未被占用,后续接入的客户端可以使用该套接字;

具体实现BluetoothServer2.c如下,代码已经详细注释:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/rfcomm.h>
#include <pthread.h>

#define ClientMax 20
#define BUFSIZE 512
int c_fd[ClientMax];
char recBuf[BUFSIZE] = {0};		//用于记录接入的客户端的mac地址

/*******************
用于广播信息到各个蓝牙的线程,广播的消息这里通过终端直接输入
的形式,实际应用时,可自行修改为其他信息源
*******************/
void *sendmsg_func(void *p)
{
	int j;
	printf("启动信息发送线程:\n");
    printf("直接在空白处输入即可\n");

    char sendBuf[BUFSIZE] = {'\0'};		//用于存储要广播的消息
	while(1)
	{
        memset(sendBuf,0,BUFSIZE);
		fgets(sendBuf,BUFSIZE,stdin);	//用于用户输入要广播的消息
        
		//给所有在线的客户端发送信息
		for(j = 0;c_fd[j] > 0 && j < ClientMax;j++)
		{
			if (c_fd[j] == -1)
			{
				continue;	//如果是已退出或未使用的客户端,则不发送信息
			}
			else
			{
				if(write(c_fd[j],sendBuf,BUFSIZE) < 0 )
   				{
        			perror("write");
        			exit(-1);
    			}
			}
		}
	}
}


/*******************
用于接收新接入的蓝牙客户端消息
*******************/

void *recv_func(void *p)
{
    int tmp_c_fd = *((int *)p);		//拿到接入的客户端的套接字
    
    char nameBuf[BUFSIZE] = {0};	//存储接入的客户端的mac地址,用于区别不同客户端
    char readBuf[BUFSIZE] = {0};	//用于存储接收到对应客户端的消息
    int n_read = 0;
    
    //将全局变量recBuf接收到的mac地址,copy到nameBuf中
    strcpy(nameBuf,recBuf);    //这里其实最好要考虑线程并发对recBuf值的改变,可以考虑使用互斥量等方法
    pthread_t tid;
    tid = pthread_self();
    printf("启动线程tid:%lu,用于接收新蓝牙从机%s的信息\n" ,tid,nameBuf);
    
    while(1)
    {
        memset(readBuf,0,BUFSIZE);
        n_read = read(tmp_c_fd,readBuf,sizeof(readBuf));
		if(n_read <= 0)
		{
			//perror("read");	//调试语句
        	printf("%s中断或者下线了\n",nameBuf);
			tmp_c_fd = -1;		//如果对应的客户端退出,则令对应的c_fd的值为-1,表示掉线
			pthread_exit(NULL);	//如果客户端掉线,结束线程
		}
    	else 
   		{
        	printf("%s:#%s\n",nameBuf,readBuf);	//将用户发送的信息打印在服务端,若有数据库,这里可以将聊天记录存在数据库
		}
    }
    
}



int main()
{
    struct sockaddr_rc loc_addr = { 0 }, rem_addr = { 0 };
    
    int s,bytes_read,i,err,ret;
    pthread_t rec_tid[ClientMax] = {0};		
    pthread_t send_tid; 
    int opt = sizeof(rem_addr);

	//让本机蓝牙处于可见状态
	ret = system("hciconfig hci0 piscan");
	if(ret < 0)
	{
		perror("bluetooth discovering fail");
	}
    
    // allocate socket
    s = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);

    // bind socket to port 1 of the first available
    // local bluetooth adapter
    loc_addr.rc_family = AF_BLUETOOTH;
    loc_addr.rc_bdaddr = *BDADDR_ANY;   //相当于tcp的ip地址
    loc_addr.rc_channel = (uint8_t) 1;  //这里的通道就是SPP的通道,相当于网络编程里的端口

    bind(s, (struct sockaddr *)&loc_addr, sizeof(loc_addr));

    // put socket into listening mode
    listen(s, ClientMax);
    printf("bluetooth_server listen success\n");

    //初始化客户端套接字
    for(i = 0;i < ClientMax;i++)
    {
        c_fd[i] = -1;
    }

    //创建线程用于广播消息
    err = pthread_create(&send_tid,NULL,sendmsg_func,NULL);
	if(err)
	{
		fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
		exit(1);
	}

    //不断等待是否有新蓝牙接入
    while(1)
    {
        i = 0;
        
        //从数组中选取一个可用的客户端套接字,值等于-1即为可用的套接字
        while(1)
        {
            if((i < ClientMax) && (c_fd[i] != -1))
            {
                i++;
            }
            else if(i >= ClientMax)
            {
                fprintf(stderr,"client fd has more than 20\n");
                exit(-1);
            }
            else
            {
                break;
            }
        }

        //accept新的蓝牙接入
        c_fd[i] = accept(s, (struct sockaddr *)&rem_addr, &opt);
        if (c_fd[i] > 0){
            printf("client connected success\n");
        }
        else{
            printf("accept client fail\n");
            continue;
        }
        
        // ba2str把6字节的bdaddr_t结构
        //转为为形如XX:XX:XX:XX:XX:XX(XX标识48位蓝牙地址的16进制的一个字节)的字符串
        ba2str( &rem_addr.rc_bdaddr, recBuf);	
        fprintf(stdout, "accepted connection from %s\n", recBuf);


        //为每个新的客户端创建自己的线程用于接收信息
        err = pthread_create((rec_tid+i),NULL,recv_func,(c_fd+i));
		if (err)
		{
			fprintf(stderr,"Create pthread fail:%s\n",strerror(err));
			exit(1);
		}	
    }
    // close connection
    //close(client);
    close(s);
    return 0;
}

编译语句

将BluetoothServer2.c编译为可执行文件BluetoothServer2

gcc -o BluetoothServer2 BluetoothServer2.c -lbluetooth -lpthread

执行结果

开启服务端后,分别用两台手机的蓝牙接入服务端,并向服务端发送消息;然后服务端再广播消息到两台设备上

服务端结果
在这里插入图片描述
手机蓝牙1
在这里插入图片描述

手机蓝牙2

在这里插入图片描述

可以看到已经可以实现多客户端蓝牙通信;

局限性

未考虑多并发的情况,所以代码可以引入互斥量、条件变量等极致,防止因为并发导致的数据不准确

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/606054.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

三波混频下的相位失配原理

原理推导 在四波混频情况下&#xff0c;实现零相位失配是一件很困难的事情。因为在四波混频中&#xff0c;相位调制和增益都依赖于相同的参数&#xff0c;即克尔非线性 γ \gamma γ。这个问题可以用嵌入在传输线上的辅助共振元件的复杂色散工程来部分解决。 但是在三波混频中…

ceph集群监控

文章目录 Ceph Dashboard启用dashboard插件dashboard启用ssl Promethues监控ceph启用prometheus模块配置prometheus采集数据grafana数据展示 Ceph Dashboard ceph-dashboard官方介绍&#xff1a;https://docs.ceph.com/en/latest/mgr/dashboard/ Ceph Dashboard是一个内置的c…

数据库系统概论---选择题刷题实训

&#xff08;一&#xff09;选择题 1.下列选项中&#xff0c;不属于关系模型三要素的是&#xff08; C &#xff09; A&#xff0e;数据结构 B&#xff0e;数据操纵 C&#xff0e;数据安全 D&#xff0e;数据完整性规则 2.保证数据库…

【Spring】透过Spring源码查看Bean的命名转换规则

近期在写Spring项目的时候&#xff0c;需要通过注解的形式去替代之前直接将Bean存放在Spring容器这种方式&#xff0c;以此来简化对于Bean对象的操作&#xff0c;但是这样无法通过准确的Id去获取到相应的Bean对象了 测试观察 首先&#xff0c;如果要将指定的对象存放到Spring中…

SQL注入基础知识

文章目录 一、注入的分类1.基于服务器收到的响应2.基于处理输入的SQL查询&#xff08;数据类型&#xff09;3.基于程度和顺序的注入&#xff08;哪里受了影响&#xff09;4、基于注入点位置 二、系统函数1.字符串连接函数2.一般用于尝试的语句3.union操作符的介绍 总结 一、注入…

caught (in promise) RangeError: Maximum call stack size exceeded-vue前置导航守卫死循环

报错图 产生场景 1.近期在搭建移动端的架子时&#xff0c;在写路由守卫时&#xff0c;发现陷入死循环&#xff0c;报错意思是循环超出栈。。 2.后面排查了一圈问题之后&#xff0c;发现这个问题很小&#xff0c;但很难发现&#xff0c;在此记录。 3.vue 路由的导航守卫并不是…

类和对象【4】static成员、const对象、友元

全文目录 引言static成员static成员变量static成员函数 const对象友元友元函数友元类 总结 引言 通过前面的三篇文章&#xff0c;相信大家对类和对象已经有了一个基本的认识。 类和对象1&#xff08;初识&#xff09; 类和对象2&#xff08;默认成员函数&#xff09; 类和对象…

数据结构与算法11:堆

目录 【堆】 堆中插入和删除元素 堆排序 【堆的常见应用】 应用1&#xff1a;优先级队列 &#xff08;1&#xff09;合并有序小文件 &#xff08;2&#xff09;定时器功能 应用2&#xff1a;计算排行榜中前K个数据 应用3&#xff1a;求中位数 应用4&#xff1a;计算…

算法基础--MD5算法介绍

1、简介 MD5再开发过程中经常碰到的一种算法&#xff0c;因此感觉有必要对其原理进行更深入的了解一下。 2、算法概念 散列函数&#xff0c;也称作哈希函数&#xff0c;消息摘要函数&#xff0c;单向函数或者杂凑函数。散列函数主要用于验证数据的完整性。通过散列函数&#x…

自然语言处理从入门到应用——自然语言处理的应用任务

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 本文介绍信息抽取、情感分析、问答系统、机器翻译和对话系统等自然语言处理应用任务。这些任务可以直接或间接地以产品的形式为终端用户提供服务&#xff0c;是自然语言处理研究应用落地的主要技术。 信息抽取 信息抽…

天气预报信息获取程序--GUI--可以使用

上次正对项目中需要填写项目日志&#xff0c;制作了一个命令行版本的下载天气信息的程序&#xff0c;满足日常需要&#xff0c;调整一下界面版本的程序 如果大家使用命令行的可以使用下面的版本&#xff08;连接&#xff09; https://ht666666.blog.csdn.net/article/details…

逻辑漏洞学习-知识点总结

逻辑漏洞就是程序在实现业务逻辑上存在的错误&#xff0c;辑漏洞的出现通常是因为程序在设计业务逻辑时考虑不够全面&#xff0c;或者程序员的思维过程存在瑕疵&#xff0c;没有充分考虑到各种可能的情况 大部分程序员在设计的时候&#xff0c;目标是实现功能需求&#xff0c;…

Linux基础知识点 有这篇就足够了!!

❄️作者介绍&#xff1a;奇妙的大歪❄️ &#x1f380;个人名言&#xff1a;但行前路&#xff0c;不负韶华&#xff01;&#x1f380; &#x1f43d;个人简介&#xff1a;云计算网络运维专业人员&#x1f43d; 目录 一、 从认识操作系统开始 1.1 操作系统简介 1.2 操作系统…

chatgpt赋能Python-python分表

介绍 Python是一种流行的编程语言&#xff0c;适用于各种应用程序开发&#xff0c;包括网络应用程序、数据库应用程序以及数据分析和科学计算。Python分表是基于Python编写的分表工具&#xff0c;可以帮助开发人员更轻松地管理大型数据库表格。 Python分表是如何工作的&#…

0219-810

3GPP TS 02.19 V8.1.0 (2005-06) 前言 本技术规范由第三代合作伙伴计划 (3GPP) 制定。 本文件的内容取决于 TSG 的持续工作&#xff0c;并可能在 TSG 正式批准后发生变化。 如果 TSG 修改本文档的内容&#xff0c;TSG 将重新发布 确定发布日期的变化和版本号的增加如下&…

低代码开发平台选择指南:如何选出最适合企业的低代码平台?

低代码平台的兴起改变了公司处理软件开发的方式。这些平台使组织能够快速高效地构建应用程序&#xff0c;该应用程序可以利用预设组件和开箱功能。但是&#xff0c;因为有这么多低代码平台可以使用&#xff0c;所以为你的组织选择合适的平台可能是一个挑战。本文将探索如何低代…

FreeRTOS任务切换

PendSV异常 SVC 用于产生系统函数的调用请求。例如&#xff0c;操作系统不让用户程序直接访问硬件&#xff0c;而是通过提供一些系统服务函数&#xff0c;用户程序使用 SVC 发出对系统服务函数的呼叫请求&#xff0c;以这种方法调用它们来间接访问硬件。因此&#xff0c;当用户…

(字符串 ) 459. 重复的子字符串——【Leetcode每日一题】

❓459. 重复的子字符串 难度&#xff1a;简单 给定一个非空的字符串 s &#xff0c;检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s “abab” 输出: true 解释: 可由子串 “ab” 重复两次构成。 示例 2: 输入: s “aba” 输出: false 示例 3: 输入: s “…

IDEA+Mysql调试常见异常解决办法_kaic

IDEA导入项目出现Error: java: 程序包javax.servlet.http不存在错误的解决办法 解决方法&#xff1a;打开File>Project Structure>Libraries&#xff0c;点击右侧加号&#xff0c;寻找到tomcat的lib文件夹&#xff0c;点击ok&#xff0c;IDEA会自动重新Rebuild Project&…

数据结构 | 图的深度优先遍历和广度优先遍历(C语言)

一、数据结构定义 1、图 #define MaxVertexNum 100 // 最大可存储的节点数目/*图*/ typedef char VexterType; typedef int EdgeType;typedef struct GraphMatrix {VexterType Vexs[MaxVertexNum]; //结点 EdgeType Edges[MaxVertexNum][MaxVertexNum]; //边int vexnum, a…