Windows原生蓝牙编程 第三章 配对后进行蓝牙通信【C++】

news2024/11/16 15:46:21

蓝牙系列文章目录
第一章 获取本地蓝牙并扫描周围蓝牙信息并输出
第二章 选取设备输入配对码并配对
第三章 配对后进行蓝牙通信


文章目录

  • 前言
  • 头文件
  • 一、建立连接套接字
  • 二、设置发送信息函数
  • 三、全部代码
  • 四、测试服务端选择及蓝牙通信
  • 总结


前言

接着第二章,我们已经配对完成,接下来就是通过winsock进行通信了。


头文件

#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable : 4995)

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
#include <windows.h>
#include <stdlib.h>
#include<bluetoothapis.h>
#include <winsock.h>
#include <ws2bth.h>
#include <locale>
#include <codecvt>
#pragma comment(lib, "wsock32.lib")
#pragma comment(lib, "bthprops.lib")
#pragma comment(lib,"ws2_32.lib")
using namespace std;
string PassKey;
HANDLE btdi;
vector<BLUETOOTH_DEVICE_INFO> devices;
BLUETOOTH_DEVICE_INFO device;
SOCKET sockClient;
WSADATA wsaData;

我又加了个SOCKET全局变量来保存套接字和WSADATA wsaData用于初始化套接字库。

一、建立连接套接字

bool buildConnect(BLUETOOTH_DEVICE_INFO device, SOCKET& sockClient) {
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		cout << "Winsock库初始化失败" << endl;
		return FALSE;
	}
	SOCKET client_socket = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
	if (client_socket == INVALID_SOCKET) {
		cout << "套接字创建失败" << endl;
		WSACleanup();
		return FALSE;
	}

	sockClient = client_socket;
	SOCKADDR_BTH serverAddress;
	memset(&serverAddress, 0, sizeof(serverAddress));
	serverAddress.addressFamily = AF_BTH;
	BTH_ADDR deviceAddress = device.Address.ullLong;
	serverAddress.btAddr = deviceAddress;
	serverAddress.port = 0;
	serverAddress.serviceClassId = SerialPortServiceClass_UUID;

	int err = ::connect(client_socket, (SOCKADDR*)&serverAddress, sizeof(serverAddress));
	if (0 == err) {
	}
	else {
		cout << "connect连接失败" << endl;
		closesocket(client_socket);
		WSACleanup();
		return FALSE;
	}

	while (true) {
		cout << "选择您的操作:1.发送信息 2.退出" << endl;
		int choice;
		cin >> choice;
		if (choice == 1) {
			cout << "请输入您想要发送的信息:";
			string message;
			cin >> message;
			int iCount = 0;
			if (sendMessage(client_socket, message, iCount)) {
				cout << "信息传输成功,发送字符数:" << iCount << endl;
			}
			else {
				cout << "send失败" << endl;
				return false;
			}
		}
		else if (choice == 2) {
			break;
		}
		else {
			cout << "输入错误,请重新输入" << endl;
		}
	}
	closesocket(client_socket);
	WSACleanup();
	BluetoothUpdateDeviceRecord(&device);
	return TRUE;
}

二、设置发送信息函数

bool sendMessage(SOCKET sockClient, string message, int& iCount) {
	int sum = 0;
	while (iCount < message.size())
	{
		wstring_convert<std::codecvt_utf8<char32_t>, char32_t> converter;
		string utf8String = converter.to_bytes(converter.from_bytes(message));
		iCount = send(sockClient, utf8String.data() + sum, utf8String.size() - sum, 0);
		if (iCount == SOCKET_ERROR)
		{
			cout << "发送失败" << endl;
			return false;
		}
		sum += iCount;
	}
}

因为我测试服务端选的utf8格式,所以要把string转为utf8格式再进行传输

string String_To_UTF8(const std::string& str)
{
	int nwLen = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);

	wchar_t* pwBuf = new wchar_t[nwLen + 1];//一定要加1,不然会出现尾巴
	ZeroMemory(pwBuf, nwLen * 2 + 2);

	::MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), pwBuf, nwLen);

	int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL);

	char* pBuf = new char[nLen + 1];
	ZeroMemory(pBuf, nLen + 1);

	::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);

	std::string retStr(pBuf);

	delete[]pwBuf;
	delete[]pBuf;

	pwBuf = NULL;
	pBuf = NULL;

	return retStr;
}

