Python编程.Bluetooth HID Mouse and Keyboard(一)

有时候在家里用手机或者Pad打字时觉得太慢,很希望有个蓝牙键盘用用。可惜家里只有一个USB蓝牙适配器,没有蓝牙键盘。本想在Windows上实现一个虚拟蓝牙键鼠的软件,把我这个BenQ海湾键盘变成蓝牙键盘,但后来发现微软的Bluetooth协议栈实在太烂,难以实现这个功能。于是退而求其次,看看在Ubuntu上用Bluez协议栈怎么来实现。

首先用简短的文字来聊聊Bluetooth的HID协议。Bth HID协议里面有2个角色:Host主机和Device设备。主机通常是PC、笔记本或者平板电脑甚至手机,而设备就是蓝牙的鼠标、键盘或者游戏手柄之类的输入设备。

蓝牙设备通过SDP协议,增加一个SDP Record用以对外宣告自己提供HID服务,并在对应的SDP Record中附带HID Report Descriptor数据块。这个所谓的“HID报告描述符”说明了该设备所发出的数据格式。与此同时,蓝牙设备必须在0x11和0x13这两个PSM上监听L2CAP信道,并接受从蓝牙主机端发来的L2CAP连接请求。

蓝牙主机通过SDP协议发现周围的蓝牙设备,蓝牙配对成功后,如果主机发现蓝牙设备支持HID服务,则主机会主动向蓝牙设备的0x11和0x13这两个PSM发起L2CAP连接。一旦建立连接,则PSM 0x11将作为HID Control端口,而PSM 0x13则作为HID Interrupt端口。此后,蓝牙设备即可通过HID Interrupt端口向蓝牙主机发送HID数据包,而这些数据包的格式必须符合HID Report Descriptor的约定。

因为我不清楚的某些原因,Bluez的HID Host服务也占用了0x11和0x13这两个PSM,这导致我在Bluez HID Host Service存在时,不能再次打开并监听它们。所以首先我得先停掉Bluez的HID Host服务:打开/etc/bluetooth/main.conf,解除第2行代码的注释,或者在第一行代码之前插入:

DisablePlugins = input

然后重启Bluez(推荐直接重启系统):

sudo service bluetooth restart

这样,我就可以随意使用0x11和0x13这两个PSM了。

其实Python有个叫pybluez的Packet,封装了Bluetooth的接口,理论上对我有很大帮助。但当我试图打开、绑定并监听0x11和0x13这两个PSM时,listen函数报错,告诉我不能监听0x11这个PSM。这就奇怪了,因为此时Bluez的HID Host服务已经关闭,我直接用C语言编程是可以在0x11和0x13上建立socket并绑定、监听、接受连接且连接成功的。所以果断放弃这个Packet,决定用C语言写个简单的.so文件给Python来调用。(以下代码部分参考了hidclient.c这个开源代码)

 首先我们来写一个SDP HID Helper函数库,把构造SDP HID Record的繁琐步骤封装一下,只暴露必要的接口。

SDP Record由一系列Attribute组成,每个Attribute有自己的ID和数据结构,不同的AttributeID代表着不同的含义,比如0x0001代表ServiceClassIDList,而0x0004代表ProtocolDescriptorList等等。Attribute数据结构的基础元素是“类型-值”的对,多个基础元素可以组合成一个列表(Sequence),列表中的元素可以是基础元素也可以是列表。所以,特别是对于某些数据结构比较复杂的Attribute来说,组成一个合法的Attribute数据块也挺费事。

组成一个表示HID服务的SDP Record,大约需要二十几个Attribute数据。我先把这些Attribute数据的构造函数实现。

#include <errno.h>
#include <stdlib.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>

#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0]))

#define	PSMHIDCTL	0x11
#define	PSMHIDINT	0x13

