More on NTP.

This commit is contained in:
Martin Brodbeck 2022-12-28 17:35:58 +01:00
parent 10bb31b123
commit 8b5f6f99e3
4 changed files with 191 additions and 2 deletions

View file

@ -1,6 +1,7 @@
#set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(SOURCES
ntp_time.cpp
abfall.cpp
)
@ -29,6 +30,7 @@ target_link_libraries(${CMAKE_PROJECT_NAME}
pico_stdlib
pico_cyw43_arch_lwip_threadsafe_background
pico_lwip_http
hardware_rtc
)
pico_add_extra_outputs(${CMAKE_PROJECT_NAME})

View file

@ -1,10 +1,16 @@
#include <stdio.h>
// #include <ctime>
#include <string>
#include "lwip/apps/http_client.h"
#include "hardware/rtc.h"
#include "pico/cyw43_arch.h"
#include "pico/stdlib.h"
#include "pico/util/datetime.h"
#include "lwip/apps/http_client.h"
#include "ntp_time.h"
using std::string;
@ -116,8 +122,18 @@ int main() {
&settings, body_callback, nullptr, nullptr);
printf("Status %d\n", err);
set_rtc();
while (true) {
sleep_ms(1);
sleep_ms(1000);
datetime_t *dt = new datetime_t();
if (rtc_get_datetime(dt) == false) {
printf("RTC NOT RUNNING !!\n");
}
char dt_str[256];
datetime_to_str(dt_str, 128, dt);
printf("DateTime: %s\n", dt_str);
delete dt;
}
cyw43_arch_deinit();

170
src/ntp_time.cpp Normal file
View file

@ -0,0 +1,170 @@
#include <string.h>
#include <time.h>
#include "hardware/rtc.h"
#include "pico/cyw43_arch.h"
#include "pico/stdlib.h"
#include "lwip/dns.h"
#include "lwip/pbuf.h"
#include "lwip/udp.h"
typedef struct NTP_T_ {
ip_addr_t ntp_server_address;
bool dns_request_sent;
struct udp_pcb *ntp_pcb;
absolute_time_t ntp_test_time;
alarm_id_t ntp_resend_alarm;
} NTP_T;
#define NTP_SERVER "pool.ntp.org"
#define NTP_MSG_LEN 48
#define NTP_PORT 123
#define NTP_DELTA 2208988800 // seconds between 1 Jan 1900 and 1 Jan 1970
#define NTP_TEST_TIME (30 * 1000)
#define NTP_RESEND_TIME (10 * 1000)
// Called with results of operation
static void ntp_result(NTP_T *state, int status, time_t *result) {
if (status == 0 && result) {
struct tm *utc = gmtime(result);
printf("got ntp response: %02d/%02d/%04d %02d:%02d:%02d\n", utc->tm_mday, utc->tm_mon + 1,
utc->tm_year + 1900, utc->tm_hour, utc->tm_min, utc->tm_sec);
// TODO: Set hardware RTC
datetime_t test;
test.year = utc->tm_year + 1900;
test.month = utc->tm_mon + 1;
test.day = utc->tm_mday;
test.hour = utc->tm_hour;
test.min = utc->tm_min;
test.sec = utc->tm_sec;
test.dotw = utc->tm_wday - 1;
if (rtc_set_datetime(&test) == true) {
printf("RTC successfully set.\n");
}
}
if (state->ntp_resend_alarm > 0) {
cancel_alarm(state->ntp_resend_alarm);
state->ntp_resend_alarm = 0;
}
state->ntp_test_time = make_timeout_time_ms(NTP_TEST_TIME);
state->dns_request_sent = false;
}
static int64_t ntp_failed_handler(alarm_id_t id, void *user_data);
// Make an NTP request
static void ntp_request(NTP_T *state) {
// cyw43_arch_lwip_begin/end should be used around calls into lwIP to ensure correct locking.
// You can omit them if you are in a callback from lwIP. Note that when using pico_cyw_arch_poll
// these calls are a no-op and can be omitted, but it is a good practice to use them in
// case you switch the cyw43_arch type later.
cyw43_arch_lwip_begin();
struct pbuf *p = pbuf_alloc(PBUF_TRANSPORT, NTP_MSG_LEN, PBUF_RAM);
uint8_t *req = (uint8_t *)p->payload;
memset(req, 0, NTP_MSG_LEN);
req[0] = 0x1b;
udp_sendto(state->ntp_pcb, p, &state->ntp_server_address, NTP_PORT);
pbuf_free(p);
cyw43_arch_lwip_end();
}
static int64_t ntp_failed_handler(alarm_id_t id, void *user_data) {
NTP_T *state = (NTP_T *)user_data;
printf("ntp request failed\n");
ntp_result(state, -1, NULL);
return 0;
}
// Call back with a DNS result
static void ntp_dns_found(const char *hostname, const ip_addr_t *ipaddr, void *arg) {
NTP_T *state = (NTP_T *)arg;
if (ipaddr) {
state->ntp_server_address = *ipaddr;
printf("ntp address %s\n", ip4addr_ntoa(ipaddr));
ntp_request(state);
} else {
printf("ntp dns request failed\n");
ntp_result(state, -1, NULL);
}
}
// NTP data received
static void ntp_recv(void *arg, struct udp_pcb *pcb, struct pbuf *p, const ip_addr_t *addr,
u16_t port) {
NTP_T *state = (NTP_T *)arg;
uint8_t mode = pbuf_get_at(p, 0) & 0x7;
uint8_t stratum = pbuf_get_at(p, 1);
// Check the result
if (ip_addr_cmp(addr, &state->ntp_server_address) && port == NTP_PORT &&
p->tot_len == NTP_MSG_LEN && mode == 0x4 && stratum != 0) {
uint8_t seconds_buf[4] = {0};
pbuf_copy_partial(p, seconds_buf, sizeof(seconds_buf), 40);
uint32_t seconds_since_1900 =
seconds_buf[0] << 24 | seconds_buf[1] << 16 | seconds_buf[2] << 8 | seconds_buf[3];
uint32_t seconds_since_1970 = seconds_since_1900 - NTP_DELTA;
time_t epoch = seconds_since_1970;
ntp_result(state, 0, &epoch);
} else {
printf("invalid ntp response\n");
ntp_result(state, -1, NULL);
}
pbuf_free(p);
}
// Perform initialisation
static NTP_T *ntp_init(void) {
NTP_T *state = new NTP_T();
if (!state) {
printf("failed to allocate state\n");
return NULL;
}
state->ntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY);
if (!state->ntp_pcb) {
printf("failed to create pcb\n");
free(state);
return NULL;
}
udp_recv(state->ntp_pcb, ntp_recv, state);
return state;
}
// Runs ntp test forever
void set_rtc() {
NTP_T *state = ntp_init();
if (!state)
return;
if (absolute_time_diff_us(get_absolute_time(), state->ntp_test_time) < 0 &&
!state->dns_request_sent) {
// Set alarm in case udp requests are lost
state->ntp_resend_alarm = add_alarm_in_ms(NTP_RESEND_TIME, ntp_failed_handler, state, true);
// cyw43_arch_lwip_begin/end should be used around calls into lwIP to ensure correct
// locking. You can omit them if you are in a callback from lwIP. Note that when using
// pico_cyw_arch_poll these calls are a no-op and can be omitted, but it is a good
// practice to use them in case you switch the cyw43_arch type later.
cyw43_arch_lwip_begin();
int err = dns_gethostbyname(NTP_SERVER, &state->ntp_server_address, ntp_dns_found, state);
cyw43_arch_lwip_end();
state->dns_request_sent = true;
if (err == ERR_OK) {
ntp_request(state); // Cached result
} else if (err != ERR_INPROGRESS) { // ERR_INPROGRESS means expect a callback
printf("dns request failed\n");
ntp_result(state, -1, NULL);
}
}
// if you are not using pico_cyw43_arch_poll, then WiFI driver and lwIP work
// is done via interrupt in the background. This sleep is just an example of some (blocking)
// work you might be doing.
// sleep_ms(1000);
delete state;
}

1
src/ntp_time.h Normal file
View file

@ -0,0 +1 @@
void set_rtc();