三、全部代码

#define _CRT_SECURE_NO_WARNINGS
#pragma warning(disable : 4995)

#include <iostream>
#include <string>
#include <vector>
#include <sstream>
#include <iomanip>
#include <windows.h>
#include <stdlib.h>
#include<bluetoothapis.h>
#include <winsock.h>
#include <ws2bth.h>
#include<Stringapiset.h>
#pragma comment(lib, "wsock32.lib")
#pragma comment(lib, "bthprops.lib")
#pragma comment(lib,"ws2_32.lib")
using namespace std;
string PassKey;
HANDLE btdi;
vector<BLUETOOTH_DEVICE_INFO> devices;
BLUETOOTH_DEVICE_INFO device;
SOCKET sockClient;
WSADATA wsaData;

string wstring2string(const wstring& ws)
{
	string curLocale = setlocale(LC_ALL, NULL);
	setlocale(LC_ALL, "chs");
	const wchar_t* _Source = ws.c_str();
	size_t _Dsize = 2 * ws.size() + 1;
	char* _Dest = new char[_Dsize];
	memset(_Dest, 0, _Dsize);
	wcstombs(_Dest, _Source, _Dsize);
	string result = _Dest;
	delete[]_Dest;
	setlocale(LC_ALL, curLocale.c_str());
	return result;
}
string String_To_UTF8(const std::string& str)
{
	int nwLen = ::MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, NULL, 0);

	wchar_t* pwBuf = new wchar_t[nwLen + 1];//一定要加1,不然会出现尾巴
	ZeroMemory(pwBuf, nwLen * 2 + 2);

	::MultiByteToWideChar(CP_ACP, 0, str.c_str(), str.length(), pwBuf, nwLen);

	int nLen = ::WideCharToMultiByte(CP_UTF8, 0, pwBuf, -1, NULL, NULL, NULL, NULL);

	char* pBuf = new char[nLen + 1];
	ZeroMemory(pBuf, nLen + 1);

	::WideCharToMultiByte(CP_UTF8, 0, pwBuf, nwLen, pBuf, nLen, NULL, NULL);

	std::string retStr(pBuf);

	delete[]pwBuf;
	delete[]pBuf;

	pwBuf = NULL;
	pBuf = NULL;

	return retStr;
}
string getMAC(BLUETOOTH_ADDRESS Daddress)
{
	/*string addr;
	addr = addr.sprintf("%02x:%02x:%02x:%02x:%02x:%02x", Daddress.rgBytes[5], Daddress.rgBytes[4], Daddress.rgBytes[3], Daddress.rgBytes[2], Daddress.rgBytes[1], Daddress.rgBytes[0]);
	return addr;*/
	ostringstream oss;
	oss << hex << setfill('0') << uppercase;
	for (int i = 5; i >= 0; --i) {
		oss << setw(2) << static_cast<int>(Daddress.rgBytes[i]);
		if (i > 0) {
			oss << ":";
		}
	}
	return oss.str();
}

