如何实现两个机器时间同步-计算rtt+offset
背景:
之前的项目大致可以分为两层,逻辑层和设备层,运行在同一个主机上。
最近在着手搭建一个仿真平台,在另外一台主机上部署机器人机器相关硬件设备,比如陀螺仪,轮机,雷达等。
由于两台主机的时间戳不同步问题,导致定位系统有问题,为此需要实现两个主机的时间同步。
具体分两步:
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_ts1 int64 A_ts2 = 2; // 接收端不用赋值。收到回复后,再记录一下时间戳。 int64 B_ts1 = 3; // 接收端赋值。GetNow_Steady recv time int64 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); // 放到一个数组里,主要为了取平均值,提高精度。 } // 计算offset long 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的方式。