From fc24139f5d960329a42764006c2d02c1730ba745 Mon Sep 17 00:00:00 2001 From: PoliEcho Date: Fri, 12 Sep 2025 13:08:14 +0200 Subject: [PATCH] first request works --- CMakeLists.txt | 6 +- http_client_util.c | 141 --------------------------------------- http_client_util.h | 126 ----------------------------------- lwipopts.h | 12 +++- net_utils.cpp | 67 +++++++++++++++++++ net_utils.h | 6 ++ smart_alarm.cpp | 162 ++++++++++++++++++++++++++++++++++++++++++--- 7 files changed, 241 insertions(+), 279 deletions(-) delete mode 100644 http_client_util.c delete mode 100644 http_client_util.h create mode 100644 net_utils.cpp create mode 100644 net_utils.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0ba7c69..92ecb50 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,10 +28,11 @@ pico_sdk_init() # Add executable. Default name is the project name, version 0.1 -add_executable(smart_alarm smart_alarm.cpp http_client_util.c) +add_executable(smart_alarm smart_alarm.cpp + net_utils.cpp) pico_set_program_name(smart_alarm "smart_alarm") -pico_set_program_version(smart_alarm "0.1") +pico_set_program_version(smart_alarm "0.2") pico_enable_stdio_uart(smart_alarm 1) pico_enable_stdio_usb(smart_alarm 1) @@ -55,7 +56,6 @@ target_link_libraries(smart_alarm pico_cyw43_arch_lwip_threadsafe_background pico_lwip_sntp hardware_rtc - pico_lwip_http ) target_compile_definitions(smart_alarm PRIVATE diff --git a/http_client_util.c b/http_client_util.c deleted file mode 100644 index d67f598..0000000 --- a/http_client_util.c +++ /dev/null @@ -1,141 +0,0 @@ -/** - * Copyright (c) 2023 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#include -#include -#include "pico/async_context.h" -#include "lwip/altcp.h" -#include "lwip/altcp_tls.h" -#include "example_http_client_util.h" - -#ifndef HTTP_INFO -#define HTTP_INFO printf -#endif - -#ifndef HTTP_INFOC -#define HTTP_INFOC putchar -#endif - -#ifndef HTTP_INFOC -#define HTTP_INFOC putchar -#endif - -#ifndef HTTP_DEBUG -#ifdef NDEBUG -#define HTTP_DEBUG -#else -#define HTTP_DEBUG printf -#endif -#endif - -#ifndef HTTP_ERROR -#define HTTP_ERROR printf -#endif - -// Print headers to stdout -err_t http_client_header_print_fn(__unused httpc_state_t *connection, __unused void *arg, struct pbuf *hdr, u16_t hdr_len, __unused u32_t content_len) { - HTTP_INFO("\nheaders %u\n", hdr_len); - u16_t offset = 0; - while (offset < hdr->tot_len && offset < hdr_len) { - char c = (char)pbuf_get_at(hdr, offset++); - HTTP_INFOC(c); - } - return ERR_OK; -} - -// Print body to stdout -err_t http_client_receive_print_fn(__unused void *arg, __unused struct altcp_pcb *conn, struct pbuf *p, err_t err) { - HTTP_INFO("\ncontent err %d\n", err); - u16_t offset = 0; - while (offset < p->tot_len) { - char c = (char)pbuf_get_at(p, offset++); - HTTP_INFOC(c); - } - return ERR_OK; -} - - -static err_t internal_header_fn(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len) { - assert(arg); - EXAMPLE_HTTP_REQUEST_T *req = (EXAMPLE_HTTP_REQUEST_T*)arg; - if (req->headers_fn) { - return req->headers_fn(connection, req->callback_arg, hdr, hdr_len, content_len); - } - return ERR_OK; -} - -static err_t internal_recv_fn(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err) { - assert(arg); - EXAMPLE_HTTP_REQUEST_T *req = (EXAMPLE_HTTP_REQUEST_T*)arg; - if (req->recv_fn) { - return req->recv_fn(req->callback_arg, conn, p, err); - } - return ERR_OK; -} - -static void internal_result_fn(void *arg, httpc_result_t httpc_result, u32_t rx_content_len, u32_t srv_res, err_t err) { - assert(arg); - EXAMPLE_HTTP_REQUEST_T *req = (EXAMPLE_HTTP_REQUEST_T*)arg; - HTTP_DEBUG("result %d len %u server_response %u err %d\n", httpc_result, rx_content_len, srv_res, err); - req->complete = true; - req->result = httpc_result; - if (req->result_fn) { - req->result_fn(req->callback_arg, httpc_result, rx_content_len, srv_res, err); - } -} - -// Override altcp_tls_alloc to set sni -static struct altcp_pcb *altcp_tls_alloc_sni(void *arg, u8_t ip_type) { - assert(arg); - EXAMPLE_HTTP_REQUEST_T *req = (EXAMPLE_HTTP_REQUEST_T*)arg; - struct altcp_pcb *pcb = altcp_tls_alloc(req->tls_config, ip_type); - if (!pcb) { - HTTP_ERROR("Failed to allocate PCB\n"); - return NULL; - } - mbedtls_ssl_set_hostname(altcp_tls_context(pcb), req->hostname); - return pcb; -} - -// Make a http request, complete when req->complete returns true -int http_client_request_async(async_context_t *context, EXAMPLE_HTTP_REQUEST_T *req) { -#if LWIP_ALTCP - const uint16_t default_port = req->tls_config ? 443 : 80; - if (req->tls_config) { - if (!req->tls_allocator.alloc) { - req->tls_allocator.alloc = altcp_tls_alloc_sni; - req->tls_allocator.arg = req; - } - req->settings.altcp_allocator = &req->tls_allocator; - } -#else - const uint16_t default_port = 80; -#endif - req->complete = false; - req->settings.headers_done_fn = req->headers_fn ? internal_header_fn : NULL; - req->settings.result_fn = internal_result_fn; - async_context_acquire_lock_blocking(context); - err_t ret = httpc_get_file_dns(req->hostname, req->port ? req->port : default_port, req->url, &req->settings, internal_recv_fn, req, NULL); - async_context_release_lock(context); - if (ret != ERR_OK) { - HTTP_ERROR("http request failed: %d", ret); - } - return ret; -} - -// Make a http request and only return when it has completed. Returns true on success -int http_client_request_sync(async_context_t *context, EXAMPLE_HTTP_REQUEST_T *req) { - assert(req); - int ret = http_client_request_async(context, req); - if (ret != 0) { - return ret; - } - while(!req->complete) { - async_context_poll(context); - async_context_wait_for_work_ms(context, 1000); - } - return req->result; -} diff --git a/http_client_util.h b/http_client_util.h deleted file mode 100644 index 95f2e88..0000000 --- a/http_client_util.h +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2024 Raspberry Pi (Trading) Ltd. - * - * SPDX-License-Identifier: BSD-3-Clause - */ - -#ifndef EXAMPLE_HTTP_CLIENT_UTIL_H -#define EXAMPLE_HTTP_CLIENT_UTIL_H - -#include "lwip/apps/http_client.h" - -/*! \brief Parameters used to make HTTP request - * \ingroup pico_lwip - */ -typedef struct HTTP_REQUEST { - /*! - * The name of the host, e.g. www.raspberrypi.com - */ - const char *hostname; - /*! - * The url to request, e.g. /favicon.ico - */ - const char *url; - /*! - * Function to callback with headers, can be null - * @see httpc_headers_done_fn - */ - httpc_headers_done_fn headers_fn; - /*! - * Function to callback with results from the server, can be null - * @see altcp_recv_fn - */ - altcp_recv_fn recv_fn; - /*! - * Function to callback with final results of the request, can be null - * @see httpc_result_fn - */ - httpc_result_fn result_fn; - /*! - * Callback to pass to calback functions - */ - void *callback_arg; - /*! - * The port to use. A default port is chosen if this is set to zero - */ - uint16_t port; -#if LWIP_ALTCP && LWIP_ALTCP_TLS - /*! - * TLS configuration, can be null or set to a correctly configured tls configuration. - * e.g altcp_tls_create_config_client(NULL, 0) would use https without a certificate - */ - struct altcp_tls_config *tls_config; - /*! - * TLS allocator, used internall for setting TLS server name indication - */ - altcp_allocator_t tls_allocator; -#endif - /*! - * LwIP HTTP client settings - */ - httpc_connection_t settings; - /*! - * Flag to indicate when the request is complete - */ - int complete; - /*! - * Overall result of http request, only valid when complete is set - */ - httpc_result_t result; - -} HTTP_REQUEST_T; - -struct async_context; - -/*! \brief Perform a http request asynchronously - * \ingroup pico_lwip - * - * Perform the http request asynchronously - * - * @param context async context - * @param req HTTP request parameters. As a minimum this should be initialised to zero with hostname and url set to valid values - * @return If zero is returned the request has been made and is complete when \em req->complete is true or the result callback has been called. - * A non-zero return value indicates an error. - * - * @see async_context - */ -int http_client_request_async(struct async_context *context, HTTP_REQUEST_T *req); - -/*! \brief Perform a http request synchronously - * \ingroup pico_lwip - * - * Perform the http request synchronously - * - * @param context async context - * @param req HTTP request parameters. As a minimum this should be initialised to zero with hostname and url set to valid values - * @param result Returns the overall result of the http request when complete. Zero indicates success. - */ -int http_client_request_sync(struct async_context *context, HTTP_REQUEST_T *req); - -/*! \brief A http header callback that can be passed to \em http_client_init or \em http_client_init_secure - * \ingroup pico_http_client - * - * An implementation of the http header callback which just prints headers to stdout - * - * @param arg argument specified on initialisation - * @param hdr header pbuf(s) (may contain data also) - * @param hdr_len length of the headers in 'hdr' - * @param content_len content length as received in the headers (-1 if not received) - * @return if != zero is returned, the connection is aborted - */ -err_t http_client_header_print_fn(httpc_state_t *connection, void *arg, struct pbuf *hdr, u16_t hdr_len, u32_t content_len); - -/*! \brief A http recv callback that can be passed to http_client_init or http_client_init_secure - * \ingroup pico_http_client - * - * An implementation of the http recv callback which just prints the http body to stdout - * - * @param arg argument specified on initialisation - * @param conn http client connection - * @param p body pbuf(s) - * @param err Error code in the case of an error - * @return if != zero is returned, the connection is aborted - */ -err_t http_client_receive_print_fn(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err); - -#endif diff --git a/lwipopts.h b/lwipopts.h index d1243ab..db49f95 100644 --- a/lwipopts.h +++ b/lwipopts.h @@ -237,11 +237,19 @@ hardware: #define SZT_F "u" #endif +#define LWIP_DNS 1 + #if (LWIP_DNS || LWIP_IGMP || LWIP_IPV6) && !defined(LWIP_RAND) /* When using IGMP or IPv6, LWIP_RAND() needs to be defined to a random-function * returning an u32_t random value*/ #include "lwip/arch.h" -u32_t lwip_rand(void); +#ifdef __cplusplus +extern "C" { +#endif + u32_t lwip_rand(void); +#ifdef __cplusplus +} +#endif #define LWIP_RAND() lwip_rand() #endif @@ -255,6 +263,8 @@ void sync_system_time(unsigned int sec, unsigned int usec); } #endif + + #endif /* __LWIPOPTS_H__ */ /*****END OF FILE****/ diff --git a/net_utils.cpp b/net_utils.cpp new file mode 100644 index 0000000..52c87c6 --- /dev/null +++ b/net_utils.cpp @@ -0,0 +1,67 @@ +#include "hardware/clocks.h" +#include "lwip/ip_addr.h" +#include "nlohmann/json.hpp" +#include "pico/cyw43_arch.h" +#include "pico/util/datetime.h" +#include +#include "lwipopts.h" + +#include "lwip/dns.h" +#include "lwip/sys.h" +#include "lwip/err.h" + + +#include "lwip/timeouts.h" + +typedef struct { + volatile bool done; + ip_addr_t addr; + bool success; +} dns_blocking_result_t; + +static void blocking_dns_callback(const char *name, const ip_addr_t *ipaddr, void *arg) { + dns_blocking_result_t *result = (dns_blocking_result_t *)arg; + if (ipaddr != NULL) { + result->addr = *ipaddr; + result->success = true; + } else { + result->success = false; + } + result->done = true; +} + +// Function to synchronously resolve DNS hostname to IP +// Returns true if resolved, false otherwise +bool dns_resolve_sync(const char *hostname, ip_addr_t *resolved_ip, uint32_t timeout_ms) { + dns_blocking_result_t result = {0}; + uint32_t start_time = sys_now(); + + err_t err = dns_gethostbyname(hostname, &result.addr, blocking_dns_callback, &result); + + if (err == ERR_OK) { + // Already resolved from cache + *resolved_ip = result.addr; + return true; + } else if (err == ERR_INPROGRESS) { + // Wait for resolution with timeout + while (!result.done) { + if (timeout_ms > 0 && (sys_now() - start_time) > timeout_ms) { + return false; // Timeout + } + sys_check_timeouts(); + // Sleep or delay depends on your platform + sys_msleep(10); + } + + if (result.success) { + *resolved_ip = result.addr; + return true; + } + } + + return false; // Failed +} + +extern "C" u32_t lwip_rand(void) { + return get_rand_32(); +} diff --git a/net_utils.h b/net_utils.h new file mode 100644 index 0000000..00fee71 --- /dev/null +++ b/net_utils.h @@ -0,0 +1,6 @@ +#ifndef SMART_ALARM_NET_UTILS_H +#define SMART_ALARM_NET_UTILS_H +#include "lwip/ip_addr.h" + +bool dns_resolve_sync(const char *hostname, ip_addr_t *resolved_ip, uint32_t timeout_ms); +#endif //SMART_ALARM_NET_UTILS_H \ No newline at end of file diff --git a/smart_alarm.cpp b/smart_alarm.cpp index 70aeb27..5a9d504 100644 --- a/smart_alarm.cpp +++ b/smart_alarm.cpp @@ -2,20 +2,22 @@ #include "hardware/rtc.h" #include "hardware/spi.h" #include "hardware/timer.h" -#include "lwip/apps/http_client.h" #include "lwip/apps/sntp.h" #include "lwip/err.h" #include "lwip/ip_addr.h" #include "nlohmann/json.hpp" -#include "pico/async_context.h" #include "pico/cyw43_arch.h" #include "pico/stdlib.h" #include "pico/util/datetime.h" #include "wifi_conf.h" #include #include -#include -#include + +#include "net_utils.h" +#include "lwip/dns.h" +#include "lwip/tcp.h" + +using nlohmann::json; // SPI Defines // We are going to use SPI 0, and allocate it to the following GPIO pins @@ -27,17 +29,153 @@ #define PIN_SCK 18 #define PIN_MOSI 19 -err_t get_timezone_offset() { +//constexpr char* timezone_api_host = "worldtimeapi.org"; +//constexpr char* timezone_request = "GET /api/ip HTTP/1.1\r\nHost: worldtimeapi.org\r\nUser-Agent: smart_clock/0.1.0\r\nAccept: */*\r\n\r\n"; +constexpr char* timezone_api_host = "example.org"; +constexpr char* timezone_request = "GET / HTTP/1.1\r\nHost: example.org\r\nUser-Agent: curl/8.15.0\r\nAccept: */*\r\n\r\n"; +static volatile bool request_complete = false; +static volatile bool request_error = false; +static char buffer[1024]; + +static err_t connected_callback(void *arg, struct tcp_pcb *tpcb, err_t err) { + if (err == ERR_OK) { + printf("Connected to server successfully!\n"); + } else { + printf("Connection failed with error: %d\n", err); + } + return ERR_OK; } -#define TIMEZONE_OFFSET_SEC 7200 + +err_t set_timezone_offset(void *arg, struct altcp_pcb *conn, struct pbuf *p, err_t err) { + printf("\ncontent err %d\n", err); + + if (err != ERR_OK || p == NULL) { + return err; + } + + u16_t offset = 0; + while (offset < p->tot_len) { + char c = (char)pbuf_get_at(p, offset++); + putchar(c); + } + + pbuf_free(p); // Important: Free the pbuf + return ERR_OK; +} + +err_t timezone_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { + extern volatile bool request_complete, request_error; + u16_t *offset = static_cast(arg); + + if (p == NULL) { + printf("Connection closed by remote host\n"); + request_complete = true; + return ERR_OK; + } + + if (err == ERR_OK) { + printf("Received %u bytes: ", p->tot_len); + pbuf_copy_partial(p, buffer, p->tot_len, 0); + buffer[p->tot_len] = '\0'; + + try { + json response = json::parse(buffer); + *offset = response["raw_offset"].get() + response["dst_offset"].get(); + request_complete = true; + } catch (const std::exception& e) { + printf("JSON parse error: %s\n", e.what()); + request_error = true; + } + + tcp_recved(tpcb, p->tot_len); + } else { + request_error = true; + } + + pbuf_free(p); + return ERR_OK; +} + +void timezone_err(void *arg, const err_t err) { + extern volatile bool request_error; + printf("timezone_err: %d:%s\n", err, lwip_strerr(err)); + request_error = true; +} + + + +std::tuple get_timezone_offset() { + printf("getting timezone offset\n"); + tcp_pcb *pcb = tcp_new(); + tcp_recv(pcb, timezone_recv); + tcp_err(pcb, timezone_err); + static u16_t offset = 0; + + // Reset flags for new request + request_complete = false; + request_error = false; + offset = 0; + + tcp_arg(pcb, &offset); + ip_addr_t server_ip; + dns_resolve_sync(timezone_api_host, &server_ip, 5000); + + if (tcp_connect(pcb, &server_ip, 80, connected_callback) == ERR_OK) { + tcp_write(pcb, timezone_request, strlen(timezone_request), TCP_WRITE_FLAG_COPY); + tcp_output(pcb); + + // Wait until request completes or errors out + uint32_t timeout_ms = 10000; // 10 second timeout + uint32_t elapsed_ms = 0; + const uint32_t poll_interval = 100; // Check every 100ms + + while (!request_complete && !request_error && elapsed_ms < timeout_ms) { + sleep_ms(poll_interval); + elapsed_ms += poll_interval; + } + + tcp_close(pcb); + + if (request_error) { + printf("Request failed due to error\n"); + return {0,ERR_ABRT}; + } else if (elapsed_ms >= timeout_ms) { + printf("Request timed out\n"); + return {0,ERR_TIMEOUT}; + } + } else { + printf("Failed to connect\n"); + tcp_close(pcb); + return {0,ERR_ABRT}; + } + + return {offset,ERR_OK}; +} + + + extern "C" void sync_system_time(unsigned int sec, unsigned int usec) { printf("SNTP callback received: %lu\n", (unsigned long)sec); time_t unix_time = (time_t)sec; + u16_t timezone_offset_sec = []()->u16_t + { + while (true) + { + auto [offset,err] = get_timezone_offset(); + if (err == ERR_OK) + { + return offset; + } + printf("failed to get timezone offset, retrying\n"); + } + + }(); + // Apply timezone offset for local time - unix_time += TIMEZONE_OFFSET_SEC; + unix_time += timezone_offset_sec; struct tm *time_info = gmtime(&unix_time); if (time_info == NULL) { @@ -64,7 +202,7 @@ extern "C" void sync_system_time(unsigned int sec, unsigned int usec) { if (rtc_set_datetime(&dt)) { printf("SNTP: Time set to %04d-%02d-%02d %02d:%02d:%02d %s\n", dt.year, dt.month, dt.day, dt.hour, dt.min, dt.sec, - (TIMEZONE_OFFSET_SEC == 0) ? "UTC" : "Local"); + (timezone_offset_sec == 0) ? "UTC" : "Local"); } else { printf("SNTP: Failed to set RTC\n"); } @@ -103,6 +241,13 @@ int main() { printf("Mask: %s\n", ip4addr_ntoa(netif_ip_netmask4(netif_default))); printf("Gateway: %s\n", ip4addr_ntoa(netif_ip_gw4(netif_default))); + ip_addr_t dns_server; + IP_ADDR4(&dns_server, 1, 1, 1, 1); + dns_setserver(0, &dns_server); + + IP_ADDR4(&dns_server, 8, 8, 8, 8); + dns_setserver(1, &dns_server); + printf("Initializing SNTP\n"); sntp_setoperatingmode(SNTP_OPMODE_POLL); sntp_init(); @@ -134,5 +279,6 @@ get_timezone_offset(); printf("failed to get rtc\n"); } } + cyw43_arch_lwip_end(); return 0; }