vector<BLUETOOTH_DEVICE_INFO> scanDevices()
{
	HBLUETOOTH_RADIO_FIND hbf = NULL;
	HANDLE hbr = NULL;
	HBLUETOOTH_DEVICE_FIND hbdf = NULL;
	BLUETOOTH_FIND_RADIO_PARAMS btfrp = { sizeof(BLUETOOTH_FIND_RADIO_PARAMS) }; //调用BluetoothFindFirstDevice搜索本机蓝牙收发器所需要的搜索参数对象
	BLUETOOTH_RADIO_INFO bri = { sizeof(BLUETOOTH_RADIO_INFO) }; //初始化一个储存蓝牙收发器信息(BLUETOOTH_RADIO_INFO)的对象bri
	BLUETOOTH_DEVICE_SEARCH_PARAMS btsp = { sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS) };//调用BluetoothFindFirstDevice搜索本所需要的搜索参数对象
	BLUETOOTH_DEVICE_INFO btdi = { sizeof(BLUETOOTH_DEVICE_INFO) };  //初始化一个远程蓝牙设备信息(BLUETOOTH_DEVICE_INFO)对象btdi,以储存搜索到的蓝牙设备信息
	hbf = BluetoothFindFirstRadio(&btfrp, &hbr); //得到第一个被枚举的蓝牙收发器的句柄hbf可用于BluetoothFindNextRadio,hbr可用于BluetoothFindFirstDevice。若没有找到本机的蓝牙收发器,则得到的句柄hbf=NULL

	vector<BLUETOOTH_DEVICE_INFO> res;

	bool brfind = hbf != NULL;
	while (brfind)
	{
		if (BluetoothGetRadioInfo(hbr, &bri) == ERROR_SUCCESS)//获取蓝牙收发器的信息,储存在bri中
		{
			cout << "[Local Device Name]:" << wstring2string(bri.szName) << "\t";   //蓝牙收发器的名字
			cout << "[Local Device Address]: " << getMAC(bri.address) << endl;
			cout << "------------------------蓝牙扫描结果如下----------------------------" << endl;

			btsp.hRadio = hbr;  //设置执行搜索设备所在的句柄,应设为执行BluetoothFindFirstRadio函数所得到的句柄
			btsp.fReturnAuthenticated = TRUE;//是否搜索已配对的设备
			btsp.fReturnConnected = FALSE;//是否搜索已连接的设备
			btsp.fReturnRemembered = TRUE;//是否搜索已记忆的设备
			btsp.fReturnUnknown = TRUE;//是否搜索未知设备
			btsp.fIssueInquiry = TRUE;//是否重新搜索,True的时候会执行新的搜索,时间较长,FALSE的时候会直接返回上次的搜索结果。
			btsp.cTimeoutMultiplier = 30;//指示查询超时的值,以1.28秒为增量。 例如,12.8秒的查询的cTimeoutMultiplier值为10.此成员的最大值为48.当使用大于48的值时,调用函数立即失败并返回
			hbdf = BluetoothFindFirstDevice(&btsp, &btdi);//通过找到第一个设备得到的HBLUETOOTH_DEVICE_FIND句柄hbdf来枚举远程蓝牙设备,搜到的第一个远程蓝牙设备的信息储存在btdi对象中。若没有远程蓝牙设备,hdbf=NULL。
			bool bfind = hbdf != NULL;
			int i = 0;

			while (bfind)
			{
				cout << ++i << " " << "[Name]:" << wstring2string(btdi.szName);  //远程蓝牙设备的名字
				cout << "\t[Address]:" << getMAC(btdi.Address) << endl;
				res.push_back(btdi);
				bfind = BluetoothFindNextDevice(hbdf, &btdi);//通过BluetoothFindFirstDevice得到的HBLUETOOTH_DEVICE_FIND句柄来枚举搜索下一个远程蓝牙设备,并将远程蓝牙设备的信息储存在btdi中
			}
			BluetoothFindDeviceClose(hbdf);//使用完后记得关闭HBLUETOOTH_DEVICE_FIND句柄hbdf。
		}
		CloseHandle(hbr);
		brfind = BluetoothFindNextRadio(hbf, &hbr);//通过BluetoothFindFirstRadio得到的HBLUETOOTH_RADIO_FIND句柄hbf来枚举搜索下一个本地蓝牙收发器,得到可用于BluetoothFindFirstDevice的句柄hbr。
	}
	return res;
}

void setPassKey() {
	cout << "请输入配对码:(如果不知道请输入默认0000):";
	cin >> PassKey;
	cout << "您输入的配对码为: " << PassKey << endl;
}

