diff --git a/docs/WiFi.md b/docs/WiFi.md index 38ab3b4f0..7155e501d 100644 --- a/docs/WiFi.md +++ b/docs/WiFi.md @@ -886,11 +886,13 @@ send retries only that broker every 30 seconds; successful brokers keep their normal status interval. Each slot has a stable, distinct client ID, including when two slots point at the same broker. -G3 also places the SDK's two padded WebSocket buffers per initialized MQTT -client in PSRAM, saving 2,050 internal bytes per client when PSRAM allocation -succeeds. G3 repeaters move their complete scope tables and advert-limit -history there too. Capacities, saved formats, task stacks and DMA reserves -remain unchanged; safe internal fallback is retained. +PSRAM-enabled ESP32 builds also place the SDK's two padded WebSocket buffers +per initialized MQTT client in PSRAM, saving 2,050 internal bytes per client when PSRAM allocation +succeeds. ESP32-S3 repeaters with PSRAM move their complete scope tables and +advert-limit history there too, allocating after QSPI/OPI PSRAM initialization. +Capacities, saved formats, task stacks and DMA reserves remain unchanged; +safe internal fallback is retained. Classic ESP32 T-Beam keeps its existing +table storage while receiving the MQTT buffer change. Outbox diagnostics are cached by the network owner, so `get mqtt.stats` does not wait for an SDK mutex held by a network write. `rec:N/write` records a diff --git a/docs/research/firmware_memory_budget.md b/docs/research/firmware_memory_budget.md index 817c8b80e..bda0f12d2 100644 --- a/docs/research/firmware_memory_budget.md +++ b/docs/research/firmware_memory_budget.md @@ -190,22 +190,29 @@ other tasks. Available linked internal heap cannot establish how much DMA-capable heap remains or how it is fragmented after boot. Only runtime observations can establish that. -G3 repeater builds also place their channel/scope tables and full advert-source -history in PSRAM. Their capacities and persisted formats remain unchanged. +ESP32-S3 repeater builds with PSRAM also place their channel/scope tables and +full advert-source history there. Their capacities and persisted formats +remain unchanged. The RAM check still reserves internal fallback for both complete tables; moving them out of BSS does not relax startup admission or the DMA reserve. If neither heap can safely hold a complete table, startup reports the failed allocation and stops rather than running with reduced filtering protection. -Other targets keep their existing storage. `MESH_REPEATER_PSRAM_TABLES=0` -restores inline storage for a custom G3 build. +Allocation occurs in `MyMesh::begin`, after Arduino initializes either QSPI or +OPI PSRAM. Global constructors retain only the owners' pointers. Other targets +keep their existing storage. `MESH_REPEATER_PSRAM_TABLES=0` restores inline +storage for a custom build. The checker resolves the actual board MCU so the +complete fallback cannot disappear from a non-G3 S3's budget. -G3's IDF 4 WebSocket wrapper also prefers PSRAM for its two padded 1,025-byte -SDK buffers per initialized MQTT client, including clients using plain MQTT. +The IDF 4 WebSocket wrapper on any PSRAM-enabled ESP32 build also prefers PSRAM +for its two padded 1,025-byte SDK buffers per initialized MQTT client, including +clients using plain MQTT. Five initialized clients move 10,250 bytes of payload from internal RAM. Transport objects, task stacks, DMA buffers and radio packet queues keep their existing placement. The existing extra-byte overflow fix and guarded internal fallback remain in force. These figures describe allocation placement, not a measurement of running free heap or qualification of five simultaneous brokers. +See the [cross-board checks](../test-results/esp32_psram_storage_2026-10-27.md) +for covered families and the distinction between linked and physical results. Maximum-load qualification still needs physical boot and sustained reconnect, publish and configuration-change tests with the selected number of brokers, diff --git a/docs/test-results/esp32_psram_storage_2026-10-27.md b/docs/test-results/esp32_psram_storage_2026-10-27.md new file mode 100644 index 000000000..940ec1c73 --- /dev/null +++ b/docs/test-results/esp32_psram_storage_2026-10-27.md @@ -0,0 +1,96 @@ + +
View this page on MeshCore Docs.
+ + +# ESP32 PSRAM offload, 2026-10-27 + +V4, G2 Full and T-Deck Full each free **36,792 bytes of internal RAM** by moving +their scope tables and advert history to PSRAM. MQTT buffers can save another +**2,050 bytes per initialized client**. Features, capacities and saved formats +remain unchanged. No board was flashed during these checks. + +The [measured evidence](esp32_psram_storage_2026-10-27.safe.json) binds source +hashes, matched builds, capabilities and the two existing packaging failures. +The paired measurements isolate these PSRAM changes on baseline `74c03cb30`. +Concurrent Companion PIN and startup-screen changes were integrated afterward; +a further G2 Full build at `6a5a04b1b` passes with the same RAM result and +15/15 capability checks. It fits its OTA slots at 2,125,736 application bytes. + +## Enabled by default + +ESP32-S3 repeater builds with `BOARD_HAS_PSRAM` move complete scope tables and +advert history to PSRAM. Existing explicit RTC/IRAM advert backends retain +priority. This covers these configured board families and their repeater aliases: + +| Board families | PSRAM interface | +| --- | --- | +| Station G2/G3, T-Deck, T-ETH Elite, T-Beam 1W | OPI | +| V4 R8, RC32, E213, E290, T190 | OPI | +| W12, Wio Tracker L2, XIAO ESP32-S3/WIO, ThinkNode M7/M9 | OPI | +| Original SenseCAP Indicator | OPI | +| Standard V4, T3-S3 SX1262/SX1276, EoRa-S3 | QSPI | +| T-Beam S3 Supreme, Waveshare S3 LR1121, Indicator N16R2 | QSPI | + +The IDF 4 WebSocket wrapper also prefers PSRAM on all PSRAM-enabled ESP32 +builds that initialize MQTT clients, including companions, room servers and +classic T-Beam. It retains both 1,025-byte padded buffers per initialized +client, including plain MQTT clients. IDF 5 remains a passthrough. + +Classic T-Beam keeps its established table placement. Its qualified Full +MQTT profiles already store all 17,920 advert-history bytes in separate +word-accessible IRAM; only 3,162 scope-table bytes remain. This extension +retains that backend and its cache-workaround flags. Boards without compiled +PSRAM support retain existing allocation paths. + +## Startup and fallback + +QSPI PSRAM initializes in `initArduino()`, after global constructors. The +tables now allocate in `MyMesh::begin`, before routing and persistence start, +on both QSPI and OPI boards. Constructors retain only pointers. Getters never +allocate or move storage, and return complete array references so `sizeof`, +saved bytes, rollback and const access remain correct. + +Internal fallback must preserve the 16 KiB DMA reserve and hold each complete +table; otherwise startup reports failure and stops. The RAM check resolves +the actual board MCU and retains both full fallback allowances. Packet pools, +task stacks, ISR state and DMA buffers retain their existing placement. +`MESH_REPEATER_PSRAM_TABLES=0` restores inline table storage for custom builds. + +## Matched Full builds + +| Target | Internal heap before | Internal heap after | Gain | Capability checks | +| --- | ---: | ---: | ---: | ---: | +| V4 | 203,456 | 240,248 | 36,792 | 16/16 | +| G2 | 200,328 | 237,120 | 36,792 | 15/15 | +| T-Deck | 223,960 | 260,752 | 36,792 | 14/14 | + +All three pass compilation, RAM admission, dual-slot OTA fit and artifact +checks with identical capability lists and no declared reductions. The RAM +model still reserves 36,828 bytes for complete internal fallback, so its +conservative admission margins remain similar. These are linked capacity +measurements, not running free-heap or maximum broker-load measurements. + +Two packaging failures were reproduced before and after these changes: + +- G2 standard exceeds its old 1,310,720-byte slot: linked program size falls + from 1,375,509 to 1,374,093 bytes but still fails. G2 Full passes separately. +- Classic T-Beam Full MQTT compiles and passes RAM admission, but fails its + existing dual-slot OTA fit check. This image was not qualified or flashed. + +Builds used `build.sh build-firmware TARGET --full-exact` with +`--firmware-version v1.18.0.2-psram-test --profile cascade +--radio-preset usa-cascade-fixed --require-ota`; G2 legacy testing substituted +`--standard`. PlatformIO processes ran one at a time. + +## Software coverage + +- All 65 RAM-policy tests pass, including real-MCU selection and unchanged + fallback, reserve and contiguous-block thresholds. +- Scope storage, advert storage and existing capacity/RTC/IRAM tests pass + (23 methods), including constructor-no-allocation, QSPI startup, full + persisted-byte comparisons, rollback, lifetime and allocation failures. +- WebSocket tests pass three methods and 32 scenarios over seven platform + configurations, with padding, cleanup, reserve and IDF 4/5 coverage. + +Relevant native storage/transport tests run under ASan/UBSan. Physical boot, +RF timing, persistence and sustained MQTT load still need hardware validation. diff --git a/docs/test-results/esp32_psram_storage_2026-10-27.safe.json b/docs/test-results/esp32_psram_storage_2026-10-27.safe.json new file mode 100644 index 000000000..f3c5a719d --- /dev/null +++ b/docs/test-results/esp32_psram_storage_2026-10-27.safe.json @@ -0,0 +1,1635 @@ +{ + "schema_version": 1, + "baseline_commit": "74c03cb30f76551d8eedbe2b78ec98345c514d09", + "physical_validation_performed": false, + "maximum_runtime_load_qualified": false, + "websocket_payload_bytes_per_initialized_client": 2050, + "websocket_payload_bytes_for_five_initialized_clients": 10250, + "psram_repeater_table_policy": "ESP32-S3 + BOARD_HAS_PSRAM + qualified repeater; deferred begin; existing RTC/IRAM backends retained", + "tests": { + "ram_policy_methods_passed": 65, + "scope_advert_capacity_rtc_iram_methods_passed": 23, + "ws_methods_passed": 3, + "ws_scenarios_passed": 32, + "ws_platform_configurations": 7, + "native_storage_transport_sanitizers": "ASan/UBSan", + "pio_execution": "single process; RAM config hook ran separately from builds" + }, + "matched_builds": { + "heltec_v4_repeater": { + "baseline": { + "memory": { + "schema_version": 1, + "target": "heltec_v4_repeater", + "platform": "ESP32_PLATFORM", + "mcu": "esp32s3", + "elf_sha256": "26a554d320b8d14d67d1eab59c74f6c83b286b2ff8b73f64deeccf261b1a39a5", + "passed": true, + "available_internal_bytes": 203456, + "largest_internal_region_bytes": 170688, + "required_heap_bytes": 201904, + "required_contiguous_bytes": 19168, + "components": { + "display_i2c_spi_and_probe": 4096, + "core_tasks_usb_filesystems": 24576, + "wireless_stacks": 49152, + "mqtt_startup_allowance": 24576, + "udp_bridge_and_socket": 5120, + "ota_source_scratch": 8192, + "browser_terminal_session": 2048, + "neighbor_history": 18448, + "host_kiss_codec": 2048, + "radio_packet_pool": 10240, + "client_table": 9104, + "flood_filter_table": 19168, + "ota_generic_source_buffers": 3616, + "ota_encoder_workspace": 1040, + "display_pixels_and_driver": 2048, + "screen_objects_and_history": 2048, + "allocation_and_transient_margin": 16384 + }, + "display": "HeltecV4Display", + "full_companion": false, + "mqtt": { + "budget_scope": "startup minimum with runtime connection admission", + "startup_allowance_bytes": 24576, + "startup_active_slot_allowance": 2, + "runtime_slot_count": 6, + "maximum_active_slots": 5, + "maximum_active_slots_without_psram": 2, + "worker_stack_bytes": 8192, + "client_task_stack_bytes": 6144, + "client_task_priority": 5, + "client_rx_tx_buffer_bytes": 1792, + "maximum_slot_stack_buffer_allowance_bytes": 48384, + "cold_start_allocation_allowance_bytes": 16384, + "cold_start_dma_admission_free_bytes": 32768, + "cold_start_dma_admission_contiguous_bytes": 8192, + "additional_broker_cold_start_dma_admission_contiguous_bytes": 20480, + "restart_allocation_allowance_bytes": 8192, + "restart_dma_admission_free_bytes": 24576, + "restart_dma_admission_contiguous_bytes": 8192, + "additional_broker_restart_dma_admission_contiguous_bytes": 12288, + "dma_admission_free_bytes": 16384, + "dma_admission_contiguous_bytes": 4096, + "publish_contiguous_bytes": 4096, + "publish_contiguous_without_psram_bytes": 8000, + "publish_contiguous_with_psram_bytes": 4096, + "tls_psram_admission_contiguous_bytes": 16000, + "additional_broker_cold_start_tls_psram_admission_contiguous_bytes": 32384, + "additional_broker_restart_tls_psram_admission_contiguous_bytes": 24192, + "additional_broker_tls_psram_admission_contiguous_bytes": 16000, + "tls_internal_admission_free_bytes": 61440, + "tls_record_allocation_bytes": 17408, + "additional_broker_cold_start_tls_internal_admission_contiguous_bytes": 41792, + "additional_broker_restart_tls_internal_admission_contiguous_bytes": 33600, + "tls_internal_admission_contiguous_bytes": 17408, + "additional_broker_tls_internal_admission_contiguous_bytes": 25408, + "maximum_runtime_load_qualified": false, + "physical_validation_performed": false, + "limitations": "Maximum TLS/WSS/outbox workload is not covered by the startup allowance; runtime free DMA heap and fragmentation must satisfy connection admission; additional brokers reserve publication headroom beside startup allocations." + }, + "regions": [ + { + "start": 1070333008, + "end": 1070503696 + }, + { + "start": 1070530560, + "end": 1070563328 + } + ], + "scope": "Linked capacity before runtime allocation; physical boot/load/soak validation remains required.", + "artifact_target": "heltec_v4_repeater", + "pio_environment": "heltec_v4_repeater_observer_mqtt", + "files": { + "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.bin": "785fe1fc26492aff14c10dadf27f52ce104bf50e5f5f6ab0595a4812530a5de8", + "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3-merged.bin": "efa39aa3bd0a17aa5885f81dc6b7a37522be7dca6a6275e501c81b0e2b0516ee", + "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.capabilities.json": "75a95825edb208e5b2f600ad65798da382e3e59d4c237965b767afb0d730319e", + "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + }, + "capabilities": { + "artifact_target": "heltec_v4_repeater", + "build_profile": "full", + "build_recipe": { + "schema_version": 1, + "sha256": "f435455067a794665ec3cf09a1277c7b30526fc5ed53376512ff9ecbafb17750" + }, + "capabilities": [ + "bridge.espnow", + "bridge.udp", + "profile.full", + "release.full.same_partition_successor", + "radio2.kiss.host", + "radio2.kiss.usb", + "radio2.kiss.tcp", + "ota.update.wifi", + "ota.update.lora", + "cli.retry_preset", + "telemetry.history", + "ota.cli", + "web.webconfig", + "logging.usb.packets", + "logging.usb.control", + "logging.usb.output", + "display.heltec_v4.unified", + "bridge.udp.configuration" + ], + "ota_update_evidence": "firmware fits both OTA application slots; otadata present", + "ota_update_methods": [ + "wifi", + "lora" + ], + "ota_update_verified": true, + "platform": "ESP32_PLATFORM", + "platformio_env": "heltec_v4_repeater_observer_mqtt", + "reductions": [], + "schema_version": 2, + "target": "heltec_v4_repeater", + "third_party_notices": [ + { + "component": "espp-meshtext", + "file": "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt", + "license": "MIT", + "sha256": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + ], + "verification": [ + { + "capability": "radio2.kiss.host", + "evidence": "_ZN4mesh8hostkiss5Codec4feedEh; _ZN4mesh8hostkiss5Codec11queueOutputEhPKhj; _ZN4mesh10Dispatcher22queueForeignRadioFrameEPKhjhjNS_20RadioProfileProtocolE", + "present": true, + "source": "linked ELF symbols" + }, + { + "capability": "radio2.kiss.usb", + "evidence": "Error: use set kiss on|off; Error: use set kiss.transport usb|tcp; USB: send KISS FEND to start", + "present": true, + "source": "packaged application" + }, + { + "capability": "radio2.kiss.tcp", + "evidence": "station TCP 5004", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.espnow", + "evidence": "_ZN12ESPNowBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "bridge.udp", + "evidence": "_ZN9UDPBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.wifi", + "evidence": "Started: http://%s/update", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.lora", + "evidence": "image_hash MISMATCH after decode", + "present": true, + "source": "linked image" + }, + { + "capability": "cli.retry_preset", + "evidence": "retry.preset", + "present": true, + "source": "linked image" + }, + { + "capability": "telemetry.history", + "evidence": "telemetry.volt.i2c", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.cli", + "evidence": "OTA: status", + "present": true, + "source": "linked image" + }, + { + "capability": "web.webconfig", + "evidence": "start webconfig", + "present": true, + "source": "linked image" + }, + { + "capability": "logging.usb.packets", + "evidence": "%s: %s, len=%d (type=%d, route=%s, payload_len=%d)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.control", + "evidence": "OK - USB logging %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.output", + "evidence": "OK - logging.output %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "display.heltec_v4.unified", + "evidence": "meshcore.capability.display.heltec_v4.unified.v1", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.udp.configuration", + "evidence": "udp.enabled", + "present": true, + "source": "packaged application" + } + ], + "verified": true + }, + "application_bytes": 2158824, + "application_slot_bytes": 6553600 + }, + "final": { + "memory": { + "schema_version": 1, + "target": "heltec_v4_repeater", + "platform": "ESP32_PLATFORM", + "mcu": "esp32s3", + "elf_sha256": "f03df26d6ed65237095dbd0513323b19291eaa172ea52b62c5f324d5a1101943", + "passed": true, + "available_internal_bytes": 240248, + "largest_internal_region_bytes": 207480, + "required_heap_bytes": 238732, + "required_contiguous_bytes": 19168, + "components": { + "display_i2c_spi_and_probe": 4096, + "core_tasks_usb_filesystems": 24576, + "wireless_stacks": 49152, + "mqtt_startup_allowance": 24576, + "udp_bridge_and_socket": 5120, + "ota_source_scratch": 8192, + "browser_terminal_session": 2048, + "neighbor_history": 18448, + "host_kiss_codec": 2048, + "radio_packet_pool": 10240, + "client_table": 9104, + "flood_filter_table": 19168, + "repeater_scope_storage_fallback": 18892, + "repeater_advert_storage_fallback": 17936, + "ota_generic_source_buffers": 3616, + "ota_encoder_workspace": 1040, + "display_pixels_and_driver": 2048, + "screen_objects_and_history": 2048, + "allocation_and_transient_margin": 16384 + }, + "display": "HeltecV4Display", + "full_companion": false, + "mqtt": { + "budget_scope": "startup minimum with runtime connection admission", + "startup_allowance_bytes": 24576, + "startup_active_slot_allowance": 2, + "runtime_slot_count": 6, + "maximum_active_slots": 5, + "maximum_active_slots_without_psram": 2, + "worker_stack_bytes": 8192, + "client_task_stack_bytes": 6144, + "client_task_priority": 5, + "client_rx_tx_buffer_bytes": 1792, + "maximum_slot_stack_buffer_allowance_bytes": 48384, + "cold_start_allocation_allowance_bytes": 16384, + "cold_start_dma_admission_free_bytes": 32768, + "cold_start_dma_admission_contiguous_bytes": 8192, + "additional_broker_cold_start_dma_admission_contiguous_bytes": 20480, + "restart_allocation_allowance_bytes": 8192, + "restart_dma_admission_free_bytes": 24576, + "restart_dma_admission_contiguous_bytes": 8192, + "additional_broker_restart_dma_admission_contiguous_bytes": 12288, + "dma_admission_free_bytes": 16384, + "dma_admission_contiguous_bytes": 4096, + "publish_contiguous_bytes": 4096, + "publish_contiguous_without_psram_bytes": 8000, + "publish_contiguous_with_psram_bytes": 4096, + "tls_psram_admission_contiguous_bytes": 16000, + "additional_broker_cold_start_tls_psram_admission_contiguous_bytes": 32384, + "additional_broker_restart_tls_psram_admission_contiguous_bytes": 24192, + "additional_broker_tls_psram_admission_contiguous_bytes": 16000, + "tls_internal_admission_free_bytes": 61440, + "tls_record_allocation_bytes": 17408, + "additional_broker_cold_start_tls_internal_admission_contiguous_bytes": 41792, + "additional_broker_restart_tls_internal_admission_contiguous_bytes": 33600, + "tls_internal_admission_contiguous_bytes": 17408, + "additional_broker_tls_internal_admission_contiguous_bytes": 25408, + "maximum_runtime_load_qualified": false, + "physical_validation_performed": false, + "limitations": "Maximum TLS/WSS/outbox workload is not covered by the startup allowance; runtime free DMA heap and fragmentation must satisfy connection admission; additional brokers reserve publication headroom beside startup allocations." + }, + "regions": [ + { + "start": 1070296216, + "end": 1070503696 + }, + { + "start": 1070530560, + "end": 1070563328 + } + ], + "scope": "Linked capacity before runtime allocation; physical boot/load/soak validation remains required.", + "artifact_target": "heltec_v4_repeater", + "pio_environment": "heltec_v4_repeater_observer_mqtt", + "files": { + "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.bin": "2262c682c4e1bafe1703a26cd97f3cf5aa18d5f0ebeb15ad8b352552f83cc128", + "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3-merged.bin": "e2fad3e879b28d32dcb8983cf2c024dd2455475dedde125a461d2acbf09a61b8", + "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.capabilities.json": "b365e7b7864f49ab2888690b2b5d7e044a42d25450d2f3e1ff1ca1200e2b46da", + "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + }, + "capabilities": { + "artifact_target": "heltec_v4_repeater", + "build_profile": "full", + "build_recipe": { + "schema_version": 1, + "sha256": "b9db24a63c5eac6316114b8a05e3c09c3f9f0d01d9e2e29648b9ba5c57a96d64" + }, + "capabilities": [ + "bridge.espnow", + "bridge.udp", + "profile.full", + "release.full.same_partition_successor", + "radio2.kiss.host", + "radio2.kiss.usb", + "radio2.kiss.tcp", + "ota.update.wifi", + "ota.update.lora", + "cli.retry_preset", + "telemetry.history", + "ota.cli", + "web.webconfig", + "logging.usb.packets", + "logging.usb.control", + "logging.usb.output", + "display.heltec_v4.unified", + "bridge.udp.configuration" + ], + "ota_update_evidence": "firmware fits both OTA application slots; otadata present", + "ota_update_methods": [ + "wifi", + "lora" + ], + "ota_update_verified": true, + "platform": "ESP32_PLATFORM", + "platformio_env": "heltec_v4_repeater_observer_mqtt", + "reductions": [], + "schema_version": 2, + "target": "heltec_v4_repeater", + "third_party_notices": [ + { + "component": "espp-meshtext", + "file": "heltec_v4_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt", + "license": "MIT", + "sha256": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + ], + "verification": [ + { + "capability": "radio2.kiss.host", + "evidence": "_ZN4mesh8hostkiss5Codec4feedEh; _ZN4mesh8hostkiss5Codec11queueOutputEhPKhj; _ZN4mesh10Dispatcher22queueForeignRadioFrameEPKhjhjNS_20RadioProfileProtocolE", + "present": true, + "source": "linked ELF symbols" + }, + { + "capability": "radio2.kiss.usb", + "evidence": "Error: use set kiss on|off; Error: use set kiss.transport usb|tcp; USB: send KISS FEND to start", + "present": true, + "source": "packaged application" + }, + { + "capability": "radio2.kiss.tcp", + "evidence": "station TCP 5004", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.espnow", + "evidence": "_ZN12ESPNowBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "bridge.udp", + "evidence": "_ZN9UDPBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.wifi", + "evidence": "Started: http://%s/update", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.lora", + "evidence": "image_hash MISMATCH after decode", + "present": true, + "source": "linked image" + }, + { + "capability": "cli.retry_preset", + "evidence": "retry.preset", + "present": true, + "source": "linked image" + }, + { + "capability": "telemetry.history", + "evidence": "telemetry.volt.i2c", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.cli", + "evidence": "OTA: status", + "present": true, + "source": "linked image" + }, + { + "capability": "web.webconfig", + "evidence": "start webconfig", + "present": true, + "source": "linked image" + }, + { + "capability": "logging.usb.packets", + "evidence": "%s: %s, len=%d (type=%d, route=%s, payload_len=%d)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.control", + "evidence": "OK - USB logging %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.output", + "evidence": "OK - logging.output %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "display.heltec_v4.unified", + "evidence": "meshcore.capability.display.heltec_v4.unified.v1", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.udp.configuration", + "evidence": "udp.enabled", + "present": true, + "source": "packaged application" + } + ], + "verified": true + }, + "application_bytes": 2158216, + "application_slot_bytes": 6553600 + }, + "linked_internal_bytes_freed": 36792 + }, + "LilyGo_TDeck_repeater": { + "baseline": { + "memory": { + "schema_version": 1, + "target": "LilyGo_TDeck_repeater", + "platform": "ESP32_PLATFORM", + "mcu": "esp32s3", + "elf_sha256": "7a81e26e7ca6db8241ded72c7f25c171cfddf867dcf04018b17bec2841314fc8", + "passed": true, + "available_internal_bytes": 223960, + "largest_internal_region_bytes": 191192, + "required_heap_bytes": 171184, + "required_contiguous_bytes": 19168, + "components": { + "core_tasks_usb_filesystems": 24576, + "wireless_stacks": 49152, + "udp_bridge_and_socket": 5120, + "ota_source_scratch": 8192, + "browser_terminal_session": 2048, + "neighbor_history": 18448, + "host_kiss_codec": 2048, + "radio_packet_pool": 10240, + "client_table": 9104, + "flood_filter_table": 19168, + "ota_generic_source_buffers": 3616, + "ota_encoder_workspace": 1040, + "display_pixels_and_driver": 0, + "screen_objects_and_history": 2048, + "allocation_and_transient_margin": 16384 + }, + "display": "ST7789LCDDisplay", + "full_companion": false, + "regions": [ + { + "start": 1070312504, + "end": 1070503696 + }, + { + "start": 1070530560, + "end": 1070563328 + } + ], + "scope": "Linked capacity before runtime allocation; physical boot/load/soak validation remains required.", + "artifact_target": "LilyGo_TDeck_repeater", + "pio_environment": "LilyGo_TDeck_repeater", + "files": { + "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3.bin": "58645d4a0221c47aced270ebd2d36334e5eff5f47f5443cea3eec552d72a6d48", + "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3-merged.bin": "1af4afdf12e53e194515a6193f6080701515e644365dc829f075597c562f7b1f", + "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3.capabilities.json": "6e2aa96cfc2baa32aedc22a48b34fa37392a6acbea79ec77433dbe4a965dfb02", + "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + }, + "capabilities": { + "artifact_target": "LilyGo_TDeck_repeater", + "build_profile": "full", + "build_recipe": { + "schema_version": 1, + "sha256": "a0fdec3a295e5d8a1e55aa25f7a6461c0c7d944b026d4b94c6e2d399711f9ede" + }, + "capabilities": [ + "bridge.espnow", + "bridge.udp", + "profile.full", + "release.full.same_partition_successor", + "radio2.kiss.host", + "radio2.kiss.usb", + "radio2.kiss.tcp", + "ota.update.wifi", + "ota.update.lora", + "cli.retry_preset", + "telemetry.history", + "ota.cli", + "web.webconfig", + "logging.usb.packets", + "logging.usb.control", + "bridge.udp.configuration" + ], + "ota_update_evidence": "firmware fits both OTA application slots; otadata present", + "ota_update_methods": [ + "wifi", + "lora" + ], + "ota_update_verified": true, + "platform": "ESP32_PLATFORM", + "platformio_env": "LilyGo_TDeck_repeater", + "reductions": [], + "schema_version": 2, + "target": "LilyGo_TDeck_repeater", + "third_party_notices": [ + { + "component": "espp-meshtext", + "file": "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt", + "license": "MIT", + "sha256": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + ], + "verification": [ + { + "capability": "radio2.kiss.host", + "evidence": "_ZN4mesh8hostkiss5Codec4feedEh; _ZN4mesh8hostkiss5Codec11queueOutputEhPKhj; _ZN4mesh10Dispatcher22queueForeignRadioFrameEPKhjhjNS_20RadioProfileProtocolE", + "present": true, + "source": "linked ELF symbols" + }, + { + "capability": "radio2.kiss.usb", + "evidence": "Error: use set kiss on|off; Error: use set kiss.transport usb|tcp; USB: send KISS FEND to start", + "present": true, + "source": "packaged application" + }, + { + "capability": "radio2.kiss.tcp", + "evidence": "station TCP 5004", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.espnow", + "evidence": "_ZN12ESPNowBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "bridge.udp", + "evidence": "_ZN9UDPBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.wifi", + "evidence": "Started: http://%s/update", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.lora", + "evidence": "image_hash MISMATCH after decode", + "present": true, + "source": "linked image" + }, + { + "capability": "cli.retry_preset", + "evidence": "retry.preset", + "present": true, + "source": "linked image" + }, + { + "capability": "telemetry.history", + "evidence": "telemetry.volt.i2c", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.cli", + "evidence": "OTA: status", + "present": true, + "source": "linked image" + }, + { + "capability": "web.webconfig", + "evidence": "start webconfig", + "present": true, + "source": "linked image" + }, + { + "capability": "logging.usb.packets", + "evidence": "%s: %s, len=%d (type=%d, route=%s, payload_len=%d)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.control", + "evidence": "OK - USB logging %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.udp.configuration", + "evidence": "udp.enabled", + "present": true, + "source": "packaged application" + } + ], + "verified": true + }, + "application_bytes": 1749000, + "application_slot_bytes": 6553600 + }, + "final": { + "memory": { + "schema_version": 1, + "target": "LilyGo_TDeck_repeater", + "platform": "ESP32_PLATFORM", + "mcu": "esp32s3", + "elf_sha256": "1b380ad615448e5b24992a4f78cb1e59a123e271bd6ac6ef0b8203579e620068", + "passed": true, + "available_internal_bytes": 260752, + "largest_internal_region_bytes": 227984, + "required_heap_bytes": 208012, + "required_contiguous_bytes": 19168, + "components": { + "core_tasks_usb_filesystems": 24576, + "wireless_stacks": 49152, + "udp_bridge_and_socket": 5120, + "ota_source_scratch": 8192, + "browser_terminal_session": 2048, + "neighbor_history": 18448, + "host_kiss_codec": 2048, + "radio_packet_pool": 10240, + "client_table": 9104, + "flood_filter_table": 19168, + "repeater_scope_storage_fallback": 18892, + "repeater_advert_storage_fallback": 17936, + "ota_generic_source_buffers": 3616, + "ota_encoder_workspace": 1040, + "display_pixels_and_driver": 0, + "screen_objects_and_history": 2048, + "allocation_and_transient_margin": 16384 + }, + "display": "ST7789LCDDisplay", + "full_companion": false, + "regions": [ + { + "start": 1070275712, + "end": 1070503696 + }, + { + "start": 1070530560, + "end": 1070563328 + } + ], + "scope": "Linked capacity before runtime allocation; physical boot/load/soak validation remains required.", + "artifact_target": "LilyGo_TDeck_repeater", + "pio_environment": "LilyGo_TDeck_repeater", + "files": { + "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3.bin": "aacc9f578fc6e6185061f4939e4f6f58c3f7553c0de3b87c8e95b4f231a9f845", + "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3-merged.bin": "5741fb5c0f9b744f2895a3db5e207d873c172192ccc6ab473d3a7f5da4d88d4d", + "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3.capabilities.json": "f1628e243c28a4619597f6236a6b1ea593885e581c4949a9cbac084aecf6f891", + "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + }, + "capabilities": { + "artifact_target": "LilyGo_TDeck_repeater", + "build_profile": "full", + "build_recipe": { + "schema_version": 1, + "sha256": "4637703ef279754b9752767c3d6cdc1c00ae852c9c2c2cf34b85f42a3d24b028" + }, + "capabilities": [ + "bridge.espnow", + "bridge.udp", + "profile.full", + "release.full.same_partition_successor", + "radio2.kiss.host", + "radio2.kiss.usb", + "radio2.kiss.tcp", + "ota.update.wifi", + "ota.update.lora", + "cli.retry_preset", + "telemetry.history", + "ota.cli", + "web.webconfig", + "logging.usb.packets", + "logging.usb.control", + "bridge.udp.configuration" + ], + "ota_update_evidence": "firmware fits both OTA application slots; otadata present", + "ota_update_methods": [ + "wifi", + "lora" + ], + "ota_update_verified": true, + "platform": "ESP32_PLATFORM", + "platformio_env": "LilyGo_TDeck_repeater", + "reductions": [], + "schema_version": 2, + "target": "LilyGo_TDeck_repeater", + "third_party_notices": [ + { + "component": "espp-meshtext", + "file": "LilyGo_TDeck_repeater-full-logging-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt", + "license": "MIT", + "sha256": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + ], + "verification": [ + { + "capability": "radio2.kiss.host", + "evidence": "_ZN4mesh8hostkiss5Codec4feedEh; _ZN4mesh8hostkiss5Codec11queueOutputEhPKhj; _ZN4mesh10Dispatcher22queueForeignRadioFrameEPKhjhjNS_20RadioProfileProtocolE", + "present": true, + "source": "linked ELF symbols" + }, + { + "capability": "radio2.kiss.usb", + "evidence": "Error: use set kiss on|off; Error: use set kiss.transport usb|tcp; USB: send KISS FEND to start", + "present": true, + "source": "packaged application" + }, + { + "capability": "radio2.kiss.tcp", + "evidence": "station TCP 5004", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.espnow", + "evidence": "_ZN12ESPNowBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "bridge.udp", + "evidence": "_ZN9UDPBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.wifi", + "evidence": "Started: http://%s/update", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.lora", + "evidence": "image_hash MISMATCH after decode", + "present": true, + "source": "linked image" + }, + { + "capability": "cli.retry_preset", + "evidence": "retry.preset", + "present": true, + "source": "linked image" + }, + { + "capability": "telemetry.history", + "evidence": "telemetry.volt.i2c", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.cli", + "evidence": "OTA: status", + "present": true, + "source": "linked image" + }, + { + "capability": "web.webconfig", + "evidence": "start webconfig", + "present": true, + "source": "linked image" + }, + { + "capability": "logging.usb.packets", + "evidence": "%s: %s, len=%d (type=%d, route=%s, payload_len=%d)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.control", + "evidence": "OK - USB logging %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.udp.configuration", + "evidence": "udp.enabled", + "present": true, + "source": "packaged application" + } + ], + "verified": true + }, + "application_bytes": 1747624, + "application_slot_bytes": 6553600 + }, + "linked_internal_bytes_freed": 36792 + }, + "Station_G2_full": { + "baseline": { + "memory": { + "schema_version": 1, + "target": "Station_G2_repeater", + "platform": "ESP32_PLATFORM", + "mcu": "esp32s3", + "elf_sha256": "e9ef328f902caeb6bb46e922c2d058f159fe8ef27775688f837bf4bf29a983e3", + "passed": true, + "available_internal_bytes": 200328, + "largest_internal_region_bytes": 167560, + "required_heap_bytes": 197808, + "required_contiguous_bytes": 19168, + "components": { + "core_tasks_usb_filesystems": 24576, + "wireless_stacks": 49152, + "mqtt_startup_allowance": 24576, + "udp_bridge_and_socket": 5120, + "ota_source_scratch": 8192, + "browser_terminal_session": 2048, + "neighbor_history": 18448, + "host_kiss_codec": 2048, + "radio_packet_pool": 10240, + "client_table": 9104, + "flood_filter_table": 19168, + "ota_generic_source_buffers": 3616, + "ota_encoder_workspace": 1040, + "display_pixels_and_driver": 2048, + "screen_objects_and_history": 2048, + "allocation_and_transient_margin": 16384 + }, + "display": "SH1106Display", + "full_companion": false, + "mqtt": { + "budget_scope": "startup minimum with runtime connection admission", + "startup_allowance_bytes": 24576, + "startup_active_slot_allowance": 2, + "runtime_slot_count": 6, + "maximum_active_slots": 5, + "maximum_active_slots_without_psram": 2, + "worker_stack_bytes": 8192, + "client_task_stack_bytes": 6144, + "client_task_priority": 5, + "client_rx_tx_buffer_bytes": 1792, + "maximum_slot_stack_buffer_allowance_bytes": 48384, + "cold_start_allocation_allowance_bytes": 16384, + "cold_start_dma_admission_free_bytes": 32768, + "cold_start_dma_admission_contiguous_bytes": 8192, + "additional_broker_cold_start_dma_admission_contiguous_bytes": 20480, + "restart_allocation_allowance_bytes": 8192, + "restart_dma_admission_free_bytes": 24576, + "restart_dma_admission_contiguous_bytes": 8192, + "additional_broker_restart_dma_admission_contiguous_bytes": 12288, + "dma_admission_free_bytes": 16384, + "dma_admission_contiguous_bytes": 4096, + "publish_contiguous_bytes": 4096, + "publish_contiguous_without_psram_bytes": 8000, + "publish_contiguous_with_psram_bytes": 4096, + "tls_psram_admission_contiguous_bytes": 16000, + "additional_broker_cold_start_tls_psram_admission_contiguous_bytes": 32384, + "additional_broker_restart_tls_psram_admission_contiguous_bytes": 24192, + "additional_broker_tls_psram_admission_contiguous_bytes": 16000, + "tls_internal_admission_free_bytes": 61440, + "tls_record_allocation_bytes": 17408, + "additional_broker_cold_start_tls_internal_admission_contiguous_bytes": 41792, + "additional_broker_restart_tls_internal_admission_contiguous_bytes": 33600, + "tls_internal_admission_contiguous_bytes": 17408, + "additional_broker_tls_internal_admission_contiguous_bytes": 25408, + "maximum_runtime_load_qualified": false, + "physical_validation_performed": false, + "limitations": "Maximum TLS/WSS/outbox workload is not covered by the startup allowance; runtime free DMA heap and fragmentation must satisfy connection admission; additional brokers reserve publication headroom beside startup allocations." + }, + "regions": [ + { + "start": 1070336136, + "end": 1070503696 + }, + { + "start": 1070530560, + "end": 1070563328 + } + ], + "scope": "Linked capacity before runtime allocation; physical boot/load/soak validation remains required.", + "artifact_target": "Station_G2_repeater", + "pio_environment": "Station_G2_repeater_observer_mqtt", + "files": { + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.bin": "5c85a2296472ca98896f19c6f03896edcefb05940eabfc0ff371300ae7b48e5c", + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3-merged.bin": "85db924b667bc5d6a68c13a71db3cc9383d60698e662822f42d40c30e67a9075", + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.capabilities.json": "9f6ad5ae8ad0f1f7867b4e6747802c9fd96c85a0381d5512b5d21faf4b373cf9", + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + }, + "capabilities": { + "artifact_target": "Station_G2_repeater", + "build_profile": "full", + "build_recipe": { + "schema_version": 1, + "sha256": "097d01b27ee89898eba7aeafad8ec3aee1f2e28faabe2b615760230323aa9207" + }, + "capabilities": [ + "bridge.espnow", + "bridge.udp", + "profile.full", + "release.full.partition_migration_ota", + "radio2.kiss.host", + "radio2.kiss.usb", + "radio2.kiss.tcp", + "ota.update.wifi", + "ota.update.lora", + "cli.retry_preset", + "telemetry.history", + "ota.cli", + "web.webconfig", + "logging.usb.packets", + "logging.usb.control", + "logging.usb.output", + "bridge.udp.configuration" + ], + "ota_update_evidence": "firmware fits both OTA application slots; otadata present", + "ota_update_methods": [ + "wifi", + "lora" + ], + "ota_update_verified": true, + "platform": "ESP32_PLATFORM", + "platformio_env": "Station_G2_repeater_observer_mqtt", + "reductions": [], + "schema_version": 2, + "target": "Station_G2_repeater", + "third_party_notices": [ + { + "component": "espp-meshtext", + "file": "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt", + "license": "MIT", + "sha256": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + ], + "verification": [ + { + "capability": "radio2.kiss.host", + "evidence": "_ZN4mesh8hostkiss5Codec4feedEh; _ZN4mesh8hostkiss5Codec11queueOutputEhPKhj; _ZN4mesh10Dispatcher22queueForeignRadioFrameEPKhjhjNS_20RadioProfileProtocolE", + "present": true, + "source": "linked ELF symbols" + }, + { + "capability": "radio2.kiss.usb", + "evidence": "Error: use set kiss on|off; Error: use set kiss.transport usb|tcp; USB: send KISS FEND to start", + "present": true, + "source": "packaged application" + }, + { + "capability": "radio2.kiss.tcp", + "evidence": "station TCP 5004", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.espnow", + "evidence": "_ZN12ESPNowBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "bridge.udp", + "evidence": "_ZN9UDPBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.wifi", + "evidence": "Started: http://%s/update", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.lora", + "evidence": "image_hash MISMATCH after decode", + "present": true, + "source": "linked image" + }, + { + "capability": "cli.retry_preset", + "evidence": "retry.preset", + "present": true, + "source": "linked image" + }, + { + "capability": "telemetry.history", + "evidence": "telemetry.volt.i2c", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.cli", + "evidence": "OTA: status", + "present": true, + "source": "linked image" + }, + { + "capability": "web.webconfig", + "evidence": "start webconfig", + "present": true, + "source": "linked image" + }, + { + "capability": "logging.usb.packets", + "evidence": "%s: %s, len=%d (type=%d, route=%s, payload_len=%d)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.control", + "evidence": "OK - USB logging %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.output", + "evidence": "OK - logging.output %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.udp.configuration", + "evidence": "udp.enabled", + "present": true, + "source": "packaged application" + } + ], + "verified": true + }, + "application_bytes": 2125896, + "application_slot_bytes": 6553600 + }, + "final": { + "memory": { + "schema_version": 1, + "target": "Station_G2_repeater", + "platform": "ESP32_PLATFORM", + "mcu": "esp32s3", + "elf_sha256": "61910fcb243d37fa99771fe2c7cca5c3fb28e077abcb98e4f14b62215ab21460", + "passed": true, + "available_internal_bytes": 237120, + "largest_internal_region_bytes": 204352, + "required_heap_bytes": 234636, + "required_contiguous_bytes": 19168, + "components": { + "core_tasks_usb_filesystems": 24576, + "wireless_stacks": 49152, + "mqtt_startup_allowance": 24576, + "udp_bridge_and_socket": 5120, + "ota_source_scratch": 8192, + "browser_terminal_session": 2048, + "neighbor_history": 18448, + "host_kiss_codec": 2048, + "radio_packet_pool": 10240, + "client_table": 9104, + "flood_filter_table": 19168, + "repeater_scope_storage_fallback": 18892, + "repeater_advert_storage_fallback": 17936, + "ota_generic_source_buffers": 3616, + "ota_encoder_workspace": 1040, + "display_pixels_and_driver": 2048, + "screen_objects_and_history": 2048, + "allocation_and_transient_margin": 16384 + }, + "display": "SH1106Display", + "full_companion": false, + "mqtt": { + "budget_scope": "startup minimum with runtime connection admission", + "startup_allowance_bytes": 24576, + "startup_active_slot_allowance": 2, + "runtime_slot_count": 6, + "maximum_active_slots": 5, + "maximum_active_slots_without_psram": 2, + "worker_stack_bytes": 8192, + "client_task_stack_bytes": 6144, + "client_task_priority": 5, + "client_rx_tx_buffer_bytes": 1792, + "maximum_slot_stack_buffer_allowance_bytes": 48384, + "cold_start_allocation_allowance_bytes": 16384, + "cold_start_dma_admission_free_bytes": 32768, + "cold_start_dma_admission_contiguous_bytes": 8192, + "additional_broker_cold_start_dma_admission_contiguous_bytes": 20480, + "restart_allocation_allowance_bytes": 8192, + "restart_dma_admission_free_bytes": 24576, + "restart_dma_admission_contiguous_bytes": 8192, + "additional_broker_restart_dma_admission_contiguous_bytes": 12288, + "dma_admission_free_bytes": 16384, + "dma_admission_contiguous_bytes": 4096, + "publish_contiguous_bytes": 4096, + "publish_contiguous_without_psram_bytes": 8000, + "publish_contiguous_with_psram_bytes": 4096, + "tls_psram_admission_contiguous_bytes": 16000, + "additional_broker_cold_start_tls_psram_admission_contiguous_bytes": 32384, + "additional_broker_restart_tls_psram_admission_contiguous_bytes": 24192, + "additional_broker_tls_psram_admission_contiguous_bytes": 16000, + "tls_internal_admission_free_bytes": 61440, + "tls_record_allocation_bytes": 17408, + "additional_broker_cold_start_tls_internal_admission_contiguous_bytes": 41792, + "additional_broker_restart_tls_internal_admission_contiguous_bytes": 33600, + "tls_internal_admission_contiguous_bytes": 17408, + "additional_broker_tls_internal_admission_contiguous_bytes": 25408, + "maximum_runtime_load_qualified": false, + "physical_validation_performed": false, + "limitations": "Maximum TLS/WSS/outbox workload is not covered by the startup allowance; runtime free DMA heap and fragmentation must satisfy connection admission; additional brokers reserve publication headroom beside startup allocations." + }, + "regions": [ + { + "start": 1070299344, + "end": 1070503696 + }, + { + "start": 1070530560, + "end": 1070563328 + } + ], + "scope": "Linked capacity before runtime allocation; physical boot/load/soak validation remains required.", + "artifact_target": "Station_G2_repeater", + "pio_environment": "Station_G2_repeater_observer_mqtt", + "files": { + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.bin": "181cd48ddcb3f59d6248dc8d0eb56d24e078cb1570d2c3849982e994944de2db", + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3-merged.bin": "2333bffd21cfba9499af5e09693cb942056827a088cae8b1bb451824a7eee33a", + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.capabilities.json": "0d0947fa3b07da87f70b1935386e7d17d873906318e2abcd08192b2e3629a956", + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + }, + "capabilities": { + "artifact_target": "Station_G2_repeater", + "build_profile": "full", + "build_recipe": { + "schema_version": 1, + "sha256": "fc24f56be0d6bca5ed94476d117ef22613bcbdde132356a70a88cc4c9a00b697" + }, + "capabilities": [ + "bridge.espnow", + "bridge.udp", + "profile.full", + "release.full.partition_migration_ota", + "radio2.kiss.host", + "radio2.kiss.usb", + "radio2.kiss.tcp", + "ota.update.wifi", + "ota.update.lora", + "cli.retry_preset", + "telemetry.history", + "ota.cli", + "web.webconfig", + "logging.usb.packets", + "logging.usb.control", + "logging.usb.output", + "bridge.udp.configuration" + ], + "ota_update_evidence": "firmware fits both OTA application slots; otadata present", + "ota_update_methods": [ + "wifi", + "lora" + ], + "ota_update_verified": true, + "platform": "ESP32_PLATFORM", + "platformio_env": "Station_G2_repeater_observer_mqtt", + "reductions": [], + "schema_version": 2, + "target": "Station_G2_repeater", + "third_party_notices": [ + { + "component": "espp-meshtext", + "file": "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-74c03cb3.meshtext-license.txt", + "license": "MIT", + "sha256": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + ], + "verification": [ + { + "capability": "radio2.kiss.host", + "evidence": "_ZN4mesh8hostkiss5Codec4feedEh; _ZN4mesh8hostkiss5Codec11queueOutputEhPKhj; _ZN4mesh10Dispatcher22queueForeignRadioFrameEPKhjhjNS_20RadioProfileProtocolE", + "present": true, + "source": "linked ELF symbols" + }, + { + "capability": "radio2.kiss.usb", + "evidence": "Error: use set kiss on|off; Error: use set kiss.transport usb|tcp; USB: send KISS FEND to start", + "present": true, + "source": "packaged application" + }, + { + "capability": "radio2.kiss.tcp", + "evidence": "station TCP 5004", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.espnow", + "evidence": "_ZN12ESPNowBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "bridge.udp", + "evidence": "_ZN9UDPBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.wifi", + "evidence": "Started: http://%s/update", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.lora", + "evidence": "image_hash MISMATCH after decode", + "present": true, + "source": "linked image" + }, + { + "capability": "cli.retry_preset", + "evidence": "retry.preset", + "present": true, + "source": "linked image" + }, + { + "capability": "telemetry.history", + "evidence": "telemetry.volt.i2c", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.cli", + "evidence": "OTA: status", + "present": true, + "source": "linked image" + }, + { + "capability": "web.webconfig", + "evidence": "start webconfig", + "present": true, + "source": "linked image" + }, + { + "capability": "logging.usb.packets", + "evidence": "%s: %s, len=%d (type=%d, route=%s, payload_len=%d)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.control", + "evidence": "OK - USB logging %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.output", + "evidence": "OK - logging.output %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.udp.configuration", + "evidence": "udp.enabled", + "present": true, + "source": "packaged application" + } + ], + "verified": true + }, + "application_bytes": 2125160, + "application_slot_bytes": 6553600 + }, + "linked_internal_bytes_freed": 36792 + } + }, + "existing_failures": { + "Station_G2_repeater": { + "baseline": { + "log_sha256": "62693f3b03281ef3d0531d08492854aaba0c9c482199a58779f17a7f06833459", + "linked_program_bytes": 1375509, + "application_slot_bytes": 1310720, + "result": "preexisting legacy flash-slot overflow" + }, + "final": { + "log_sha256": "a12c2d92958176a080a1413fac999290636d2e8b053400ff3d990683f81b0f13", + "linked_program_bytes": 1374093, + "application_slot_bytes": 1310720, + "result": "preexisting legacy flash-slot overflow" + } + }, + "Tbeam_SX1262_repeater_observer_mqtt": { + "baseline": { + "log_sha256": "ac25a522c11462e2bec8e198a477e378828c59f5aa207ea90abde87cd903e884", + "result": "compile and RAM admission passed; existing dual-slot OTA-fit check failed" + }, + "final": { + "log_sha256": "1f1b2485a341b88a1b31e57c5acc199d4d17a59b6877cbb836410126d6dd53b3", + "result": "compile and RAM admission passed; existing dual-slot OTA-fit check failed" + } + } + }, + "source_sha256": { + "src/helpers/RepeaterPsramStorage.h": "9286a1aea6f7fa6da30c1d0788717d82f2479edf45b35181d5d6bdb22c5b211b", + "src/helpers/FloodAdvertLimiter.h": "72dda9cafa0b5f599b4858829c5b9e9bf9439b97bcbeece92d20514a9acdf829", + "src/helpers/ESP32WsTransportFix.cpp": "1a946f2c6061118e55995b38387cfc2e3462d9bcb1576764989edd68cf99ab2c", + "examples/simple_repeater/MyMesh.h": "7b95cee5e9e148bbbf76aea8394b0a075047aac74064795fedb1256cb66c5712", + "examples/simple_repeater/MyMesh.cpp": "9c6fcd42f6e1ce17dec0209e83d7ae6ec71dbf4c9bdaae17c77924f396e70f77", + "scripts/check_firmware_ram.py": "833e842048fbe72463da547d52e1f8f291ea248142f2abf6735f596d3be601ed", + "test/test_firmware_ram.py": "1402904d4d4907a0743b6d09c3d0c72534efc14ff7f288c4c118b04cf454cb09", + "test/test_repeater_scope_psram.py": "c5974097405f89db5a529513a6c52d517de458c5acbd5fa0efe193fd2936ccf4", + "test/test_flood_advert_psram.py": "839d97c99215ba8c2e26b490398968049de668e386f4e1c3d4e8d2541d19fb5b", + "test/test_ws_transport_psram.py": "1a41b4efb069e604275c132eda286e5e2f34d3257f5d7ff3fd28f7e6fb653425" + }, + "limitations": [ + "No board flashed.", + "Linked capacity is not running free heap.", + "Physical startup, RF timing, persistence and broker-load qualification were not performed." + ], + "post_integration": { + "tested_commit": "6a5a04b1b881022f59f6489c67b62aed4629fea0", + "target": "Station_G2_repeater", + "memory": { + "schema_version": 1, + "target": "Station_G2_repeater", + "platform": "ESP32_PLATFORM", + "mcu": "esp32s3", + "elf_sha256": "fb5e4cfbf7fe68af87abbaa7ae06cfe43abedb8ff1450179020f34750f24e4f1", + "passed": true, + "available_internal_bytes": 237120, + "largest_internal_region_bytes": 204352, + "required_heap_bytes": 234636, + "required_contiguous_bytes": 19168, + "components": { + "core_tasks_usb_filesystems": 24576, + "wireless_stacks": 49152, + "mqtt_startup_allowance": 24576, + "udp_bridge_and_socket": 5120, + "ota_source_scratch": 8192, + "browser_terminal_session": 2048, + "neighbor_history": 18448, + "host_kiss_codec": 2048, + "radio_packet_pool": 10240, + "client_table": 9104, + "flood_filter_table": 19168, + "repeater_scope_storage_fallback": 18892, + "repeater_advert_storage_fallback": 17936, + "ota_generic_source_buffers": 3616, + "ota_encoder_workspace": 1040, + "display_pixels_and_driver": 2048, + "screen_objects_and_history": 2048, + "allocation_and_transient_margin": 16384 + }, + "display": "SH1106Display", + "full_companion": false, + "mqtt": { + "budget_scope": "startup minimum with runtime connection admission", + "startup_allowance_bytes": 24576, + "startup_active_slot_allowance": 2, + "runtime_slot_count": 6, + "maximum_active_slots": 5, + "maximum_active_slots_without_psram": 2, + "worker_stack_bytes": 8192, + "client_task_stack_bytes": 6144, + "client_task_priority": 5, + "client_rx_tx_buffer_bytes": 1792, + "maximum_slot_stack_buffer_allowance_bytes": 48384, + "cold_start_allocation_allowance_bytes": 16384, + "cold_start_dma_admission_free_bytes": 32768, + "cold_start_dma_admission_contiguous_bytes": 8192, + "additional_broker_cold_start_dma_admission_contiguous_bytes": 20480, + "restart_allocation_allowance_bytes": 8192, + "restart_dma_admission_free_bytes": 24576, + "restart_dma_admission_contiguous_bytes": 8192, + "additional_broker_restart_dma_admission_contiguous_bytes": 12288, + "dma_admission_free_bytes": 16384, + "dma_admission_contiguous_bytes": 4096, + "publish_contiguous_bytes": 4096, + "publish_contiguous_without_psram_bytes": 8000, + "publish_contiguous_with_psram_bytes": 4096, + "tls_psram_admission_contiguous_bytes": 16000, + "additional_broker_cold_start_tls_psram_admission_contiguous_bytes": 32384, + "additional_broker_restart_tls_psram_admission_contiguous_bytes": 24192, + "additional_broker_tls_psram_admission_contiguous_bytes": 16000, + "tls_internal_admission_free_bytes": 61440, + "tls_record_allocation_bytes": 17408, + "additional_broker_cold_start_tls_internal_admission_contiguous_bytes": 41792, + "additional_broker_restart_tls_internal_admission_contiguous_bytes": 33600, + "tls_internal_admission_contiguous_bytes": 17408, + "additional_broker_tls_internal_admission_contiguous_bytes": 25408, + "maximum_runtime_load_qualified": false, + "physical_validation_performed": false, + "limitations": "Maximum TLS/WSS/outbox workload is not covered by the startup allowance; runtime free DMA heap and fragmentation must satisfy connection admission; additional brokers reserve publication headroom beside startup allocations." + }, + "regions": [ + { + "start": 1070299344, + "end": 1070503696 + }, + { + "start": 1070530560, + "end": 1070563328 + } + ], + "scope": "Linked capacity before runtime allocation; physical boot/load/soak validation remains required.", + "artifact_target": "Station_G2_repeater", + "pio_environment": "Station_G2_repeater_observer_mqtt", + "files": { + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-6a5a04b1.bin": "d9233e7d7282a7f31f1cb23c8c807a59a84ad0fcdca31536b2e5aac8c9749566", + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-6a5a04b1-merged.bin": "22a230d3267af891acc36f0b33c9e05a74b074390191c2c9a73789aaa7d5dd30", + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-6a5a04b1.capabilities.json": "fe8e50e019a13549e74e4be6f60a5c351a723d92dbfa90496752413a954a7864", + "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-6a5a04b1.meshtext-license.txt": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + }, + "capabilities": { + "artifact_target": "Station_G2_repeater", + "build_profile": "full", + "build_recipe": { + "schema_version": 1, + "sha256": "3f316af418f394d506a1af9ad4f5746d3ffaf91a4b240e9f0c6e2c5e4c5677f4" + }, + "capabilities": [ + "bridge.espnow", + "bridge.udp", + "profile.full", + "release.full.partition_migration_ota", + "radio2.kiss.host", + "radio2.kiss.usb", + "radio2.kiss.tcp", + "ota.update.wifi", + "ota.update.lora", + "cli.retry_preset", + "telemetry.history", + "ota.cli", + "web.webconfig", + "logging.usb.packets", + "logging.usb.control", + "logging.usb.output", + "bridge.udp.configuration" + ], + "ota_update_evidence": "firmware fits both OTA application slots; otadata present", + "ota_update_methods": [ + "wifi", + "lora" + ], + "ota_update_verified": true, + "platform": "ESP32_PLATFORM", + "platformio_env": "Station_G2_repeater_observer_mqtt", + "reductions": [], + "schema_version": 2, + "target": "Station_G2_repeater", + "third_party_notices": [ + { + "component": "espp-meshtext", + "file": "Station_G2_repeater-full-usb-wifi-ota-v1.18.0.2-psram-test-6a5a04b1.meshtext-license.txt", + "license": "MIT", + "sha256": "c37d0a453a2491514bac33ed835c1877f207390f134ae439a97b06b004e236d2" + } + ], + "verification": [ + { + "capability": "radio2.kiss.host", + "evidence": "_ZN4mesh8hostkiss5Codec4feedEh; _ZN4mesh8hostkiss5Codec11queueOutputEhPKhj; _ZN4mesh10Dispatcher22queueForeignRadioFrameEPKhjhjNS_20RadioProfileProtocolE", + "present": true, + "source": "linked ELF symbols" + }, + { + "capability": "radio2.kiss.usb", + "evidence": "Error: use set kiss on|off; Error: use set kiss.transport usb|tcp; USB: send KISS FEND to start", + "present": true, + "source": "packaged application" + }, + { + "capability": "radio2.kiss.tcp", + "evidence": "station TCP 5004", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.espnow", + "evidence": "_ZN12ESPNowBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "bridge.udp", + "evidence": "_ZN9UDPBridge5beginEv", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.wifi", + "evidence": "Started: http://%s/update", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.update.lora", + "evidence": "image_hash MISMATCH after decode", + "present": true, + "source": "linked image" + }, + { + "capability": "cli.retry_preset", + "evidence": "retry.preset", + "present": true, + "source": "linked image" + }, + { + "capability": "telemetry.history", + "evidence": "telemetry.volt.i2c", + "present": true, + "source": "linked image" + }, + { + "capability": "ota.cli", + "evidence": "OTA: status", + "present": true, + "source": "linked image" + }, + { + "capability": "web.webconfig", + "evidence": "start webconfig", + "present": true, + "source": "linked image" + }, + { + "capability": "logging.usb.packets", + "evidence": "%s: %s, len=%d (type=%d, route=%s, payload_len=%d)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.control", + "evidence": "OK - USB logging %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "logging.usb.output", + "evidence": "OK - logging.output %s (saved)", + "present": true, + "source": "packaged application" + }, + { + "capability": "bridge.udp.configuration", + "evidence": "udp.enabled", + "present": true, + "source": "packaged application" + } + ], + "verified": true + }, + "application_bytes": 2125736, + "note": "Combined with upstream Companion PIN and startup-screen changes; isolated PSRAM source hashes unchanged." + } +} diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index e99837958..4fed902fb 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -3707,11 +3707,6 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc : mesh::Mesh(radio, ms, rng, rtc, *createObserverPacketManager(32), tables), region_map(key_store), temp_map(key_store), _cli(board, rtc, sensors, region_map, acl, &_prefs, this), -#if MESH_REPEATER_PSRAM_TABLES - flood_channel_scopes(flood_channel_storage.require("channel scope tables").scopes), - flood_channel_direct_scopes(flood_channel_storage.require("channel scope tables").direct), - flood_channel_scope_requirements(flood_channel_storage.require("channel scope tables").requirements), -#endif telemetry(MAX_PACKET_PAYLOAD - 4), discover_limiter(4, 120), // max 4 every 2 minutes anon_limiter(4, 180) // max 4 every 3 minutes @@ -3796,11 +3791,13 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc if (flood_packet_filters) memset(flood_packet_filters, 0, sizeof(FloodPacketFilterEntry) * flood_packet_filter_slots); flood_packet_filter_blacklist_count = 0; memset(flood_packet_filter_blacklist, 0, sizeof(flood_packet_filter_blacklist)); +#if !MESH_REPEATER_PSRAM_TABLES memset(flood_channel_scopes, 0, sizeof(flood_channel_scopes)); memset(flood_channel_direct_scopes, 0, sizeof(flood_channel_direct_scopes)); memset(flood_channel_scope_requirements, 0, sizeof(flood_channel_scope_requirements)); +#endif #if MESH_ENABLE_FLOOD_GROUP_MODERATION memset(flood_group_moderation, 0, sizeof(flood_group_moderation)); #endif @@ -3998,6 +3995,14 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc // OTA mesh-integration (receive/begin/loop) is centralized in mesh::Mesh - no per-example wiring. void MyMesh::begin(FILESYSTEM *fs) { +#if MESH_REPEATER_PSRAM_TABLES + // initArduino() has now initialized either QSPI or OPI PSRAM. Allocate + // complete tables before any routing or persistence can obtain an address. + if (!flood_channel_storage.begin("channel scope tables")) abort(); +#ifndef PORTABLE_MQTT_OBSERVER + if (!flood_advert_limiter.begin()) abort(); +#endif +#endif #if MESH_ENABLE_FLOOD_RULE_ENGINE flood_filter_probability_seed = getRNG()->nextInt(0, UINT32_MAX); #endif @@ -6651,9 +6656,9 @@ bool MyMesh::loadFloodPacketFilters() { if (flood_packet_filters) memset(flood_packet_filters, 0, sizeof(FloodPacketFilterEntry) * flood_packet_filter_slots); flood_channel_data_rule_slot = 0xFF; flood_channel_data_rule_max_hops = FLOOD_CHANNEL_HOPS_ALL; - memset(flood_channel_scopes, 0, sizeof(flood_channel_scopes)); - memset(flood_channel_direct_scopes, 0, - sizeof(flood_channel_direct_scopes)); + memset(floodChannelScopes(), 0, sizeof(floodChannelScopes())); + memset(floodChannelDirectScopes(), 0, + sizeof(floodChannelDirectScopes())); flood_packet_filter_blacklist_count = 0; memset(flood_packet_filter_blacklist, 0, sizeof(flood_packet_filter_blacklist)); @@ -6957,7 +6962,7 @@ bool MyMesh::loadFloodPacketFilters() { size_t retained_scope_bytes = (size_t)retained_scope * sizeof(FloodChannelScopeEntry); success = success - && file.read((uint8_t*)flood_channel_scopes, retained_scope_bytes) + && file.read((uint8_t*)floodChannelScopes(), retained_scope_bytes) == retained_scope_bytes; FloodChannelScopeEntry discarded_scope; for (uint16_t i = retained_scope; success && i < scope_count; i++) { @@ -6981,13 +6986,13 @@ bool MyMesh::loadFloodPacketFilters() { success = false; } if (success && i < retained_direct) { - memcpy(flood_channel_direct_scopes[i], direct_name, + memcpy(floodChannelDirectScopes()[i], direct_name, sizeof(direct_name)); } } for (uint16_t i = 0; success && i < retained_scope; i++) { - auto& entry = flood_channel_scopes[i]; + auto& entry = floodChannelScopes()[i]; uint8_t selector = FloodFilterPolicy::channelScopeMatchSelectorValue(entry.selector); bool direct_target = @@ -6996,7 +7001,7 @@ bool MyMesh::loadFloodPacketFilters() { memset(&entry, 0, sizeof(entry)); } else if (direct_target && (entry.target_id > retained_direct - || flood_channel_direct_scopes[entry.target_id - 1][0] == 0)) { + || floodChannelDirectScopes()[entry.target_id - 1][0] == 0)) { success = false; } else if (selector <= FLOOD_CHANNEL_SCOPE_OTHER_ANY) { entry.channel_hash = 0; @@ -7051,9 +7056,9 @@ bool MyMesh::loadFloodPacketFilters() { file.close(); if (!success) { if (flood_packet_filters) memset(flood_packet_filters, 0, sizeof(FloodPacketFilterEntry) * flood_packet_filter_slots); - memset(flood_channel_scopes, 0, sizeof(flood_channel_scopes)); - memset(flood_channel_direct_scopes, 0, - sizeof(flood_channel_direct_scopes)); + memset(floodChannelScopes(), 0, sizeof(floodChannelScopes())); + memset(floodChannelDirectScopes(), 0, + sizeof(floodChannelDirectScopes())); flood_packet_filter_blacklist_count = 0; memset(flood_packet_filter_blacklist, 0, sizeof(flood_packet_filter_blacklist)); @@ -7577,12 +7582,12 @@ bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase, uint8_t direct_count = 0; if (!empty_scope_phase) { for (uint16_t i = 0; i < FLOOD_CHANNEL_SCOPE_SLOTS; i++) { - if (flood_channel_scopes[i].target_id != 0) { + if (floodChannelScopes()[i].target_id != 0) { scope_count = (uint8_t)(i + 1U); } } for (uint16_t i = 0; i < FLOOD_CHANNEL_DIRECT_SCOPE_SLOTS; i++) { - if (flood_channel_direct_scopes[i][0] != 0) { + if (floodChannelDirectScopes()[i][0] != 0) { direct_count = (uint8_t)(i + 1U); } } @@ -7594,12 +7599,12 @@ bool MyMesh::saveFloodPacketFilters(bool empty_scope_phase, sizeof(flood_channel_data_rule_max_hops)) && writeExact(&scope_count, sizeof(scope_count)); if (success && scope_count != 0) { - success = writeExact(flood_channel_scopes, + success = writeExact(floodChannelScopes(), scope_count * sizeof(FloodChannelScopeEntry)); } success = success && writeExact(&direct_count, sizeof(direct_count)); if (success && direct_count != 0) { - success = writeExact(flood_channel_direct_scopes, + success = writeExact(floodChannelDirectScopes(), direct_count * FLOOD_PACKET_FILTER_SCOPE_NAME_LEN); } @@ -9304,9 +9309,9 @@ static const char* wildcardFloodChannelScopeName(uint8_t selector) { bool MyMesh::loadFloodChannelScopes() { static_assert(sizeof(FloodChannelScopeEntry) == 36, "channel scope persistence requires 36-byte entries"); - memset(flood_channel_scopes, 0, sizeof(flood_channel_scopes)); - memset(flood_channel_direct_scopes, 0, - sizeof(flood_channel_direct_scopes)); + memset(floodChannelScopes(), 0, sizeof(floodChannelScopes())); + memset(floodChannelDirectScopes(), 0, + sizeof(floodChannelDirectScopes())); if (_fs == NULL || !_fs->exists(FLOOD_CHANNEL_SCOPE_FILE)) return true; File file = openFloodSettingsRead(_fs, FLOOD_CHANNEL_SCOPE_FILE); if (!file) return false; @@ -9323,7 +9328,7 @@ bool MyMesh::loadFloodChannelScopes() { && file.read(&count, sizeof(count)) == sizeof(count); uint8_t retained = count < FLOOD_CHANNEL_SCOPE_SLOTS ? count : FLOOD_CHANNEL_SCOPE_SLOTS; size_t retained_bytes = (size_t)retained * sizeof(FloodChannelScopeEntry); - success = success && file.read((uint8_t*)flood_channel_scopes, retained_bytes) == retained_bytes; + success = success && file.read((uint8_t*)floodChannelScopes(), retained_bytes) == retained_bytes; FloodChannelScopeEntry discarded; for (int i = retained; success && i < count; i++) { success = file.read((uint8_t*)&discarded, sizeof(discarded)) == sizeof(discarded); @@ -9338,10 +9343,10 @@ bool MyMesh::loadFloodChannelScopes() { size_t retained_direct_bytes = (size_t)retained_direct * FLOOD_PACKET_FILTER_SCOPE_NAME_LEN; success = success - && file.read((uint8_t*)flood_channel_direct_scopes, + && file.read((uint8_t*)floodChannelDirectScopes(), retained_direct_bytes) == retained_direct_bytes; for (int i = 0; success && i < retained_direct; i++) { - char* name = flood_channel_direct_scopes[i]; + char* name = floodChannelDirectScopes()[i]; if (name[0] != 0 && !isValidStoredFloodFilterScopeName(name)) { success = false; } @@ -9349,7 +9354,7 @@ bool MyMesh::loadFloodChannelScopes() { } for (int i = 0; success && i < retained; i++) { - auto& entry = flood_channel_scopes[i]; + auto& entry = floodChannelScopes()[i]; if ((version_1 && (FloodFilterPolicy::scopeUsesSlowTiming(entry.selector) || FloodFilterPolicy::scopeRequiresPath(entry.selector))) @@ -9370,7 +9375,7 @@ bool MyMesh::loadFloodChannelScopes() { } else if (direct_target && (!version_5 || entry.target_id > FLOOD_CHANNEL_DIRECT_SCOPE_SLOTS - || flood_channel_direct_scopes[entry.target_id - 1][0] == 0)) { + || floodChannelDirectScopes()[entry.target_id - 1][0] == 0)) { success = false; break; } else if (selector <= FLOOD_CHANNEL_SCOPE_OTHER_ANY) { @@ -9390,9 +9395,9 @@ bool MyMesh::loadFloodChannelScopes() { // A malformed table is inert; corrupt storage must never assign a scope. if (!success) { - memset(flood_channel_scopes, 0, sizeof(flood_channel_scopes)); - memset(flood_channel_direct_scopes, 0, - sizeof(flood_channel_direct_scopes)); + memset(floodChannelScopes(), 0, sizeof(floodChannelScopes())); + memset(floodChannelDirectScopes(), 0, + sizeof(floodChannelDirectScopes())); } return success; } @@ -9410,17 +9415,17 @@ bool MyMesh::saveFloodChannelScopes(bool empty_table) { bool success = file.write(magic, sizeof(magic)) == sizeof(magic) && file.write(&count, sizeof(count)) == sizeof(count); if (success && count != 0) { - success = file.write((const uint8_t*)flood_channel_scopes, - sizeof(flood_channel_scopes)) == sizeof(flood_channel_scopes); + success = file.write((const uint8_t*)floodChannelScopes(), + sizeof(floodChannelScopes())) == sizeof(floodChannelScopes()); } uint8_t direct_count = empty_table ? 0 : FLOOD_CHANNEL_DIRECT_SCOPE_SLOTS; success = success && file.write(&direct_count, sizeof(direct_count)) == sizeof(direct_count); if (success && direct_count != 0) { - success = file.write((const uint8_t*)flood_channel_direct_scopes, - sizeof(flood_channel_direct_scopes)) - == sizeof(flood_channel_direct_scopes); + success = file.write((const uint8_t*)floodChannelDirectScopes(), + sizeof(floodChannelDirectScopes())) + == sizeof(floodChannelDirectScopes()); } file.close(); if (!success || !_fs->rename(FLOOD_CHANNEL_SCOPE_TEMP_FILE, FLOOD_CHANNEL_SCOPE_FILE)) { @@ -9432,8 +9437,8 @@ bool MyMesh::saveFloodChannelScopes(bool empty_table) { } void MyMesh::loadFloodChannelScopeRequirements() { - memset(flood_channel_scope_requirements, 0, - sizeof(flood_channel_scope_requirements)); + memset(floodChannelScopeRequirements(), 0, + sizeof(floodChannelScopeRequirements())); if (_fs == NULL || !_fs->exists(FLOOD_CHANNEL_SCOPE_REQUIRE_FILE)) return; File file = @@ -9450,7 +9455,7 @@ void MyMesh::loadFloodChannelScopeRequirements() { size_t retained_bytes = (size_t)retained * sizeof(FloodChannelScopeRequireEntry); success = success - && file.read((uint8_t*)flood_channel_scope_requirements, retained_bytes) + && file.read((uint8_t*)floodChannelScopeRequirements(), retained_bytes) == retained_bytes; FloodChannelScopeRequireEntry discarded; for (int i = retained; success && i < count; i++) { @@ -9458,7 +9463,7 @@ void MyMesh::loadFloodChannelScopeRequirements() { == sizeof(discarded); } for (int i = 0; success && i < retained; i++) { - auto& entry = flood_channel_scope_requirements[i]; + auto& entry = floodChannelScopeRequirements()[i]; if (entry.key_len == 0) { memset(&entry, 0, sizeof(entry)); } else if (entry.key_len == CIPHER_KEY_SIZE @@ -9476,8 +9481,8 @@ void MyMesh::loadFloodChannelScopeRequirements() { file.close(); if (!success) { - memset(flood_channel_scope_requirements, 0, - sizeof(flood_channel_scope_requirements)); + memset(floodChannelScopeRequirements(), 0, + sizeof(floodChannelScopeRequirements())); } } @@ -9500,9 +9505,9 @@ bool MyMesh::saveFloodChannelScopeRequirements(bool empty_table) { } } else if (success) { success = file.write( - (const uint8_t*)flood_channel_scope_requirements, - sizeof(flood_channel_scope_requirements)) - == sizeof(flood_channel_scope_requirements); + (const uint8_t*)floodChannelScopeRequirements(), + sizeof(floodChannelScopeRequirements())) + == sizeof(floodChannelScopeRequirements()); } file.close(); if (!success || !_fs->rename(FLOOD_CHANNEL_SCOPE_REQUIRE_TEMP_FILE, @@ -9528,7 +9533,7 @@ bool MyMesh::findFloodChannelScopeRequirementMatch( % CIPHER_BLOCK_SIZE) == 0; for (int i = 0; i < FLOOD_CHANNEL_SCOPE_REQUIRE_SLOTS; i++) { - const auto& entry = flood_channel_scope_requirements[i]; + const auto& entry = floodChannelScopeRequirements()[i]; if (entry.key_len == 0) continue; table_active = true; if (!valid_layout || packet->payload[0] != entry.channel_hash) continue; @@ -9558,7 +9563,7 @@ bool MyMesh::applyFloodChannelScopeTarget(mesh::Packet* packet, bool direct_target = FloodFilterPolicy::channelScopeUsesDirectTarget(entry.selector); if (direct_target) { - target_name = flood_channel_direct_scopes[entry.target_id - 1]; + target_name = floodChannelDirectScopes()[entry.target_id - 1]; deriveFloodFilterScopeKey(target_name, scope); regionless_scope_set = true; } else { @@ -9644,7 +9649,7 @@ bool MyMesh::applyFloodChannelScope(mesh::Packet* packet, bool& fast_track, uint8_t data[MAX_PACKET_PAYLOAD]; for (int path_pass = 1; path_pass >= 0; path_pass--) { for (int i = 0; i < FLOOD_CHANNEL_SCOPE_SLOTS; i++) { - const auto& entry = flood_channel_scopes[i]; + const auto& entry = floodChannelScopes()[i]; if (entry.target_id == 0 || !isExactFloodChannelScopeSelector(entry.selector) || packet->payload[0] != entry.channel_hash || !matches_path_pass(entry.selector, path_pass != 0)) { @@ -9665,7 +9670,7 @@ bool MyMesh::applyFloodChannelScope(mesh::Packet* packet, bool& fast_track, uint8_t wildcard = wildcardFloodChannelScopeSelector(type); for (int path_pass = 1; path_pass >= 0; path_pass--) { for (int i = 0; i < FLOOD_CHANNEL_SCOPE_SLOTS; i++) { - const auto& entry = flood_channel_scopes[i]; + const auto& entry = floodChannelScopes()[i]; if (entry.target_id != 0 && FloodFilterPolicy::channelScopeMatchSelectorValue(entry.selector) == wildcard @@ -9819,7 +9824,7 @@ void MyMesh::formatFloodChannelScopeDetail(int index, char* reply, size_t reply_ snprintf(reply, reply_len, "Err - scope slot must be 1-%d", FLOOD_CHANNEL_SCOPE_SLOTS); return; } - const auto& entry = flood_channel_scopes[index]; + const auto& entry = floodChannelScopes()[index]; if (entry.target_id == 0) { snprintf(reply, reply_len, "> %d empty", index + 1); return; @@ -9856,7 +9861,7 @@ void MyMesh::formatFloodChannelScopeDetail(int index, char* reply, size_t reply_ if (FloodFilterPolicy::channelScopeUsesDirectTarget(entry.selector)) { snprintf(reply, reply_len, "> %d match=%s%s scope=%s%s", index + 1, channel, path, - flood_channel_direct_scopes[entry.target_id - 1], timing); + floodChannelDirectScopes()[entry.target_id - 1], timing); return; } @@ -9888,7 +9893,7 @@ void MyMesh::formatFloodChannelScopes(const char* args, char* reply) { int active = 0; for (int i = 0; i < FLOOD_CHANNEL_SCOPE_SLOTS; i++) { - if (flood_channel_scopes[i].target_id != 0) active++; + if (floodChannelScopes()[i].target_id != 0) active++; } #if defined(STM32_PLATFORM) snprintf(reply, 160, "> %d/%d", @@ -10016,7 +10021,7 @@ void MyMesh::setFloodChannelScope(const char* args, char* reply) { int matching_slot = -1; int free_slot = -1; for (int i = 0; i < FLOOD_CHANNEL_SCOPE_SLOTS; i++) { - const auto& entry = flood_channel_scopes[i]; + const auto& entry = floodChannelScopes()[i]; if (entry.target_id == 0) { if (free_slot < 0) free_slot = i; } else if (FloodFilterPolicy::channelScopeMatchSelectorValue( @@ -10042,7 +10047,7 @@ void MyMesh::setFloodChannelScope(const char* args, char* reply) { if (direct_target) { int reusable_direct_scope_slot = -1; for (int i = 0; i < FLOOD_CHANNEL_DIRECT_SCOPE_SLOTS; i++) { - if (strcmp(flood_channel_direct_scopes[i], direct_scope_name) == 0) { + if (strcmp(floodChannelDirectScopes()[i], direct_scope_name) == 0) { direct_scope_slot = i; break; } @@ -10050,7 +10055,7 @@ void MyMesh::setFloodChannelScope(const char* args, char* reply) { bool referenced = false; for (int row = 0; row < FLOOD_CHANNEL_SCOPE_SLOTS; row++) { if (row == slot) continue; - const auto& entry = flood_channel_scopes[row]; + const auto& entry = floodChannelScopes()[row]; if (entry.target_id == i + 1 && FloodFilterPolicy::channelScopeUsesDirectTarget( entry.selector)) { @@ -10070,15 +10075,15 @@ void MyMesh::setFloodChannelScope(const char* args, char* reply) { strcpy(reply, "Err - scope table full"); return; } - if (strcmp(flood_channel_direct_scopes[direct_scope_slot], + if (strcmp(floodChannelDirectScopes()[direct_scope_slot], direct_scope_name) != 0) { changed_direct_scope_slot = direct_scope_slot; memcpy(previous_direct_scope, - flood_channel_direct_scopes[direct_scope_slot], + floodChannelDirectScopes()[direct_scope_slot], sizeof(previous_direct_scope)); - StrHelper::strzcpy(flood_channel_direct_scopes[direct_scope_slot], + StrHelper::strzcpy(floodChannelDirectScopes()[direct_scope_slot], direct_scope_name, - sizeof(flood_channel_direct_scopes[direct_scope_slot])); + sizeof(floodChannelDirectScopes()[direct_scope_slot])); } } @@ -10086,8 +10091,8 @@ void MyMesh::setFloodChannelScope(const char* args, char* reply) { FloodFilterPolicy::encodeChannelScopeTargetSelector(selector, direct_target); - FloodChannelScopeEntry previous = flood_channel_scopes[slot]; - auto& entry = flood_channel_scopes[slot]; + FloodChannelScopeEntry previous = floodChannelScopes()[slot]; + auto& entry = floodChannelScopes()[slot]; memset(&entry, 0, sizeof(entry)); entry.target_id = direct_target ? direct_scope_slot + 1 : region->id; entry.selector = @@ -10100,7 +10105,7 @@ void MyMesh::setFloodChannelScope(const char* args, char* reply) { if (!saveFloodChannelScopes()) { entry = previous; if (changed_direct_scope_slot >= 0) { - memcpy(flood_channel_direct_scopes[changed_direct_scope_slot], + memcpy(floodChannelDirectScopes()[changed_direct_scope_slot], previous_direct_scope, sizeof(previous_direct_scope)); } strcpy(reply, "Err - save failed"); @@ -10115,9 +10120,9 @@ void MyMesh::deleteFloodChannelScope(const char* args, char* reply) { if (!saveFloodChannelScopes(true)) { strcpy(reply, "Err - save failed"); } else { - memset(flood_channel_scopes, 0, sizeof(flood_channel_scopes)); - memset(flood_channel_direct_scopes, 0, - sizeof(flood_channel_direct_scopes)); + memset(floodChannelScopes(), 0, sizeof(floodChannelScopes())); + memset(floodChannelDirectScopes(), 0, + sizeof(floodChannelDirectScopes())); strcpy(reply, "OK"); } return; @@ -10129,14 +10134,14 @@ void MyMesh::deleteFloodChannelScope(const char* args, char* reply) { strcpy(reply, "Err - use: del flood.channel.scope.