Windows原生C/C++ BLE开发:绕过WinRT直连手环传感器

简介:本资源是一份面向Windows平台C/C++开发者的BLE(低功耗蓝牙)通信实践工程,适用于具备基础WinRT编程经验的中高级开发者,解决在桌面端调用系统原生API实现蓝牙设备扫描、连接、GATT服务发现与数据读写等核心场景问题。压缩包为10KB的ZIP格式,共含11个文件,包括4个头文件(.h)封装BLE操作接口与回调定义,4个源文件(.cpp)实现设备枚举、广告监听、特征值读写等关键逻辑,另含项目配置文件(.vcxproj)、过滤器(.filters)及用户设置(.user),结构完整、开箱即用。已有5965人学习下载,代码基于Windows SDK 10+,充分使用winrt/Windows.Devices.Bluetooth等命名空间,注释清晰,模块划分明确——BleHandle.h/.cpp专注通信抽象,BLEComm.h/.cpp负责主控调度,便于读者理解WinRT异步模型与蓝牙协议栈集成方式,并可快速迁移至实际工业控制、IoT设备调试等项目中。

1. Windows 平台下用纯 C/C++ 直接操作 BLE 设备:不依赖 Qt、.NET 或 UWP,绕过 WinRT 黑匣子,连上手环/标签/传感器只要 300 行核心代码

你有没有试过在 Windows 上用 C++ 写一个能扫描、连接、读写某款国产蓝牙温湿度标签的控制台程序,结果卡在BluetoothGATTGetServices返回ERROR_INVALID_HANDLE?或者发现用Windows.Devices.Bluetooth的 C++/CX 代码一编译就报winrt/base.h not found,而你压根不想装 VS 2019+、不想配 C++/WinRT 工具链、更不想把整个工程迁到 UWP 沙箱里?这个项目就是为这类场景写的——它用 Windows 10 1809+ 原生支持的 Bluetooth LE API(BluetoothAPIs.h+BthLEEnum.h),纯 Win32 C/C++ 实现完整 BLE 生命周期:设备发现 → GATT 服务枚举 → 特征值读写 → 通知订阅。没有 Qt 的QBluetoothDeviceDiscoveryAgent抽象层,没有 .NET 的BluetoothLEDevice.FromIdAsync异步黑盒,所有调用直通系统内核驱动。适合嵌入式网关、工业采集终端、自动化测试脚本等需要轻量、可控、无运行时依赖的场景。如果你手头有 Visual Studio 2017 或更高版本,5 分钟就能跑通ble_scan.exe和ble_read_temp.exe,后续改 UUID、换设备、加日志全在.c文件里动几行。


2. 架构与技术栈:基于 Windows Bluetooth LE 原生 API 的三层结构设计

这个项目不是封装库,而是一个可执行、可调试、可裁剪的最小可行 BLE 控制台工具集。它不引入任何第三方 SDK,完全基于 Windows SDK 自带头文件和静态链接库,编译产物是单个.exe,无 DLL 依赖。整个结构按职责划分为三层:底层驱动交互层(btapi_wrap.c)、GATT 协议逻辑层(gatt_client.c)、应用命令行胶水层(main.c)。这种分法不是为了炫技,而是为了让你在调试时能快速定位问题:比如扫描失败,先看btapi_wrap.c里BluetoothFindFirstRadio是否返回有效句柄;连接后读不到数据,就进gatt_client.c查BluetoothGATTGetCharacteristics的返回码和pCharValue缓冲区长度;命令行参数解析错了,直接改main.c的switch (argc)分支就行。所有模块都用标准 C99 编写,C++ 仅用于main.cpp中的少量字符串拼接(可轻松转回 C),确保你在老旧工控机或 CI 环境中也能用 MinGW-w64 或 Clang 编译通过。

2.1 底层驱动交互层:btapi_wrap.c封装 Windows 蓝牙无线电句柄管理