//ATTR 0x0001
static void my_sdp_set_ServiceClassIDList(sdp_record_t *record) {
	uuid_t hidsvcls_uuid;
	sdp_list_t *svclass_id;
	sdp_uuid16_create(&hidsvcls_uuid, HID_SVCLASS_ID);
	svclass_id = sdp_list_append(0, &hidsvcls_uuid);
	sdp_set_service_classes(record, svclass_id);
}

//ATTR 0x0004
static void my_sdp_set_ProtocolDescriptorList(sdp_record_t *record) {
	uuid_t hidp_uuid, l2cap_uuid;
	sdp_list_t *proto_l2cap, *proto_hidp, *apseq, *aproto;
	sdp_data_t *psm;
	unsigned short ctrl = PSMHIDCTL;

	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
	proto_l2cap = sdp_list_append(0, &l2cap_uuid);
	psm = sdp_data_alloc(SDP_UINT16, &ctrl);
	proto_l2cap = sdp_list_append(proto_l2cap, psm);

	sdp_uuid16_create(&hidp_uuid, HIDP_UUID);
	proto_hidp = sdp_list_append(0, &hidp_uuid);

	apseq = sdp_list_append(0, proto_l2cap);
	apseq = sdp_list_append(apseq, proto_hidp);
	aproto = sdp_list_append(0, apseq);
	sdp_set_access_protos(record, aproto);
}

//ATTR 0x0005
static void my_sdp_set_BrowseGroupList(sdp_record_t *record) {
	uuid_t browsegroup_uuid;
	sdp_list_t *browsegroup_seq;
	sdp_uuid16_create(&browsegroup_uuid, PUBLIC_BROWSE_GROUP);
	browsegroup_seq = sdp_list_append(0, &browsegroup_uuid);
	sdp_set_browse_groups(record, browsegroup_seq);
}

//ATTR 0x0006
static void my_sdp_set_LanguageBaseAttributeIDList(sdp_record_t *record) {
	sdp_lang_attr_t base_lang;
	sdp_list_t *langs = 0;
	base_lang.code_ISO639 = (0x65 << 8) | 0x6e;
	base_lang.encoding = 106;
	base_lang.base_offset = SDP_PRIMARY_LANG_BASE;
	langs = sdp_list_append(0, &base_lang);
	sdp_set_lang_attr(record, langs);
	sdp_list_free(langs, 0);
}

//ATTR 0x0009
static void my_sdp_set_BluetoothProfileDescriptorList(sdp_record_t *record) {
	sdp_profile_desc_t profile;
	sdp_list_t *pfseq;
	sdp_uuid16_create(&profile.uuid, HID_PROFILE_ID);
	profile.version = 0x0100;
	pfseq = sdp_list_append(0, &profile);
	sdp_set_profile_descs(record, pfseq);
}

//ATTR 0x000d
static void my_sdp_set_AdditionalProtocolDescriptorLists(sdp_record_t *record) {
	uuid_t hidp_uuid, l2cap_uuid;
	sdp_list_t *proto_l2cap, *proto_hidp, *apseq, *aproto;
	sdp_data_t *psm;
	unsigned short intr = PSMHIDINT;

	sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
	proto_l2cap = sdp_list_append(0, &l2cap_uuid);
	psm = sdp_data_alloc(SDP_UINT16, &intr);
	proto_l2cap = sdp_list_append(proto_l2cap, psm);

	sdp_uuid16_create(&hidp_uuid, HIDP_UUID);
	proto_hidp = sdp_list_append(0, &hidp_uuid);

	apseq = sdp_list_append(0, proto_l2cap);
	apseq = sdp_list_append(apseq, proto_hidp);
	aproto = sdp_list_append(0, apseq);
	sdp_set_add_access_protos(record, aproto);
}

//ATTR 0x0100
static void my_sdp_set_ServiceName(sdp_record_t *record, const char *name) {
	sdp_attr_add_new(record, SDP_ATTR_SVCNAME_PRIMARY, SDP_TEXT_STR8, name);
}