bool pairDevice() {
	cout << "输入你想要配对的设备的序号:";
	int index;
	cin >> index;
	//获取设备信息
	device = devices[index - 1];
	//输入配对码
	setPassKey();
	//string转PWSTR
	wstringstream wss;
	for (char c : PassKey) {
		wss << wchar_t(c);
	}
	wstring wstr = wss.str();
	const wchar_t* wcharPtr = wstr.c_str();
	PWSTR AUTHENTICATION_PASSKEY = const_cast<PWSTR>(wcharPtr);
	//开始配对
	wstring ws = device.szName;
	HBLUETOOTH_AUTHENTICATION_REGISTRATION hCallbackHandle = 0;
	DWORD result = -1;
	if (!device.fAuthenticated) {
		result = BluetoothAuthenticateDevice(NULL, btdi, &device, AUTHENTICATION_PASSKEY, (ULONG)wcslen(AUTHENTICATION_PASSKEY));  //配对函数,AUTHENTICATION_PASSKEY是我的蓝牙配对码
		if (result != ERROR_SUCCESS) {
			switch (result)
			{
			case ERROR_CANCELLED:
				cout << "用户取消了身份验证或配对操作" << endl;
				break;
			case ERROR_INVALID_PARAMETER:
				cout << "传递给函数的参数无效" << endl;
				break;
			case ERROR_NO_MORE_ITEMS:
				cout << "没有更多的设备可以配对" << endl;
				break;
			case ERROR_NOT_SUPPORTED:
				cout << "不支持请求的操作" << endl;
				break;
			case ERROR_GEN_FAILURE:
				cout << "通用失败错误" << endl;
				break;
			case ERROR_BUSY:
				cout << "蓝牙堆栈忙" << endl;
				break;
			case ERROR_TIMEOUT:
				cout << "操作超时" << endl;
				break;
			case ERROR_DEVICE_NOT_CONNECTED:
				cout << "蓝牙设备未连接" << endl;
				break;
			case ERROR_DEVICE_NOT_AVAILABLE:
				cout << "设备不可用" << endl;
				break;
			default:
				cout << "校验码出错,请手动进行设备连接" << endl;
				break;
			}
			return false;
		}
	}
	cout << "身份验证成功,蓝牙设备已成功配对" << endl;;
	BluetoothUpdateDeviceRecord(&device);
	return true;
}

bool sendMessage(SOCKET sockClient, string message, int& iCount) {
	int sum = 0;
	while (iCount < message.size())
	{
		string utf8message = String_To_UTF8(message);
		iCount = send(sockClient, utf8message.data() + sum, utf8message.size() - sum, 0);
		if (iCount == SOCKET_ERROR)
		{
			cout << "发送失败" << endl;
			return false;
		}
		sum += iCount;
	}
}

bool buildConnect(BLUETOOTH_DEVICE_INFO device, SOCKET& sockClient) {
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		cout << "Winsock库初始化失败" << endl;
		return FALSE;
	}
	SOCKET client_socket = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM);
	if (client_socket == INVALID_SOCKET) {
		cout << "套接字创建失败" << endl;
		WSACleanup();
		return FALSE;
	}

	sockClient = client_socket;
	SOCKADDR_BTH serverAddress;
	memset(&serverAddress, 0, sizeof(serverAddress));
	serverAddress.addressFamily = AF_BTH;
	BTH_ADDR deviceAddress = device.Address.ullLong;
	serverAddress.btAddr = deviceAddress;
	serverAddress.port = 0;
	serverAddress.serviceClassId = SerialPortServiceClass_UUID;

	int err = ::connect(client_socket, (SOCKADDR*)&serverAddress, sizeof(serverAddress));
	if (0 == err) {
	}
	else {
		cout << "connect连接失败" << endl;
		closesocket(client_socket);
		WSACleanup();
		return FALSE;
	}

	while (true) {
		cout << "选择您的操作:1.发送信息 2.退出" << endl;
		int choice;
		cin >> choice;
		if (choice == 1) {
			cout << "请输入您想要发送的信息:";
			string message;
			cin >> message;
			int iCount = 0;
			if (sendMessage(client_socket, message, iCount)) {
				cout << "信息传输成功,发送字符数:" << iCount << endl;
			}
			else {
				cout << "send失败" << endl;
				return false;
			}
		}
		else if (choice == 2) {
			break;
		}
		else {
			cout << "输入错误,请重新输入" << endl;
		}
	}
	closesocket(client_socket);
	WSACleanup();
	BluetoothUpdateDeviceRecord(&device);
	return TRUE;
}

void main() {
	devices = scanDevices();
	if (pairDevice()) {
		buildConnect(device, sockClient);
	}
	return;
}

四、测试服务端选择及蓝牙通信

随便在网上下载了个蓝牙测试的app
在这里插入图片描述
测试一下,选择要连接的设备并且输入配对码
在这里插入图片描述
连接成功的同时手机显示连接成功
在这里插入图片描述
发送两条信息
在这里插入图片描述

同时手机收到
在这里插入图片描述
返回发送字符数那有点问题,因为转成utf-8格式,一个中文占3个字符,我懒得改了,不过测试结果还是很成功的,完美实现蓝牙通信。

总结

如果您觉得有用的话,务必点赞评论收藏一波,万分感谢咯!=v=

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

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

相关文章

爱写bug的小邓程序员个人博客

博客网址: http://www.006969.xyz 欢迎来到我的个人博客&#xff0c;这里主要分享我对于前后端相关技术的学习笔记、项目实战经验以及一些技术感悟。 在我的博客中&#xff0c;你将看到以下主要内容&#xff1a; 技术文章 我将会分享我在学习前后端技术过程中的一些感悟&am…

【并发编程】进程与线程

主要知识点&#xff1a; 进程和线程的概念 并行和并发的概念 线程基本应用 一、进程与线程 进程 程序由指令和数据组成&#xff0c;但这些指令要运行&#xff0c;数据要读写&#xff0c;就必须将指令加载至 CPU&#xff0c;数据加载至内存。在指令运行过程中还需要用到磁盘、…

【SpringMVC篇】讲解RESTful相关知识

&#x1f38a;专栏【SpringMVC】 &#x1f354;喜欢的诗句&#xff1a;天行健&#xff0c;君子以自强不息。 &#x1f386;音乐分享【如愿】 &#x1f384;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f384;REST简介&#x1f33a;RESTful入门案例⭐案例一⭐…

【Java笔试强训】Day7(WY22 Fibonacci数列、CM46 合法括号序列判断)

Fibonacci数列 链接&#xff1a;Fibonacci数列 题目&#xff1a; Fibonacci数列是这样定义的&#xff1a; F[0] 0 F[1] 1 for each i ≥ 2: F[i] F[i-1] F[i-2] 因此&#xff0c;Fibonacci数列就形如&#xff1a;0, 1, 1, 2, 3, 5, 8, 13, …&#xff0c;在Fibonacci数列…

虚拟机上的linux centos7无法连接ssh

1、排查有没有安装 openssh-server&#xff0c;在终端中输入 yum list installed | grep openssh-server此处显示已经安装了 openssh-server&#xff0c;如果又没任何输出显示表示没有安装 openssh-server&#xff0c;通过输入 yum install openssh-server进行安装 2、找到了…

Spring本地jar包依赖项目改为maven依赖

1.简介 我们在做项目的时候&#xff0c;可能会偶尔接手较为古老的项目&#xff0c;这些项目使用了较为老旧的版本管理或依赖管理方法&#xff0c;对于新开发项目来说&#xff0c;这些老旧的依赖管理方式会影响开发效率&#xff0c;所以&#xff0c;一般我们会选择将老项目的依…

MySQL教程笔记

MySQL 关系型数据库&#xff1a;建立在关系模型基础上&#xff0c;由多张相互连接的二维表组成的数据库。 MYSQL基础 DDL Data Definition Language&#xff0c;数据定义语言&#xff0c;用来定义数据库对象(数据库&#xff0c;表&#xff0c;字段) 。 相关操作如下&#x…

golang中的Interface接口 类型断言、接口赋值、空接口的使用、接口嵌套

Interface整理 文章目录 Interface整理接口嵌套接口类型断言类型判断 type-switch使用方法集与接口空接口实例 接口赋值给接口 接口是一种契约&#xff0c;实现类型必须满足它&#xff0c;它描述了类型的行为&#xff0c;规定类型可以做什么。接口彻底将类型能做什么&#xff0…

如何在十亿级别用户中检查用户名是否存在?

