PTP协议有多种实现方式,其同步模式分为请求应答模式,和端到端模式。请求应答模式又分为单步和双步。包含域,时钟类型,等基本概念,域代表参与同步的域号,每个参与同步的设备都会有一个域号,相同的域号代表他们属于同一个域。相同域内的设备需要保证时钟同步,不同域则彼此之间不需要保证同步。
本文是基于UDP socket实现了两步请求应答模式的软件同步协议。前期研究花了一些时间,用Qt的UDP实现,用了一天时间。
之前研究的时候,看到的都是C语言的示例,C语言实现网络通信,需要基于socket,自行封装解析,写起来很复杂,如github上的ptpd开源项目。
研究了其协议字段,直接用Qt的udp硬撸了一下,差不多花了一天半的功夫,编码加调试,期间有顿悟!算是从不懂到手撕了!
talk is cheap,show me your code!王德法!
ptp.h
#ifndef PTP_H
#define PTP_H
#include <QFile>
#include <QTextStream>
#include <QTimer>
#include <QUdpSocket>
#include <iostream>
#ifdef Q_OS_LINUX
# include <time.h>
#else
# include <windows.h>
#endif
struct PtpHeader {
char transportSpecific_messageType; // 0-3bit messageType,4-7bit是transportSpecific
char reserved_versionPTP; // 0-3bit versionPtp,4-7bit是reserved
unsigned short messageLength; //报文长度
unsigned char domainNumber; //域号
unsigned char reserved_0;
unsigned short flagField; //标识位
char correctionField[8]; //修正域,各报文都有,主要用在 Sync 报文中,用于补偿网络中的传输时延,E2E 的频率同步
char reserved1[4];
char sourcePortIdentity[10]; //源端口标识符,发送该消息时钟的 ID 和端口号
unsigned short sequenceId; //帧序号
char controlField; //控制类型由messageType决定
char logMessageInterval; //消息间隔
char send_time[10]; // follow up 带有时间长度
char requestingPortIdentity[10]; // Delay_Resp 响应 Delay_Req 报文的发送设备端口 ID
PtpHeader() {}
};
//表示消息类型。1588V2消息分为两类:事件消息(EVENT Message)和通用消息(General
// Message)。事件报文是时间概念报文,进出设备端口时需要打上精确的时间戳,而通用报文则是非时间概念报文,进出设备不会产生时戳。
//类型值0~3的为事件消息,8~D为通用消息。
// messageType
// 0x00: Sync
// 0x01: Delay_Req
// 0x02: Pdelay_Req
// 0x03: Pdelay_Resp
// 0x04-7: Reserved
// 0x08: Follow_Up
// 0x09: Delay_Resp
// 0x0A: Pdelay_Resp_Follow_Up
// 0x0B: Announce
// 0x0C: Signaling
// 0x0D: Management
// 0x0E-0x0F: Reserved
BOOL EnableSetTimePriviledge();
#define PTP_EVENT_PORT 319
#define PTP_GENERAL_PORT 320
#define DEFAULT_PTP_DOMAIN_ADDRESS "224.0.1.129"
#define PTP_ANNOUNCE 0x0B
#define PTP_SYNC 0x00
#define PTP_FOLLOW_UP 0x08
#define PTP_DELAY_REQ 0x01
#define PTP_DELAY_RESP 0x09
#define SECOND_DIV_NASECOND 1000000000
struct Nanosecond {
long long seconds;
long na_seconds;
void reset() {
seconds = 0;
na_seconds = 0;
}
Nanosecond operator+(const Nanosecond& t) const {
Nanosecond sum;
long na_res = na_seconds + t.na_seconds;
sum.na_seconds = na_res % SECOND_DIV_NASECOND;
int add_seconds = na_res / SECOND_DIV_NASECOND;
sum.seconds = t.seconds + add_seconds + seconds;
return sum;
}
Nanosecond operator-(const Nanosecond& t) const {
Nanosecond sub;
long na_res = na_seconds - t.na_seconds;
int sub_sec = 0;
if (na_res < 0) {
na_res = na_seconds + SECOND_DIV_NASECOND - t.na_seconds;
sub_sec = 1;
}
sub.na_seconds = na_res % SECOND_DIV_NASECOND;
sub.seconds = seconds - sub_sec - t.seconds;
return sub;
}
Nanosecond operator/(int div_) const {
Nanosecond res;
long long ns = seconds * SECOND_DIV_NASECOND + na_seconds;
ns /= div_;
res.seconds = ns / SECOND_DIV_NASECOND;
res.na_seconds = ns % SECOND_DIV_NASECOND;
return res;
}
void printf_value() const { qDebug() << "second = " << seconds << " nasecond " << na_seconds << endl; }
};
struct RespData {
unsigned short sync_seq_id;
int req_device_id;
Nanosecond req_access_time;
};
struct PTPTimeInf {
Nanosecond t1;
Nanosecond t2;
Nanosecond t3;
Nanosecond t4;
Nanosecond offset; //从时钟相对主时钟的偏移
Nanosecond delay;
void reset() {
t1.reset();
t2.reset();
t3.reset();
t4.reset();
offset.reset();
delay.reset();
}
Nanosecond get_offset() {
// t1 + delay = t2 + offset;
// t3 + offset + delay = t4;
// delay = t2 + offset - t1;
// offset = t4-t3- t2 - offset + t1
offset = ((t4 - t3) - (t2 - t1)) / 2;
return offset;
}
bool ready() {
if (t2.seconds == 0 || t1.seconds == 0 || t3.seconds == 0 || t4.seconds == 0) {
return false;
} else {
return true;
}
}
};
class Ptp : public QObject {
Q_OBJECT
public:
Ptp();
void start();
void write_data_test(char* header, int value);
QNetworkInterface get_bind_network_interface(QString ip_prefix);
void parse_general_buffer(const QByteArray& data, const QString ip);
void parse_event_buffer(const QByteArray& data, const QString ip);
Nanosecond get_cur_nanosecond();
void create_announce(char* header, int my_id, unsigned int sequence_id);
void create_sync(char* header, int my_id, unsigned int sequence_id);
void create_follow_up(char* header, int my_id, unsigned int sequence_id, const Nanosecond& send_time);
void create_delay_req(char* header, int my_id, unsigned int sequence_id);
void create_delay_resp(char* header, int my_id, unsigned int sequence_id, int req_id,
const Nanosecond& receive_time);
void send_announce();
void send_sync_follow();
void send_req_delay(int delay_req_seq_id);
void send_resp_delay();
void set_adjust_time(Nanosecond offset);
void write_log(QString log);
void check_ready_sync_seq();
public slots:
void receive_general_msg();
void receive_event_msg();
void announce_timeout();
void sync_timeout();
void delay_resp_timeout();
private:
QString bind_ip_prefix_;
int device_ip_; //内网ip最后一位
int domain_number_;
int clock_id_;
int device_id_;
bool is_master_;
unsigned short announce_seq_id_;
unsigned short sync_seq_id_;
QMap<int, PTPTimeInf> sync_id_ptp_time_inf_;
QUdpSocket* ptp_general_udp_;
QUdpSocket* ptp_event_udp_;
QTimer* announce_timer_;
QTimer* sync_timer_;
QTimer* delay_resp_timer_;
QList<RespData> delay_response_inf_;
QFile log_file_;
};
#endif // PTP_H
ptp.cpp
#include "ptp.h"
#include <QDateTime>
#include <QDebug>
#include <QNetworkAddressEntry>
#include <QNetworkInterface>
#include <chrono>
#include "Bits.h"
Ptp::Ptp() :
bind_ip_prefix_("192.168.105"), device_ip_(40), domain_number_(1), clock_id_(0), device_id_(0), is_master_(true),
announce_seq_id_(0), sync_seq_id_(0), ptp_general_udp_(nullptr), ptp_event_udp_(nullptr), announce_timer_(nullptr),
sync_timer_(nullptr), delay_resp_timer_(nullptr), log_file_("log.txt") {
bit_init();
char data[4];
write_data_test(data, 0x12345678);
for (int i = 0; i != 4; ++i) {
printf("write data test i = %x\n", data[i]);
}
if (!log_file_.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) {
printf("open file error !! \n");
}
if (is_big_endian) {
write_log("pc is big endian");
} else {
write_log("pc is small endian");
}
}
void Ptp::start() {
get_cur_nanosecond();
ptp_general_udp_ = new QUdpSocket(this);
ptp_general_udp_->setSocketOption(QAbstractSocket::MulticastTtlOption, 32);
//加入组播之前,必须先绑定端口,端口为多播组统一的一个端口。
bool res = ptp_general_udp_->bind(QHostAddress::AnyIPv4, PTP_GENERAL_PORT,
QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
QNetworkInterface join_interface;
if (res) {
qDebug() << "ptp_general_udp_ bind_port_ " << PTP_GENERAL_PORT << "success" << endl;
join_interface = get_bind_network_interface(bind_ip_prefix_);
ptp_general_udp_->setMulticastInterface(join_interface);
ptp_general_udp_->joinMulticastGroup(QHostAddress(DEFAULT_PTP_DOMAIN_ADDRESS), join_interface);
connect(ptp_general_udp_, &QUdpSocket::readyRead, this, &Ptp::receive_general_msg);
}
ptp_event_udp_ = new QUdpSocket(this);
ptp_event_udp_->setSocketOption(QAbstractSocket::MulticastTtlOption, 32);
//加入组播之前,必须先绑定端口,端口为多播组统一的一个端口。
res = ptp_event_udp_->bind(QHostAddress::AnyIPv4, PTP_EVENT_PORT,
QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint);
if (res) {
qDebug() << "ptp_event_udp_ bind_port_ " << PTP_EVENT_PORT << "success" << endl;
ptp_event_udp_->setMulticastInterface(join_interface);
ptp_event_udp_->joinMulticastGroup(QHostAddress(DEFAULT_PTP_DOMAIN_ADDRESS), join_interface);
connect(ptp_event_udp_, &QUdpSocket::readyRead, this, &Ptp::receive_event_msg);
}
announce_timer_ = new QTimer(this);
connect(announce_timer_, &QTimer::timeout, this, &Ptp::announce_timeout);
// announce_timer_->setInterval(1000);
announce_timer_->setTimerType(Qt::PreciseTimer);
announce_timer_->start(1000);
sync_timer_ = new QTimer(this);
connect(sync_timer_, &QTimer::timeout, this, &Ptp::sync_timeout);
announce_timer_->setTimerType(Qt::PreciseTimer);
sync_timer_->setInterval(1000);
delay_resp_timer_ = new QTimer(this);
connect(delay_resp_timer_, &QTimer::timeout, this, &Ptp::delay_resp_timeout);
delay_resp_timer_->setTimerType(Qt::PreciseTimer);
delay_resp_timer_->setInterval(1000);
//首次加入时,宣告自己的主时钟信息,和原来的做对比
send_announce();
}
void Ptp::write_data_test(char* header, int value) {
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = 4;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char*)(header);
memset(bitsBuffer.p_data, 0, 4);
bits_write(&bitsBuffer, 32, value);
}
QNetworkInterface Ptp::get_bind_network_interface(QString ip_prefix) {
QNetworkInterface join_interface;
QList<QNetworkInterface> all_networks = QNetworkInterface::allInterfaces();
for (int i = 0; i != all_networks.size(); ++i) {
QNetworkInterface inetwork = all_networks.at(i);
QList<QNetworkAddressEntry> all_address_entrys = inetwork.addressEntries();
bool ok = false;
for (int j = 0; j != all_address_entrys.size(); ++j) {
QNetworkAddressEntry entry = all_address_entrys[j];
join_interface = inetwork;
QString ip = entry.ip().toString();
if (ip.contains(ip_prefix)) {
ok = true;
device_ip_ = ip.mid(ip_prefix.size() + 1, ip.size() - ip_prefix.size() - 1).toInt();
clock_id_ = device_ip_;
device_id_ = device_ip_;
qDebug() << "device_ip_ = " << device_ip_ << endl;
qDebug() << "clock_id_ = " << clock_id_ << endl;
qDebug() << "device_id_ = " << device_id_ << endl;
break;
}
}
if (ok) {
break;
}
}
return join_interface;
}
void Ptp::parse_general_buffer(const QByteArray& data, const QString ip) {
const char* data_ptr = data.data();
char domian_num = 0;
memcpy(&domian_num, data_ptr + 4, 1);
if (domain_number_ != (int)domian_num) {
return;
}
unsigned char message_type = data_ptr[0] & 0x0F;
qDebug() << "message_type = " << message_type << endl;
if (message_type == PTP_ANNOUNCE) {
int clock_quality = 0;
if (!is_big_endian) {
memcpy(&((char*)&clock_quality)[3], data_ptr + 48, 1);
memcpy(&((char*)&clock_quality)[2], data_ptr + 49, 1);
memcpy(&((char*)&clock_quality)[1], data_ptr + 50, 1);
memcpy(&((char*)&clock_quality)[0], data_ptr + 51, 1);
} else {
memcpy((char*)&clock_quality, data_ptr + 48, 4);
}
qDebug() << "clock_quality = " << clock_quality << endl;
if (clock_quality > clock_id_) {
is_master_ = false;
if (sync_timer_->isActive()) {
sync_timer_->stop();
sync_id_ptp_time_inf_.clear();
delay_response_inf_.clear();
sync_seq_id_ = 0;
// timer启动需要1秒时间,如果是新加入的设备加入时,原来是有主时钟的,如果新加入的设备id最大,那么将会更换主时钟
//原来的主时钟会发送announce,当前加入的也会发送,两者竞争必有一个是失败的。用ip当作clockid_作为唯一是可以保证这个结果
}
if (delay_resp_timer_->isActive()) {
delay_resp_timer_->stop();
}
} else {
//开始执行timer,发送sync
is_master_ = true;
if (!sync_timer_->isActive()) {
sync_timer_->start();
sync_id_ptp_time_inf_.clear();
delay_response_inf_.clear();
sync_seq_id_ = 0;
// timer启动需要1秒时间,如果是新加入的设备加入时,原来是有主时钟的,如果新加入的设备id最大,那么将会更换主时钟
//原来的主时钟会发送announce,当前加入的也会发送,两者竞争必有一个是失败的。用ip当作clockid_作为唯一是可以保证这个结果
}
if (!delay_resp_timer_->isActive()) {
delay_resp_timer_->start();
}
}
} else if (message_type == PTP_FOLLOW_UP && !is_master_) {
Nanosecond t1;
t1.seconds = 0;
if (!is_big_endian) {
memcpy(&((char*)&t1.seconds)[5], data_ptr + 34, 1);
memcpy(&((char*)&t1.seconds)[4], data_ptr + 35, 1);
memcpy(&((char*)&t1.seconds)[3], data_ptr + 36, 1);
memcpy(&((char*)&t1.seconds)[2], data_ptr + 37, 1);
memcpy(&((char*)&t1.seconds)[1], data_ptr + 38, 1);
memcpy(&((char*)&t1.seconds)[0], data_ptr + 39, 1);
memcpy(&((char*)&t1.na_seconds)[3], data_ptr + 40, 1);
memcpy(&((char*)&t1.na_seconds)[2], data_ptr + 41, 1);
memcpy(&((char*)&t1.na_seconds)[1], data_ptr + 42, 1);
memcpy(&((char*)&t1.na_seconds)[0], data_ptr + 43, 1);
} else {
memcpy((char*)&t1.na_seconds + 2, data_ptr + 34, 6);
memcpy((char*)&t1.na_seconds, data_ptr + 40, 4);
}
qDebug() << "receive PTP_FOLLOW_UP t1 seconds = " << t1.seconds << " t1.na_seconds" << t1.na_seconds << endl;
unsigned short req_delay_seq_id;
if (!is_big_endian) {
for (int i = 0; i != 2; ++i) {
memcpy(&((char*)&req_delay_seq_id)[1 - i], data_ptr + 30 + i, 1);
}
} else {
memcpy((char*)&req_delay_seq_id, data_ptr + 30, 2);
}
if (sync_id_ptp_time_inf_.contains(req_delay_seq_id)) {
sync_id_ptp_time_inf_[req_delay_seq_id].t1 = t1;
} else {
PTPTimeInf t;
t.reset();
t.t1 = t1;
sync_id_ptp_time_inf_[req_delay_seq_id] = t;
}
send_req_delay(req_delay_seq_id);
} else if (message_type == PTP_DELAY_RESP && !is_master_) {
int req_id = 0;
if (!is_big_endian) {
for (int i = 0; i != 4; ++i) {
memcpy(&((char*)&req_id)[3 - i], data_ptr + 50 + i, 1);
}
} else {
memcpy((char*)&req_id, data_ptr + 50, 4);
}
qDebug() << "resp req device id = " << req_id << endl;
if (req_id == device_id_) {
Nanosecond t4;
t4.seconds = 0;
if (!is_big_endian) {
memcpy(&((char*)&t4.seconds)[5], data_ptr + 34, 1);
memcpy(&((char*)&t4.seconds)[4], data_ptr + 35, 1);
memcpy(&((char*)&t4.seconds)[3], data_ptr + 36, 1);
memcpy(&((char*)&t4.seconds)[2], data_ptr + 37, 1);
memcpy(&((char*)&t4.seconds)[1], data_ptr + 38, 1);
memcpy(&((char*)&t4.seconds)[0], data_ptr + 39, 1);
memcpy(&((char*)&t4.na_seconds)[3], data_ptr + 40, 1);
memcpy(&((char*)&t4.na_seconds)[2], data_ptr + 41, 1);
memcpy(&((char*)&t4.na_seconds)[1], data_ptr + 42, 1);
memcpy(&((char*)&t4.na_seconds)[0], data_ptr + 43, 1);
} else {
memcpy((char*)&t4.na_seconds + 2, data_ptr + 34, 6);
memcpy((char*)&t4.na_seconds, data_ptr + 40, 4);
}
unsigned short sync_resp_id;
if (!is_big_endian) {
for (int i = 0; i != 2; ++i) {
memcpy(&((char*)&sync_resp_id)[1 - i], data_ptr + 30 + i, 1);
}
} else {
memcpy((char*)&sync_resp_id, data_ptr + 30, 2);
}
if (sync_id_ptp_time_inf_.contains(sync_resp_id)) {
sync_id_ptp_time_inf_[sync_resp_id].t4 = t4;
} else {
PTPTimeInf t;
t.reset();
t.t4 = t4;
sync_id_ptp_time_inf_[sync_resp_id] = t;
}
qDebug() << "resp PTP_DELAY_RESP time t4.seconds = " << t4.seconds << ",na second " << t4.na_seconds
<< endl;
check_ready_sync_seq();
}
}
}
void Ptp::parse_event_buffer(const QByteArray& data, const QString ip) {
const char* data_ptr = data.data();
char domian_num = 0;
memcpy(&domian_num, data_ptr + 4, 1);
if (domain_number_ != (int)domian_num) {
return;
}
unsigned char message_type = data_ptr[0] & 0x0F;
qDebug() << "message_type = " << message_type << endl;
if (message_type == PTP_SYNC && !is_master_) {
unsigned short sync_seq_id;
if (!is_big_endian) {
for (int i = 0; i != 2; ++i) {
memcpy(&((char*)&sync_seq_id)[1 - i], data_ptr + 30 + i, 1);
}
} else {
memcpy((char*)&sync_seq_id, data_ptr + 30, 2);
}
Nanosecond t2 = get_cur_nanosecond();
if (sync_id_ptp_time_inf_.contains(sync_seq_id)) {
sync_id_ptp_time_inf_[sync_seq_id].t2 = t2;
} else {
PTPTimeInf t;
t.reset();
t.t2 = t2;
sync_id_ptp_time_inf_[sync_seq_id] = t;
}
QString log_str = QString("send req_delay time t2 ,s = %1 ,ns= %2").arg(t2.seconds).arg(t2.na_seconds);
write_log(log_str);
qDebug() << "receive PTP_SYNC t2 seconds = " << t2.seconds << " t2.na_seconds" << t2.na_seconds << endl;
} else if (message_type == PTP_DELAY_REQ && is_master_) {
//解析数据存下来
Nanosecond t4 = get_cur_nanosecond();
int req_id = 0;
if (!is_big_endian) {
for (int i = 0; i != 4; ++i) {
memcpy(&((char*)&req_id)[3 - i], data_ptr + 26 + i, 1);
}
} else {
memcpy((char*)&req_id, data_ptr + 26, 4);
}
qDebug() << "receive PTP_DELAY_REQ t4 seconds = " << t4.seconds << " t4.na_seconds" << t4.na_seconds << endl;
qDebug() << "req device id = " << req_id << endl;
RespData resp;
unsigned short delay_req_seq_id;
if (!is_big_endian) {
for (int i = 0; i != 2; ++i) {
memcpy(&((char*)&delay_req_seq_id)[1 - i], data_ptr + 30 + i, 1);
}
} else {
memcpy((char*)&delay_req_seq_id, data_ptr + 30, 2);
}
resp.req_device_id = req_id;
resp.req_access_time = t4;
resp.sync_seq_id = delay_req_seq_id;
delay_response_inf_.append(resp);
}
}
Nanosecond Ptp::get_cur_nanosecond() {
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
std::time_t now_time_t = std::chrono::system_clock::to_time_t(now);
Nanosecond nano_second;
std::chrono::nanoseconds ns;
ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now.time_since_epoch()) % SECOND_DIV_NASECOND;
nano_second.na_seconds = ns.count();
nano_second.seconds = now_time_t;
return nano_second;
}
void Ptp::create_announce(char* header, int my_id, unsigned int sequence_id) {
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = 64;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char*)(header);
memset(bitsBuffer.p_data, 0, 64);
bits_write(&bitsBuffer, 4, 0); // 0-3bit messageType,
bits_write(&bitsBuffer, 4, PTP_ANNOUNCE); // 4-7bit是transportSpecific
bits_write(&bitsBuffer, 4, 0); // 0-3bit versionPtp,
bits_write(&bitsBuffer, 4, 2); // 4-7bit是reserved
bits_write(&bitsBuffer, 16, 64); //报文长度
bits_write(&bitsBuffer, 8, domain_number_); //域号
bits_write(&bitsBuffer, 8, 0); // reserved
bits_write(&bitsBuffer, 16, 0); // flagField
bits_write(&bitsBuffer, 64, 0); //修正域
bits_write(&bitsBuffer, 32, 0); // reserved[4]
bits_write(&bitsBuffer, 80, my_id);
// 设备唯一id,相当于身份,每个域中的每个参与同步的设备id唯一不重复,相当于用户id
bits_write(&bitsBuffer, 16, sequence_id); //序号
bits_write(&bitsBuffer, 8, 5); // announce的controlField=5.other
bits_write(&bitsBuffer, 8, 1); //消息间隔1s一次
//下面是时钟信息
auto cur_time = get_cur_nanosecond();
char* second_ptr = (char*)&cur_time.seconds;
if (!is_big_endian) {
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[1]);
bits_write(&bitsBuffer, 8, second_ptr[0]);
} else {
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[6]);
bits_write(&bitsBuffer, 8, second_ptr[7]);
}
bits_write(&bitsBuffer, 32, cur_time.na_seconds);
// 34 10 Origin Timestamp 数值为 0 或精度为 ±1 ns 的时间戳
bits_write(&bitsBuffer, 16, 0);
// 44 2 CurrentUtcOffset UTC 与 TAI 时间标尺间的闰秒时间差
bits_write(&bitsBuffer, 8, 0);
// 46 1 Reserved
bits_write(&bitsBuffer, 8, 96);
// 47 1 GrandmasterPriority1 用户定义的 grandmaster 优先级
bits_write(&bitsBuffer, 32, clock_id_);
// 48 4 GrandmasterClockQuality grandmaster 的时间质量级别
bits_write(&bitsBuffer, 8, 91);
// 52 1 GrandmasterPriority2
bits_write(&bitsBuffer, 64, clock_id_);
// 53 8 GrandmasterIdentity grandmaster 的时钟设备 ID
bits_write(&bitsBuffer, 16, 0);
// 61 2 StepRemoved grandmaster 与 Slave 设备间的时钟路径跳数
bits_write(&bitsBuffer, 8, 0);
// 63 1 TimeSource 时间源头类型:
// GPS - GPS 卫星传送时钟
// PTP - PTP 时钟
// NTF - NTP 时钟
// Hand_set - 人工调整校准时钟
}
void Ptp::create_sync(char* header, int my_id, unsigned int sequence_id) {
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = 44;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char*)(header);
memset(bitsBuffer.p_data, 0, 44);
bits_write(&bitsBuffer, 4, 0); // 0-3bit messageType,
bits_write(&bitsBuffer, 4, PTP_SYNC); // 4-7bit是transportSpecific
bits_write(&bitsBuffer, 4, 0); // 0-3bit versionPtp,
bits_write(&bitsBuffer, 4, 2); // 4-7bit是reserved
bits_write(&bitsBuffer, 16, 44); //报文长度
bits_write(&bitsBuffer, 8, domain_number_); //域号
bits_write(&bitsBuffer, 8, 0); // reserved
bits_write(&bitsBuffer, 16, 0); // flagField
bits_write(&bitsBuffer, 64, 0); //修正域
bits_write(&bitsBuffer, 32, 0); // reserved[4]
bits_write(&bitsBuffer, 48, 0);
bits_write(&bitsBuffer, 32, my_id); // //发送方id
// 设备唯一id,相当于身份,每个域中的每个参与同步的设备id唯一不重复,相当于用户id
bits_write(&bitsBuffer, 16, sequence_id); //序号
bits_write(&bitsBuffer, 8, 0); // sync的controlField=0
bits_write(&bitsBuffer, 8, 1); //消息间隔1s一次
Nanosecond data_init_time = get_cur_nanosecond();
// second 是8个字节,实际存储是6个字节,且是小端模式,所以要去掉头部两个字节
char* second_ptr = (char*)&data_init_time.seconds;
if (!is_big_endian) {
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[1]);
bits_write(&bitsBuffer, 8, second_ptr[0]);
} else {
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[6]);
bits_write(&bitsBuffer, 8, second_ptr[7]);
}
bits_write(&bitsBuffer, 32, data_init_time.na_seconds);
}
void Ptp::create_follow_up(char* header, int my_id, unsigned int sequence_id, const Nanosecond& send_time) {
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = 44;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char*)(header);
memset(bitsBuffer.p_data, 0, 44);
bits_write(&bitsBuffer, 4, 0); // 0-3bit messageType,
bits_write(&bitsBuffer, 4, PTP_FOLLOW_UP); // 4-7bit是transportSpecific
bits_write(&bitsBuffer, 4, 0); // 0-3bit versionPtp,
bits_write(&bitsBuffer, 4, 2); // 4-7bit是reserved
bits_write(&bitsBuffer, 16, 44); //报文长度
bits_write(&bitsBuffer, 8, domain_number_); //域号
bits_write(&bitsBuffer, 8, 0); // reserved
bits_write(&bitsBuffer, 16, 0x0200); // flagField
bits_write(&bitsBuffer, 64, 0); //修正域
bits_write(&bitsBuffer, 32, 0); // reserved[4]
bits_write(&bitsBuffer, 48, 0);
bits_write(&bitsBuffer, 32, my_id); // //发送方id
bits_write(&bitsBuffer, 16, sequence_id); //序号
bits_write(&bitsBuffer, 8, 2); // follow up 的controlField=2
bits_write(&bitsBuffer, 8, 1); //消息间隔1s一次
char* second_ptr = (char*)&send_time.seconds;
if (!is_big_endian) {
for (int i = 0; i != 8; ++i) {
QString wirte_value =
QString("write follow up t1, 34 + i = %1").arg(second_ptr[7 - i] & 0xFF, 2, 16, QChar('0'));
qDebug() << wirte_value << endl;
}
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[1]);
bits_write(&bitsBuffer, 8, second_ptr[0]);
} else {
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[6]);
bits_write(&bitsBuffer, 8, second_ptr[7]);
}
bits_write(&bitsBuffer, 32, send_time.na_seconds);
qDebug() << "**********create_follow_up***********" << endl;
send_time.printf_value();
}
void Ptp::create_delay_req(char* header, int my_id, unsigned int sequence_id) {
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = 44;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char*)(header);
memset(bitsBuffer.p_data, 0, 44);
bits_write(&bitsBuffer, 4, 0); // 0-3bit messageType,
bits_write(&bitsBuffer, 4, PTP_DELAY_REQ); // 4-7bit是transportSpecific
bits_write(&bitsBuffer, 4, 0); // 0-3bit versionPtp,
bits_write(&bitsBuffer, 4, 2); // 4-7bit是reserved
bits_write(&bitsBuffer, 16, 44); //报文长度
bits_write(&bitsBuffer, 8, domain_number_); //域号
bits_write(&bitsBuffer, 8, 0); // reserved
bits_write(&bitsBuffer, 16, 0x0200); // flagField //两步
bits_write(&bitsBuffer, 64, 0); //修正域
bits_write(&bitsBuffer, 32, 0); // reserved[4]
bits_write(&bitsBuffer, 48, 0);
bits_write(&bitsBuffer, 32, my_id); // //发送方id
bits_write(&bitsBuffer, 16, sequence_id); //序号
bits_write(&bitsBuffer, 8, 1); // delay_req的controlField=1
bits_write(&bitsBuffer, 8, 1); //消息间隔1s一次
Nanosecond data_init_time = get_cur_nanosecond();
char* second_ptr = (char*)&data_init_time.seconds;
if (!is_big_endian) {
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[1]);
bits_write(&bitsBuffer, 8, second_ptr[0]);
} else {
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[6]);
bits_write(&bitsBuffer, 8, second_ptr[7]);
}
bits_write(&bitsBuffer, 32, data_init_time.na_seconds);
qDebug() << "******* create_delay_req my_id = " << my_id << endl;
}
void Ptp::create_delay_resp(char* header, int my_id, unsigned int sequence_id, int req_id,
const Nanosecond& receive_time) {
bits_buffer_t bitsBuffer;
bitsBuffer.i_size = 54;
bitsBuffer.i_data = 0;
bitsBuffer.i_mask = 0x80;
bitsBuffer.p_data = (unsigned char*)(header);
memset(bitsBuffer.p_data, 0, 54);
bits_write(&bitsBuffer, 4, 0); // 0-3bit messageType, delay_resp type =0x09
bits_write(&bitsBuffer, 4, PTP_DELAY_RESP); // 4-7bit是transportSpecific
bits_write(&bitsBuffer, 4, 0); // 0-3bit versionPtp,
bits_write(&bitsBuffer, 4, 2); // 4-7bit是reserved
bits_write(&bitsBuffer, 16, 54); //报文长度
bits_write(&bitsBuffer, 8, domain_number_); //域号
bits_write(&bitsBuffer, 8, 0); // reserved
bits_write(&bitsBuffer, 16, 0x0200); // flagField
bits_write(&bitsBuffer, 64, 0); //修正域
bits_write(&bitsBuffer, 32, 0); // reserved[4]
bits_write(&bitsBuffer, 48, 0);
bits_write(&bitsBuffer, 32, my_id); // //发送方id
bits_write(&bitsBuffer, 16, sequence_id); //序号
bits_write(&bitsBuffer, 8, 3); // delay_resp的controlField=0x03
bits_write(&bitsBuffer, 8, 1); //消息间隔1s一次
char* second_ptr = (char*)&receive_time.seconds;
if (!is_big_endian) {
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[1]);
bits_write(&bitsBuffer, 8, second_ptr[0]);
} else {
bits_write(&bitsBuffer, 8, second_ptr[2]);
bits_write(&bitsBuffer, 8, second_ptr[3]);
bits_write(&bitsBuffer, 8, second_ptr[4]);
bits_write(&bitsBuffer, 8, second_ptr[5]);
bits_write(&bitsBuffer, 8, second_ptr[6]);
bits_write(&bitsBuffer, 8, second_ptr[7]);
}
bits_write(&bitsBuffer, 32, receive_time.na_seconds);
bits_write(&bitsBuffer, 48, 0); //响应 Delay_Req 报文的发送设备端口 ID。
bits_write(&bitsBuffer, 32, req_id);
}
void Ptp::send_announce() {
qDebug() << "************send_announce***************" << endl;
char announce_inf[64];
create_announce(announce_inf, device_id_, announce_seq_id_);
QByteArray byte_data(announce_inf, 64);
int send_size =
ptp_general_udp_->writeDatagram(byte_data, QHostAddress(DEFAULT_PTP_DOMAIN_ADDRESS), PTP_GENERAL_PORT);
qDebug() << "send ip address = " << DEFAULT_PTP_DOMAIN_ADDRESS << Qt::endl;
qDebug() << "send port = " << PTP_GENERAL_PORT << Qt::endl;
qDebug() << "send size = " << send_size << Qt::endl;
announce_seq_id_++;
}
void Ptp::send_sync_follow() {
qDebug() << "************send_sync_follow***************" << endl;
char sync_inf[44];
create_sync(sync_inf, device_id_, sync_seq_id_);
QByteArray byte_data(sync_inf, 44);
int send_size = ptp_event_udp_->writeDatagram(byte_data, QHostAddress(DEFAULT_PTP_DOMAIN_ADDRESS), PTP_EVENT_PORT);
Nanosecond ns = get_cur_nanosecond();
QString log_str = QString("send sync t1 time ,s = %1 ,ns= %2").arg(ns.seconds).arg(ns.na_seconds);
write_log(log_str);
if (send_size == 44) {
char follow_up_inf[44];
create_follow_up(follow_up_inf, device_id_, sync_seq_id_, ns);
//每一次同步,使用同一个id,发送 sync,follow up, delay req,delay resp协议
QByteArray byte_data_follow(follow_up_inf, 44);
ptp_general_udp_->writeDatagram(byte_data_follow, QHostAddress(DEFAULT_PTP_DOMAIN_ADDRESS), PTP_GENERAL_PORT);
}
sync_seq_id_++;
}
void Ptp::send_req_delay(int delay_req_seq_id) {
qDebug() << "************send_req_delay***************" << endl;
char dealy_req_inf[44];
create_delay_req(dealy_req_inf, device_id_, delay_req_seq_id);
QByteArray byte_data(dealy_req_inf, 44);
int send_size = ptp_event_udp_->writeDatagram(byte_data, QHostAddress(DEFAULT_PTP_DOMAIN_ADDRESS), PTP_EVENT_PORT);
Nanosecond t3 = get_cur_nanosecond();
if (sync_id_ptp_time_inf_.contains(delay_req_seq_id)) {
sync_id_ptp_time_inf_[delay_req_seq_id].t3 = t3;
} else {
PTPTimeInf t;
t.reset();
t.t3 = t3;
sync_id_ptp_time_inf_[delay_req_seq_id] = t;
}
QString log_str = QString("send req_delay time T3 ,s = %1 ,ns= %2").arg(t3.seconds).arg(t3.na_seconds);
write_log(log_str);
qDebug() << "send PTP_DELAY_REQ t3 seconds = " << t3.seconds << " t3.na_seconds" << t3.na_seconds << endl;
}
void Ptp::send_resp_delay() {
qDebug() << "************send_resp_delay***************" << endl;
for (RespData resp : delay_response_inf_) {
char dealy_resp_inf[54];
qDebug() << "send_resp_delay req_id = " << resp.req_device_id << " resp.sync_seq_id = " << resp.sync_seq_id
<< endl;
QString log_str = QString("send t4 time ,s = %1 ,ns= %2")
.arg(resp.req_access_time.seconds)
.arg(resp.req_access_time.na_seconds);
write_log(log_str);
create_delay_resp(dealy_resp_inf, device_id_, resp.sync_seq_id, resp.req_device_id, resp.req_access_time);
QByteArray byte_data(dealy_resp_inf, 54);
int send_size =
ptp_event_udp_->writeDatagram(byte_data, QHostAddress(DEFAULT_PTP_DOMAIN_ADDRESS), PTP_GENERAL_PORT);
}
delay_response_inf_.clear();
}
void Ptp::set_adjust_time(Nanosecond offset) {
qDebug() << "************set_adjust_time***************" << endl;
Nanosecond cur = get_cur_nanosecond();
cur.printf_value();
Nanosecond adjuest = cur + offset;
long long offset_naseconds = offset.seconds * SECOND_DIV_NASECOND + offset.na_seconds;
qDebug() << "**********offset naseconds = " << offset.na_seconds << endl;
qDebug() << "**********offset second = " << offset.seconds << endl;
// windows下没有设置纳秒级的接口,一般window始终精度没那么高
// linux下直接调用
#ifdef Q_OS_LINUX
timespec time;
time.tv_nsec = adjuest.na_seconds;
time.tv_sec = adjuest.seconds;
clock_settime(CLOCK_REALTIME, &time);
//#elif defined(Q_OS_WIN)
#else
QDateTime date_adj;
long long msecond = (adjuest.seconds * SECOND_DIV_NASECOND + adjuest.na_seconds) / 1000000;
qDebug() << "**********all millseconds = " << msecond << endl;
date_adj.setMSecsSinceEpoch(msecond);
SYSTEMTIME stNew;
stNew.wYear = date_adj.date().year();
stNew.wMonth = date_adj.date().month();
stNew.wDay = date_adj.date().day();
stNew.wHour = date_adj.time().hour();
stNew.wMinute = date_adj.time().minute();
stNew.wSecond = date_adj.time().second();
stNew.wMilliseconds = msecond % 1000;
qDebug() << "adjust time = " << date_adj.toString("yyyy-MM-dd hh:mm:ss") << endl;
qDebug() << "stNew wHour = " << stNew.wHour << endl;
qDebug() << "stNew wMinute = " << stNew.wMinute << endl;
qDebug() << "stNew wSecond = " << stNew.wSecond << endl;
qDebug() << "**********stNew.wMilliseconds = " << stNew.wMilliseconds << endl;
bool res = SetLocalTime(&stNew);
if (res) {
qDebug() << "**********adjust time success!!! " << endl;
} else {
// windows 因为权限问题,调整一般就是失败的,需要以管理员权限运行qtcreator或者程序
qDebug() << "**********adjust time failed!!! " << endl;
}
#endif
}
void Ptp::write_log(QString log) {
QTextStream out(&log_file_);
//把数据写到html文件中
out << log << endl;
out.flush();
}
void Ptp::check_ready_sync_seq() {
// 收到一次时间,由于网络可能不稳定,收到t1,t2,和t4的顺序可能不同
for (int seq_id : sync_id_ptp_time_inf_.keys()) {
bool ready_ok = sync_id_ptp_time_inf_[seq_id].ready();
if (ready_ok) {
Nanosecond offset_time = sync_id_ptp_time_inf_[seq_id].get_offset();
set_adjust_time(offset_time);
sync_id_ptp_time_inf_.remove(seq_id);
break;
}
}
}
void Ptp::receive_general_msg() {
while (ptp_general_udp_->hasPendingDatagrams()) {
QByteArray data;
data.resize(ptp_general_udp_->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
ptp_general_udp_->readDatagram(data.data(), data.size(), &peerAddr, &peerPort);
// QString peer = "[From ] +" + peerAddr.toString() + ":" + QString::number(peerPort) + "] ";
// qDebug() << "receive data = " << peer << Qt::endl;
parse_general_buffer(data, peerAddr.toString());
};
}
void Ptp::receive_event_msg() {
while (ptp_event_udp_->hasPendingDatagrams()) {
QByteArray data;
data.resize(ptp_event_udp_->pendingDatagramSize());
QHostAddress peerAddr;
quint16 peerPort;
ptp_event_udp_->readDatagram(data.data(), data.size(), &peerAddr, &peerPort);
// QString str = data.data();
// QString peer = "[From ] +" + peerAddr.toString() + ":" + QString::number(peerPort) + "] ";
// qDebug() << "receive data = " << peer << Qt::endl;
parse_event_buffer(data, peerAddr.toString());
};
}
void Ptp::announce_timeout() {
if (is_master_) {
qDebug() << "is master send announce" << endl;
send_announce();
}
}
void Ptp::sync_timeout() {
if (is_master_) {
qDebug() << "is master send sync_follow" << endl;
send_sync_follow();
}
}
void Ptp::delay_resp_timeout() {
//定时回复delay_req
if (is_master_) {
send_resp_delay();
}
}
BOOL EnableSetTimePriviledge() {
HANDLE m_hToken;
TOKEN_PRIVILEGES m_TokenPriv;
BOOL m_bTakenPriviledge;
BOOL bOpenToken = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &m_hToken);
m_bTakenPriviledge = FALSE;
if (!bOpenToken) {
if (GetLastError() == ERROR_CALL_NOT_IMPLEMENTED) {
// Must be running on 95 or 98 not NT. In that case just ignore the error
SetLastError(ERROR_SUCCESS);
if (!m_hToken)
CloseHandle(m_hToken);
return TRUE;
}
if (!m_hToken)
CloseHandle(m_hToken);
return FALSE;
}
ZeroMemory(&m_TokenPriv, sizeof(TOKEN_PRIVILEGES));
if (!LookupPrivilegeValue(NULL, SE_SYSTEMTIME_NAME, &m_TokenPriv.Privileges[0].Luid)) {
if (!m_hToken)
CloseHandle(m_hToken);
return FALSE;
}
m_TokenPriv.PrivilegeCount = 1;
m_TokenPriv.Privileges[0].Attributes |= SE_PRIVILEGE_ENABLED;
m_bTakenPriviledge = TRUE;
BOOL bSuccess = AdjustTokenPrivileges(m_hToken, FALSE, &m_TokenPriv, 0, NULL, 0);
if (!m_hToken)
CloseHandle(m_hToken);
return bSuccess;
}
main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <chrono>
#include <thread>
#include "ptp.h"
int main(int argc, char* argv[]) {
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, &app,
[url](QObject* obj, const QUrl& objUrl) {
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
},
Qt::QueuedConnection);
engine.load(url);
Ptp ptp;
Nanosecond n1;
Nanosecond n2;
n1.seconds = 1679996994;
n1.na_seconds = 400000000;
n2.seconds = 1679996995;
n2.na_seconds = 800000000;
Nanosecond ns2 = n2 / 2;
ns2.printf_value();
// std::thread thread([&]() {
// Nanosecond ns0;
// for (int i = i; i != 10; ++i) {
// ns0 = ptp.get_cur_nanosecond();
// Nanosecond ns1 = ptp.get_cur_nanosecond();
// Nanosecond n2 = ns0 - ns1;
// ns0.printf_value();
// ns1.printf_value();
// n2.printf_value();
// std::this_thread::sleep_for(std::chrono::milliseconds(10));
// }
// });
// thread.detach();
ptp.start();
return app.exec();
}