first request works

This commit is contained in:
PoliEcho 2025-09-12 13:08:14 +02:00
parent ed366c9638
commit fc24139f5d
7 changed files with 241 additions and 279 deletions

View File

@ -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

View File

@ -1,141 +0,0 @@
/**
* Copyright (c) 2023 Raspberry Pi (Trading) Ltd.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <string.h>
#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;
}

View File

@ -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

View File

@ -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****/

67
net_utils.cpp Normal file
View File

@ -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 <lwip/arch.h>
#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();
}

6
net_utils.h Normal file
View File

@ -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

View File

@ -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 <lwip/arch.h>
#include <pico/stdio.h>
#include <stdio.h>
#include <time.h>
#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<u16_t*>(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<int>() + response["dst_offset"].get<int>();
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<u16_t, err_t> 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;
}