由于 NTP时间服务器经常炸,无缘无故无来由的炸,各自UDP包的丢包,各自封锁导致不得不使用其他方法进行授时。无奈Arduino的库也存在2038问题,年最大仅255。试问2225年之后怎么办?所以我自己写了一套时间算法,解决时间问题。
总的来说用了AsyncUDP库https://github.com/me-no-dev/ESPAsyncUDP
和各自基础库
原理 任何HTTP包都含请求报头和响应报头
响应报头中截取出 Date: Tue, 27 Jun 2023 23:10:58 GMT
这个字符串 再进行分析即可得到当前的时间。但有的服务器设置存在问题或压根没有设置时间所以报头中也没有,所以就选几个大厂的发包就可以了,内容数据直接抛弃。
HTTPS算法来自https://www.bilibili.com/read/cv17367361/
准确来说授时不需要HTTPS.
#include <Arduino.h>
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
//加密算法库
// #include <mbedtls/pk.h>
// #include <mbedtls/rsa.h>
// #include <mbedtls/pem.h> //https://blog.csdn.net/qdlyd/article/details/131084726?spm=1001.2014.3001.5501
// #include <mbedtls/sha256.h> //https://blog.csdn.net/imba_wolf/article/details/122417540
// #include <mbedtls/ctr_drbg.h> //https://www.trustedfirmware.org/projects/mbed-tls/
// #include <mbedtls/entropy.h> //https://johanneskinzig.de/index.php/files/26/Arduino-mbedtls/9/gettingstartedmbedtlsarduino.7z
// #include <arduino_base64.hpp> //https://github.com/dojyorin/arduino_base64 随便拉的库 库管理搜base64_encode作者dojyorin
//URL处理
//#include <UrlEncode.h>
//UDPz底层库
#include <AsyncUDP.h>
AsyncUDP udp; //UDP监听常驻内存
//全局时间变量
const unsigned int localPort = 23511;
unsigned long long ntpTime = 0; //最后一次对时距离1970年的秒数
typedef struct {
uint8_t Second;
uint8_t Minute;
uint8_t Hour;
uint8_t Wday; // day of week, Sunday is day 1
uint8_t Day;
uint8_t Month;
uint32_t Year; // offset from 1970;END AY 4294967295YEAR
} tmElement64s_t;
#define SECS_PER_MIN ((time_t)(60UL))
#define SECS_PER_HOUR ((time_t)(3600UL))
#define SECS_PER_DAY ((time_t)(SECS_PER_HOUR * 24UL))
#define LEAP_YEAR(Y) (((1970 + (Y)) > 0) && !((1970 + (Y)) % 4) && (((1970 + (Y)) % 100) || !((1970 + (Y)) % 400)))
static const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; // API starts months from 1, this array starts from 0
bool needupadte = 1; //需要授时标志位 1代表需要联网授时
unsigned long timesendudp; //发出UDP包的时间(micros())开机后微秒数
unsigned long timegetudp; //接收UDP包的时间(micros())开机后微秒数
const char *rootCACertificate =
"-----BEGIN CERTIFICATE-----\n"
"MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh\n"
"MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3\n"
"d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD\n"
"QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT\n"
"MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n"
"b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG\n"
"9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB\n"
"CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97\n"
"nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt\n"
"43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P\n"
"T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4\n"
"gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO\n"
"BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR\n"
"TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw\n"
"DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr\n"
"hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg\n"
"06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF\n"
"PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls\n"
"YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk\n"
"CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=\n"
"-----END CERTIFICATE-----\n";
const char *pemKey = R"(
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----
)";
/*
String alipaysign(String alipaydata) {
// 需要包含以下库
// #include <mbedtls/pk.h>
// #include <mbedtls/rsa.h>
// #include <mbedtls/pem.h>
// #include <mbedtls/sha256.h>
// #include <mbedtls/ctr_drbg.h>
// #include <mbedtls/entropy.h>//https://johanneskinzig.de/index.php/files/26/Arduino-mbedtls/9/gettingstartedmbedtlsarduino.7z
// #include <arduino_base64.hpp>//https://github.com/dojyorin/arduino_base64 随便拉的库 库管理搜base64_encode作者dojyorin
//关键私钥从LITTLE FS中获得 由于私钥太占内存所以我将其写至签名函数内,避免继续在上层函数中继续占用内存
//私钥 这里通过读取littleFS从SPIflash中读取
//存在重大漏洞 如果有人取下SPIflash做逆向工程可以读出您的私钥伪造您的签名使用支付宝转账接口盗取您的支付宝资金
//需要恢复
// String alipayprivatekey = getValueByKey("alipayprivatekey"); //从Little存储中的简易键值存储中读取私钥
const char *data = alipaydata.c_str();
//需要恢复
// const char *pemKey = alipayprivatekey.c_str();
mbedtls_pk_context key;
mbedtls_rsa_context *rsa = NULL; //实际未利用子RSA函数库中的签名算法
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_pk_init(&key);
mbedtls_ctr_drbg_init(&ctr_drbg);
const char *personalization = "random_seed142"; //随机数种子 签名计算不牵扯随机数生成器 无需设置根据真实事件产生的随机数种子
mbedtls_entropy_context entropy;
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)personalization, strlen(personalization));
//载入RSA密钥
int ret = mbedtls_pk_parse_key(&key, (const unsigned char *)pemKey, strlen(pemKey) + 1, NULL, 0);
if (ret != 0) {
Serial.println("Failed to parse private key.");
Serial.println(ret);
return "";
}
// 计算SHA256哈希值
unsigned char hash[32];
mbedtls_sha256((const unsigned char *)data, strlen(data), hash, 0);
// 使用密钥对哈希值进行数字签名
unsigned char signature[256];
size_t signatureLen;
ret = mbedtls_pk_sign(&key, MBEDTLS_MD_SHA256, hash, sizeof(hash), signature, &signatureLen, mbedtls_ctr_drbg_random, &ctr_drbg);
if (ret != 0) {
Serial.println("Failed to sign the data.");
return "";
}
char base64signoutput[base64::encodeLength(sizeof(signature))]; //制造一个长度仅为base生成长度一致的字符串
base64::encode(signature, sizeof(signature), base64signoutput); //把字符串base64算法存入字符数组base64signoutput
// 清理资源
mbedtls_pk_free(&key);
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
String strbase64signoutput;
strbase64signoutput = base64signoutput;
return strbase64signoutput;
}
*/
/*
String alipaytradequery(const String &tradeno) {
// String url = "https://openapi.alipay.com/gateway.do?biz_content=%7B%22out_trade_no%22%3A%22202108230101010014b%22%2C%22total_amount%22%3A0.01%2C%22subject%22%3A%22%5Cu5546%5Cu54c1%22%2C%22extend_params%22%3A%7B%22sys_service_provider_id%22%3A%222088931278114101%22%7D%7D&app_id=2021001194613478&version=1.0&format=json&sign_type=RSA2&method=alipay.trade.precreate×tamp=2023-06-26+12%3A11%3A20&auth_token=&alipay_sdk=alipay-sdk-php-2020-04-15&terminal_type=&terminal_info=&prod_code=¬ify_url=&charset=UTF-8&app_auth_token=&app_cert_sn=0fbfea6f1c396ed1ee67302f2d035201&alipay_root_cert_sn=687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6&target_app_id=&sign=eg%2FkZa8%2BEj5GmfZDZfpMDgnpwOOglZtN7rfeBKSerBOUuaxjEKdaxpuSuYK4RSyBmwgiWM%2BA%2BRLH4MCVSOaCC64QnMrW1bi6fRaSW%2BGXzLlyh1%2FCl5BV5TVFgFWPqPbR%2FfUj1sbs3woRc%2FJ9KM3WudojIY2uzOTl6grojy0k5BvMRcQY6Km8rAu3UwCodv9YWGxSW5AzxY%2Fi6y8gRoYSSEbKrLsLNnE6IVAShi%2Bl7QlY%2BoFXdfpieen7ou0s6lTaonkPM3xjB1ONlWKMINQi3b7FyTnlFG5jZXCzpTmWHkeGq8GFmRo9%2FMRlC0WFG%2BrTQbcEXY9lBRa%2FOGk0XE8akQ%3D%3D";
String param = "alipay_root_cert_sn=687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6&alipay_sdk=alipay-sdk-php-2020-04-15&app_cert_sn=0fbfea6f1c396ed1ee67302f2d035201&app_id=2021001194613478&biz_content={\"out_trade_no\":\"202108230101010012b\"}&charset=UTF-8&format=json&method=alipay.trade.query&sign_type=RSA2×tamp=2023-06-28 04:46:27&version=1.0";
String sign = urlEncode(alipaysign(param));
// param =urlEncode(param);
param = "alipay_root_cert_sn=687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6&alipay_sdk=alipay-sdk-php-2020-04-15&app_auth_token=&app_cert_sn=0fbfea6f1c396ed1ee67302f2d035201&app_id=2021001194613478&auth_token=&biz_content=%7B%22out_trade_no%22%3A%22202108230101010012b%22%7D&charset=UTF-8&format=json&method=alipay.trade.query¬ify_url=&prod_code=&sign_type=RSA2&target_app_id=&terminal_info=&terminal_type=×tamp=2023-06-28+04%3A46%3A27&version=1.0";
param = param + "&sign=" + sign;
param = "/gateway.do?" + param; //
Serial.print(param);
return param;
// String param = "alipay_root_cert_sn=";
// param += getValueByKey("alipayrootcertsn");
// param += String("&app_cert_sn=");
// param += getValueByKey("appcertsn");
// param += String("&app_id=");
// param += getValueByKey("appid");
// param += String("&biz_content={\"out_trade_no\":\"");
// param += tradeno;
// param += String("\"}&charset=UTF-8&format=json&method=alipay.trade.query&sign_type=RSA2×tamp=");
// param += getalipaytime();
// param += String("&version=1.0");
// String sign = alipaysign(param);
// param += "&sign=";
// param += urlEncode(sign);
// url+=param;
// char* charurlArray = new char[url.length() + 1];
// url.toCharArray(charurlArray, url.length() + 1);
// return charurlArray;
}
*/
/**
* 功能:HTTPS请求封装!
* @param host:请求域名(String类型)
* @param url:请求地址(String类型)
* @param parameter:请求参数(String类型)(默认""")
* @param fingerprint:服务器证书指纹 (String类型)(默认""")
* @param Port:请求端口(int类型)(默认:443)
* @param Receive_cache:接收缓存(int类型)(默认:1024)
* @return 成功返回请求的内容(String类型) 失败则返回"0"
* */
String HTTPS_request(String host, String postRequest, int Port = 443, int Receive_cache = 1024) {
WiFiClientSecure HTTPS; //建立WiFiClientSecure对象
HTTPS.setCACert(rootCACertificate);
// HTTPClient https;//不用官方库
// https.begin(wificlient,String(url));
// Serial.print("原始URLA");
// Serial.println(url);
postRequest = (String)("GET ") + postRequest + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1 Edg/103.0.5060.53" + "\r\n\r\n";
HTTPS.setInsecure(); //不进行服务器身份认证
int cache = sizeof(postRequest) + 10;
Serial.print("原始URLB");
Serial.println(postRequest);
Serial.print("发送缓存:");
Serial.println(postRequest);
Serial.print("发送缓存大小:");
Serial.println(cache);
// HTTPS.setBufferSizes(Receive_cache, cache); //接收和发送缓存大小
HTTPS.setTimeout(15000); //设置等待的最大毫秒数
Serial.println("初始化参数完毕!\n开始连接服务器==>>>>>");
if (!HTTPS.connect(host.c_str(), Port)) {
delay(100);
Serial.println();
Serial.println("服务器连接失败!");
return "0";
} else {
Serial.println("服务器连接成功!\r");
Serial.println("发送请求:\n" + postRequest);
}
HTTPS.print(postRequest.c_str()); // 发送HTTP请求
// 检查服务器响应信息。通过串口监视器输出服务器状态码和响应头信息
// 从而确定ESP8266已经成功连接服务器
Serial.println("获取响应信息========>:\r");
Serial.println("响应头:");
while (HTTPS.connected()) {
String line = HTTPS.readStringUntil('\n');
Serial.println(line);
if (line == "\r") {
Serial.println("响应头输出完毕!"); // Serial.println("响应头屏蔽完毕!\r");
break;
}
}
Serial.println("截取响应体==========>");
String line;
while (HTTPS.connected()) {
line = HTTPS.readStringUntil('\n'); // Serial.println(line);
// if (line.length() > 10)
break;
}
Serial.println("响应体信息:\n" + line);
Serial.println("====================================>");
Serial.println("变量长度:" + String(line.length()));
Serial.println("变量大小:" + String(sizeof(line)) + "字节");
Serial.println("====================================>");
HTTPS.stop(); //操作结束,断开服务器连接
delay(500);
return line;
}
bool HTTPS_GETTIME(String host, String postRequest, int Port = 443, int Receive_cache = 1024) {
WiFiClientSecure HTTPS; //建立WiFiClientSecure对象
HTTPS.setCACert(rootCACertificate);
postRequest = (String)("GET ") + postRequest + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "User-Agent: ESP32WiFiClientSecure" + "\r\n\r\n";
HTTPS.setInsecure(); //不进行服务器身份认证
int cache = sizeof(postRequest) + 10;
// Serial.print("原始URLB");
// Serial.println(postRequest);
// Serial.print("发送缓存:");
// Serial.println(postRequest);
// Serial.print("发送缓存大小:");
// Serial.println(cache);
// HTTPS.setBufferSizes(Receive_cache, cache); //接收和发送缓存大小
HTTPS.setTimeout(15000); //设置等待的最大毫秒数
// Serial.println("初始化参数完毕!\n开始连接服务器==>>>>>");
if (!HTTPS.connect(host.c_str(), Port)) {
// delay(100);
// Serial.println();
Serial.println("服务器连接失败!");
return 0;
} else {
Serial.println("服务器连接成功!\r");
// Serial.println("发送请求:\n" + postRequest);
}
HTTPS.print(postRequest.c_str()); // 发送HTTP请求
// 检查服务器响应信息。通过串口监视器输出服务器状态码和响应头信息
// 从而确定ESP8266已经成功连接服务器
Serial.println("获取响应信息========>:\r");
Serial.println("响应头:");
String inputString; //用一个字符串接收时间变量
while (HTTPS.connected()) {
inputString = HTTPS.readStringUntil('\n');
if (inputString.startsWith("Date: ")) {
Serial.println(inputString);
break;
}
if (inputString == "\r") {
Serial.println("响应头输出完毕!"); // Serial.println("响应头屏蔽完毕!\r");
break;
}
}
char *tokens[6]; // 用于存储分割后的子字符串
int tokenCount = 0;
char *str = strdup(inputString.c_str()); // 复制输入字符串,并将其转换为字符数组
char *token = strtok(str, " "); // 使用空格作为分隔符
while (token != NULL && tokenCount < 6) {
tokens[tokenCount] = token; // 存储子字符串
tokenCount++;
token = strtok(NULL, " "); // 继续分割下一个子字符串
}
// 检查是否成功分割字符串
if (tokenCount != 6) { return 0; }
// 分隔后的子字符串数组
String year = String(tokens[4]);
String month = String(tokens[3]);
String day = String(tokens[2]);
String time = String(tokens[5]);
// 进一步处理截取到的子字符串
Serial.println("Year: " + year);
Serial.println("Month: " + month);
Serial.println("Day: " + day);
// 分割时间部分,使用冒号作为分隔符
char *timeTokens[3]; // 用于存储分割后的时间子字符串
int timeTokenCount = 0;
char *timeStr = strdup(time.c_str()); // 复制时间字符串,并将其转换为字符数组
char *timeToken = strtok(timeStr, ":"); // 使用冒号作为分隔符
while (timeToken != NULL && timeTokenCount < 3) {
timeTokens[timeTokenCount] = timeToken; // 存储时间子字符串
timeTokenCount++;
timeToken = strtok(NULL, ":"); // 继续分割下一个时间子字符串
}
if (timeTokenCount != 3) { return 0; }
String hour = String(timeTokens[0]);
String minute = String(timeTokens[1]);
String second = String(timeTokens[2]);
Serial.println("Hour: " + hour);
Serial.println("Minute: " + minute);
Serial.println("Second: " + second);
free(timeStr); // 释放分配的内存
free(str); // 释放分配的内存
//此时已分割好所有字符串
//对月份进行转换 先做一个对象数组
// String monthAbbreviation = String(month);
String monthAbbreviations[] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
int monthNumbers[] = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
};
int monthNumber = -1; // 初始化月份数字
// 在数组中查找月份缩写,并获取对应的月份数字
for (int i = 0; i < sizeof(monthAbbreviations) / sizeof(monthAbbreviations[0]); i++) {
if (month == monthAbbreviations[i]) {
monthNumber = monthNumbers[i];
break;
}
}
if (monthNumber == -1) { return 0; } //如果月份不存在则返回报错
// 输出转换后的结果
// if (monthNumber != -1) {
// Serial.println("Month number: " + String(monthNumber));
// } else {
// Serial.println("Invalid input");
// }
// monthNumber = monthMap[month];
//此时可以使用Arduino自带的setTime(int hr,int min,int sec,int dy, int mnth, int yr) 但该函数存在32位年溢出问题
tmElement64s_t tm;
tm.Year = strtoul(year.c_str(), nullptr, 10) - 1970;
tm.Month = monthNumber;
tm.Day = strtoul(day.c_str(), nullptr, 10);
tm.Hour = strtoul(hour.c_str(), nullptr, 10);
tm.Minute = strtoul(minute.c_str(), nullptr, 10);
tm.Second = strtoul(second.c_str(), nullptr, 10);
ntpTime = makeTime(tm); //将时间戳赋值给全局变量ntpTime;
Serial.println("授时时间戳");
Serial.println(ntpTime);
struct timeval tv; //ESP32核心库定义此结构体用于赋值ESP32内置RTC
tv.tv_sec = 0;
tv.tv_usec = 0;
settimeofday(&tv, NULL);
needupadte = 0;
return 1;
}
uint64_t makeTime(const tmElement64s_t &tm) {
// assemble time elements into time_t
// note year argument is offset from 1970 (see macros in time.h to convert to other formats)
// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9
int i;
uint64_t seconds;
// seconds from 1970 till 1 jan 00:00:00 of the given year
seconds = tm.Year * (SECS_PER_DAY * 365);
for (i = 0; i < tm.Year; i++) {
if (LEAP_YEAR(i)) {
seconds += SECS_PER_DAY; // add extra days for leap years
}
}
// add days for this year, months start from 1
for (i = 1; i < tm.Month; i++) {
if ((i == 2) && LEAP_YEAR(tm.Year)) {
seconds += SECS_PER_DAY * 29;
} else {
seconds += SECS_PER_DAY * monthDays[i - 1]; //monthDay array starts from 0
}
}
seconds += (tm.Day - 1) * SECS_PER_DAY;
seconds += tm.Hour * SECS_PER_HOUR;
seconds += tm.Minute * SECS_PER_MIN;
seconds += tm.Second;
return seconds;
}
String getalipaytime() {
struct timeval tv; //创造一个结构体用于重置内部RTC
gettimeofday(&tv, NULL);
tmElement64s_t tm;
break64Time(ntpTime + tv.tv_sec + 28800, tm);
char month[2];
sprintf(month, "%02d", tm.Month);
char day[2];
sprintf(day, "%02d", tm.Day);
char hour[2];
sprintf(hour, "%02d", tm.Hour);
char minute[2];
sprintf(minute, "%02d", tm.Minute);
char second[2];
sprintf(second, "%02d", tm.Second);
String result = String(tm.Year) + String("-") + String(month) + String("-") + String(day) + String(" ") + String(hour) + String(":") + String(minute) + String(":") + String(second);
return result;
}
void break64Time(uint64_t timeInput, tmElement64s_t &tm) {
// break the given time_t into time components
// this is a more compact version of the C library localtime function
// note that year is offset from 1970 !!!
uint32_t year;
uint8_t month, monthLength;
uint64_t time;
unsigned long days;
time = (uint64_t)timeInput;
tm.Second = time % 60;
time /= 60; // now it is minutes
tm.Minute = time % 60;
time /= 60; // now it is hours
tm.Hour = time % 24;
time /= 24; // now it is days
tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1
year = 0;
days = 0;
while ((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
year++;
}
tm.Year = year + 1970; // year is offset from 1970
// Serial.println("内部年");
// Serial.println(year);
days -= LEAP_YEAR(year) ? 366 : 365;
time -= days; // now it is days in this year, starting at 0
days = 0;
month = 0;
monthLength = 0;
for (month = 0; month < 12; month++) {
if (month == 1) { // february
if (LEAP_YEAR(year)) {
monthLength = 29;
} else {
monthLength = 28;
}
} else {
monthLength = monthDays[month];
}
if (time >= monthLength) {
time -= monthLength;
} else {
break;
}
}
tm.Month = month + 1; // jan is month 1
tm.Day = time + 1; // day of month
}
void getNtpTimeAsync() { //发UDP包请求时间后调用句柄监听
IPAddress ntpIpAddress;
byte packetBuffer[48];
packetBuffer[0] = 0b00101011; // LI, Version, Mode 不闰秒 ntpv5 客户端
packetBuffer[1] = 0; // Stratum, or type of clock时钟精度 1最高15最低
packetBuffer[2] = 6;
packetBuffer[3] = 0x00;
char *ntpServer = "pool.ntp.org"; //time.nist.gov只有协议3 pool.ntp.org CN.NTP.ORG.CN
if (WiFi.hostByName(ntpServer, ntpIpAddress)) {
udp.writeTo(packetBuffer, 48, ntpIpAddress, 123);
timesendudp = micros();
} else {
// Serial.println("Failed to resolve NTP server");
}
}
//回调后调用句柄
void handleNtpResponse(AsyncUDPPacket packet) {
int packlength = packet.length();
if (packlength != 0) {
timegetudp = micros();
if (timegetudp < timesendudp) { return; }
if ((timegetudp - timesendudp) > 3000000) { return; } //如果对时包返回的过晚则代表网络拥塞此次对时失败
byte packetBuffer[packlength];
packet.read(packetBuffer, packlength);
// Serial.println("当前服务器");
// Serial.println(ntpServer);
unsigned long bite1 = word(0x00, packetBuffer[0]);
bite1 = bite1 & 0x38;
bite1 = bite1 >> 3;
Serial.println("版本号 只有第5版的NTP协议才能避免2038问题 如无需请注释下方2行");
Serial.println(bite1);
if (bite1 != 5) { return; } //如果对时版本号不对则代表服务器不支持ntp5
// Serial.println("原始数据");
// for (size_t i = 0; i < packlength; i++) {
// if(i%4==0){Serial.println();}
// Serial.printf("%02x",packetBuffer[i]);
// Serial.print(packetBuffer[i], HEX);}
// Serial.print(" ");
// }
// 提取时间戳字段
unsigned Era = packetBuffer[5];
unsigned ntpgetTimehighWord = word(packetBuffer[32], packetBuffer[33]);
unsigned ntpgetTimelowWord = word(packetBuffer[34], packetBuffer[35]);
unsigned ntpgetTimeMicrohighWord = word(packetBuffer[36], packetBuffer[37]);
unsigned ntpgetTimeMicrolowWord = word(packetBuffer[38], packetBuffer[39]);
unsigned long long ntpgetTime = (static_cast<unsigned long long>(ntpgetTimehighWord) << 48) | (static_cast<unsigned long long>(ntpgetTimelowWord) << 32) | (static_cast<unsigned long long>(ntpgetTimeMicrohighWord) << 16) | ntpgetTimeMicrolowWord;
unsigned ntpsendTimehighWord = word(packetBuffer[40], packetBuffer[41]);
unsigned ntpsendTimelowWord = word(packetBuffer[42], packetBuffer[43]);
unsigned ntpsendTimeMicrohighWord = word(packetBuffer[44], packetBuffer[45]);
unsigned ntpsendTimeMicrolowWord = word(packetBuffer[46], packetBuffer[47]);
unsigned long long ntpsendTime = (static_cast<unsigned long long>(ntpsendTimehighWord) << 48) | (static_cast<unsigned long long>(ntpsendTimelowWord) << 32) | (static_cast<unsigned long long>(ntpsendTimeMicrohighWord) << 16) | ntpsendTimeMicrolowWord;
unsigned long long ntpserverspendTime = ntpsendTime - ntpgetTime;
unsigned long long totalroadtime = (timegetudp - timesendudp); //总路程时间
totalroadtime = (totalroadtime << 32) / 1000000; //总路程时间毫秒
unsigned long long roadtime = (totalroadtime - ntpserverspendTime) / 2; //单程时间
unsigned long long deviceGetTime = ntpsendTime + roadtime; //设备得到UDP包时间16进制
struct timeval tv; //创造一个结构体用于重置内部RTC
tv.tv_sec = 0; //定义当前时刻为0时0分0秒 也就是说对时时刻为纪元原点 对时136年后时间戳溢出
unsigned long long microtimenow = micros() + 63; //63位最后的计算补时即下7行代码需要消耗63微秒时间
if (microtimenow < timegetudp) { return; } //如果微妙时间戳恰好溢出则对时失败
unsigned long long calculationTime = (microtimenow - timegetudp); //计算耗时
calculationTime = (calculationTime << 32) / 1000000; //计算耗时微秒
unsigned long long devicesetTime = deviceGetTime + calculationTime; //计算当前时间戳
unsigned long long microtime = (devicesetTime & 0xFFFFFFFF); //计算当前时间秒小数
microtime = (microtime * 1000000) >> 32; //计算当前时间微秒秒数
tv.tv_usec = microtime; //设置毫秒数
settimeofday(&tv, NULL); //RTC对时
unsigned long long ErastartTime = (static_cast<unsigned long long>(Era) << 32) | (devicesetTime >> 32);
ntpTime = ErastartTime;
// Serial.println(ErastartTime);
// Serial.println("纪元开始时间");//正确
// Serial.println(microtime);
// Serial.println("纪元开始微秒");//正确
// 保存 NTP 时间
ntpTime -= 2208988800ULL; // 从 1900 年转换为 Unix 时间戳
// ntpTime += timeZone * 3600; // 考虑时区偏移
// ntpTime *= 1000; // 转换为毫秒
// Serial.println(ntpTime);
// 更新全局变量
// ntpTime = ntpTime;
needupadte = 0;
}
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
WiFi.begin("S23", "12345678");
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.println("Connecting to WiFi...");
}
udp.listen(localPort);
udp.onPacket([](AsyncUDPPacket packet) {
// 处理接收到的 UDP 数据包
handleNtpResponse(packet);
});
//HTTPS_GETTIME("openapi.alipay.com", "/gateway.do?method=alipay.trade.query", 443, 1024); //乱请求根据响应头的内容获得时间
}
void loop() {
// put your main code here, to run repeatedly:
// HTTPS_request(String host, String url, int Port = 443, int Receive_cache = 1024)
// HTTPS_request("openapi.alipay.com","/gateway.do?biz_content=%7B%22out_trade_no%22%3A%22202108230101010012b%22%7D&app_id=2021001194613478&version=1.0&format=json&sign_type=RSA2&method=alipay.trade.query×tamp=2023-06-27+06%3A19%3A09&auth_token=&alipay_sdk=alipay-sdk-php-2020-04-15&terminal_type=&terminal_info=&prod_code=¬ify_url=&charset=UTF-8&app_auth_token=&app_cert_sn=0fbfea6f1c396ed1ee67302f2d035201&alipay_root_cert_sn=687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6&target_app_id=&sign=HoOkV2%2BeyCqsUunlknDHFbUy9aaDu%2FtA79kMH2nggOa4xiehi6z7LtngohCPBKX8Lb2ROgCZ%2BqWto39Q8Gbm%2B4bgeRwxcCmTEsDw4DcjkxJMxCQBO4IwWUvcvYGqkUGT2X1dRb7NOnb8jl8CM%2BfQxZzgR2UijCczCIQP3zMk27VNOCRyRFrVAWPPRwQPSS7hlEYhvngiLPkA5tj%2Bhh85KlvQRopQrUWaKmPqANwVYb73TsyEdybmPoyoteqAGuGIiPpLUgHX7JHM96AKFNWyctIBAOuaxU%2B9%2FeuV49stHlCbNsR0UggTeu7Sr0dKcTB3IVbnZUH8%2FoCRUYTwN707Gw%3D%3D",443,1024);
// HTTPS_request("openapi.alipay.com",alipaytradequery("123"),443,1024);
// HTTPS_request("openapi.alipay.com","gateway.do?","?biz_content=%7B%22out_trade_no%22%3A%22202108230101010012b%22%7D&app_id=2021001194613478&version=1.0&format=json&sign_type=RSA2&method=alipay.trade.query×tamp=2023-06-27+06%3A19%3A09&auth_token=&alipay_sdk=alipay-sdk-php-2020-04-15&terminal_type=&terminal_info=&prod_code=¬ify_url=&charset=UTF-8&app_auth_token=&app_cert_sn=0fbfea6f1c396ed1ee67302f2d035201&alipay_root_cert_sn=687b59193f3f462dd5336e5abf83c5d8_02941eef3187dddf3d3b83462e1dfcf6&target_app_id=&sign=HoOkV2%2BeyCqsUunlknDHFbUy9aaDu%2FtA79kMH2nggOa4xiehi6z7LtngohCPBKX8Lb2ROgCZ%2BqWto39Q8Gbm%2B4bgeRwxcCmTEsDw4DcjkxJMxCQBO4IwWUvcvYGqkUGT2X1dRb7NOnb8jl8CM%2BfQxZzgR2UijCczCIQP3zMk27VNOCRyRFrVAWPPRwQPSS7hlEYhvngiLPkA5tj%2Bhh85KlvQRopQrUWaKmPqANwVYb73TsyEdybmPoyoteqAGuGIiPpLUgHX7JHM96AKFNWyctIBAOuaxU%2B9%2FeuV49stHlCbNsR0UggTeu7Sr0dKcTB3IVbnZUH8%2FoCRUYTwN707Gw%3D%3D");
struct timeval tv; //创造一个结构体用于重置内部RTC
gettimeofday(&tv, NULL);
if (tv.tv_sec > 864000) { needupadte = 1; } //如果大于10天就申请授时 10天RTC与标准时间差最大约20S影响不大
if (needupadte) {
//HTTPS授时
//HTTPS_GETTIME("openapi.alipay.com", "/gateway.do?method=alipay.trade.query", 443, 1024); //乱请求根据响应头的内容获得时间
}
if (needupadte) {
//NTP授时
getNtpTimeAsync();
delay(3000);
Serial.println("等待授时");
} else {
Serial.println(getalipaytime());
delay(3000);
}
}