diff options
author | Thomas White <taw@physics.org> | 2023-02-18 22:39:32 +0100 |
---|---|---|
committer | Thomas White <taw@physics.org> | 2023-02-18 22:39:32 +0100 |
commit | fb7f9ca6a388dcc7ce902030ba281147c2bcc950 (patch) | |
tree | 359d006c393f4314543e4ecc2ecc8e334c795a53 | |
parent | 164ae66e9abda4c540d41562dbbc87001ea5d023 (diff) |
Synchronise using NTP
-rw-r--r-- | CMakeLists.txt | 18 | ||||
-rwxr-xr-x | compile | 4 | ||||
-rw-r--r-- | lwipopts.h | 75 | ||||
-rw-r--r-- | morningtown.c | 180 | ||||
-rw-r--r-- | ntp_client.c | 228 | ||||
-rw-r--r-- | ntp_client.h | 45 |
6 files changed, 511 insertions, 39 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index af55c1f..5019c8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,6 +9,20 @@ set(CMAKE_CXX_STANDARD 17) # Initialize the SDK pico_sdk_init() -add_executable(morningtown morningtown.c) -target_link_libraries(morningtown pico_stdlib hardware_rtc) +add_executable(morningtown morningtown.c ntp_client.c) + +# For verbose debugging, uncomment this line and see ntp_client.h +#pico_enable_stdio_usb(morningtown ENABLED) + +target_link_libraries(morningtown + pico_stdlib + hardware_rtc + pico_cyw43_arch_lwip_threadsafe_background) + pico_add_extra_outputs(morningtown) + +target_compile_definitions(morningtown PRIVATE + WIFI_SSID=\"${WIFI_SSID}\" + WIFI_PASSWORD=\"${WIFI_PASSWORD}\") + +target_include_directories(morningtown PRIVATE ${CMAKE_CURRENT_LIST_DIR}) @@ -4,7 +4,9 @@ export PICO_SDK_PATH=/mnt/datassd/2023/pico/pico-sdk/ rm -rf build mkdir build cd build -cmake .. +cmake .. -DPICO_BOARD=pico_w \ + -DWIFI_SSID=MyWifi \ + -DWIFI_PASSWORD=MyPassword cd .. make -C build diff --git a/lwipopts.h b/lwipopts.h new file mode 100644 index 0000000..cae44f7 --- /dev/null +++ b/lwipopts.h @@ -0,0 +1,75 @@ +#ifndef _LWIPOPTS_H +#define _LWIPOPTS_H + +#define NO_SYS 1 +#define LWIP_SOCKET 0 +#define MEM_LIBC_MALLOC 0 +#define MEM_ALIGNMENT 4 +#define MEM_SIZE 4000 +#define MEMP_NUM_TCP_SEG 32 +#define MEMP_NUM_ARP_QUEUE 10 +#define PBUF_POOL_SIZE 24 +#define LWIP_ARP 1 +#define LWIP_ETHERNET 1 +#define LWIP_ICMP 1 +#define LWIP_RAW 1 +#define TCP_WND (8 * TCP_MSS) +#define TCP_MSS 1460 +#define TCP_SND_BUF (8 * TCP_MSS) +#define TCP_SND_QUEUELEN ((4 * (TCP_SND_BUF) + (TCP_MSS - 1)) / (TCP_MSS)) +#define LWIP_NETIF_STATUS_CALLBACK 1 +#define LWIP_NETIF_LINK_CALLBACK 1 +#define LWIP_NETIF_HOSTNAME 1 +#define LWIP_NETCONN 0 +#define MEM_STATS 0 +#define SYS_STATS 0 +#define MEMP_STATS 0 +#define LINK_STATS 0 +// #define ETH_PAD_SIZE 2 +#define LWIP_CHKSUM_ALGORITHM 3 +#define LWIP_DHCP 1 +#define LWIP_IPV4 1 +#define LWIP_TCP 1 +#define LWIP_UDP 1 +#define LWIP_DNS 1 +#define LWIP_TCP_KEEPALIVE 1 +#define LWIP_NETIF_TX_SINGLE_PBUF 1 +#define DHCP_DOES_ARP_CHECK 0 +#define LWIP_DHCP_DOES_ACD_CHECK 0 + +#ifndef NDEBUG +#define LWIP_DEBUG 1 +#define LWIP_STATS 1 +#define LWIP_STATS_DISPLAY 1 +#endif + +#define ETHARP_DEBUG LWIP_DBG_OFF +#define NETIF_DEBUG LWIP_DBG_OFF +#define PBUF_DEBUG LWIP_DBG_OFF +#define API_LIB_DEBUG LWIP_DBG_OFF +#define API_MSG_DEBUG LWIP_DBG_OFF +#define SOCKETS_DEBUG LWIP_DBG_OFF +#define ICMP_DEBUG LWIP_DBG_OFF +#define INET_DEBUG LWIP_DBG_OFF +#define IP_DEBUG LWIP_DBG_OFF +#define IP_REASS_DEBUG LWIP_DBG_OFF +#define RAW_DEBUG LWIP_DBG_OFF +#define MEM_DEBUG LWIP_DBG_OFF +#define MEMP_DEBUG LWIP_DBG_OFF +#define SYS_DEBUG LWIP_DBG_OFF +#define TCP_DEBUG LWIP_DBG_OFF +#define TCP_INPUT_DEBUG LWIP_DBG_OFF +#define TCP_OUTPUT_DEBUG LWIP_DBG_OFF +#define TCP_RTO_DEBUG LWIP_DBG_OFF +#define TCP_CWND_DEBUG LWIP_DBG_OFF +#define TCP_WND_DEBUG LWIP_DBG_OFF +#define TCP_FR_DEBUG LWIP_DBG_OFF +#define TCP_QLEN_DEBUG LWIP_DBG_OFF +#define TCP_RST_DEBUG LWIP_DBG_OFF +#define UDP_DEBUG LWIP_DBG_OFF +#define TCPIP_DEBUG LWIP_DBG_OFF +#define PPP_DEBUG LWIP_DBG_OFF +#define SLIP_DEBUG LWIP_DBG_OFF +#define DHCP_DEBUG LWIP_DBG_OFF + +#endif diff --git a/morningtown.c b/morningtown.c index baa029c..5f26626 100644 --- a/morningtown.c +++ b/morningtown.c @@ -1,11 +1,73 @@ +/* + * morningtown.c + * + * Silent alarm clock + * + * Copyright © 2023 Thomas White <taw@physics.org> + * + * This file is part of MorningTown + * + * MorningTown is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MorningTown is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MorningTown. If not, see <http://www.gnu.org/licenses/>. + * + */ + #include <pico/stdlib.h> #include <hardware/rtc.h> -#include <pico/util/datetime.h> +#include <pico/cyw43_arch.h> + +#include "ntp_client.h" #define LED_GREEN 21 #define LED_RED 22 -static void alarm_callback() +int ntp_sent = 0; +int ntp_ok = 0; + + +static void flash_red(int n) +{ + int i; + + gpio_put(LED_GREEN, 0); + for ( i=0; i<10; i++ ) { + gpio_put(LED_RED, 1); + sleep_ms(100); + gpio_put(LED_RED, 0); + sleep_ms(100); + } +} + + +static void celebrate() +{ + int i; + + for ( i=0; i<10; i++ ) { + gpio_put(LED_RED, 1); + gpio_put(LED_GREEN, 0); + sleep_ms(300); + gpio_put(LED_RED, 0); + gpio_put(LED_GREEN, 1); + sleep_ms(300); + } + + gpio_put(LED_GREEN, 0); + gpio_put(LED_RED, 0); +} + + +static void check_clock() { datetime_t t = {0}; rtc_get_datetime(&t); @@ -23,57 +85,103 @@ static void alarm_callback() } -static void start_demo() +static void ntp_callback(int status) { - int i; - for ( i=0; i<10; i++ ) { - gpio_put(LED_RED, 1); - gpio_put(LED_GREEN, 0); - sleep_ms(250); - gpio_put(LED_RED, 0); - gpio_put(LED_GREEN, 1); - sleep_ms(250); + switch ( status ) { + + case NTP_DNS_ERROR: + flash_red(5); + ntp_sent = 0; + break; + + case NTP_DNS_NO_ADDR: + flash_red(10); + ntp_sent = 0; + break; + + case NTP_BAD_REPLY: + flash_red(15); + ntp_sent = 0; + break; + + case NTP_TIMEOUT: + flash_red(20); + ntp_sent = 0; + break; + + case NTP_REQUEST_SENT: + break; + + case NTP_REPLY_RECEIVED: + celebrate(); + ntp_ok = 1; + break; } } int main() { + NTP_T *ntp_state; + int last_conn; + gpio_init(LED_GREEN); gpio_init(LED_RED); gpio_set_dir(LED_GREEN, GPIO_OUT); gpio_set_dir(LED_RED, GPIO_OUT); - datetime_t t = { - .year = 2023, - .month = 2, - .day = 5, - .dotw = 0, /* 0=Sunday */ - .hour = 22, - .min = 25, - .sec = 0 - }; - rtc_init(); - rtc_set_datetime(&t); + cyw43_arch_init(); + cyw43_arch_enable_sta_mode(); - /* Prove we're awake, then reset everything */ - start_demo(); + stdio_init_all(); + + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 1); + gpio_put(LED_RED, 1); + gpio_put(LED_GREEN, 1); + sleep_ms(2000); + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, 0); gpio_put(LED_RED, 0); gpio_put(LED_GREEN, 0); - datetime_t alarm = { - .year = -1, - .month = -1, - .day = -1, - .dotw = -1, - .hour = -1, - .min = -1, - .sec = 0 - }; - - rtc_set_alarm(&alarm, &alarm_callback); + rtc_init(); + ntp_state = ntp_init(ntp_callback); + last_conn = 10; while (1) { - sleep_ms(1000); + + int st = cyw43_wifi_link_status(&cyw43_state, CYW43_ITF_STA); + cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, + !ntp_ok && (st == CYW43_LINK_JOIN)); + + if ( (st != CYW43_LINK_JOIN) && (last_conn > 10) ) { + cyw43_arch_wifi_connect_async(WIFI_SSID, + WIFI_PASSWORD, + CYW43_AUTH_WPA2_AES_PSK); + debug_print("connecting to wifi...\n"); + last_conn = 0; + } + + if ( (st == CYW43_LINK_JOIN) && !ntp_sent ) { + ntp_send_request(ntp_state); + debug_print("sending NTP request\n"); + ntp_sent = 1; + } + + if ( ntp_ok ) { + debug_print("connected OK. Checking clock..\n"); + check_clock(); + } + + ntp_poll(ntp_state); + + debug_print("tick\n"); + + last_conn += 1; + if ( ntp_ok ) { + sleep_ms(10000); + } else { + sleep_ms(1000); + } + } } diff --git a/ntp_client.c b/ntp_client.c new file mode 100644 index 0000000..08f7783 --- /dev/null +++ b/ntp_client.c @@ -0,0 +1,228 @@ +/* + * ntp_client.c + * + * Copyright © 2023 Thomas White <taw@physics.org> + * + * This file is part of MorningTown + * + * Based on pico-examples/pico_w/ntp_client/picow_ntp_client.c + * Original license: BSD-3-Clause + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * MorningTown is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MorningTown is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MorningTown. If not, see <http://www.gnu.org/licenses/>. + * + */ + +#include <string.h> +#include <time.h> + +#include <hardware/rtc.h> +#include <pico/stdlib.h> +#include <pico/util/datetime.h> +#include <pico/cyw43_arch.h> + +#include "lwip/dns.h" +#include "lwip/pbuf.h" +#include "lwip/udp.h" + +#include "ntp_client.h" + + +typedef struct NTP_T_ { + ip_addr_t ntp_server_address; + bool dns_request_sent; + struct udp_pcb *ntp_pcb; + alarm_id_t ntp_resend_alarm; + void (*callback)(int status); + time_t utc; + int have_new_reply; + int last_status; +} 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_RESEND_TIME (10 * 1000) +#define UTC_OFFSET_SEC 3600 + + +static void ntp_result(NTP_T *state, int status, time_t *result) +{ + debug_print("NTP result:\n"); + state->have_new_reply = 1; + state->last_status = status; + if (status == NTP_REPLY_RECEIVED && result) { + + state->utc = *result; + debug_print("yay!\n"); + + if ( state->ntp_resend_alarm > 0 ) { + cancel_alarm(state->ntp_resend_alarm); + state->ntp_resend_alarm = 0; + } + + } else { + debug_print("other reply.\n"); + } + + state->dns_request_sent = false; +} + + +static void ntp_request(NTP_T *state) +{ + debug_print("sending NTP UDP\n"); + 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(); + state->callback(NTP_REQUEST_SENT); +} + + +static void ntp_dns_found(const char *hostname, + const ip_addr_t *ipaddr, + void *arg) +{ + NTP_T *state = (NTP_T*)arg; + if (ipaddr) { + debug_print("DNS ok (%i)\n", *ipaddr); + state->ntp_server_address = *ipaddr; + ntp_request(state); + } else { + debug_print("NTP failed: DNS no address\n"); + ntp_result(state, NTP_DNS_NO_ADDR, NULL); + } +} + + +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); + + debug_print("got UDP: %i %i\n", addr, state->ntp_server_address); + debug_print("port %i len %i mode %i stratum %i\n", port, p->tot_len, mode, stratum); + + 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; + debug_print("second since 1970 = %i\n", epoch); + ntp_result(state, NTP_REPLY_RECEIVED, &epoch); + } else { + ntp_result(state, NTP_BAD_REPLY, NULL); + } + pbuf_free(p); +} + + +static int64_t ntp_failed_handler(alarm_id_t id, void *user_data) +{ + NTP_T *state = (NTP_T*)user_data; + ntp_result(state, NTP_TIMEOUT, NULL); + return 0; +} + + +NTP_T *ntp_init(void (*reply_func)(int)) +{ + NTP_T *state = calloc(1, sizeof(NTP_T)); + if (!state) return NULL; + + state->callback = reply_func; + state->have_new_reply = 0; + + state->ntp_pcb = udp_new_ip_type(IPADDR_TYPE_ANY); + if (!state->ntp_pcb) { + free(state); + return NULL; + } + udp_recv(state->ntp_pcb, ntp_recv, state); + return state; +} + + +void ntp_send_request(NTP_T *state) +{ + if ( state == NULL ) return; + + cyw43_arch_lwip_begin(); + int err = dns_gethostbyname(NTP_SERVER, + &state->ntp_server_address, + ntp_dns_found, + state); + cyw43_arch_lwip_end(); + + state->ntp_resend_alarm = add_alarm_in_ms(NTP_RESEND_TIME, + ntp_failed_handler, + state, + true); + + state->dns_request_sent = true; + if ( err == ERR_OK ) { + /* Immediate reply -> cached result */ + ntp_request(state); + } else if (err != ERR_INPROGRESS) { + /* Error sending DNS request + * (ERR_INPROGRESS means expect a callback) */ + ntp_result(state, NTP_DNS_ERROR, NULL); + } +} + + +void ntp_poll(NTP_T *state) +{ + if ( !state->have_new_reply ) return; + state->have_new_reply = 0; + + if ( state->last_status == NTP_REPLY_RECEIVED ) { + + time_t tv = state->utc + UTC_OFFSET_SEC; + struct tm *utc = gmtime(&tv); + datetime_t t; + + t.year = utc->tm_year + 1900; + t.month = utc->tm_mon + 1; + t.day = utc->tm_mday; + t.dotw = utc->tm_wday; + t.hour = utc->tm_hour; + t.min = utc->tm_min; + t.sec = utc->tm_sec; + + debug_print("time is %i/%i/%i %i %i:%i:%i\n", + t.year, t.month, t.day, t.dotw, t.hour, t.min, t.sec); + + rtc_set_datetime(&t); + + } + state->callback(state->last_status); +} diff --git a/ntp_client.h b/ntp_client.h new file mode 100644 index 0000000..aedb411 --- /dev/null +++ b/ntp_client.h @@ -0,0 +1,45 @@ +/* + * ntp_client.h + * + * Copyright © 2023 Thomas White <taw@physics.org> + * + * This file is part of MorningTown + * + * Based on pico-examples/pico_w/ntp_client/picow_ntp_client.c + * Original license: BSD-3-Clause + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * MorningTown is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * MorningTown is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with MorningTown. If not, see <http://www.gnu.org/licenses/>. + * + */ + +typedef struct NTP_T_ NTP_T; + +enum { + NTP_DNS_ERROR, + NTP_DNS_NO_ADDR, + NTP_REQUEST_SENT, + NTP_BAD_REPLY, + NTP_TIMEOUT, + NTP_REPLY_RECEIVED +}; + +extern NTP_T *ntp_init(void (*reply_func)(int)); +extern void ntp_send_request(NTP_T *state); +extern void ntp_poll(NTP_T *state); + +/* For verbose debugging, switch the commenting of these two lines. + * .. and see CMakeLists.txt */ +static void debug_print(const char *str, ...) {} +//#define debug_print printf |