From 8b5f6f99e3bdc0b15328e06853befc02e835a37d Mon Sep 17 00:00:00 2001 From: Martin Brodbeck Date: Wed, 28 Dec 2022 17:35:58 +0100 Subject: [PATCH] More on NTP. --- src/CMakeLists.txt | 2 + src/abfall.cpp | 20 +++++- src/ntp_time.cpp | 170 +++++++++++++++++++++++++++++++++++++++++++++ src/ntp_time.h | 1 + 4 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 src/ntp_time.cpp create mode 100644 src/ntp_time.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7f5e8ac..aeae271 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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}) diff --git a/src/abfall.cpp b/src/abfall.cpp index bc9f4e4..e78fde2 100644 --- a/src/abfall.cpp +++ b/src/abfall.cpp @@ -1,10 +1,16 @@ #include +// #include #include -#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(); diff --git a/src/ntp_time.cpp b/src/ntp_time.cpp new file mode 100644 index 0000000..c83bc8a --- /dev/null +++ b/src/ntp_time.cpp @@ -0,0 +1,170 @@ +#include +#include + +#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; +} \ No newline at end of file diff --git a/src/ntp_time.h b/src/ntp_time.h new file mode 100644 index 0000000..2458cad --- /dev/null +++ b/src/ntp_time.h @@ -0,0 +1 @@ +void set_rtc(); \ No newline at end of file