Skip to content

fix(ble): drop the forced station switch before the ap scan - #762

Merged
shiliu-yang merged 1 commit into
tuya:masterfrom
KOKO-cyco:fix/ble-drop-sta-switch
Oct 26, 2026
Merged

shiliu-yang merged 1 commit into
tuya:masterfrom
KOKO-cyco:fix/ble-drop-sta-switch

Conversation

@KOKO-cyco

Copy link
Copy Markdown
Contributor

tal_wifi_all_ap_scan() no longer forces WWM_STATION first. Switching the radio tore down the soft AP the device was serving for provisioning; platforms that need it (ESP32) now switch to APSTA inside the TKL scan.

Stacked on #744.

Querying the wifi list switched the radio to station mode first. On BK
based platforms that stops the softap and tears down the ongoing ap
netcfg. The scan does not need the switch.

(cherry picked from commit 0e26266)
@KOKO-cyco
KOKO-cyco force-pushed the fix/ble-drop-sta-switch branch from 71305e5 to 982fd3f Compare October 22, 2026 02:47
@shiliu-yang
shiliu-yang merged commit c10f435 into tuya:master Oct 26, 2026
1 check passed
maidang-xing added a commit that referenced this pull request Oct 27, 2026
…tests/docs (#782)

* feat(ai-components): language-neutral alert tones + guided WeChat UI fixes (#713)

* ai_components/assets: add language-neutral alert tones as local alert pack

Replace the localized voice prompts with short synthesized tones when
ENABLE_AI_ALERT_TONE is enabled (now the default):

- add 4 semantic tones (success / wait / fail / wake) as MP3 C arrays,
  generated from assets/media/tone_*.mp3
- map all local alert types onto the 4 tones in media_src.h
- compile out the zh/en speech packs while tones are enabled, keeping
  the sources in tree so voice prompts come back by disabling the option

The language option still drives on-screen text; only alert playback
changes. Set ENABLE_AI_ALERT_TONE=n to restore the speech packs.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* fix(ai-ui): cache storage license in guided wechat state read

guided_wechat_state_read() runs from the 1 Hz UI refresh timer and called
tuya_authorize_read() on every invocation, re-reading uuid/authkey from
KV flash once per second and logging "Authorization read succeeds." at
the same rate.

The storage license never changes at runtime (no code path writes it
after boot and the UI offers no authorization reset), so read it once
and serve the cached pointers. The pointers target the authorize module
persistent buffers, which stay valid for the firmware lifetime.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* feat(ai-ui): show SoftAP hotspot name on guided network page

While pairing runs, BLE and AP netcfg operate in parallel. Surface the
live AP hotspot name under the guide QR so an app-less phone can join
the hotspot and reach the captive setup page.

- ap_netcfg: store hotspot ssid/ip when the SoftAP starts and expose a
  read-only getter
- guided wechat state: carry ap_ssid/ap_ip in the device state snapshot
- guided wechat UI: one muted hint line under the QR
  ("配网热点:<ssid>" / "Setup hotspot: <ssid>"), only while pairing is
  live and the hotspot is up

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: Claude Code <noreply@anthropic.com>

* Fix/ai http ca use after free (#719)

* fix(ai-http): keep iotdns CA alive until the TLS handshake parses it

http_session_send() freed the CA buffer returned by
tuya_iotdns_query_domain_certs() right after TUYA_TRANSPORTER_SET_TLS_CONFIG,
but the TLS layer only keeps the pointer: tuya_tls_config_set() memcpy-s the
config struct, not the certificate data, and mbedtls_x509_crt_parse() does not
run until tuya_transporter_connect().

The freed DER block is typically reused by the allocations inside connect
(socket, mbedtls contexts), so mbedtls reads garbage and fails with
MBEDTLS_ERR_X509_INVALID_FORMAT (-0x2180) -- the outer SEQUENCE tag is no
longer 0x30. TLS connections to CDN hosts then fail, e.g. the US-region
*.cdn5th.com TTS download, while CN region often survives because the block
happens to stay intact.

Free cacert after connect returns instead, and reject an empty CA up front the
way http_session.c already does.

* fix(http-manager): add handler to read response body from HTTP session

* chore(platform): bump SIWX917 platform commit (#727)

Co-authored-by: guanshan.xu <guanshan.xu@tuya.com>
Co-authored-by: Claude Code <noreply@anthropic.com>

* fix(p2p): bound sscanf field widths in tuya_sdp.c to prevent stack overflow (#726)

tuya_p2p_rtc_sdp_decode() parsed attacker-controlled SDP attributes with
unbounded "%s" conversions into fixed-size stack buffers (wms_name, wms_id,
msid, track_id, m1-m3, str_pt, str_attr, ssrc, ssrc_rtx). A malicious P2P
peer could send overlong tokens in SDP lines such as "a=msid-semantic:" and
overflow these buffers, corrupting the stack and potentially achieving
remote code execution.

Add maximum field widths (size - 1) to every %s conversion so input is
safely truncated to the destination buffer size. Verified with an
AddressSanitizer PoC that the original code crashes with a
stack-buffer-overflow (WRITE of 201 bytes into char[65]) while the patched
code parses the same malicious SDP cleanly.

* Fix/esp32c2 tls memory (#729)

* chore(platform): pin ESP32 platform to 6117745 (esp32c2 support)

* feat(board): add esp32c2 boards (ESP32-C2, ESP32C2_DEVKITM_1)

- ESP32-C2: generic module board (UART0 20/19, 26MHz-crystal modules)
- ESP32C2_DEVKITM_1: Espressif DevKitM-1, BOOT button on GPIO9, WS2812
  RGB LED on GPIO8, both registered in board_com_api
- saved board configs for the led/button examples

* feat(board): default bluetooth off on esp32c2 and drop dead symbol

- esp32c2 has only 132KB heap; WiFi + BLE controller do not fit together,
  so ENABLE_BLUETOOTH now defaults off when CHIP_CHOICE is esp32c2 (still
  manually enableable); add the missing prompt, following the LINUX
  platform precedent
- remove ENABLE_ESP_DISPLAY: unprompted, default n, zero references; the
  DISPLAY_NAME it wrapped is owned by src/peripherals/display/Kconfig

* feat(board): add WS2812 SPI LED driver for chips without RMT

esp32c2 has no RMT peripheral, so the existing ws1280 driver cannot link
there (esp_driver_rmt registers nothing). tdd_led_esp_ws1280_spi encodes
each 1.25us WS2812 bit as 10 SPI bits @ 8MHz with a 64-byte zero tail for
latch reset; requires DMA since polling mode is capped by the 64-byte
FIFO. The RMT driver is excluded from esp32c2 builds.

* build(platform): pin ESP32 platform revision

* fix(tls): handle idle transporter reads

* feat(switch-demo): monitor heap after cloud connect

* chore(board): remove ESP32C2 devkit config

* style(board): trim trailing whitespace

* fix(ai): validate media header length before parsing (#734)

Signed-off-by: jaeyeong lee <lee@jaeyeong.cc>

* feat(display): add SSD2677 hardware SPI driver (#735)

* feat(display): support SSD2677 partial refresh and picture demo (#740)

* fix(ble): bound peer-controlled lengths on the BLE command path (#742)

The BLE receive path sized buffers and copy lengths from fields the peer
supplies and validated none of them, so an unauthenticated peer in range
of an advertising device could reach memory corruption, a NULL
dereference or an unauthenticated factory reset with a single GATT write.

Two patterns account for almost all of it: a length computed as `a - b`
in unsigned arithmetic without first establishing `a >= b`, and an
allocation and a copy sized by different peer-supplied fields.

Add the missing bound at each site:

  - bound the reassembly varints, and the copy, by the received length
    and the allocation
  - bound the transparent-channel allocation and copy, and reject a
    declared length below the channel header or above one frame
  - validate the sub-packet size the peer requests against a floor and a
    ceiling, and allocate the replacement before releasing the live buffer
  - require a per-opcode minimum length before dispatch, and NULL-guard
    the paths that consume a decoded packet
  - NUL-terminate the payload handed to cJSON_Parse
  - require a completed pairing exchange before the commands that destroy
    device state, and reject keyless frames on an activated device
  - check the schema datapoint type before reading its enum table
  - initialise the netcfg response buffer

One reported finding is latent and is deliberately left unchanged: with
the current field set it produces no out-of-bounds access, and no peer
input can change how much is written.

Every fix is a check on an existing path: no function is restructured, no
interface changes and no frame shape that was accepted before is now
rejected, apart from inputs that previously produced a wrapped length, a
NULL dereference or an out-of-bounds access.

Build-verified for ESP32-S3 and T5AI.

* fix(l511g): restore chatbot build and audio playback (#751)

* Codex/l511g chatbot audio fix (#753)

* fix(l511g): restore chatbot build and audio playback

* feat(opus): integrate configurable fixed-point source codec

* fix(l511g): enable Opus upload and reserve encoder stack

* feat(examples): add Opus encoding benchmark and validation tools

* feat(linux): register RDK X5 and update the platform pin (#750)

* feat(linux): register RDK X5 and pin merged cross-build support

Add the RDK_X5 Linux board selection, minimal board registration, default
configuration and build instructions. Follow the existing Ubuntu board
template and keep shared peripheral implementations and numbering intact.

Pin TuyaOpen-ubuntu to 83e9183e4988b6878cfbe251e7df57c68b124c93,
which merged the host cross-compilation support in platform PR #41.

Validation: SDK preflight, clean Windows-to-AArch64 switch_demo build, and
X5 runtime binding restoration, MQTT connection and time synchronization.
Checked configuration selection for Ubuntu, Raspberry_Pi, DshanPi_A1,
TaishanPi_3 and RDK_X5; other boards were not built or hardware tested.
New C/H files passed the repository format and header checker.

* style(linux): trim X5 board template comments

* refactor(linux): reuse shared Linux config for RDK X5

* fix(platform): update T5AI commit reference to the latest version (#754)

* feat(ble): add WiFi hotspot list query over the transparent channel (#748)

* feat(ble): add WiFi hotspot list query over the transparent channel

The app can now ask the device for the surrounding WiFi hotspots over the
BLE large data transparent channel (0x801E, subcmd 0x0003) and get a JSON
list back on 0x801F, so the netcfg UI can show the scan result before the
user picks a network.

The list is built in ble_wifi_list.c: tal_wifi_all_ap_scan() the APs, sort
them by RSSI, cap the count, and emit {"ssid","rssi","sec"} entries. The
scan blocks for up to 5s, so the handler only copies the request and runs
the work on WORKQ_SYSTEM.

Two fixes in ble_channel.c were needed to carry a payload this large:
__response_to_app_by_subpack() only ever sent the first subpacket and kept
its progress in a global that ble_channle_ack() never reset, so a response
longer than one subpacket was truncated and a retransmit could not restart.
It now loops over the whole buffer with local progress, and ble_channle_ack()
validates its arguments and releases the buffer when the send is done.

ble_mgr.c advertises the capability in both places the app looks for it:
ADV_FLAG_WIFI_LIST in the scan response flag byte and CombosFlag bit0 in
the device info response.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* fix(ble): send every subpacket of a large response

ble_packet_resp() wrapped the HAL send in TUYA_CALL_ERR_GOTO(), whose body assigns rt unconditionally. A successful send therefore overwrote the OPRT_SVC_BT_API_TRSMITR_CONTINUE returned by the frame encoder with OPRT_OK, so the while (rt == OPRT_SVC_BT_API_TRSMITR_CONTINUE) loop never ran a second time and only the first subpacket went out.

With the app-negotiated pkg_len of 243 the 401-byte WiFi hotspot list needs two subpackets (243 + 161). The app received a truncated JSON and fell back to "scan failed, please enter the SSID manually".

Co-Authored-By: Claude Code <noreply@anthropic.com>

* feat(ble): let the wifi list query be disabled and reuse a recent scan

Every hotspot list request ran its own tal_wifi_all_ap_scan(), which
blocks for seconds, and there was no way to turn the capability off on a
product that does not want it.

Cache the result for 60s so the netcfg UI and the backup network fallback
share one scan, force the station mode before scanning, and add
ble_wifi_list_enable()/ble_wifi_list_is_enable() so the capability can be
switched at runtime. The flag drives ADV_FLAG_WIFI_LIST through
tuya_ble_adv_update(), whose declaration moves from a local extern in
ble_netcfg.c into ble_mgr.h, and tuya_ble_deinit() releases the cache.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* fix(ble): bound the channel payload handed to cJSON

ble_channel_process() dropped the payload length on the floor and handed
the channel callback a bare pointer, so both JSON parsers had to rely on
the byte after the payload being a NUL that nothing wrote. A legitimate
{"cnt":10} request is enough to read past the buffer: cJSON_Parse() calls
strlen() on it.

Pass the length through ble_channel_fn_t and parse with
cJSON_ParseWithLength() in ble_wifi_list.c and ble_netcfg.c. The
transparent-channel path already knows the reassembled length, and the
non-subpacket path has the raw length, so nothing has to guess.

Also reject a payload shorter than the two type bytes instead of reading
them, and note on the callback that the buffer is not NUL terminated.

Co-Authored-By: Claude Code <noreply@anthropic.com>

* fix(ble): guard the send path against a deinitialized manager

The wifi list scan runs on WORKQ_SYSTEM and tuya_ble_deinit() neither
cancels nor waits for it, so a task that finishes after the manager is
freed reaches tuya_ble_send_packet() with s_ble_mgr already NULL and
dereferences it. Bail out early instead, the way tuya_ble_deinit() and
tal_ble_event_callback() already do.

This only removes the crash. The task still runs to completion and its
response is silently dropped.

Co-Authored-By: Claude Code <noreply@anthropic.com>

---------

Co-authored-by: guanshan.xu <guanshan.xu@tuya.com>
Co-authored-by: Claude Code <noreply@anthropic.com>

* fix(boards): register generic ESP32 boards in CMake (#756)

* fix(boards): register generic ESP32-S3 board in CMake

* fix(boards): add remaining generic ESP32 board CMake files

* Yj/add l511c (#757)

* feat(board): add L511C_Y6E board support

* feat(platform): register L511C platform

* refactor(platform): normalize L511C_Y6E platform identity

* refactor(platform): normalize L511G_Y7PM platform identity

* docs(boards): remove L511 module README files

* fix(ai_ui): add null screen checks in UI init functions (#758)

* fix(ai_ui): add null screen checks in UI init functions

* fix(cli_flash): guard Windows subprocess decode for CP936

On Windows, tyutool and external tools may output ANSI/OEM bytes (e.g. GBK/CP936). Use errors='replace' and system preferred encoding in _do_flash_pipe to prevent UnicodeDecodeError during flash.

* fix(cli_flash): robust multi-encoding decode and safe print on Windows

- Support hybrid decoding: try UTF-8 first, fallback to system preferred encoding (e.g. CP936/GBK on Windows), and fallback to replace.
- Prevent UnicodeEncodeError when printing \ufffd replacement chars to strict GBK stdout on Windows by reconfiguring stdout errors='replace' and fallback safe printing.
- Add unit tests in test_cli_flash.py covering stream decoding, safe printing, and pipe execution.

* Codex/l511c y7pm (#770)

* feat(platform): add L511C_Y7PM SDK porting bridge

* feat(boards): register L511C_Y7PM platform and board

* feat(switch_demo): add L511C_Y7PM preset and bring-up guide

* fix(switch_demo): use board defaults for L511C_Y7PM

* fix(platform): keep L511C_Y7PM adaptation in platform repository

* build(platform): pin L511C_Y7PM TuyaOpen adaptation

* chore(switch_demo): restore README baseline whitespace

* feat(peripherals): add SSD2677 rotation and guard button reads

Rotate logical MONO frames at 0/90/180/270 degrees in the SSD2677 driver and skip failed button samples.

* build(platform): update L511C_Y6E and L511G_Y7PM pins

* fix(display): allow platform GPIO numbers beyond common enum range

* Revert "Codex/l511c y7pm (#770)" (#775)

This reverts commit 3c6fc15.

* fix(lan): bound the UDP discovery frame length against the received data (#771)

__udp_serv_is_in_packet_vaild() takes the 32-bit length field of an
unauthenticated LAN discovery datagram (FRM_TYPE_APP_UDP_BOARDCAST,
validated with the fixed app_key2) and uses it as an offset to the frame
tail without checking it against recv_datalen. A datagram that passes the
50 byte minimum with length = 0x7FFFFFFF makes the tail memcmp read about
2 GiB past the 512 byte receive buffer, and a frame whose head and tail
checks pass is handed to lpv35_frame_parse() with ilen = 22 + length, so
the AES-GCM decrypt reads length bytes out of the same buffer.

The TCP (lan_tcp_client_sock_read) and AP (ap_netcfg) paths already
reject frame_len > received bytes; do the same here. The check subtracts
from recv_datalen, and the function has already rejected
recv_datalen < LPV35_FRAME_MINI_SIZE (50 > 22), so it cannot wrap.

Reproduced at logic level under AddressSanitizer with the function copied
verbatim from this tree and a 512 byte heap receive buffer: the unpatched
code segfaults in the tail memcmp for length = 0x7FFFFFFF, the patched
code rejects it, and well formed frames, frames with trailing padding and
the 50 byte minimum frame are still accepted.

Refs: #759

* Codex/l511c y7pm (#776)

* feat(platform): add L511C_Y7PM SDK porting bridge

* feat(boards): register L511C_Y7PM platform and board

* feat(switch_demo): add L511C_Y7PM preset and bring-up guide

* fix(switch_demo): use board defaults for L511C_Y7PM

* fix(platform): keep L511C_Y7PM adaptation in platform repository

* build(platform): pin L511C_Y7PM TuyaOpen adaptation

* chore(switch_demo): restore README baseline whitespace

* feat(peripherals): add SSD2677 rotation and guard button reads

Rotate logical MONO frames at 0/90/180/270 degrees in the SSD2677 driver and skip failed button samples.

* build(platform): update L511C_Y6E and L511G_Y7PM pins

* fix(display): allow platform GPIO numbers beyond common enum range

* build(l511c-y7pm): update platform revision for SPI and GPIO support

* feat(ble): advertise the wifi list capability and answer netcfg status queries (#764)

* feat(ble): answer netcfg status queries and rescan on every list request

* feat(ble): advertise the wifi list capability

Set the vendor adv flag bit and the dev info combos flag bits for the
wifi list, netcfg status, log and ap name so the app issues the list and
status queries while provisioning.

(cherry picked from commit 29b68a2)

---------

Co-authored-by: guanshan.xu <guanshan.xu@tuya.com>

* fix(ble): drop the forced station switch before the ap scan (#762)

Querying the wifi list switched the radio to station mode first. On BK
based platforms that stops the softap and tears down the ongoing ap
netcfg. The scan does not need the switch.

(cherry picked from commit 0e26266)

Co-authored-by: guanshan.xu <guanshan.xu@tuya.com>

* feat(ble): gate the WiFi list query behind a Kconfig option (#778)

* feat(ble): make the WiFi list capability configurable

The query runs a blocking all-AP scan. Boards whose WiFi driver cannot scan
while connected (no APSTA) cannot serve it, so add ENABLE_BT_WIFI_LIST under
ENABLE_BT_SERVICE to pick the default state. It defaults to off; a platform
opts in from its OS_SERVICE_Kconfig once validated.

* feat(boards): opt T5AI and ESP32 into the BLE WiFi list

Both can scan while connected, so select ENABLE_BT_WIFI_LIST. On ESP32 it is
conditional on ENABLE_BLUETOOTH, which stays off on esp32c2.

---------

Co-authored-by: guanshan.xu <guanshan.xu@tuya.com>

* chore: remove redundant platform tests and docs to sync tests and docs with master

- Remove obsolete tests/platform/ test suite and tests/__init__.py to match master's test layout
- Remove docs/jieli_ac791x_ac792x_project_guide.md and docs/jieli_power_management_design.md
- Eliminate all remaining references to AC7916A and local drive paths

---------

Signed-off-by: jaeyeong lee <lee@jaeyeong.cc>
Co-authored-by: jianning-liu <jianning.liu@tuya.com>
Co-authored-by: Claude Code <noreply@anthropic.com>
Co-authored-by: YangJie <yangjie6759@163.com>
Co-authored-by: KOKO-cyco <354146137@qq.com>
Co-authored-by: guanshan.xu <guanshan.xu@tuya.com>
Co-authored-by: yingtao450 <144092910+yingtao450@users.noreply.github.com>
Co-authored-by: Fin <101492831+Finder16@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants