本农16,17年做过蓝牙开发,开发的是一个上位机COM通信驱动,和单片机通过蓝牙模拟出的COM口进行通信。项目完成后的空余时间,本农就做做了个测试工具,想要和手机之间通信,但不是通过COM传输数据,而是传输文件,传输文件相比com的优点是:com通信传输必须两端都有通讯socket,所以要想使用com通讯必须针对手机也要编写程序接收数据.但是传输文件不需要,如下链接,我想完成的就是这个功能:
https://support.microsoft.com/en-us/windows/send-files-over-bluetooth-in-windows-36f8cf26-d1ff-50d1-4b73-3a56e5b43e6a
最终没有成功,随着时间推移,本农就放弃了。
七年后的现在,本农闲着没事干,想起这件往事,一直耿耿于怀。这次,本农想凭借这么多年处理问题的经验,在技术上实现一次突破,完成这个事情。
1. 首先要更改GUID
GUID是决定通信的方式,com使用SerialPort:
SerialPortServiceClass_UUID
传输文件呢?七年前我卡在连接那里,当时我使用的就是下面的OBEXFileTransfer,因为我觉得这个单词很贴切啊,其他的我看哪个都不像,我坚持这个GUID肯定没问题
OBEXFileTransferServiceClass_UUID
当年连接都没有成功,
errcode 10049
实在找不出到底是哪里有问题,这次我蒙了一把,换成如下,没想到啊,连接成功了:
// RemoteEndPoint.serviceClassId = SerialPortServiceClass_UUID; // COM Protocol
// RemoteEndPoint.serviceClassId = OBEXFileTransferServiceClass_UUID; // error, connect return 10049
// RemoteEndPoint.serviceClassId = HandsfreeServiceClass_UUID; // RFCOMM_PROTOCOL_UUID
RemoteEndPoint.serviceClassId = OBEXObjectPushServiceClass_UUID;
2. 通过OBEX协议通讯
终于找到方向了,下面就是查找相关资料,obex到底是个什么鬼.来自这位博主的提问,我才发现通讯接口就是socket send:
https://stackoverflow.com/questions/66454638/how-does-the-obex-protocol-look-like
const char package[] =
/* Connect | 2B of length| OBEX Ver 1.0| Flag| Max Size */
{0x80, 0x00, 0x07, 0x10, 0x00, 2048>>8, 2048&0xFF};
3. 使用OBEX协议PUT指令发送文件:
离真相越来越近了,但是怎样传输文件呢?又费了几天时间,我在git上找到了一个开源库,这位大神没有进行任何封装,展示了全部源码.将源码编译后运行,我从中解析出了OBEX数据,原来如此,感觉和com差不多,区别就是com都是各家厂商定制的,但obex是标准协议.
通过这些数据,我再对比OBEX文档进行数据解析:
const char* str = "82002bc30000000f01001100620074002e007400780074000049001268656c6c6f2c20776f726c64200d0a";
/* Put | 2B len| HI len | Name| b t . t x t |HI end|h e l l o , w o r l d \r\n */
4. Final,我耗费了接近2周时间完成了七年间一直悬而未决的问题
下面,放上相关源码及文档:
openbox source: https://github.com/zuckschwerdt/openobex
OBEX Protocol: https://irda.org/standards/pubs/OBEX13.pdf
MS-IRDA
这是一位博主的蓝牙AT指令源码,我做了一些改进,写了一个测试程序:
https://stackoverflow.com/questions/37098557/c-winsock-bluetooth-connection-at-command-error-received
/
//
// MSDN Bluetooth and Connect
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa362901(v=vs.85).aspx
//
// MSDN Send Function
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms740149(v=vs.85).aspx
//
// MSDN Bluetooth and read or write operations:
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa362907(v=vs.85).aspx
//
// Error Codes:
// See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx
//
#include "zss.cmnlib.h"
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
// #include <afxwin.h>
#include <WinSock2.h>
#include <bthsdpdef.h>
#include <bluetoothapis.h>
#include <ws2bth.h>
// #include "Zss.Zsslog.h"
// Preprocessor Directive: Pragma's:
// Need to link with Ws2_32.lib
#pragma comment(lib, "ws2_32.lib")
// define Directive's:
#define DEFAULT_BUFLEN 512
DEFINE_GUID(GUID_NULL, "00000000-0000-0000-0000-000000000000");
int __cdecl main(int argc, char **argv)
{
std::cout << "******************************************************\r\n";
//--------------------------------------------
// Locals:
//--------------------------------------------
int result = 0;
ULONG iResult = 0;
//--------------------------------------------
// Prep the Buffer's:
//--------------------------------------------
int recvbuflen = DEFAULT_BUFLEN;
char recvbuf[DEFAULT_BUFLEN] = "";
// char *sendbuf = "AT+COPS?\r";
char *sendbuf = "AT+BTVER?\r";
int len = (int)strlen(sendbuf);
//--------------------------------------------
// Initialise WinSock.
//--------------------------------------------
WSADATA WSAData = { 0 };
WORD wVersionRequested = MAKEWORD(2, 2);
if ((iResult = WSAStartup(wVersionRequested, &WSAData)) != 0)
{
}
std::cout << "WINSOCK: 'WSAData' Return Code: " << WSAGetLastError() << "\r\n";
//--------------------------------------------
// The WinSock Socket.
//--------------------------------------------
SOCKET LocalSocket = INVALID_SOCKET;
//--------------------------------------------
// Local End Point SOCKADDR_BTH.
//--------------------------------------------
SOCKADDR_BTH LocalEndpoint;
// number of service channel, 0 or BT_PORT_ANY;
LocalEndpoint.port = 0;
LocalEndpoint.addressFamily = AF_BTH;
LocalEndpoint.btAddr = 0;
LocalEndpoint.serviceClassId = GUID_NULL;
std::cout << " Local EndPoint Address: " << LocalEndpoint.btAddr << "\r\n";
//--------------------------------------------
// Remote End Point SOCKADDR_BTH.
//--------------------------------------------
SOCKADDR_BTH RemoteEndPoint;
// number of service channel, 0 or BT_PORT_ANY;
RemoteEndPoint.port = 0;
RemoteEndPoint.addressFamily = AF_BTH;
// bt address of my smartphone ;
RemoteEndPoint.btAddr = BTH_ADDR(0xD428D57436B0);
// RemoteEndPoint.serviceClassId = SerialPortServiceClass_UUID; // COM Protocol
// RemoteEndPoint.serviceClassId = OBEXFileTransferServiceClass_UUID; // error, connect return 10049
//RemoteEndPoint.serviceClassId = HandsfreeServiceClass_UUID; // RFCOMM_PROTOCOL_UUID
RemoteEndPoint.serviceClassId = OBEXObjectPushServiceClass_UUID;
int BTHAddrLength = sizeof(RemoteEndPoint);
std::cout << " Remote EndPoint Address: " << RemoteEndPoint.btAddr << "\r\n";
//--------------------------------------------
// Create the socket.
//--------------------------------------------
if ((LocalSocket = socket(AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM)) == INVALID_SOCKET)
{
}
std::cout << "WINSOCK: 'socket' Return Code: " << WSAGetLastError() << "\r\n";
//--------------------------------------------
// Bind the socket.
//--------------------------------------------
if ((iResult = bind(LocalSocket, (SOCKADDR *)&LocalEndpoint, sizeof(LocalEndpoint))) == SOCKET_ERROR)
{
}
std::cout << "WINSOCK: 'bind' Return Code: " << WSAGetLastError() << "\r\n";
//--------------------------------------------
// Connect the socket.
//--------------------------------------------
if ((iResult = connect(LocalSocket, (SOCKADDR *)&RemoteEndPoint, sizeof(RemoteEndPoint))) == INVALID_SOCKET)
{
}
std::cout << "WINSOCK: 'connect' Return Code: " << WSAGetLastError() << "\r\n";
//--------------------------------------------
// Send the Buffer.
//--------------------------------------------
// if ((iResult = send(LocalSocket, sendbuf, len, MSG_OOB)) == SOCKET_ERROR)
char *strPath = "D:/bt.txt";
const char package[] =
/* Connect | 2B of length| OBEX Ver 1.0| Flag| Max Size */
{0x80, 0x00, 0x07, 0x10, 0x00, 2048>>8, 2048&0xFF};
if ((iResult = send(LocalSocket, package, sizeof(package)/sizeof(char), 0)) == SOCKET_ERROR)
{
}
std::cout << "WINSOCK: 'send' Return Code: " << WSAGetLastError() << "\r\n";
//--------------------------------------------
// Receive until the peer termination.
//--------------------------------------------
if ((iResult = recv(LocalSocket, recvbuf, recvbuflen, 0)) == SOCKET_ERROR){
std::cout << "WINSOCK: 'recv' Return Code: " << WSAGetLastError() << "\r\n";
return -1;
}
CString strdata = Byte2Ascn((BYTE*)recvbuf, iResult, 16);
std::wcout << "recv data: " << (LPCTSTR) strdata << std::endl;
if(0xA0 == static_cast<unsigned char>(recvbuf[0])){
const char* str = "82002bc30000000f01001100620074002e007400780074000049001268656c6c6f2c20776f726c64200d0a";
/* Put | 2B len| HI len | Name| b t . t x t |HI end|h e l l o , w o r l d \r\n */
int dstlen = strlen((const char*)str)/2;
BYTE* strdst = (BYTE*)malloc(dstlen * sizeof(BYTE));
Asc2Byten((void*)str, strdst, dstlen);
const char package[] =
{
0x85,0x00,0x08,0x02,0x00,0x01,0x00,0x03
// 0x82,0x01,0x65,0x01,0x00,0x0F,0x00,0x31,0x00,0x2E,0x00,0x6D,0x00,0x69,0x00,0x64,
// 0x00,0x00,0xC3,0x00,0x00,0x01,0x39,0x44,0x00,0x12,0x32,0x30,0x30,0x35,0x30,0x32,
// 0x31,0x34,0x54,0x31,0x39,0x34,0x39,0x33,0x38,0x49,0x01,0x3C,0x4D,0x54,0x68,0x64
};
if ((iResult = send(LocalSocket, (const char*)strdst, dstlen, 0)) == SOCKET_ERROR)
{
}
std::cout << "WINSOCK: 'send' Return Code: " << WSAGetLastError() << "\r\n";
delete strdst;
if ((iResult = recv(LocalSocket, recvbuf, recvbuflen, 0)) == SOCKET_ERROR){
std::cout << "WINSOCK: 'recv' Return Code: " << WSAGetLastError() << "\r\n";
return -1;
}
strdata = Byte2Ascn((BYTE*)recvbuf, iResult, 16);
std::wcout << "recv data: " << (LPCTSTR) strdata << std::endl;
}
//--------------------------------------------
// Shutdown the connection.
//--------------------------------------------
if ((iResult = shutdown(LocalSocket, SD_SEND)) == SOCKET_ERROR)
{
}
std::cout << "WINSOCK: 'shutdown' Return Code: " << WSAGetLastError() << "\r\n";
//--------------------------------------------
// Close the Socket.
//--------------------------------------------
if ((iResult = closesocket(LocalSocket)) == SOCKET_ERROR)
{
}
std::cout << "WINSOCK: 'closesocket' Return Code: " << WSAGetLastError() << "\r\n";
WSACleanup();
std::cout << "END: " << "Application has completed!" << "\r\n";
std::getchar();
return 0;
}
5. 最后吐槽一下:
csdn上的某些博主特别恶心,有的随便放些文档的图片,有的长篇大论给你解析obex协议,有的胡乱搞些数据,问题是竟然误导你,按照他的数据根本无法正常通讯,我在csdn上搜索到的大部分都是这些垃圾内容,耗费了我大量时间和精力.我不明白他们为啥就不能贴点代码,他们和代码有仇吗?
我想起祖师爷那句话
No bibi, show me the code!