//ATTR 0x0101
static void my_sdp_set_ServiceDescription(sdp_record_t *record, const char *desc) {
	sdp_attr_add_new(record, SDP_ATTR_SVCDESC_PRIMARY, SDP_TEXT_STR8, desc);
}

//ATTR 0x0102
static void my_sdp_set_ProviderName(sdp_record_t *record, const char *prov) {
	sdp_attr_add_new(record, SDP_ATTR_PROVNAME_PRIMARY, SDP_TEXT_STR8, prov);
}

//ATTR 0x0200
static void my_sdp_set_HID_DeviceReleaseNumber(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_DEVICE_RELEASE_NUMBER, SDP_UINT16, &val);
}

//ATTR 0x0201
static void my_sdp_set_HID_ParserVersion(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_PARSER_VERSION, SDP_UINT16, &val);
}

//ATTR 0x0202
static void my_sdp_set_HID_DeviceSubClass(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_DEVICE_SUBCLASS, SDP_UINT8, &val);
}

//ATTR 0x0203
static void my_sdp_set_HID_CountryCode(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_COUNTRY_CODE, SDP_UINT8, &val);
}

//ATTR 0x0204
static void my_sdp_set_HID_VirtualCable(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_VIRTUAL_CABLE, SDP_BOOL, &val);
}

//ATTR 0x0205
static void my_sdp_set_HID_ReconnectInitiate(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_RECONNECT_INITIATE, SDP_BOOL, &val);
}

/*
data - hid report descriptor data string.
 len - data length in byte.
*/
//ATTR 0x0206
static void my_sdp_set_HID_DescriptorList(sdp_record_t *record, void *data, int len) {
	void *dtds[2];
	void *values[2];
	unsigned char dtd2=SDP_UINT8;
	unsigned char hid_spec_type=0x22;
	unsigned char dtd_data=SDP_TEXT_STR8;
	int leng[2];
	sdp_data_t *hid_spec_lst;
	sdp_data_t *hid_spec_lst2;

	dtds[0] = &dtd2;
	values[0] = &hid_spec_type;
	dtds[1] = &dtd_data;
	values[1] = data;
	leng[0] = 0;
	leng[1] = len;
	hid_spec_lst = sdp_seq_alloc_with_length(dtds, values, leng, 2);
	hid_spec_lst2 = sdp_data_alloc(SDP_SEQ8, hid_spec_lst);
	sdp_attr_add(record, SDP_ATTR_HID_DESCRIPTOR_LIST, hid_spec_lst2);
}

/*
hid_attr_langs - array of uint16.
          size - how many elements in the array.
*/
//ATTR 0x0207
static void my_sdp_set_HID_LangIdBaseList(sdp_record_t *record, unsigned short *hid_attr_langs, int size) {
	void *dtds2[2];
	unsigned char dtd = SDP_UINT16;
	void *values2[2];
	sdp_data_t *lang_lst;
	sdp_data_t *lang_lst2;
	int i;

	for (i = 0; i < size; i++) {
		dtds2[i] = &dtd;
		values2[i] = &hid_attr_langs[i];
	}
	lang_lst = sdp_seq_alloc(dtds2, values2, size);
	lang_lst2 = sdp_data_alloc(SDP_SEQ8, lang_lst);
	sdp_attr_add(record, SDP_ATTR_HID_LANG_ID_BASE_LIST, lang_lst2);
}

//ATTR 0x0208
static void my_sdp_set_HID_SdpDisable(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_SDP_DISABLE, SDP_BOOL, &val);
}

//ATTR 0x0209
static void my_sdp_set_HID_BatteryPower(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_BATTERY_POWER, SDP_BOOL, &val);
}

//ATTR 0x020a
static void my_sdp_set_HID_RemoteWakeUp(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_REMOTE_WAKEUP, SDP_BOOL, &val);
}