Windows 的 BLE 操作必须先获取本地蓝牙无线电(Radio)句柄,这是所有后续 API 的起点。btapi_wrap.c就干这一件事:安全打开、枚举、关闭 Radio,并提供线程安全的句柄缓存。关键不是“怎么写”,而是“怎么不出错”——很多开发者第一次调用BluetoothFindFirstRadio就失败,根本原因是没检查BLUETOOTH_FIND_RADIO_PARAMS结构体的dwSize字段是否初始化为sizeof(BLUETOOTH_FIND_RADIO_PARAMS)。这个字段是 Windows API 的经典陷阱:大小不对,整个结构体传参就错位,后续所有调用都返回ERROR_INVALID_PARAMETER。

// btapi_wrap.c #include <windows.h> #include <bluetoothapis.h> #include <bthleenum.h> static HBLUETOOTH_RADIO_FIND s_hRadioFind = NULL; static BLUETOOTH_RADIO_INFO s_radioInfo = {0}; // 初始化并获取第一个可用蓝牙无线电句柄 HBLUETOOTH_RADIO_HANDLE bt_open_radio() { BLUETOOTH_FIND_RADIO_PARAMS findParams = {0}; findParams.dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS); // 必须显式赋值!玄学坑点1 s_hRadioFind = BluetoothFindFirstRadio(&findParams, &s_radioInfo); if (s_hRadioFind == NULL) { DWORD err = GetLastError(); printf("BluetoothFindFirstRadio failed: %lu\n", err); return NULL; } return s_radioFind; }

这段代码里findParams.dwSize是硬编码的sizeof(...),不是宏定义也不是offsetof,因为 Windows API 文档明确要求该字段必须是结构体实际大小。我见过太多人把它写成0或sizeof(void*),结果在不同 Windows 版本上行为不一致——1809 可能容忍,22H2 就直接拒绝。另外注意s_hRadioFind是全局静态变量,避免多线程并发调用时句柄被覆盖。bt_open_radio()返回的是HBLUETOOTH_RADIO_HANDLE,不是HANDLE,不能用CloseHandle()关闭,必须用BluetoothFindRadioClose(),否则资源泄漏。这个细节在 MSDN 示例里藏得很深,但项目里已封装好bt_close_radio()来兜底。

2.2 GATT 协议逻辑层:gatt_client.c实现服务发现与特征值操作

BLE 的核心是 GATT(Generic Attribute Profile),所有通信都围绕 Service → Characteristic → Descriptor 展开。gatt_client.c不做抽象,直接调用BluetoothGATTGetServices、BluetoothGATTGetCharacteristics等原生函数,把每个步骤的错误码、缓冲区长度、内存分配逻辑全部暴露出来。比如服务发现,Windows 要求你先调用一次获取所需缓冲区大小,再分配内存重试——这不是设计缺陷,而是 Windows 内核驱动的内存管理策略。项目里用两阶段模式处理:

// gatt_client.c #include <windows.h> #include <bluetoothapis.h> #include <bthleenum.h> // 获取指定设备的所有 GATT 服务 BOOL gatt_discover_services(HBLUETOOTH_RADIO_HANDLE hRadio, BLUETOOTH_DEVICE_ADDRESS addr, PBLUETOOTH_GATT_SERVICE *ppServices, PDWORD pNumServices) { DWORD dwSize = 0; DWORD dwError = 0; // 第一阶段:获取所需缓冲区大小 BluetoothGATTGetServices(hRadio, addr, 0, NULL, &dwSize, 0); dwError = GetLastError(); if (dwError != ERROR_MORE_DATA && dwError != ERROR_SUCCESS) { printf("First call to BluetoothGATTGetServices failed: %lu\n", dwError); return FALSE; } // 分配缓冲区(注意:Windows 要求按字节对齐,这里用 malloc 而非 HeapAlloc) *ppServices = (PBLUETOOTH_GATT_SERVICE)malloc(dwSize); if (!*ppServices) { printf("Failed to allocate %lu bytes for services\n", dwSize); return FALSE; } // 第二阶段:真正获取服务列表 if (!BluetoothGATTGetServices(hRadio, addr, dwSize, *ppServices, &dwSize, 0)) { dwError = GetLastError(); printf("Second call to BluetoothGATTGetServices failed: %lu\n", dwError); free(*ppServices); *ppServices = NULL; return FALSE; } *pNumServices = (*ppServices)->NumberOfCharacteristics; // 注意:这里存的是特征值数量,不是服务数 return TRUE; }

