背景:
之前的项目大致可以分为两层,逻辑层和设备层,运行在同一个主机上。
最近在着手搭建一个仿真平台,在另外一台主机上部署机器人机器相关硬件设备,比如陀螺仪,轮机,雷达等。
由于两台主机的时间戳不同步问题,导致定位系统有问题,为此需要实现两个主机的时间同步。
具体分两步:
1)测量两主机的网络延迟(round trip time,RTT)。
2)根据RTT计算时间偏移offset。
一、计算RTT
参考:https://blog.csdn.net/ingnight/article/details/100518409#:~:text=%E4%B8%80%E3%80%81roun
用于测量两台机器之间的网络延迟,即“往返时间(round trip time,RTT)”,原理如下:
大致逻辑图:
相关proto:
/* 定时运行,比如每5分钟执行一次。 1. A->B. 发3个TimestampRTT_Req 2. B会收到3个TimestampRTT_Req. B-A:再回复3个TimestampRTT_Res 3. A收到TimestampRTT_Res后,记录下收到的时间戳A_ts2. RTT1 = ((A_ts2 - A_ts1) - (B_ts2 - B_ts1)) / 2; 4. 再求平均 RTT_Avg 5. 求 offsetTs = A_ts1 - (B_ts1 - RTT_Avg) = A_ts1 + RTT_Avg - B_ts1如何使用 offsetTs: B_ts转换成A的时间戳 Ats = offsetTs + B_ts */ message TimestampRTT_Req{int64 A_ts1 = 1; // GetNow_Steady }message TimestampRTT_Res{int64 A_ts1 = 1; // 接收端赋值。就是收到的 A_ts1int64 A_ts2 = 2; // 接收端不用赋值。收到回复后,再记录一下时间戳。int64 B_ts1 = 3; // 接收端赋值。GetNow_Steady recv timeint64 B_ts2 = 4; // 接收端赋值。GetNow_Steady res time }
伪代码:
主机A:
// 发送端:实际调用时,需要连续发5-10个void sendSync(){printf("[%s] EcalPubManager::sendSync() 发送 TimestampRTT_Req +++++++++++++++ \n", dros::utils::GetCurTimeStamp_MilSec().c_str());m_startSyncTs = dros::utils::GetNow_Steady();static eCAL::protobuf::CPublisher<dros::pb::timestampTest::TimestampRTT_Req> pub(m_DROS_DOMAIN + "TimestampRTT_Req");dros::pb::timestampTest::TimestampRTT_Req data;data.set_a_ts1(dros::utils::GetNow_Steady());pub.Send(data);}// 接收void ProtoCallbackRecvTimestampRTT_Res(const dros::pb::timestampTest::TimestampRTT_Res& message) {printf("[%s] EcalPubManager::ProtoCallbackRecvTimestampRTT_Res() 收到 TimestampRTT_Res:[%s] \n", dros::utils::GetCurTimeStamp_MilSec().c_str(), message.DebugString().c_str());dros::pb::timestampTest::TimestampRTT_Res message1;message1.CopyFrom(message);message1.set_a_ts2(dros::utils::GetNow_Steady());m_vecTimestampRTT_Res.push_back(message1); // 放到一个数组里,主要为了取平均值,提高精度。}// 计算offsetlong long GetTsOffset(){ if(!m_bComputeOffset){auto diff = dros::utils::GetNow_Steady() - m_startSyncTs;if(diff > 5000){printf("[%s] EcalPubManager::GetTsOffset() 同步指令已经接收完成, 正在计算offset ... \n", dros::utils::GetCurTimeStamp_MilSec().c_str());m_bComputeOffset = true;if(m_vecTimestampRTT_Res.size() > 2){long long rttCount = 0;int mCount = 0;for(auto iii: m_vecTimestampRTT_Res){mCount++;auto rtt = ((iii.a_ts1() - iii.a_ts2()) - (iii.b_ts1() - iii.b_ts2())) / 2;rttCount += rtt;printf("[%s] EcalPubManager::GetTsOffset() 正在计算offset iii:[%s] rtt:%lld $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ \n", dros::utils::GetCurTimeStamp_MilSec().c_str(), iii.DebugString().c_str(), rtt);}long long avgRTT = rttCount / mCount; // 求平均值,提高精度long long offset = m_vecTimestampRTT_Res[0].a_ts1() + avgRTT - m_vecTimestampRTT_Res[0].b_ts1();m_TsOffset = offset;printf("[%s] EcalPubManager::GetTsOffset() 新 offset:[%lld] avgRTT:[%lld] $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ \n", dros::utils::GetCurTimeStamp_MilSec().c_str(), offset, avgRTT);}else {printf("[%s] EcalPubManager::GetTsOffset() m_vecTimestampRTT_Res 为空。将重新开始发送Req $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ \n", dros::utils::GetCurTimeStamp_MilSec().c_str());startSyncTime();}}else{printf("[%s] EcalPubManager::GetTsOffset() m_bComputeOffset is false, 正在接收 RTT_Res $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ \n", dros::utils::GetCurTimeStamp_MilSec().c_str());}}else{printf("[%s] EcalPubManager::GetTsOffset() m_bComputeOffset is true, 无需计算offset $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ \n", dros::utils::GetCurTimeStamp_MilSec().c_str());}return m_TsOffset;}
主机B:
// 收到req,立马回复void ProtoCallbackRecvSimTimestampRTT_Req(const dros::pb::timestampTest::TimestampRTT_Req& message) {printf("[%s] ProtoCallbackRecvSimTimestampRTT_Req() +++++++++++++++ message:[%s] \n",dros::utils::GetCurTimeStamp_MilSec().c_str(), message.DebugString().c_str());static eCAL::protobuf::CPublisher<dros::pb::timestampTest::TimestampRTT_Res> pub(m_DROS_DOMAIN + "TimestampRTT_Res");dros::pb::timestampTest::TimestampRTT_Res data;data.set_a_ts1(message.a_ts1());data.set_a_ts2(0);data.set_b_ts1(dros::utils::GetNow_Steady() + offsetTest); // 接收到数据时的时间戳std::this_thread::sleep_for(std::chrono::milliseconds(10)); // 这个可以取消,主要为了模拟处理消耗。data.set_b_ts2(dros::utils::GetNow_Steady() + offsetTest); // 处理完成后的时间戳pub.Send(data); }
计算offset:
offset = ats1 + RTT - bts1;
二、如何使用offset:
主机A接收到B发来的时间戳加上offset就是转换后的时间戳
TsA = offset + TsB
三、总结及说明:
1.上述数据结构是用protobuf定义的。
2.数据传输通过ecal发送proto的方式。