//ATTR 0x020b
static void my_sdp_set_HID_ProfileVersion(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_PROFILE_VERSION, SDP_UINT16, &val);
}

//ATTR 0x020c
static void my_sdp_set_HID_SuperVersionTimeout(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_SUPERVISION_TIMEOUT, SDP_UINT16, &val);
}

//ATTR 0x020d
static void my_sdp_set_HID_NormallyConnectable(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_NORMALLY_CONNECTABLE, SDP_BOOL, &val);
}

//ATTR 0x020e
static void my_sdp_set_HID_BootDevice(sdp_record_t *record, unsigned int val) {
	sdp_attr_add_new(record, SDP_ATTR_HID_BOOT_DEVICE, SDP_BOOL, &val);
}

第一个参数 sdp_record_t *record 是Bluez声明的SDP Record数据结构。sdp_开头的函数也都是Bluez提供的sdp相关接口。

这些函数都是static,没错,我就没打算让上层逻辑看到它们,因为它们太底层,太细节了。现在,我就用这些工具函数来实现一层更友好的SDP HID Record接口。

struct hidc_info_t {
	char *       service_name;
	char *       description;
	char *       provider;
	char *       desc_data; //USB HID Report Descriptor data
	unsigned int desc_len;
	unsigned int sub_class;
};

static unsigned short g_hid_attr_lang[] = {0x409, 0x100};

// With 0xffffffff, we get assigned the first free record >= 0x10000
// Make HID service visible (add to PUBLIC BROWSE GROUP)
sdp_record_t *SdpCreateRecord(unsigned int handle) {
	sdp_record_t *record = calloc(1, sizeof(sdp_record_t));
	record->handle = handle;
	return record;
}

void SdpDestroyRecord(sdp_record_t *record) {
	if (record) free(record);
}

sdp_record_t *SdpMakeUpHidRecord(sdp_record_t *record, struct hidc_info_t *hidc_info) {
	my_sdp_set_ServiceClassIDList(record);
	my_sdp_set_ProtocolDescriptorList(record);
	my_sdp_set_BrowseGroupList(record);
	my_sdp_set_LanguageBaseAttributeIDList(record);
	my_sdp_set_BluetoothProfileDescriptorList(record);
	my_sdp_set_AdditionalProtocolDescriptorLists(record);
	my_sdp_set_HID_DeviceReleaseNumber(record, 0x0100);
	my_sdp_set_HID_ParserVersion(record, 0x0111);
	my_sdp_set_HID_CountryCode(record, 0x21);
	my_sdp_set_HID_VirtualCable(record, 1);
	my_sdp_set_HID_ReconnectInitiate(record, 1);
	my_sdp_set_HID_LangIdBaseList(record, g_hid_attr_lang, ARRAY_SIZE(g_hid_attr_lang));
	my_sdp_set_HID_SdpDisable(record, 0);
	my_sdp_set_HID_BatteryPower(record, 1);
	my_sdp_set_HID_RemoteWakeUp(record, 1);
	my_sdp_set_HID_ProfileVersion(record, 0x0100);
	my_sdp_set_HID_SuperVersionTimeout(record, 0x0c80);
	my_sdp_set_HID_NormallyConnectable(record, 0);
	my_sdp_set_HID_BootDevice(record, 1);

	if (hidc_info->service_name)
		my_sdp_set_ServiceName(record, hidc_info->service_name);
	if (hidc_info->description)
		my_sdp_set_ServiceDescription(record, hidc_info->description);
	if (hidc_info->provider)
		my_sdp_set_ProviderName(record, hidc_info->provider);
	my_sdp_set_HID_DeviceSubClass(record, hidc_info->sub_class);
	my_sdp_set_HID_DescriptorList(record, hidc_info->desc_data, hidc_info->desc_len);
	return record;
}

int SdpRegisterRecord(sdp_record_t *record) {
	sdp_session_t *session;

	// Connect to SDP server on localhost, to publish service information
	session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0);
	if (!session) {
		fprintf(stderr, "Failed to connect to SDP server: %s\n", strerror(errno));
		return 1;
	}

	// Submit our IDEA of a SDP record to the "sdpd"
	if (sdp_record_register(session, record, SDP_RECORD_PERSIST) < 0) {
		fprintf (stderr, "Service Record registration failed\n");
		return -1;
	}
	fprintf (stdout, "HID keyboard/mouse service registered. handle=0x%x\n", record->handle);

	return 0;
}

void SdpUnregisterRecord(unsigned int handle) {
	unsigned int	range=0x0000ffff;
	sdp_list_t    *	attr;
	sdp_session_t *	session;
	sdp_record_t  *	record;

	// Connect to the local SDP server
	session = sdp_connect(BDADDR_ANY, BDADDR_LOCAL, 0);
	if (!session) return;

	attr = sdp_list_append(0, &range);
	record = sdp_service_attr_req(session, handle, SDP_ATTR_REQ_RANGE, attr);
	sdp_list_free(attr, 0);
	if (!record) {
		sdp_close(session);
		return;
	}
	sdp_device_record_unregister(session, BDADDR_ANY, record);
	sdp_close(session);
	return;
}

亲爱的观众朋友们,现在我只需要构造一个struct hidc_info_t结构体,然后顺次调用SdpCreateRecord()、SdpMakeUpHidRecord()、SdpRegisterRecord()以及SdpDestroyRecord()就可以向Bluez的SDP服务中添加一条表示HID服务的SDP Record了。这其中,最关键的是准备一个HID Report Descriptor数组。

//==============================================================================
// Sample code
//==============================================================================

#define	HIDINFO_NAME	"Bluetooth HID Test"
#define	HIDINFO_PROV	"Huipeng Zhao"
#define	HIDINFO_DESC	"Test Bluetooth HID"

//  HID-Record for virtual keyboard and mouse device
static unsigned char g_hid_report_desc_moukbd[] = {
	0x05, 0x01,	// UsagePage GenericDesktop
	0x09, 0x02,	// Usage Mouse
	0xA1, 0x01,	// Collection Application
	0x85, 0x01,	// REPORT ID: 1
	0x09, 0x01,	// Usage Pointer
	0xA1, 0x00,	// Collection Physical
	0x05, 0x09,	// UsagePage Buttons
	0x19, 0x01,	// UsageMinimum 1
	0x29, 0x03,	// UsageMaximum 3
	0x15, 0x00,	// LogicalMinimum 0
	0x25, 0x01,	// LogicalMaximum 1
	0x75, 0x01,	// ReportSize 1
	0x95, 0x03,	// ReportCount 3
	0x81, 0x02,	// Input data variable absolute
	0x75, 0x05,	// ReportSize 5
	0x95, 0x01,	// ReportCount 1
	0x81, 0x01,	// InputConstant (padding)
	0x05, 0x01,	// UsagePage GenericDesktop
	0x09, 0x30,	// Usage X
	0x09, 0x31,	// Usage Y
	0x09, 0x38,	// Usage ScrollWheel
	0x15, 0x81,	// LogicalMinimum -127
	0x25, 0x7F,	// LogicalMaximum +127
	0x75, 0x08,	// ReportSize 8
	0x95, 0x02,	// ReportCount 3
	0x81, 0x06,	// Input data variable relative
	0xC0, 0xC0,	// EndCollection EndCollection
	0x05, 0x01,	// UsagePage GenericDesktop
	0x09, 0x06,	// Usage Keyboard
	0xA1, 0x01,	// Collection Application
	0x85, 0x02,	// REPORT ID: 2
	0xA1, 0x00,	// Collection Physical
	0x05, 0x07,	// UsagePage Keyboard
	0x19, 0xE0,	// UsageMinimum 224
	0x29, 0xE7,	// UsageMaximum 231
	0x15, 0x00,	// LogicalMinimum 0
	0x25, 0x01,	// LogicalMaximum 1
	0x75, 0x01,	// ReportSize 1
	0x95, 0x08,	// ReportCount 8
	0x81, 0x02,	// **Input data variable absolute
	0x95, 0x08,	// ReportCount 8
	0x75, 0x08,	// ReportSize 8
	0x15, 0x00,	// LogicalMinimum 0
	0x25, 0x65,	// LogicalMaximum 101
	0x05, 0x07,	// UsagePage Keycodes
	0x19, 0x00,	// UsageMinimum 0
	0x29, 0x65,	// UsageMaximum 101
	0x81, 0x00,	// **Input DataArray
	0xC0, 0xC0,	// EndCollection
};