重点看注释里的三处细节:

  • dwSize必须先调用一次获取,不能硬写1024或4096,否则在服务多的设备(如智能手表)上直接截断;
  • 内存分配用malloc而非HeapAlloc,因为BluetoothGATTGetServices内部不关心堆类型,但malloc更易调试内存泄漏;
  • NumberOfCharacteristics字段名有误导性——它实际表示该服务下特征值的数量,不是服务总数,项目里已加注释提醒,避免你误以为*pNumServices是服务个数。

这个函数返回的是PBLUETOOTH_GATT_SERVICE链表,每个节点包含ServiceUuid(128 位 UUID)、StartHandle、EndHandle,后续读写特征值全靠这两个 handle 定位。没有自动 UUID 解析,你要自己比对memcmp(&service.ServiceUuid, &target_uuid, 16),但好处是零依赖、零反射、零运行时开销。

2.3 应用胶水层:main.c提供可调试的命令行入口

main.c是整个项目的“开关面板”。它不处理任何协议逻辑,只做三件事:解析命令行参数(-scan,-connect,-read,-notify)、调用对应模块函数、打印结构化输出。比如-scan模式,它会循环调用bt_scan_devices(),每发现一个设备就打印 MAC 地址、信号强度(RSSI)、本地名称(如果广播了的话):

// main.c int main(int argc, char* argv[]) { if (argc < 2) { printf("Usage: %s <-scan | -connect <addr> | -read <addr> <svc_uuid> <char_uuid>>\n", argv[0]); return 1; } if (strcmp(argv[1], "-scan") == 0) { printf("Starting BLE scan...\n"); bt_scan_devices(); // 内部调用 bt_open_radio() + BluetoothFindFirstDevice() } else if (strcmp(argv[1], "-connect") == 0 && argc == 3) { BLUETOOTH_DEVICE_ADDRESS addr = parse_bt_address(argv[2]); // "AA:BB:CC:DD:EE:FF" → uint64_t if (addr == 0) { printf("Invalid address format\n"); return 1; } gatt_connect_device(addr); } // ... 其他分支 }

这里parse_bt_address()是个实用小函数,把常见的AA:BB:CC:DD:EE:FF、AABBCCDDEEFF、aa-bb-cc-dd-ee-ff格式统一转成BLUETOOTH_DEVICE_ADDRESS(即uint64_t)。Windows API 要求地址是大端序的 6 字节整数,但用户输入习惯是十六进制字符串,这个转换逻辑如果漏掉大小端处理,连接永远失败。项目里已实现健壮解析,支持大小写、分隔符可选,且对非法字符(如G)直接返回 0 并提示,而不是静默截断。

提示:main.c中所有printf都加了\n,避免 Windows 控制台缓冲导致日志不实时。这是血泪经验——某次在工控机上调试,发现printf("Connecting...")后没换行,程序卡住半天没输出,其实是缓冲区没刷,加\n立刻解决。


3. 编译与运行:从零开始构建可执行文件的完整流程

这个项目的目标是“开箱即用”,不是“配置半天”。它不依赖 CMakeLists.txt 的复杂规则,也不需要你手动设置 Windows SDK 版本号。整个编译流程基于 Visual Studio 的cl.exe命令行工具链,兼容 VS 2017/2019/2022,也支持 MinGW-w64(需额外链接BluetoothAPIs.lib)。下面是你真正需要做的三步,每一步都有明确命令和预期输出。

3.1 环境准备:确认 Windows SDK 和编译器路径

首先确认你的开发机已安装 Windows 10 SDK(1809 或更新版)。打开“Visual Studio Installer”,勾选 “Windows 10/11 SDK” 组件。然后以管理员身份运行 “x64 Native Tools Command Prompt for VS 2019”(或对应版本)。这个命令行环境会自动设置INCLUDE、LIB、PATH,让你无需手动配置头文件路径。验证是否就绪:

