From bd22c1d895c55278458e7528ccfb1e3d481f5014 Mon Sep 17 00:00:00 2001 From: Mikey Sklar Date: Sun, 13 Sep 2026 19:32:51 -0700 Subject: [PATCH] wifi: allow 5 GHz channels in Monitor The channel was clamped to 1..13, so dual-band radios could not monitor 5 GHz. Accept 1..165, matching start_scanning_networks(), and check the esp_wifi_set_channel() result: an invalid channel raises ValueError. The setter reuses the constructor's range check, dropping one message. While the station is associated the IDF refuses to move the radio. The monitor then stays on the station's channel and reports it, which is what the old code did without saying so. Fixes #11339 Co-Authored-By: Claude Fable 5.1 --- locale/circuitpython.pot | 4 ---- ports/espressif/common-hal/wifi/Monitor.c | 22 +++++++++++++++++++--- shared-bindings/wifi/Monitor.c | 19 ++++++++++++------- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index 15d859bf49f..153a283d067 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -4286,10 +4286,6 @@ msgstr "" msgid "%q must be a subclass of %q" msgstr "" -#: shared-bindings/wifi/Monitor.c -msgid "%q out of bounds" -msgstr "" - #: shared-bindings/wifi/Radio.c msgid "Invalid hex password" msgstr "" diff --git a/ports/espressif/common-hal/wifi/Monitor.c b/ports/espressif/common-hal/wifi/Monitor.c index 635ba7a5a67..f6d0e6e4d8f 100644 --- a/ports/espressif/common-hal/wifi/Monitor.c +++ b/ports/espressif/common-hal/wifi/Monitor.c @@ -9,6 +9,7 @@ #include "py/mpstate.h" #include "py/runtime.h" +#include "bindings/espidf/__init__.h" #include "shared-bindings/wifi/Monitor.h" #include "shared-bindings/wifi/Packet.h" @@ -57,9 +58,26 @@ static void wifi_monitor_cb(void *recv_buf, wifi_promiscuous_pkt_type_t type) { } } +// Tune the radio and return the channel it is on afterwards. Raises +// ValueError for a channel this radio cannot use. Any other failure, for +// example while the station is associated, leaves the radio where it was. +static uint8_t set_channel(uint8_t channel) { + esp_err_t result = esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + if (result == ESP_ERR_INVALID_ARG) { + mp_arg_error_invalid(MP_QSTR_channel); + } else if (result != ESP_OK) { + wifi_second_chan_t second; + CHECK_ESP_RESULT(esp_wifi_get_channel(&channel, &second)); + } + return channel; +} + void common_hal_wifi_monitor_construct(wifi_monitor_obj_t *self, uint8_t channel, size_t queue) { mp_rom_error_text_t monitor_mode_init_error = MP_ERROR_TEXT("monitor init failed"); + // Before anything needs undoing on failure. + channel = set_channel(channel); + self->queue = xQueueCreate(queue, sizeof(monitor_packet_t)); if (!self->queue) { mp_raise_RuntimeError(monitor_mode_init_error); @@ -74,7 +92,6 @@ void common_hal_wifi_monitor_construct(wifi_monitor_obj_t *self, uint8_t channel if (esp_wifi_set_promiscuous(true) != ESP_OK) { mp_raise_RuntimeError(monitor_mode_init_error); } - esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); self->channel = channel; self->queue_length = queue; @@ -105,8 +122,7 @@ void common_hal_wifi_monitor_deinit(wifi_monitor_obj_t *self) { } void common_hal_wifi_monitor_set_channel(wifi_monitor_obj_t *self, uint8_t channel) { - self->channel = channel; - esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + self->channel = set_channel(channel); } mp_obj_t common_hal_wifi_monitor_get_channel(wifi_monitor_obj_t *self) { diff --git a/shared-bindings/wifi/Monitor.c b/shared-bindings/wifi/Monitor.c index fb09c4b4f77..9350a160a07 100644 --- a/shared-bindings/wifi/Monitor.c +++ b/shared-bindings/wifi/Monitor.c @@ -19,7 +19,11 @@ //| def __init__(self, channel: Optional[int] = 1, queue: Optional[int] = 128) -> None: //| """Initialize `wifi.Monitor` singleton. //| -//| :param int channel: The WiFi channel to scan. +//| :param int channel: The WiFi channel to scan. On dual-band radios, +//| 5 GHz channels (36 and above) may also be given. A channel the +//| radio does not support raises `ValueError`. While the station is +//| connected the radio stays on the station's channel, and +//| `channel` reports that channel. //| :param int queue: The queue size for buffering the packet. //| //| """ @@ -35,7 +39,9 @@ static mp_obj_t wifi_monitor_make_new(const mp_obj_type_t *type, size_t n_args, mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - mp_int_t channel = mp_arg_validate_int_range(args[ARG_channel].u_int, 1, 13, MP_QSTR_channel); + // 165 is the highest channel in the scan pattern. Channels the radio + // doesn't support are rejected by the port rather than here. + mp_int_t channel = mp_arg_validate_int_range(args[ARG_channel].u_int, 1, 165, MP_QSTR_channel); mp_int_t queue = mp_arg_validate_int_min(args[ARG_queue].u_int, 0, MP_QSTR_queue); wifi_monitor_obj_t *self = MP_STATE_VM(wifi_monitor_singleton); @@ -49,17 +55,16 @@ static mp_obj_t wifi_monitor_make_new(const mp_obj_type_t *type, size_t n_args, } //| channel: int -//| """The WiFi channel to scan.""" +//| """The WiFi channel to scan. A channel the radio does not support raises +//| `ValueError`. While the station is connected the radio stays on the +//| station's channel, and this reports that channel.""" static mp_obj_t wifi_monitor_obj_get_channel(mp_obj_t self_in) { return common_hal_wifi_monitor_get_channel(self_in); } MP_DEFINE_CONST_FUN_OBJ_1(wifi_monitor_get_channel_obj, wifi_monitor_obj_get_channel); static mp_obj_t wifi_monitor_obj_set_channel(mp_obj_t self_in, mp_obj_t channel) { - mp_int_t c = mp_obj_get_int(channel); - if (c < 1 || c > 13) { - mp_raise_ValueError_varg(MP_ERROR_TEXT("%q out of bounds"), MP_QSTR_channel); - } + mp_int_t c = mp_arg_validate_int_range(mp_obj_get_int(channel), 1, 165, MP_QSTR_channel); common_hal_wifi_monitor_set_channel(self_in, c); return mp_const_none; }