struct hidc_info_t g_hidc_info = {
	.service_name = HIDINFO_NAME,
	.description  = HIDINFO_DESC,
	.provider     = HIDINFO_PROV,
	.desc_data    = (char*)&g_hid_report_desc_moukbd,
	.desc_len     = sizeof(g_hid_report_desc_moukbd),
	.sub_class    = 0x80,
};

static int sample_register_sdp_record(void) {
	sdp_record_t *record;
	record = (sdp_record_t *)SdpCreateRecord(0xffffffff);
	SdpMakeUpHidRecord(record, &g_hidc_info);
	SdpRegisterRecord(record);
	SdpDestroyRecord(record);
	return 0;
}

int main(void) {
	sample_register_sdp_record();
	return 0;
}

好,以上就是我的sdp_helper.c的全部内容,现在我来编译它。

gcc -o sdphelper sdp_helper.c -lbluetooth -Wall

当然,编译通过的前提是Ubuntu系统已经安装了Bluez的开发环境,安装方法:

sudo apt-get install libbluetooth-dev

在运行它之前,我打算先运用Bluez提供的sdptool工具,清除所有SDP Record,这样在运行sdphelper之后,Bluez就只有我新加入的一个SDP Record了,不会显得太乱。

sdptool del 0x10000
sdptool del 0x10001
......

可以先用 sdptool browse local 命令查看当前有多少条SDP Record。

好了,现在我终于要执行sdphelper了。

./sdphelper

它确实产生效果了吗?用 sdptool browse 命令和 sdptool records 命令看一下:

ubuntu$ sdptool browse local
Browsing FF:FF:FF:00:00:00 ...
Service Name: Bluetooth HID Test
Service Description: Test Bluetooth HID
Service Provider: Huipeng Zhao
Service RecHandle: 0x10000
Service Class ID List:
  "Human Interface Device" (0x1124)
Protocol Descriptor List:
  "L2CAP" (0x0100)
    PSM: 17
  "HIDP" (0x0011)
Language Base Attr List:
  code_ISO639: 0x656e
  encoding:    0x6a
  base_offset: 0x100
Profile Descriptor List:
  "Human Interface Device" (0x1124)
    Version: 0x0100