不知道大家有没有留意过&#xff0c;在使用一些app注册的时候&#xff0c;提示你用户名已经被占用了&#xff0c;需要更换一个&#xff0c;这是如何实现的呢&#xff1f;你可能想这不是很简单吗&#xff0c;去数据库里查一下有没有不就行了吗&#xff0c;那么假如用户数量很多&…

常用排序算法的理解

1.插入排序 插入排序的思想是将一个记录插入到已经排好序的有序表中&#xff0c;从而形成一个新的、记录数加1的有序表。在其实现过程使用双层循环&#xff0c;外层循环是进行插入的次数&#xff08;也可以理解为比较的轮数&#xff09;&#xff0c;内层循环是当前记录查找插入…

Echats-自定义图表2

效果图&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html lang"zh-cmn-Hans"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><title>…

iptables的四表五链以及一些应用小场景

一、前言 本文主要学习iptables的一些学习&#xff0c;讲解一些四表五链的基本概念&#xff0c;同时通过iptables实现一下场景&#xff0c;比如反向代理端口、禁用域名、限制IP和端口访问。 二、基本概念 2.1 什么是iptables iptables是Linux的防火墙管理工具而已&#xff0c…

昂利康-002940 三季报分析(20231030)

昂利康-002940 基本面分析 基本情况 公司名称&#xff1a;浙江昂利康制药股份有限公司 A股简称&#xff1a;昂利康 成立日期&#xff1a;2001-12-30 上市日期&#xff1a;2018-10-23 所属行业&#xff1a;医药制造业 周期性&#xff1a;0 主营业务&#xff1a;化学原料药及制剂…

hack_me_please靶机攻略

hack_me_please 扫描 探查无果&#xff0c;扫描js的时候有结果 访问可以看到 该页面可以看到是SeedDMS搭的 应该和CMS类似 渗透 漏洞库查找一下有没有该漏洞 使用whatweb扫描一下刚才的页面 whatweb http://10.4.7.154/seeddms51x/seeddms-5.1.22/ 这个版本高于漏洞库的&a…

软考系统架构师知识点集锦八:嵌入式系统

一、考情分析 二、考点精讲 2.1嵌入式系统概述 2.1.1基本概念 (1)嵌入式系统是以应用为中心、以计算机技术为基础,并将可配置与可裁剪的软、硬件集成于一体的专用计算机系统&#xff0c;需要满足应用对功能、可靠性、成本、体积和功耗等方面的严格要求。 (2)从计算机角度看,嵌…

0039Java程序设计-基于java校园闲置物交易系统论文

文章目录 摘 要目 录系统设计开发环境 摘 要 本文的研究方向是设计和实现学生闲置物网上交易平台。目前&#xff0c;各大高校每年都要举办热热闹闹的“跳蚤”市场&#xff0c;就是给师生的一个闲置物品交易的场所&#xff0c;由此可以看出&#xff0c;大学生对闲置物品交易掉的…

数据结构:算法(特性,时间复杂度,空间复杂度)

目录 1.算法的概念2.算法的特性1.有穷性2.确定性3.可行性4.输入5.输出 3.好算法的特质1.正确性2.可读性3.健壮性4.高效率与低存储需求 4.算法的时间复杂度1.事后统计的问题2.复杂度表示的计算1.加法规则2.乘法规则3.常见函数数量级比较 5.算法的空间复杂度1.程序的内存需求2.例…

CAN总线通信协议

Reference video: 趋近于完美的通讯 CAN总线&#xff01;4分钟看懂&#xff01; CAN通信精华整理&#xff0c;汽车工程师必备技能&#xff0c;一个视频带你轻松掌握&#xff01; 写在前面&#xff1a;CAN通信就三个要点 - 波特率的配置 - 过滤寄存器的配置与理解&#xff08;…

Django 社区志愿者管理系统

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 社区志愿者服务管理系统&#xff0c;主要的模块包括查看首页、个人中心、通知公告管理、志愿者管理、普通管理员管理、志愿活动管理、活动宣…

计算机毕业设计选题推荐-超市售货微信小程序/安卓APP-项目实战

✨作者主页&#xff1a;IT研究室✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Python…