如何实现两个机器时间同步-计算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的方式。

 

posted @ 2024-09-13 14:57  xcywt  阅读(47)  评论(0编辑  收藏  举报
作者:xcywt
出处:https://www.cnblogs.com/xcywt//
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
如果文中有什么错误,欢迎指出。以免更多的人被误导。