ubuntu$ sdptool records --raw local
Sequence
	Attribute 0x0000 - ServiceRecordHandle
		UINT32 0x00010000
	Attribute 0x0001 - ServiceClassIDList
		Sequence
			UUID16 0x1124 - HumanInterfaceDeviceService (HID)
	Attribute 0x0004 - ProtocolDescriptorList
		Sequence
			Sequence
				UUID16 0x0100 - L2CAP
				UINT16 0x0011
			Sequence
				UUID16 0x0011 - HIDP
	Attribute 0x0005 - BrowseGroupList
		Sequence
			UUID16 0x1002 - PublicBrowseGroup
	Attribute 0x0006 - LanguageBaseAttributeIDList
		Sequence
			UINT16 0x656e
			UINT16 0x006a
			UINT16 0x0100
	Attribute 0x0009 - BluetoothProfileDescriptorList
		Sequence
			Sequence
				UUID16 0x1124 - HumanInterfaceDeviceService (HID)
				UINT16 0x0100
	Attribute 0x000d - AdditionalProtocolDescriptorLists
		Sequence
			Sequence
				Sequence
					UUID16 0x0100 - L2CAP
					UINT16 0x0013
				Sequence
					UUID16 0x0011 - HIDP
	Attribute 0x0100
		String Bluetooth HID Test
	Attribute 0x0101
		String Test Bluetooth HID
	Attribute 0x0102
		String Huipeng Zhao
	Attribute 0x0200
		UINT16 0x0100
	Attribute 0x0201
		UINT16 0x0111
	Attribute 0x0202
		UINT8 0x80
	Attribute 0x0203
		UINT8 0x21
	Attribute 0x0204
		Bool True
	Attribute 0x0205
		Bool True
	Attribute 0x0206
		Sequence
			Sequence
				UINT8 0x22
				Data 05 01 09 02 a1 01 85 01 09 01 a1 00 05 09 19 01 29 03 15 00 25 01 75 01 95 03 81 02 75 05 95 01 81 01 05 01 09 30 09 31 09 38 15 81 25 7f 75 08 95 02 81 06 c0 c0 05 01 09 06 a1 01 85 02 a1 00 05 07 19 e0 29 e7 15 00 25 01 75 01 95 08 81 02 95 08 75 08 15 00 25 65 05 07 19 00 29 65 81 00 c0 c0 00
	Attribute 0x0207
		Sequence
			Sequence
				UINT16 0x0409
				UINT16 0x0100
	Attribute 0x0208
		Bool False
	Attribute 0x0209
		Bool True
	Attribute 0x020a
		Bool True
	Attribute 0x020b
		UINT16 0x0100
	Attribute 0x020c
		UINT16 0x0c80
	Attribute 0x020d
		Bool False
	Attribute 0x020e
		Bool True 

当当当——党!OK,跟我们代码里写的一样!

如果拿另一台带蓝牙模块的PC发现我的Ubuntu,蓝牙配对之后,这台PC就会主动向Ubuntu的PSM 0x11和0x13发起L2CAP连接,可以用hcidump工具来验证,此处就略过了先。所以接下来我就要实现绑定、监听和接受L2CAP连接请求的代码了。注意,hcidump工具并不是Bluez软件包自带的,需要自己apt-get install一下。

 

  • 1
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Python中,socket.AF_BLUETOOTH是用于创建蓝牙套接字的常量。你可以使用它来与蓝牙设备进行通信。它是一个在socket模块中定义的常量,用于指定套接字家族的类型。具体来说,AF_BLUETOOTH常量用于创建用于蓝牙通信的套接字。你可以通过以下代码创建一个蓝牙套接字: ```python import socket bluetooth_socket = socket.socket(socket.AF_BLUETOOTH, socket.SOCK_STREAM, socket.BTPROTO_RFCOMM) ``` 在这个例子中,我们使用socket.socket函数创建了一个蓝牙套接字对象bluetooth_socket。第一个参数指定了套接字的家族类型为蓝牙(AF_BLUETOOTH),第二个参数指定了套接字的类型为流式套接字(SOCK_STREAM),第三个参数指定了套接字的协议为RFCOMM(BTPROTO_RFCOMM)。 请注意,使用蓝牙套接字需要你的计算机支持蓝牙功能,并且你的代码必须遵循蓝牙协议和规范。具体的蓝牙通信操作和协议细节超出了本回答的范围,请根据你的具体需求查阅相关文档和资源来学习更多关于蓝牙通信的知识。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [python - AttributeError:模块'socket'没有属性'AF_PACKET' - 堆栈内存溢出](https://blog.csdn.net/weixin_39599317/article/details/110080791)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [python:socket --- 底层网络接口](https://blog.csdn.net/weixin_39145520/article/details/129396132)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值