# 在 VS 命令行中执行 cl /? # 应输出 cl.exe 版本信息,如 "Microsoft (R) C/C++ Optimizing Compiler Version 19.29.30137" echo %WindowsSdkDir% # 应输出类似 "C:\Program Files (x86)\Windows Kits\10\"

如果cl命令未识别,请不要去网上搜“如何配置环境变量”,直接重装 VS Build Tools 或使用 VS Installer 修复。这是最省时间的方案。我曾帮某高校实验室排查过,他们花两天配环境变量,结果发现是 VS Installer 里 SDK 组件根本没装——重装 SDK 后 5 分钟搞定。

3.2 编译命令:一条cl命令生成全部.exe

项目源码共 4 个文件:btapi_wrap.c、gatt_client.c、uuid_utils.c(UUID 字符串解析)、main.c。编译只需一条命令,链接BluetoothAPIs.lib和Bthprops.lib(后者提供BluetoothFindFirstDevice等函数):

cl /O2 /MT /Fe:ble_scan.exe main.c btapi_wrap.c gatt_client.c uuid_utils.c /link BluetoothAPIs.lib Bthprops.lib

参数说明:

  • /O2:最大优化,减小体积、提升性能;
  • /MT:静态链接 CRT,避免目标机器缺少vcruntime140.dll;
  • /Fe:ble_scan.exe:指定输出文件名;
  • /link ...:链接蓝牙相关库,缺一不可;

编译成功后,你会看到ble_scan.exe生成,大小约 120KB(静态链接后)。运行它:

ble_scan.exe -scan

预期输出(真实设备扫描结果):

Starting BLE scan... Found device: AA:BB:CC:DD:EE:FF, RSSI: -62, Name: 'TempSensor-01' Found device: 11:22:33:44:55:66, RSSI: -78, Name: 'HeartRate-Band' Scan completed. Found 2 devices.

如果没输出设备,请先确认:

  1. 你的 Windows 蓝牙已开启且处于可被发现状态;
  2. 扫描设备(如手环)确实在广播(有些设备默认关闭广播,需长按按钮触发);
  3. 你不是在虚拟机里运行——Hyper-V 或 VMware 的蓝牙直通支持极差,必须在物理机上测试。

3.3 运行时依赖与权限:为什么你的程序总在BluetoothFindFirstRadio失败?

这是新手最高频的翻车点。BluetoothFindFirstRadio失败,90% 的原因是权限不足。Windows 蓝牙 API 要求调用进程拥有SE_LOAD_DRIVER_PRIVILEGE(加载驱动权限),而普通用户账户默认没有。解决方案只有两个:

  • 方案 A(推荐):以管理员身份运行命令行,再执行.exe;
  • 方案 B:在程序 manifest 文件中声明requireAdministrator,双击运行时自动提权;

项目已附带app.manifest文件,内容如下:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> <security> <requestedPrivileges> <requestedExecutionLevel level="requireAdministrator" uiAccess="false"/> </requestedPrivileges> </security> </trustInfo> </assembly>

编译时用/MANIFESTINPUT:app.manifest参数嵌入即可。如果你跳过这步,ble_scan.exe在非管理员命令行下必然失败,错误码是ERROR_ACCESS_DENIED(5)。这不是代码 bug,是 Windows 安全机制,必须正视。

注意:某些企业域环境禁用了管理员提权,此时只能联系 IT 部门申请临时权限。不要尝试用CreateProcessAsUser或服务方式绕过——既违反安全策略,又增加复杂度,得不偿失。


4. 常见问题排查:5 个真实踩坑记录与速查解决方案

开发 Windows BLE 应用不是写 Hello World,很多问题现象诡异、原因隐蔽。以下是我在多个工业客户现场实测总结的 5 个高频问题,每个都按「现象 → 原因 → 解决」给出可立即执行的方案,不讲原理,只给答案。

4.1 现象:BluetoothFindFirstRadio返回NULL,GetLastError()是5(ERROR_ACCESS_DENIED)

原因:进程未以管理员权限运行,Windows 拒绝授予蓝牙无线电访问权。
解决:右键点击命令行图标 → “以管理员身份运行”,再执行ble_scan.exe -scan。若需双击运行,确保已将app.manifest嵌入可执行文件(编译时加/MANIFESTINPUT:app.manifest)。

4.2 现象:BluetoothFindFirstDevice扫描不到设备,GetLastError()是122(ERROR_INSUFFICIENT_BUFFER)

原因:BLUETOOTH_DEVICE_SEARCH_PARAMS结构体的dwSize字段未初始化为sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS)。
解决:打开btapi_wrap.c,找到BluetoothFindFirstDevice调用前的searchParams变量,确认其dwSize赋值语句存在且正确。删除所有= {0}初始化,改为显式searchParams.dwSize = sizeof(searchParams);。

4.3 现象:连接设备后BluetoothGATTGetServices返回0个服务,GetLastError()是1168(ERROR_NOT_FOUND)

原因:目标设备是经典蓝牙(BR/EDR)设备,不是 BLE 设备。Windows 的BluetoothGATT*系列 API 仅支持 BLE,对传统蓝牙无效。
解决:用手机 App(如 nRF Connect)确认该设备是否广播 BLE 广告包。若广告数据中不含0x0201(Flags)或0x0303(16-bit Service UUIDs)等 BLE 特征,则不是 BLE 设备,此项目不适用。

4.4 现象:BluetoothGATTReadCharacteristicValue读取特征值成功,但返回数据全是0x00

原因:特征值 handle 错误。BluetoothGATTGetCharacteristics返回的pChar->CharacteristicHandle是属性句柄(Attribute Handle),而读取时需用pChar->ValueHandle(值句柄)。两者常被混淆。
解决:在gatt_client.c的读取函数中,确认使用的是pChar->ValueHandle而非pChar->CharacteristicHandle。项目源码中已用注释标出:“// 注意:读取用 ValueHandle,不是 CharacteristicHandle”。

4.5 现象:启用通知(BluetoothGATTRegisterForValueNotifications)后,回调函数从未触发

原因:未向通知配置描述符(Client Characteristic Configuration Descriptor, CCCD)写入0x0001(启用通知)。Windows 不自动写 CCCD,必须手动调用BluetoothGATTWriteCharacteristicValue。
解决:在注册通知前,先获取该特征值的 CCCD handle(通常为ValueHandle + 1),然后构造 2 字节数据0x01, 0x00写入。项目gatt_client.c中gatt_enable_notify()函数已实现此逻辑,检查是否被注释掉。


5. 二次开发实战:如何快速适配你的 BLE 设备(以某款温湿度传感器为例)

现在你已经能跑通扫描和连接,下一步是让程序读出真实数据。假设你手上有某款国产 BLE 温湿度传感器,它的 GATT 结构如下:

  • Service UUID:0000181a-0000-1000-8000-00805f9b34fb(Environmental Sensing)
  • Temperature Characteristic UUID:00002a6e-0000-1000-8000-00805f9b34fb
  • Data format:2 字节有符号整数(℃),需除以 100 得到实际值

改造步骤分三步:改 UUID、加解析、验数据。全程修改不超过 20 行代码,无需重新编译整个项目。

5.1 修改目标 UUID:替换main.c中的硬编码字符串

打开main.c,找到-read命令的处理分支。原代码可能用占位 UUID,你需要替换成传感器的真实 UUID:

// main.c 中 -read 分支 else if (strcmp(argv[1], "-read") == 0 && argc == 5) { BLUETOOTH_DEVICE_ADDRESS addr = parse_bt_address(argv[2]); // 替换这两行 ↓↓↓ GUID svc_uuid = {0xfb34fb9b5f800080, 0x00100000, 0x0010, {0x80,0,0,0,0,0,0,0x1a}}; // 181a GUID char_uuid = {0xfb34fb9b5f800080, 0x00100000, 0x0010, {0x80,0,0,0,0,0,0,0x6e}}; // 2a6e // ↑↑↑ 替换结束 gatt_read_characteristic(addr, &svc_uuid, &char_uuid); }

