From 0fd2264616a3ee5683f3c6db0353caa33b72ca8a Mon Sep 17 00:00:00 2001 From: Ned Konz Date: Fri, 3 Oct 2025 19:37:21 -0700 Subject: [PATCH] zephyr: Add network module with WLAN/LAN configuration support. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add new network module providing standard MicroPython network API for configuring WiFi and Ethernet interfaces on Zephyr RTOS. This enables interface activation, connection management, and IP configuration. Features: - network.WLAN class for WiFi configuration (connect, disconnect, status) - network.LAN class for Ethernet configuration - ifconfig() method returns IP, netmask, gateway, and DNS server - Event-driven connection status tracking via Zephyr net_mgmt - DHCP support with fallback DNS configuration Files modified: - ports/zephyr/modnetwork.c (new) - ports/zephyr/modsocket.c (major refactoring) - ports/zephyr/mpconfigport.h - ports/zephyr/CMakeLists.txt - ports/zephyr/main.c - ports/zephyr/prj.conf 🤖 Generated with the help of [Claude Code](https://claude.com/claude-code) Signed-off-by: Ned Konz --- ports/zephyr/CMakeLists.txt | 2 + ports/zephyr/main.c | 5 + ports/zephyr/modnetwork.c | 548 ++++++++++++++++++++++++++++++++++++ ports/zephyr/modsocket.c | 103 +++++-- ports/zephyr/mpconfigport.h | 2 + ports/zephyr/prj.conf | 8 +- 6 files changed, 637 insertions(+), 31 deletions(-) create mode 100644 ports/zephyr/modnetwork.c diff --git a/ports/zephyr/CMakeLists.txt b/ports/zephyr/CMakeLists.txt index e7a55d804ae..2e48f3f503b 100644 --- a/ports/zephyr/CMakeLists.txt +++ b/ports/zephyr/CMakeLists.txt @@ -48,6 +48,7 @@ set(MICROPY_SOURCE_PORT machine_pin.c machine_timer.c modbluetooth_zephyr.c + modnetwork.c modsocket.c modzephyr.c modzsensor.c @@ -61,6 +62,7 @@ list(TRANSFORM MICROPY_SOURCE_PORT PREPEND ${MICROPY_PORT_DIR}/) set(MICROPY_SOURCE_SHARED libc/printf.c + netutils/netutils.c readline/readline.c runtime/gchelper_generic.c runtime/interrupt_char.c diff --git a/ports/zephyr/main.c b/ports/zephyr/main.c index f7ac997d92a..2af3e87c980 100644 --- a/ports/zephyr/main.c +++ b/ports/zephyr/main.c @@ -160,6 +160,11 @@ int real_main(void) { vfs_init(); #endif + #if MICROPY_PY_ZEPHYR_NETWORK + extern void mod_network_init(void); + mod_network_init(); + #endif + #if MICROPY_MODULE_FROZEN || MICROPY_VFS // Execute user scripts. int ret = pyexec_file_if_exists("boot.py"); diff --git a/ports/zephyr/modnetwork.c b/ports/zephyr/modnetwork.c new file mode 100644 index 00000000000..a3b13660cec --- /dev/null +++ b/ports/zephyr/modnetwork.c @@ -0,0 +1,548 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2025 Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" + +#if MICROPY_PY_ZEPHYR_NETWORK + +#include "shared/netutils/netutils.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_WIFI +#include +#endif + +#ifdef CONFIG_NET_L2_ETHERNET +#include +#endif + +// Network interface types +#define NET_IF_TYPE_WLAN 0 +#define NET_IF_TYPE_LAN 1 + +// Connection status +#define NET_STATUS_IDLE 0 +#define NET_STATUS_CONNECTING 1 +#define NET_STATUS_CONNECTED 2 +#define NET_STATUS_DISCONNECTED 3 +#define NET_STATUS_GOT_IP 4 + +typedef struct _network_if_obj_t { + mp_obj_base_t base; + struct net_if *iface; + uint8_t if_type; + bool active; + uint8_t status; +} network_if_obj_t; + +static network_if_obj_t network_wlan_obj = { + .base = {NULL}, // Will be set during type initialization + .iface = NULL, + .if_type = NET_IF_TYPE_WLAN, + .active = false, + .status = NET_STATUS_IDLE +}; + +static network_if_obj_t network_lan_obj = { + .base = {NULL}, // Will be set during type initialization + .iface = NULL, + .if_type = NET_IF_TYPE_LAN, + .active = false, + .status = NET_STATUS_IDLE +}; + +// Forward declarations +static const mp_obj_type_t network_wlan_type; +static const mp_obj_type_t network_lan_type; + +// Event handler for network events +static struct net_mgmt_event_callback net_mgmt_cb; + +static void network_event_handler(struct net_mgmt_event_callback *cb, + uint64_t mgmt_event, struct net_if *iface) { + #ifdef CONFIG_WIFI + if (mgmt_event == NET_EVENT_WIFI_CONNECT_RESULT) { + network_wlan_obj.status = NET_STATUS_CONNECTED; + } else if (mgmt_event == NET_EVENT_WIFI_DISCONNECT_RESULT) { + network_wlan_obj.status = NET_STATUS_DISCONNECTED; + } + #endif + + if (mgmt_event == NET_EVENT_IPV4_ADDR_ADD) { + if (network_wlan_obj.iface == iface) { + network_wlan_obj.status = NET_STATUS_GOT_IP; + } else if (network_lan_obj.iface == iface) { + network_lan_obj.status = NET_STATUS_GOT_IP; + } + } +} + +void mod_network_init(void) { + // Initialize event callback for network events + net_mgmt_init_event_callback(&net_mgmt_cb, network_event_handler, + NET_EVENT_IPV4_ADDR_ADD | NET_EVENT_IPV4_ADDR_DEL + #ifdef CONFIG_WIFI + | NET_EVENT_WIFI_CONNECT_RESULT | NET_EVENT_WIFI_DISCONNECT_RESULT + #endif + ); + net_mgmt_add_event_callback(&net_mgmt_cb); +} + +// Helper to find network interface by type +static struct net_if *find_iface_by_type(int if_type) { + struct net_if *iface = NULL; + + #ifdef CONFIG_WIFI + if (if_type == NET_IF_TYPE_WLAN) { + // Find WiFi interface + iface = net_if_get_first_wifi(); + } + #endif + + #ifdef CONFIG_NET_L2_ETHERNET + if (if_type == NET_IF_TYPE_LAN) { + // Find Ethernet interface + for (int i = 1; iface == NULL && i <= 10; i++) { + struct net_if *test_iface = net_if_get_by_index(i); + if (test_iface && net_if_l2(test_iface) == &NET_L2_GET_NAME(ETHERNET)) { + iface = test_iface; + break; + } + } + } + #endif + + return iface; +} + +// WLAN class implementation +#ifdef CONFIG_WIFI + +static mp_obj_t network_wlan_make_new(const mp_obj_type_t *type, size_t n_args, + size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + + network_if_obj_t *self = &network_wlan_obj; + self->base.type = &network_wlan_type; + + // Find WiFi interface if not already set + if (self->iface == NULL) { + self->iface = find_iface_by_type(NET_IF_TYPE_WLAN); + } + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t network_wlan_active(size_t n_args, const mp_obj_t *args) { + network_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + // Get active status + return mp_obj_new_bool(self->active && self->iface != NULL); + } else { + // Set active status + bool active = mp_obj_is_true(args[1]); + + if (active && !self->active) { + // Activate interface + if (self->iface == NULL) { + self->iface = find_iface_by_type(NET_IF_TYPE_WLAN); + } + if (self->iface == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("WiFi interface not found")); + } + net_if_up(self->iface); + self->active = true; + } else if (!active && self->active) { + // Deactivate interface + if (self->iface) { + net_if_down(self->iface); + } + self->active = false; + self->status = NET_STATUS_IDLE; + } + + return mp_const_none; + } +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_active_obj, 1, 2, network_wlan_active); + +static mp_obj_t network_wlan_connect(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + enum { ARG_ssid, ARG_password }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_ssid, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} }, + { MP_QSTR_password, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + + network_if_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]); + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + if (!self->active || self->iface == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("WiFi not active")); + } + + const char *ssid = mp_obj_str_get_str(args[ARG_ssid].u_obj); + const char *password = args[ARG_password].u_obj != mp_const_none ? + mp_obj_str_get_str(args[ARG_password].u_obj) : NULL; + + struct wifi_connect_req_params params = {0}; + params.ssid = (uint8_t *)ssid; + params.ssid_length = strlen(ssid); + params.channel = WIFI_CHANNEL_ANY; + params.security = password ? WIFI_SECURITY_TYPE_PSK : WIFI_SECURITY_TYPE_NONE; + + if (password) { + params.psk = (uint8_t *)password; + params.psk_length = strlen(password); + } + + self->status = NET_STATUS_CONNECTING; + + int ret = net_mgmt(NET_REQUEST_WIFI_CONNECT, self->iface, ¶ms, sizeof(params)); + if (ret < 0) { + self->status = NET_STATUS_IDLE; + mp_raise_OSError(-ret); + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_KW(network_wlan_connect_obj, 1, network_wlan_connect); + +static mp_obj_t network_wlan_disconnect(mp_obj_t self_in) { + network_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (self->iface) { + int ret = net_mgmt(NET_REQUEST_WIFI_DISCONNECT, self->iface, NULL, 0); + if (ret < 0) { + mp_raise_OSError(-ret); + } + self->status = NET_STATUS_DISCONNECTED; + } + + return mp_const_none; +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_disconnect_obj, network_wlan_disconnect); + +static mp_obj_t network_wlan_isconnected(mp_obj_t self_in) { + network_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_bool(self->status == NET_STATUS_CONNECTED || self->status == NET_STATUS_GOT_IP); +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_wlan_isconnected_obj, network_wlan_isconnected); + +static mp_obj_t network_wlan_status(size_t n_args, const mp_obj_t *args) { + network_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + // Return connection status + return MP_OBJ_NEW_SMALL_INT(self->status); + } + + // Query specific parameter - to be implemented + mp_raise_ValueError(MP_ERROR_TEXT("unknown status param")); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_status_obj, 1, 2, network_wlan_status); + +static mp_obj_t network_wlan_ifconfig(size_t n_args, const mp_obj_t *args) { + network_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (!self->iface) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("interface not available")); + } + + if (n_args == 1) { + // Get IP configuration + struct net_if_config *cfg = net_if_get_config(self->iface); + if (!cfg || !cfg->ip.ipv4) { + return mp_const_none; + } + + // Get IP address + struct in_addr *addr = &cfg->ip.ipv4->unicast[0].ipv4.address.in_addr; + + // Get netmask + struct in_addr netmask = net_if_ipv4_get_netmask_by_addr(self->iface, addr); + + // Get gateway + struct in_addr gw = net_if_ipv4_get_gw(self->iface); + + // Get DNS server + // Note: If you see a static DNS (like 192.0.2.2) instead of your DHCP DNS, + // comment out CONFIG_DNS_SERVER1 in prj.conf or board.conf to use DHCP-provided DNS + const struct dns_resolve_context *dns_ctx = dns_resolve_get_default(); + struct in_addr dns = {0}; + bool found_dhcp_dns = false; + + if (dns_ctx) { + // First pass: look for DHCP-provided DNS servers only + for (int i = 0; i < DNS_RESOLVER_MAX_SERVERS; i++) { + if (dns_ctx->servers[i].dns_server.sa_family == AF_INET && + dns_ctx->servers[i].source == DNS_SOURCE_DHCPV4) { + struct sockaddr_in *dns_addr = (struct sockaddr_in *)&dns_ctx->servers[i].dns_server; + dns = dns_addr->sin_addr; + found_dhcp_dns = true; + break; + } + } + + // Second pass: if no DHCP DNS, fall back to any configured DNS + if (!found_dhcp_dns) { + for (int i = 0; i < DNS_RESOLVER_MAX_SERVERS; i++) { + if (dns_ctx->servers[i].dns_server.sa_family == AF_INET) { + struct sockaddr_in *dns_addr = (struct sockaddr_in *)&dns_ctx->servers[i].dns_server; + dns = dns_addr->sin_addr; + break; + } + } + } + } + + mp_obj_t tuple[4] = { + netutils_format_ipv4_addr((uint8_t *)addr, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&netmask, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&gw, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&dns, NETUTILS_BIG), + }; + + return mp_obj_new_tuple(4, tuple); + } else { + // Set IP configuration - to be implemented + mp_raise_NotImplementedError(MP_ERROR_TEXT("ifconfig set not implemented")); + } +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_wlan_ifconfig_obj, 1, 2, network_wlan_ifconfig); + +static const mp_rom_map_elem_t network_wlan_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&network_wlan_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_connect), MP_ROM_PTR(&network_wlan_connect_obj) }, + { MP_ROM_QSTR(MP_QSTR_disconnect), MP_ROM_PTR(&network_wlan_disconnect_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_wlan_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_status), MP_ROM_PTR(&network_wlan_status_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_wlan_ifconfig_obj) }, +}; +static MP_DEFINE_CONST_DICT(network_wlan_locals_dict, network_wlan_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + network_wlan_type, + MP_QSTR_WLAN, + MP_TYPE_FLAG_NONE, + make_new, network_wlan_make_new, + locals_dict, &network_wlan_locals_dict + ); + +#endif // CONFIG_WIFI + +// LAN class implementation +#ifdef CONFIG_NET_L2_ETHERNET + +static mp_obj_t network_lan_make_new(const mp_obj_type_t *type, size_t n_args, + size_t n_kw, const mp_obj_t *args) { + mp_arg_check_num(n_args, n_kw, 0, 1, false); + + network_if_obj_t *self = &network_lan_obj; + self->base.type = &network_lan_type; + + // Find Ethernet interface if not already set + if (self->iface == NULL) { + self->iface = find_iface_by_type(NET_IF_TYPE_LAN); + } + + return MP_OBJ_FROM_PTR(self); +} + +static mp_obj_t network_lan_active(size_t n_args, const mp_obj_t *args) { + network_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (n_args == 1) { + // Get active status + return mp_obj_new_bool(self->active && self->iface != NULL); + } else { + // Set active status + bool active = mp_obj_is_true(args[1]); + + if (active && !self->active) { + // Activate interface + if (self->iface == NULL) { + self->iface = find_iface_by_type(NET_IF_TYPE_LAN); + } + if (self->iface == NULL) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("Ethernet interface not found")); + } + net_if_up(self->iface); + self->active = true; + } else if (!active && self->active) { + // Deactivate interface + if (self->iface) { + net_if_down(self->iface); + } + self->active = false; + self->status = NET_STATUS_IDLE; + } + + return mp_const_none; + } +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_lan_active_obj, 1, 2, network_lan_active); + +static mp_obj_t network_lan_isconnected(mp_obj_t self_in) { + network_if_obj_t *self = MP_OBJ_TO_PTR(self_in); + + if (!self->iface) { + return mp_const_false; + } + + // Check if interface has link + return mp_obj_new_bool(net_if_is_up(self->iface)); +} +static MP_DEFINE_CONST_FUN_OBJ_1(network_lan_isconnected_obj, network_lan_isconnected); + +static mp_obj_t network_lan_ifconfig(size_t n_args, const mp_obj_t *args) { + network_if_obj_t *self = MP_OBJ_TO_PTR(args[0]); + + if (!self->iface) { + mp_raise_msg(&mp_type_OSError, MP_ERROR_TEXT("interface not available")); + } + + if (n_args == 1) { + // Get IP configuration + struct net_if_config *cfg = net_if_get_config(self->iface); + if (!cfg || !cfg->ip.ipv4) { + return mp_const_none; + } + + // Get IP address + struct in_addr *addr = &cfg->ip.ipv4->unicast[0].ipv4.address.in_addr; + + // Get netmask + struct in_addr netmask = net_if_ipv4_get_netmask_by_addr(self->iface, addr); + + // Get gateway + struct in_addr gw = net_if_ipv4_get_gw(self->iface); + + // Get DNS server + // Note: If you see a static DNS (like 192.0.2.2) instead of your DHCP DNS, + // comment out CONFIG_DNS_SERVER1 in prj.conf or board.conf to use DHCP-provided DNS + const struct dns_resolve_context *dns_ctx = dns_resolve_get_default(); + struct in_addr dns = {0}; + bool found_dhcp_dns = false; + + if (dns_ctx) { + // First pass: look for DHCP-provided DNS servers only + for (int i = 0; i < DNS_RESOLVER_MAX_SERVERS; i++) { + if (dns_ctx->servers[i].dns_server.sa_family == AF_INET && + dns_ctx->servers[i].source == DNS_SOURCE_DHCPV4) { + struct sockaddr_in *dns_addr = (struct sockaddr_in *)&dns_ctx->servers[i].dns_server; + dns = dns_addr->sin_addr; + found_dhcp_dns = true; + break; + } + } + + // Second pass: if no DHCP DNS, fall back to any configured DNS + if (!found_dhcp_dns) { + for (int i = 0; i < DNS_RESOLVER_MAX_SERVERS; i++) { + if (dns_ctx->servers[i].dns_server.sa_family == AF_INET) { + struct sockaddr_in *dns_addr = (struct sockaddr_in *)&dns_ctx->servers[i].dns_server; + dns = dns_addr->sin_addr; + break; + } + } + } + } + + mp_obj_t tuple[4] = { + netutils_format_ipv4_addr((uint8_t *)addr, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&netmask, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&gw, NETUTILS_BIG), + netutils_format_ipv4_addr((uint8_t *)&dns, NETUTILS_BIG), + }; + + return mp_obj_new_tuple(4, tuple); + } else { + // Set IP configuration - to be implemented + mp_raise_NotImplementedError(MP_ERROR_TEXT("ifconfig set not implemented")); + } +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_lan_ifconfig_obj, 1, 2, network_lan_ifconfig); + +static const mp_rom_map_elem_t network_lan_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_active), MP_ROM_PTR(&network_lan_active_obj) }, + { MP_ROM_QSTR(MP_QSTR_isconnected), MP_ROM_PTR(&network_lan_isconnected_obj) }, + { MP_ROM_QSTR(MP_QSTR_ifconfig), MP_ROM_PTR(&network_lan_ifconfig_obj) }, +}; +static MP_DEFINE_CONST_DICT(network_lan_locals_dict, network_lan_locals_dict_table); + +static MP_DEFINE_CONST_OBJ_TYPE( + network_lan_type, + MP_QSTR_LAN, + MP_TYPE_FLAG_NONE, + make_new, network_lan_make_new, + locals_dict, &network_lan_locals_dict + ); + +#endif // CONFIG_NET_L2_ETHERNET + +// Module globals +static const mp_rom_map_elem_t mp_module_network_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_network) }, + + #ifdef CONFIG_WIFI + { MP_ROM_QSTR(MP_QSTR_WLAN), MP_ROM_PTR(&network_wlan_type) }, + #endif + + #ifdef CONFIG_NET_L2_ETHERNET + { MP_ROM_QSTR(MP_QSTR_LAN), MP_ROM_PTR(&network_lan_type) }, + #endif + + // Interface type constants + { MP_ROM_QSTR(MP_QSTR_STA_IF), MP_ROM_INT(0) }, + { MP_ROM_QSTR(MP_QSTR_AP_IF), MP_ROM_INT(1) }, + + // Status constants + { MP_ROM_QSTR(MP_QSTR_STAT_IDLE), MP_ROM_INT(NET_STATUS_IDLE) }, + { MP_ROM_QSTR(MP_QSTR_STAT_CONNECTING), MP_ROM_INT(NET_STATUS_CONNECTING) }, + { MP_ROM_QSTR(MP_QSTR_STAT_CONNECTED), MP_ROM_INT(NET_STATUS_CONNECTED) }, + { MP_ROM_QSTR(MP_QSTR_STAT_DISCONNECTED), MP_ROM_INT(NET_STATUS_DISCONNECTED) }, + { MP_ROM_QSTR(MP_QSTR_STAT_GOT_IP), MP_ROM_INT(NET_STATUS_GOT_IP) }, +}; +static MP_DEFINE_CONST_DICT(mp_module_network_globals, mp_module_network_globals_table); + +const mp_obj_module_t mp_module_network = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t *)&mp_module_network_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR_network, mp_module_network); + +#endif // MICROPY_PY_ZEPHYR_NETWORK diff --git a/ports/zephyr/modsocket.c b/ports/zephyr/modsocket.c index d8955bffbe4..9b706083b36 100644 --- a/ports/zephyr/modsocket.c +++ b/ports/zephyr/modsocket.c @@ -31,6 +31,7 @@ #include "py/stream.h" #include +#include #include #include #include @@ -84,21 +85,27 @@ static void parse_inet_addr(socket_obj_t *socket, mp_obj_t addr_in, struct socka } static mp_obj_t format_inet_addr(struct sockaddr *addr, mp_obj_t port) { - // We employ the fact that port and address offsets are the same for IPv4 & IPv6 - struct sockaddr_in6 *sockaddr_in6 = (struct sockaddr_in6 *)addr; char buf[40]; - net_addr_ntop(addr->sa_family, &sockaddr_in6->sin6_addr, buf, sizeof(buf)); - mp_obj_tuple_t *tuple = mp_obj_new_tuple(addr->sa_family == AF_INET ? 2 : 4, NULL); - - tuple->items[0] = mp_obj_new_str_from_cstr(buf); - // We employ the fact that port offset is the same for IPv4 & IPv6 - // not filled in - // tuple->items[1] = mp_obj_new_int(ntohs(((struct sockaddr_in*)addr)->sin_port)); - tuple->items[1] = port; - - if (addr->sa_family == AF_INET6) { + mp_obj_tuple_t *tuple; + + if (addr->sa_family == AF_INET) { + struct sockaddr_in addr_copy __attribute__((aligned(4))); + memcpy(&addr_copy, addr, sizeof(struct sockaddr_in)); + net_addr_ntop(AF_INET, &addr_copy.sin_addr, buf, sizeof(buf)); + tuple = mp_obj_new_tuple(2, NULL); + tuple->items[0] = mp_obj_new_str_from_cstr(buf); + tuple->items[1] = port; + } else if (addr->sa_family == AF_INET6) { + struct sockaddr_in6 addr_copy __attribute__((aligned(4))); + memcpy(&addr_copy, addr, sizeof(struct sockaddr_in6)); + net_addr_ntop(AF_INET6, &addr_copy.sin6_addr, buf, sizeof(buf)); + tuple = mp_obj_new_tuple(4, NULL); + tuple->items[0] = mp_obj_new_str_from_cstr(buf); + tuple->items[1] = port; tuple->items[2] = MP_OBJ_NEW_SMALL_INT(0); // flow_info - tuple->items[3] = MP_OBJ_NEW_SMALL_INT(sockaddr_in6->sin6_scope_id); + tuple->items[3] = MP_OBJ_NEW_SMALL_INT(addr_copy.sin6_scope_id); + } else { + mp_raise_OSError(MP_EINVAL); } return MP_OBJ_FROM_PTR(tuple); @@ -394,8 +401,16 @@ static MP_DEFINE_CONST_OBJ_TYPE( // getaddrinfo() implementation // +#define MAX_DNS_RESULTS 8 + +typedef struct _getaddrinfo_result_t { + sa_family_t family; + struct sockaddr_storage addr; +} getaddrinfo_result_t; + typedef struct _getaddrinfo_state_t { - mp_obj_t result; + getaddrinfo_result_t results[MAX_DNS_RESULTS]; + int num_results; struct k_sem sem; mp_obj_t port; int status; @@ -414,30 +429,40 @@ void dns_resolve_cb(enum dns_resolve_status status, struct dns_addrinfo *info, v return; } - mp_obj_tuple_t *tuple = mp_obj_new_tuple(5, NULL); - tuple->items[0] = MP_OBJ_NEW_SMALL_INT(info->ai_family); - // info->ai_socktype not filled - tuple->items[1] = MP_OBJ_NEW_SMALL_INT(SOCK_STREAM); - // info->ai_protocol not filled - tuple->items[2] = MP_OBJ_NEW_SMALL_INT(IPPROTO_TCP); - tuple->items[3] = MP_OBJ_NEW_QSTR(MP_QSTR_); - tuple->items[4] = format_inet_addr(&info->ai_addr, state->port); - mp_obj_list_append(state->result, MP_OBJ_FROM_PTR(tuple)); + // Don't allocate MicroPython objects here - we're in the network thread! + // Just store the raw address data + if (state->num_results < MAX_DNS_RESULTS) { + state->results[state->num_results].family = info->ai_family; + size_t addr_size = (info->ai_family == AF_INET) ? + sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6); + memcpy(&state->results[state->num_results].addr, &info->ai_addr, addr_size); + state->num_results++; + } } static mp_obj_t mod_getaddrinfo(size_t n_args, const mp_obj_t *args) { mp_obj_t host_in = args[0], port_in = args[1]; const char *host = mp_obj_str_get_str(host_in); mp_int_t family = 0; + mp_int_t socktype = 0; + mp_int_t proto = 0; + if (n_args > 2) { family = mp_obj_get_int(args[2]); } + if (n_args > 3) { + socktype = mp_obj_get_int(args[3]); + } + if (n_args > 4) { + proto = mp_obj_get_int(args[4]); + } + // args[5] (flags) is ignored getaddrinfo_state_t state; // Just validate that it's int (void)mp_obj_get_int(port_in); state.port = port_in; - state.result = mp_obj_new_list(0, NULL); + state.num_results = 0; k_sem_init(&state.sem, 0, UINT_MAX); for (int i = 2; i--;) { @@ -450,16 +475,34 @@ static mp_obj_t mod_getaddrinfo(size_t n_args, const mp_obj_t *args) { family = AF_INET6; } - // Raise error only if there's nothing to return, otherwise - // it may be IPv4 vs IPv6 differences. - mp_int_t len = MP_OBJ_SMALL_INT_VALUE(mp_obj_len(state.result)); - if (state.status != 0 && len == 0) { + // Raise error only if there's nothing to return + if (state.status != 0 && state.num_results == 0) { mp_raise_OSError(state.status); } - return state.result; + // Determine socktype and proto if not specified + if (socktype == 0) { + socktype = SOCK_STREAM; + } + if (proto == 0) { + proto = (socktype == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP; + } + + // Now build the result list on the MicroPython thread (safe to allocate here) + mp_obj_t result = mp_obj_new_list(0, NULL); + for (int i = 0; i < state.num_results; i++) { + mp_obj_tuple_t *tuple = mp_obj_new_tuple(5, NULL); + tuple->items[0] = MP_OBJ_NEW_SMALL_INT(state.results[i].family); + tuple->items[1] = MP_OBJ_NEW_SMALL_INT(socktype); + tuple->items[2] = MP_OBJ_NEW_SMALL_INT(proto); + tuple->items[3] = MP_OBJ_NEW_QSTR(MP_QSTR_); + tuple->items[4] = format_inet_addr((struct sockaddr *)&state.results[i].addr, state.port); + mp_obj_list_append(result, MP_OBJ_FROM_PTR(tuple)); + } + + return result; } -static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_getaddrinfo_obj, 2, 3, mod_getaddrinfo); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mod_getaddrinfo_obj, 2, 6, mod_getaddrinfo); static mp_obj_t pkt_get_info(void) { diff --git a/ports/zephyr/mpconfigport.h b/ports/zephyr/mpconfigport.h index 6f55b0d7880..47e654f2de0 100644 --- a/ports/zephyr/mpconfigport.h +++ b/ports/zephyr/mpconfigport.h @@ -89,6 +89,8 @@ // If we have networking, we likely want errno comfort #define MICROPY_PY_ERRNO (1) #define MICROPY_PY_SOCKET (1) +// Zephyr has its own network module - don't use extmod +#define MICROPY_PY_ZEPHYR_NETWORK (1) #endif #ifdef CONFIG_BT #define MICROPY_PY_BLUETOOTH (1) diff --git a/ports/zephyr/prj.conf b/ports/zephyr/prj.conf index 0939e226cfc..b56670fe4e4 100644 --- a/ports/zephyr/prj.conf +++ b/ports/zephyr/prj.conf @@ -34,13 +34,17 @@ CONFIG_NET_CONFIG_NEED_IPV4=y # DNS CONFIG_DNS_RESOLVER=y CONFIG_DNS_RESOLVER_ADDITIONAL_QUERIES=2 +# Allow multiple DNS servers (for both static fallback and DHCP) +CONFIG_DNS_RESOLVER_MAX_SERVERS=2 +# Fallback DNS - DHCP should override this CONFIG_DNS_SERVER_IP_ADDRESSES=y # Static IP addresses CONFIG_NET_CONFIG_MY_IPV6_ADDR="2001:db8::1" CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.0.2.1" CONFIG_NET_CONFIG_MY_IPV4_GW="192.0.2.2" -CONFIG_DNS_SERVER1="192.0.2.2" +# Fallback DNS server - DHCP should override this with your router's DNS +CONFIG_DNS_SERVER1="8.8.8.8" # DHCP configuration. Until DHCP address is assigned, # static configuration above is used instead. @@ -65,6 +69,8 @@ CONFIG_NET_BUF_POOL_USAGE=y # Uncomment to enable "INFO" level net_buf logging #CONFIG_NET_LOG=y +#CONFIG_NET_IPV4_LOG_LEVEL_DBG=y +#CONFIG_NET_SOCKETS_LOG_LEVEL_DBG=y #CONFIG_NET_DEBUG_NET_BUF=y # Change to 4 for "DEBUG" level #CONFIG_SYS_LOG_NET_LEVEL=3