tble:扫描、连接、断开

  • 任一时刻,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。

  1. (disconnect_peripheral)此时connecting_peripheral_ != nullptr,periphera_ == nullptr。peripheral值是connecting_peripheral_,不是nullptr。
  2. (disconnect_peripheral)把connecting_peripheral_置为nullptr。
  3. (disconnect_peripheral)因为peripheral不是nullptr,调用SDL_BleDisconnectPeripheral。
  4. ble模块反馈此次连接结果,当然极可能是没有,这里假设有。回调did_connect_peripheral。
  5. (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,不使用持久扫描的话,也不会改它。也就是说,断开连接后,不会再次扫描。

 

全部评论: 0

    写评论: