diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f1edc4d0b..ddd0155cd 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -355,53 +355,59 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks size_t serial_log_remaining = 0; size_t serial_log_pending_size = 0; char serial_log_pending[640]; + int serial_acl_next = -1; + int serial_acl_count = 0; + int serial_recent_next = -1; + int serial_recent_count = 0; + SimpleMeshTables::RecentRepeaterInfo serial_recent_cursor; + int serial_recent_cursor_index = -1; bool serial_log_active = false; bool serial_log_eof_pending = false; bool serial_log_skip_line = false; - int serial_acl_next = -1; - int serial_acl_count = 0; bool serial_acl_header = false; - int serial_recent_next = -1; - int serial_recent_count = 0; bool serial_recent_header = false; bool serial_recent_has_cursor = false; - SimpleMeshTables::RecentRepeaterInfo serial_recent_cursor; - int serial_recent_cursor_index = -1; +#endif + // Runtime only: group scalar timers and flags to avoid gaps before aligned + // members. No persisted preferences or queue capacities depend on this order. + uint64_t uptime_millis; + uint64_t last_battery_alert_sent; +#if MESH_ENABLE_HOST_CLI + uint64_t host_cli_nonce; + uint64_t host_cli_claim_challenge; #endif uint32_t last_millis; - uint64_t uptime_millis; unsigned long next_local_advert, next_flood_advert; + uint32_t primary_radio_mutation_generation = 0; + uint32_t radio_reply_deadline = 0; +#if MESH_ENABLE_HOST_CLI + unsigned long host_cli_deadline; + unsigned long host_cli_claim_emit_at; +#endif + uint32_t pending_self_advert_delay; + unsigned long next_battery_alert_check; + unsigned long next_recent_repeater_sweep; mesh::DeferredCliCommand deferred_cli_command; mesh::RemoteCliReplyCache remote_cli_reply_cache; mesh::ReplayResetNonce replay_reset_nonce; - bool replay_clock_set = false; mesh::TempRadioReplyBarrier temp_radio_reply_barrier; - uint32_t primary_radio_mutation_generation = 0; - bool primary_radio_mutation_starts_temp = false; - uint32_t radio_reply_deadline = 0; - bool radio_reply_secondary = false; void finishRadioReply(bool delivered); void serviceRadioReplyDeadline(); TransportKey deferred_cli_reply_scope; + mesh::RxInactivityWatchdog rx_inactivity_watchdog; + mesh::RepeaterRadioTiming radio_timing; + mesh::Packet* pending_battery_alert_packet; + bool replay_clock_set = false; + bool primary_radio_mutation_starts_temp = false; + bool radio_reply_secondary = false; bool deferred_cli_reply_scoped; #if MESH_ENABLE_HOST_CLI bool host_cli_waiting; bool host_cli_claimed; bool host_cli_claim_emit; - unsigned long host_cli_deadline; - unsigned long host_cli_claim_emit_at; - uint64_t host_cli_nonce; - uint64_t host_cli_claim_challenge; #endif - uint32_t pending_self_advert_delay; bool pending_self_advert; bool pending_self_advert_flood; - unsigned long next_battery_alert_check; - mesh::RxInactivityWatchdog rx_inactivity_watchdog; - mesh::RepeaterRadioTiming radio_timing; - unsigned long next_recent_repeater_sweep; - uint64_t last_battery_alert_sent; - mesh::Packet* pending_battery_alert_packet; bool battery_alert_sent; bool _logging; #ifdef DISPLAY_ACTIVITY_DASHBOARD diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 8fcbdc448..c46f4c52d 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -944,7 +944,6 @@ class CommonCLI { #if MESH_ENABLE_EMERGENCY_POWER FILESYSTEM* _emergency_power_fs = nullptr; mesh::emergencypower::State _emergency_power; - bool _emergency_radio_sleeping = false; void loadEmergencyPower(FILESYSTEM* fs); bool saveEmergencyPower(const mesh::emergencypower::Settings& settings); void loopEmergencyPower(); @@ -968,7 +967,6 @@ class CommonCLI { SensorManager* _sensors; RegionMap* _region_map; uint32_t _media_regions_retry_ms = 0; - bool _media_regions_ready = false; static bool mediaMeshRegionStoreMissing(FILESYSTEM* fs); void recoverMediaMeshRegions(); static bool updateMediaMeshScope(void* context, bool enable, bool commit); @@ -989,6 +987,12 @@ class CommonCLI { bool _observer_save_result_known = false; bool _observer_save_succeeded = false; #endif + // Runtime flags share padding; persisted NodePrefs retains its own layout. + bool _media_regions_ready = false; +#if MESH_ENABLE_EMERGENCY_POWER + bool _emergency_radio_sleeping = false; +#endif + uint8_t _rx_settings_pending = 0; bool _com_prefs_needs_upgrade = false; // old-format legacy prefs detected; rewrite once after load bool _common_save_result_known = false; bool _common_save_succeeded = false; @@ -1004,7 +1008,6 @@ class CommonCLI { RxGain = 1, RxPowerSaving = 2, RxSideDetectors = 4, RxRfRx = 8, RxPendingMask = 15, RxRfRxDisabled = 128 }; - uint8_t _rx_settings_pending = 0; uint32_t _rx_settings_retry_at = 0; uint32_t _prefs_save_failures = 0; mesh::RadioProfileCLI _radio_profiles; diff --git a/src/helpers/EmergencyPowerPolicy.h b/src/helpers/EmergencyPowerPolicy.h index 58bdbce70..7af46aa66 100644 --- a/src/helpers/EmergencyPowerPolicy.h +++ b/src/helpers/EmergencyPowerPolicy.h @@ -44,18 +44,19 @@ inline bool decode(const uint8_t* in, size_t size, Settings& settings) { } struct State { - Settings settings; + // Runtime only: keep aligned timers together without packing accesses. uint32_t sampledAt = 0; - uint16_t millivolts = 0; - uint8_t percentage = 0; - bool sampled = false; - bool valid = false; - bool lowBattery = false; uint32_t phaseMs = 0; uint32_t lastTick = 0; uint32_t activationStarted = 0; uint32_t awakeStarted = 0; uint32_t awakeDuration = 0; + uint16_t millivolts = 0; + uint8_t percentage = 0; + Settings settings; + bool sampled = false; + bool valid = false; + bool lowBattery = false; bool activationHold = false; void begin(const Settings& loaded, uint32_t now) { @@ -118,5 +119,7 @@ struct State { return phase < AwakeWindowMs ? 0 : HourMs - phase; } }; +static_assert(sizeof(State) <= 32, "Emergency timers must retain their compact RAM layout"); +static_assert(alignof(State) >= alignof(uint32_t), "Emergency timers must remain aligned"); }} // namespace mesh::emergencypower diff --git a/test/test_emergency_power.py b/test/test_emergency_power.py index bf74e3d12..da6f84029 100644 --- a/test/test_emergency_power.py +++ b/test/test_emergency_power.py @@ -174,6 +174,7 @@ class EmergencyPowerTests(unittest.TestCase): start = header.index(marker) return header[start:header.index("#endif", start) + len("#endif")] private = api_block("#if MESH_ENABLE_EMERGENCY_POWER\n FILESYSTEM* _emergency_power_fs") + private += "\n" + api_block("#if MESH_ENABLE_EMERGENCY_POWER\n bool _emergency_radio_sleeping") public = api_block("#if MESH_ENABLE_EMERGENCY_POWER\n uint32_t emergencySleepMilliseconds") implementation = (ROOT / "src/helpers/CommonCLI_Emergency.cpp").read_text(encoding="ascii") implementation = re.sub(r"^#include[^\n]*\n", "", implementation, flags=re.MULTILINE)