Add UNIT_OF_MEASUREMENT_MAX_LENGTH = 63 constant in core/config.py
and enforce it in sensor and number component validators.
Annotate unit_of_measurement proto fields with (max_data_length) = 63
so the codegen emits constant-size length varint calculations.
Replace 3-call sequence (write_raw_byte + write_raw_byte + encode_raw)
with single inlined encode_raw_short_string call for forced string
fields with max_data_length < 128. The compiler can hoist the pos
pointer across all three writes in one function boundary.
Add max_data_length field option for string/bytes fields. When
max_data_length < 128, the codegen emits constant-size length varint
calculations and direct byte writes.
Annotate all entity name and object_id fields with
(max_data_length) = 120 and (force) = true across all 25
ListEntities*Response messages (50 fields).
Generated code changes:
- calculate_size: `calc_length(1, size)` -> `2 + size` (constant)
- encode: `encode_string(N, ref)` -> `write_raw_byte(tag) + write_raw_byte(len) + encode_raw(data, len)`
Eliminates 2 function calls per field per entity list response,
removes zero-check branches, and removes varint size computation.
Refactor the constant-size varint pattern into a reusable helper
method on the TypeInfo base class, used by both UInt32Type (max_value)
and FixedArrayBytesType (fixed_array_size < 128).
Add a max_value field option to api_options.proto that tells the code
generator the maximum value a field can have. When max_value < 128,
the generated calculate_size() uses constant arithmetic instead of
calling varint size functions, and encode() uses direct byte writes
instead of varint encoding.
Also optimize FixedArrayBytesType: when fixed_array_size < 128, the
length varint is always 1 byte, so calculate_size() uses constant
arithmetic and encode() uses write_raw_byte for the length.
Applied to BluetoothLERawAdvertisement.address_type (max_value=4).
Measured on ESP32 (upstairsdesk89proxy):
- BluetoothLERawAdvertisement::calculate_size: 88 → 71 bytes (-19%)
- BluetoothLERawAdvertisement::encode: 199 → 179 bytes (-10%)
- Total BLE proxy hot path: 1807 → 1770 bytes (-37 bytes)
Replace empty() + pop() with pop() directly as the fast-path check:
- LockFreeQueue: pop() costs 1 memw (acquire on tail_) vs empty()'s
2 memw (acquire on both head_ and tail_) on Xtensa
- FreeRTOSQueue: pop() is 1 critical section vs empty() + pop() = 2
Also move dropped count check after the drain loop since drops can
only occur when the queue was full, and only this loop drains it.
On Xtensa (dual-core ESP32), even relaxed atomic loads emit a memw
barrier instruction. The dropped_count_ check in get_and_reset_dropped_count()
was executing every loop() iteration even when the queue was empty.
Since drops only occur when the queue is full, and only the main loop
drains the queue, an empty queue guarantees no new drops since the last
reset. Restructure the loop to early-return when pop() returns nullptr,
skipping the unnecessary memw.
Also moves advertising_->loop() before the queue drain so it runs
unconditionally without duplication, and converts while to do/while
since the first element is known non-null after the nullptr check.