- 任一时刻,tble只支持最多连接着一个设备。
- 只要6字节中有一个不是0,tble就认为这是一个有效的mac地址。
- 连接溢出时间固定为10秒。
- persist_scan_功能是用于持久扫描,即连接着一个peripheral,还要继续扫描。用于一个app要分时连接多个peripherl。
- 如果一个peripheral正连接着,它不发广播包,此时center是扫描不到它的。
- 每次connect_peripheral会有一次对应的回调app_connect_peripheral。和命名有点出入,当中操作不仅包括连接,还有读gatt数据库。error==0,表示两个操作都成功。error==bleerr_connecttimeout,表示当中有一个出现时间溢出,这时调用app_connect_peripheral时刻是tble产生,不是蓝牙硬件模块。连接溢出时间是10秒,读gatt数据库是6秒。
一、扫描
1.1 start_scan
start_scan用于启动扫描
void tble::start_scan() { std::vector<std::string> v; const char* uuid = NULL; if (!instance->foreground() && mac_addr_.valid()) { // Service UUID of advertisement packet maybe serval UUID, Now I only pass first. v = utils::split(mac_addr_.uuid.c_str()); if (!v.empty()) { uuid = v.front().c_str(); } if (!connecting_peripheral_ && !peripheral_) { // even if in reconnect detect, app maybe call start_scan again. if (background_id_ == INVALID_UINT32_ID) { background_reconnect_detect(true); } } } SDL_BleScanPeripherals(uuid); }
instance->foreground()==false时,意味着app正运行在后台。iOS app在后台时,断开后重连需要提供非nullptr的uuid。android是不是有这限制需要测试。总之,tble框架要求peripheral的广播数据至少出现一个uuid。
1.2 did_discover_peripheral
扫描到一个peripheral后,SDL会调用did_discover_peripheral。
void tble::did_discover_peripheral(SDL_BlePeripheral& peripheral) { #if (defined(__APPLE__) && TARGET_OS_IPHONE) // iOS cannot get macaddr normally. Call it to use private method to get it. app_calculate_mac_addr(peripheral); #endif if (!connecting_peripheral_ && !peripheral_ && connector_->match(peripheral)) { // tble think there are tow connect requirement. // one is app call connect_peripheral manuly. // the other is at here. reconnect automaticly when discover. connect_peripheral(peripheral); } app_discover_peripheral(peripheral); }
参数peripheral表示正扫出一个peripheral设备。在一轮扫描中,同一个设备可能被多次回调did_discover_peripheral。
SDL_BlePeripheral封装了一个peripheral数据。tble要求每个SDL_BlePeripheral都有一个有效的mac地址。在ios,是得不到这个地址的。于是就调用app_calculate_mac_addr算出一个“伪”mac地址。至于怎么算,一种方法是用peripheral广播出的manufacturer_data。
只要6字节中有一个不是0,tble就认为这是一个有效的mac地址。
计划中,connector_用于自动重连。想像这么种场景,因为和体温计距离远了,断牙断开。当又靠近体温计时,自动重连。但这种机制还有实验中,不要这功能的,只要不设置connector_就行。
did_discover_peripheral最后调用虚函数app_discover_peripheral,告知app扫描到了一个peripheral。
1.3 stop_scan
start_scan用于结束扫描
void tble::stop_scan() { SDL_BleStopScanPeripherals(); }
每次start_scan都须要有对应的stop_scan。即使当前没在扫描,也可调用stop_scan。
二、连接
2.1 connect_peripheral
connect_peripheral用于连接某个peripheral。
void tble::connect_peripheral(SDL_BlePeripheral& peripheral) { VALIDATE_IN_MAIN_THREAD(); VALIDATE(connecting_peripheral_ == nullptr, null_str); VALIDATE(peripheral_ == nullptr, null_str); VALIDATE(peripheral.services == nullptr && peripheral.valid_services == 0, null_str); VALIDATE(send_bufs_.empty(), null_str); // some windows os, connect is synchronous with did. set connecting_peripheral_ before connect. const int connect_threshold = 10 * 1000; // 10 second nullptr_connecting_peripheral_ticks_ = SDL_GetTicks() + connect_threshold; connecting_peripheral_ = &peripheral; // To reduce traffic on the Bluetooth bus, stop the scan first. if (!persist_scan_) { stop_scan(); } SDL_BleConnectPeripheral(&peripheral); }
不能让连接无限制等下去,最多等10秒(connect_threshold),还没连接上就认为失败。nullptr_connecting_peripheral_ticks_用于判断连接时间是否溢出了,要理解这个,结合tble::pump()。
void tble::pump() { if (connecting_peripheral_ != nullptr && SDL_GetTicks() >= nullptr_connecting_peripheral_ticks_) { disconnect_peripheral(); } }
tble::pump()是tble的时间片函数,event::pump时会调用它。功能就是判断连接时间是否溢出。
persist_scan_功能是用于持久扫描,即连接着一个peripheral,还要继续扫描。用于一个app要分时连接多个peripherl。注意,tble不支持同时连接多个peripheral,所以开始持久扫描了,无非就是断开peripheral-A,换连peripheral-B时,省去扫描等待时间罢了。而扫描待时间和peripheral的广播间隔有关,这个间隔长了,一般不会超过2秒。少这点时间,而让逻辑变复杂,是否值得有待商榷。或许将来会剔除,不建议使用。
如果不是持久扫描,connect_peripheral一定会先调用stop_scan(),即使当前没在扫描。
2.2 did_connect_peripheral
center发起连接后,会收到一个连接反馈,这个反馈会调用did_discover_peripheral。当然,有可能出现没收到反馈,这时就会触发10秒溢出,连接失败。
void tble::did_connect_peripheral(SDL_BlePeripheral& peripheral, int error) { if (connecting_peripheral_ != nullptr) { connecting_peripheral_ = nullptr; if (!error) { peripheral_ = &peripheral; // Although don't get the gatt-db at this time, but neend't delay // reference to: https://www.cswamp.com/post/57 const int get_services_threshold = 6 * 1000; // 6 second get_services_timeout_ticks_ = SDL_GetTicks() + get_services_threshold; // After connection is successful, call SDL_BleGetServices. // when get the services, then call app's app_connect_peripheral. SDL_BleGetServices(peripheral_); } else { app_connect_peripheral(peripheral, bleerr_connect); } } else if (!error) { // app call connect_peripheral, and call disconnect_peripheral at once. // step1. VALIDATE(peripheral_ == nullptr, null_str); disconnect_peripheral(); } background_reconnect_detect(false); }
===============
参数error是错误码,0表示连接成功,其它都是失败。
连接成功后,紧接是调用SDL_BleGetServices得到该peripheral的gatt数据库。这时有人会担心,此时只是连接上了,读gatt数据库需要点时间,是不是须要延时后再SDL_BleGetServices。的确,此时往往gatt数据库是没准备好的,读出来也须要点时间,但底下bt协议栈会考虑这种情况。参考“connectGatt(4/4):bta_gattc_start_discover”中p_q_cmd。
大致逻辑:bt协议栈发现此时没读出gatt数据库,p_q_cmd便就存储着此次的discoverServices请求。等后面读出gatt后,发现p_q_cmd是discoverServices,它就知道有app正在待这个结果,于是调用这个app提供的onServicesDiscovered。
如果连接失败,就调用虚函数app_connect_peripheral,让app知道连接失败了。如果成功,要调用SDL_BleGetServices得到servide数据库,要等到SDL_BleGetServices反馈时,再调用app_connect_peripheral。
2.3 did_discover_services
center发起SDL_BleGetServices后,会收到一个反馈,这个反馈会调用did_discover_services。如果出现没收到反馈,这时就会触发10秒溢出,连接失败。
void tble::did_discover_services(SDL_BlePeripheral& peripheral, int error) { VALIDATE_IN_MAIN_THREAD(); VALIDATE(peripheral_ == &peripheral, null_str); int ecode = bleerr_getservices; if (error == 0) { if (app_is_right_services()) { if (discover_connect_) { connector_->evaluate(peripheral); } ecode = bleerr_ok; } else { SDL_BleClearCache(&peripheral); ecode = bleerr_errorservices; // isn't my services, disconnect it. disconnect_peripheral(); } } app_connect_peripheral(peripheral, ecode); }
参数error是错误码,0表示连接成功,其它都是失败。
app_is_right_services给app一个机会,通过service数据库进一步确认这是否是希望连接的peripheral,是的话返回true,否则false。
discover_connect_=true时,意味着要使用发现即连接。这功能在调试中,不要使用。目前discover_connect_总是false。
最后会调用虚函数app_connect_peripheral,这个ecode虽是针对SDL_BleGetServices的,但对app来说,就是这次连接的结果。
三、断开
3.1 disconnect_peripheral
disconnect_peripheral用于断开当前连接
void tble::disconnect_peripheral() { SDL_BlePeripheral* peripheral = connecting_peripheral_ != nullptr? connecting_peripheral_: peripheral_; if (connecting_peripheral_ != nullptr) { connecting_peripheral_ = nullptr; } if (peripheral != nullptr) { disconnecting_ = true; SDL_BleDisconnectPeripheral(peripheral); } }
connecting_peripheral_ != nullptr,意味着tble还在等待和connecting_peripheral_的连接中。
是否调用SDL_BleDisconnectPeripheral(peripheral)的判断条件是“peripheral != nullptr”,不是“peripheral_ != nullptr”,也就是说,即使是因为连接时间溢出,也会调用SDL_BleDisconnectPeripheral。
结合连接、断开,让看下tble是如何处理“连接过程中强行断开”这种意外,这包括了时间溢出后主动断开。无论哪种,断开会调用tble::disconnect_peripheral。
- (disconnect_peripheral)此时connecting_peripheral_ != nullptr,periphera_ == nullptr。peripheral值是connecting_peripheral_,不是nullptr。
- (disconnect_peripheral)把connecting_peripheral_置为nullptr。
- (disconnect_peripheral)因为peripheral不是nullptr,调用SDL_BleDisconnectPeripheral。
- ble模块反馈此次连接结果,当然极可能是没有,这里假设有。回调did_connect_peripheral。
- (did_connect_peripheral)发现connecting_peripheral_是nullptr,periphera_也是nullptr,即使有调用disconnect_peripheral,那也是啥也不做。
3.2 did_disconnect_peripheral
center发起断开连接后,会收到一个断开反馈,这个反馈会调用did_disconnect_peripheral。另外,除center主动发起的断开连接,连接因意外而断开,像peripheral重启了、距离变远了,也会这收到个断开反馈。
void tble::did_disconnect_peripheral(SDL_BlePeripheral& peripheral, int error) { disconnecting_ = false; if (peripheral_) { if (current_task_ != nullptr) { current_task_ = nullptr; } VALIDATE(peripheral_ == &peripheral, null_str); peripheral_ = nullptr; app_disconnect_peripheral(peripheral, error); send_bufs_.clear(); } else if (!error) { // app call connect_peripheral, and call disconnect_peripheral at once. // step2. VALIDATE(!connecting_peripheral_, null_str); } // restart scan, persist_scan_ whether or not. if (!disable_reconnect_) { start_scan(); } }
编程时,可能会遇到这个问题,如何判断连接是否已断开?——did_disconnect_peripheral会调用app_disconnect_peripheral,通过重载它,app可明确知道连接断了。要注意,可能会出现较长时间没收到did_disconnect_peripheral,进而也不会调用app_disconnect_peripheral。如果是主动调用disconnect_peripheral导致的断开,那在disconnect_peripheral后就可认为这设备已断了,至于后面有调用app_disconnect_peripheral,执行个“空”操作。
disable_reconnect_默认值是true,不使用持久扫描的话,也不会改它。也就是说,断开连接后,不会再次扫描。