注意:Windows 的GUID结构体是DWORD Data1; WORD Data2; WORD Data3; BYTE Data4[8];,所以0000181a-...要拆成Data1=0x0000181a,Data2=0x0000,Data3=0x1000,Data4={0x80,0x00,0x00,0x80,0x5f,0x9b,0x34,0xfb}。项目已提供uuid_from_string()辅助函数(在uuid_utils.c),但手动赋值更快,且避免字符串解析开销。

5.2 添加数据解析逻辑:在gatt_client.c的读取回调中处理原始字节

BluetoothGATTReadCharacteristicValue返回的是PBLUETOOTH_GATT_VALUE,其pValue字段指向原始字节数组。你需要根据传感器文档,把字节转成有意义的数值。在gatt_read_characteristic()函数末尾添加:

// gatt_client.c void gatt_read_characteristic(BLUETOOTH_DEVICE_ADDRESS addr, GUID* pSvcUuid, GUID* pCharUuid) { // ... 前面是服务发现、特征值查找逻辑 ... PBLUETOOTH_GATT_VALUE pValue = NULL; DWORD dwValueSize = 0; if (BluetoothGATTReadCharacteristicValue(hRadio, addr, pChar->ValueHandle, &pValue, &dwValueSize, 0)) { if (dwValueSize == 2) { int16_t raw_temp = *(int16_t*)pValue->pValue; // 直接 reinterpret_cast float temp_c = (float)raw_temp / 100.0f; printf("Temperature: %.2f °C\n", temp_c); } else { printf("Unexpected value size: %lu bytes\n", dwValueSize); } free(pValue); // 必须释放,Windows 不负责 } }

这里int16_t强制转换是安全的,因为 BLE 特征值传输是小端序,而 x86/x64 也是小端,无需字节序转换。如果传感器文档写明是大端,则用ntohs(*(uint16_t*)pValue->pValue)。

5.3 验证与调试:用nRF Connect对照原始数据

不要相信自己的解析逻辑,一定要用专业工具交叉验证。下载手机 App “nRF Connect”,连接同一台传感器,找到2a6e特征值,点击 “Read” 查看原始 hex 数据(如0x2c, 0x01)。转成小端整数:0x012c = 300,除以 100 得3.00°C。然后运行你的ble_read.exe,对比输出是否一致。如果不一致,检查:

  • pValue->pValue是否为空指针(BluetoothGATTReadCharacteristicValue失败时不会分配内存);
  • dwValueSize是否真等于 2(有些传感器返回 4 字节浮点数);
  • 除法是否用了整数除法(/ 100而非/ 100.0f)导致截断。

从那以后我每次适配新设备,都强制走一遍 nRF Connect 对照流程——哪怕文档写得再清楚,硬件固件版本差异也会导致数据格式突变。这一步花 2 分钟,能省下 2 小时抓包分析时间。希望帮到你。

本文还有配套的精品资源,点击获取

相关新闻

BASE64加解密实战:可调试、可定制、可诊断的工程化实现

BASE64加解密实战:可调试、可定制、可诊断的工程化实现

简介&#xff1a;本资源是一个面向Java初学者与中级开发者的BASE64加解密工具库实践包&#xff0c;聚焦网络传输、HTTP头处理、证书解析等典型场景中二进制数据的ASCII安全编码需求。压缩包共8个文件&#xff0c;含6个核心Java源码文件&#xff08;涵盖Encoder/Decoder封装、异…

2026/10/19 20:10:47 阅读更多 →
L应用内存取证复盘:蓝队如何从内存镜像还原攻击链路

L应用内存取证复盘:蓝队如何从内存镜像还原攻击链路

我去年处理过一起很有意思的应急事件&#xff1a;某企业内部办公应用L在深夜出现异常外联&#xff0c;登录日志被攻击者清得干干净净&#xff0c;磁盘上能翻到的痕迹少得可怜。但内存镜像拉下来之后&#xff0c;攻击者的后门配置、窃取的会话凭证、甚至绕过二次认证的完整链路全…

2026/10/19 20:09:45 阅读更多 →
4G基站同步异常致小区不可用:从GPS失锁到同步板卡更换复盘

4G基站同步异常致小区不可用:从GPS失锁到同步板卡更换复盘

那天午休刚结束&#xff0c;值班手机弹出一条告警&#xff1a;某4G站点的所有小区同步异常&#xff0c;随后状态快速转成“小区不可用”。这种告警在维护群里并不少见&#xff0c;但真正让人紧张的&#xff0c;是它已经从“失步”一路演变到整站掉线&#xff0c;等于现场业务直…

2026/10/19 20:09:45 阅读更多 →

最新新闻

菲涅尔衍射模型解析WiFi呼吸检测:从原理到工程实践

菲涅尔衍射模型解析WiFi呼吸检测:从原理到工程实践

1. 从光学衍射到无线感知&#xff1a;这个项目到底在做什么 菲涅尔衍射和WiFi呼吸检测&#xff0c;乍一看是两个八竿子打不着的领域。一个是光学课本里的经典波动光学内容&#xff0c;一个是近几年火起来的无线感知应用。但把这两个东西放在一起&#xff0c;恰恰说明了一个很有…

2026/10/19 20:48:59 阅读更多 →
西门子1200与FactoryIO机械手仿真:状态机实现虚拟调试全流程

西门子1200与FactoryIO机械手仿真:状态机实现虚拟调试全流程

做工业自动化调试&#xff0c;最怕设备还没到场、程序就得交付&#xff1b;最怕机械没接线、电气没上电&#xff0c;写好的逻辑只能在脑子里跑。我前阵子捣鼓的一套FactoryIO机械手仿真取料程序&#xff0c;算是把这个困局撕开了一个口子&#xff1a;FactoryIO提供虚拟车间&…

2026/10/19 20:48:59 阅读更多 →
图表设计实战指南:从认知到维护的完整方法论

图表设计实战指南:从认知到维护的完整方法论

1. 从"diagram-design"这个标题能读出什么 第一次看到"diagram-design"这个标题&#xff0c;我的直觉是&#xff1a;这大概率不是一个单纯的"画图工具推荐"或者"设计规范文档"&#xff0c;而是一个把 图表设计 这件事当成一个完整工…

2026/10/19 20:46:54 阅读更多 →
图表设计方法论:从沟通逻辑到视觉规范的完整实践指南

图表设计方法论:从沟通逻辑到视觉规范的完整实践指南

1. 图表设计&#xff1a;从“画出来”到“讲清楚”这些年我看了不少技术文档、项目方案和系统说明&#xff0c;发现一个共性问题&#xff1a;大部分人根本不把图表当“设计”来做&#xff0c;而是当“画图”来应付。打开绘图工具&#xff0c;拖几个方框&#xff0c;连几条线&am…

2026/10/19 20:43:51 阅读更多 →
Python+Vue游戏装备交易商城系统:架构设计与实战全解析

Python+Vue游戏装备交易商城系统:架构设计与实战全解析

“基于PythonVue的游戏装备交易商城系统”这个标题&#xff0c;我太熟了。这几乎是每一届想做点东西但又不想只写个管理系统的同学都会碰到的题&#xff0c;也是后台私信里被问得最多的项目类型之一。但说句实话&#xff0c;真正把“交易”两个字做明白的&#xff0c;十个里可能…

2026/10/19 20:42:50 阅读更多 →
Java授权实战:TrueLicense密钥管理与License验证全解析

Java授权实战:TrueLicense密钥管理与License验证全解析

开头&#xff1a;从一次授权失效事故说起 做Java桌面应用授权这块&#xff0c;很多团队第一反应就是找个现成的License库&#xff0c;生成个密钥文件塞进去&#xff0c;完事。我早年也这么干过&#xff0c;结果用户改了系统时间&#xff0c;软件直接罢工&#xff1b;换台机器&a…

2026/10/19 20:42:50 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介&#xff1a;本资源为电源技术方向的学术研究文档&#xff0c;聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略&#xff0c;面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址&#xff1a; https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集&#xff0c;共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/19 10:02:04 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/19 17:32:41 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 9:10:22 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →