aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas White <taw@physics.org>2023-02-18 22:39:32 +0100
committerThomas White <taw@physics.org>2023-02-18 22:39:32 +0100
commitfb7f9ca6a388dcc7ce902030ba281147c2bcc950 (patch)
tree359d006c393f4314543e4ecc2ecc8e334c795a53
parent164ae66e9abda4c540d41562dbbc87001ea5d023 (diff)
Synchronise using NTP
-rw-r--r--CMakeLists.txt18
-rwxr-xr-xcompile4
-rw-r--r--lwipopts.h75
-rw-r--r--morningtown.c180
-rw-r--r--ntp_client.c228
-rw-r--r--ntp_client.h45
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})
diff --git a/compile b/compile
index b00f0bd..6a63079 100755
--- a/compile
+++ b/compile
@@ -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