Merge branch 'mopeka_stack_format' into integration

This commit is contained in:
J. Nick Koston
2026-01-05 08:25:55 -10:00
9 changed files with 151 additions and 43 deletions
+87 -1
View File
@@ -88,6 +88,77 @@ SYMBOL_PATTERNS = {
"sys_mbox_new",
"sys_arch_mbox_tryfetch",
],
# LibreTiny/Beken BK7231 radio calibration
"bk_radio_cal": [
"bk7011_",
"calibration_main",
"gcali_",
"rwnx_cal",
],
# LibreTiny/Beken WiFi MAC layer
"bk_wifi_mac": [
"rxu_", # RX upper layer
"txu_", # TX upper layer
"txl_", # TX lower layer
"rxl_", # RX lower layer
"scanu_", # Scan unit
"mm_hw_", # MAC management hardware
"mm_bcn", # MAC management beacon
"mm_tim", # MAC management TIM
"mm_check", # MAC management checks
"sm_connect", # Station management
"me_beacon", # Management entity beacon
"me_build", # Management entity build
"hapd_", # Host AP daemon
"chan_pre_", # Channel management
"handle_probe_", # Probe handling
],
# LibreTiny/Beken system control
"bk_system": [
"sctrl_", # System control
"icu_ctrl", # Interrupt control unit
"gdma_ctrl", # DMA control
"mpb_ctrl", # MPB control
"uf2_", # UF2 OTA
"bkreg_", # Beken registers
],
# LibreTiny/Beken BLE stack
"bk_ble": [
"gapc_", # GAP client
"gattc_", # GATT client
"attc_", # ATT client
"attmdb_", # ATT database
"atts_", # ATT server
"l2cc_", # L2CAP
"prf_env", # Profile environment
],
# LibreTiny/Beken scheduler
"bk_scheduler": [
"sch_plan_", # Scheduler plan
"sch_prog_", # Scheduler program
"sch_arb_", # Scheduler arbiter
],
# LibreTiny/Beken DMA descriptors
"bk_dma": [
"rx_payload_desc",
"rx_dma_hdrdesc",
"tx_hw_desc",
"host_event_data",
"host_cmd_data",
],
# ARM EABI compiler runtime (LibreTiny uses ARM Cortex-M)
"arm_runtime": [
"__aeabi_",
"__adddf3",
"__subdf3",
"__muldf3",
"__divdf3",
"__addsf3",
"__subsf3",
"__mulsf3",
"__divsf3",
"__gnu_unwind",
],
"xtensa": ["xt_", "_xt_", "xPortEnterCriticalTimeout"],
"heap": ["heap_", "multi_heap"],
"spi_flash": ["spi_flash"],
@@ -782,7 +853,22 @@ SYMBOL_PATTERNS = {
"math_internal": ["__mdiff", "__lshift", "__mprec_tens", "quorem"],
"character_class": ["__chclass"],
"camellia": ["camellia_", "camellia_feistel"],
"crypto_tables": ["FSb", "FSb2", "FSb3", "FSb4"],
"crypto_tables": [
"FSb",
"FSb2",
"FSb3",
"FSb4",
"Te0", # AES encryption table
"Td0", # AES decryption table
"crc32_table", # CRC32 lookup table
"crc_tab", # CRC lookup table
],
"crypto_hash": [
"SHA1Transform", # SHA1 hash function
"MD5Transform", # MD5 hash function
"SHA256",
"SHA512",
],
"event_buffer": ["g_eb_list_desc", "eb_space"],
"base_node": ["base_node_", "base_node_add_handler"],
"file_descriptor": ["s_fd_table"],
+2 -4
View File
@@ -15,10 +15,8 @@ namespace alpha3 {
namespace espbt = esphome::esp32_ble_tracker;
static const espbt::ESPBTUUID ALPHA3_GENI_SERVICE_UUID = espbt::ESPBTUUID::from_uint16(0xfe5d);
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID =
espbt::ESPBTUUID::from_raw({static_cast<char>(0xa9), 0x7b, static_cast<char>(0xb8), static_cast<char>(0x85), 0x0,
0x1a, 0x28, static_cast<char>(0xaa), 0x2a, 0x43, 0x6e, 0x3, static_cast<char>(0xd1),
static_cast<char>(0xff), static_cast<char>(0x9c), static_cast<char>(0x85)});
static const espbt::ESPBTUUID ALPHA3_GENI_CHARACTERISTIC_UUID = espbt::ESPBTUUID::from_raw(
{0xa9, 0x7b, 0xb8, 0x85, 0x00, 0x1a, 0x28, 0xaa, 0x2a, 0x43, 0x6e, 0x03, 0xd1, 0xff, 0x9c, 0x85});
static const int16_t GENI_RESPONSE_HEADER_LENGTH = 13;
static const size_t GENI_RESPONSE_TYPE_LENGTH = 8;
+3 -1
View File
@@ -31,7 +31,9 @@ class ESPBTUUID {
static ESPBTUUID from_raw(const char *data, size_t length);
static ESPBTUUID from_raw(const char *data) { return from_raw(data, strlen(data)); }
static ESPBTUUID from_raw(const std::string &data) { return from_raw(data.c_str(), data.length()); }
static ESPBTUUID from_raw(std::initializer_list<char> data) { return from_raw(data.begin(), data.size()); }
static ESPBTUUID from_raw(std::initializer_list<uint8_t> data) {
return from_raw(reinterpret_cast<const char *>(data.begin()), data.size());
}
static ESPBTUUID from_uuid(esp_bt_uuid_t uuid);
+4 -2
View File
@@ -62,7 +62,8 @@ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
const bool sync_button_pressed = (manu_data.data[3] & 0x80) != 0;
if (this->show_sensors_without_sync_ || sync_button_pressed) {
ESP_LOGI(TAG, "MOPEKA STD (CC2540) SENSOR FOUND: %s", device.address_str().c_str());
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGI(TAG, "MOPEKA STD (CC2540) SENSOR FOUND: %s", device.address_str_to(addr_buf));
}
// Is the device maybe a Mopeka Pro (NRF52) sensor.
@@ -78,7 +79,8 @@ bool MopekaListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
const bool sync_button_pressed = (manu_data.data[2] & 0x80) != 0;
if (this->show_sensors_without_sync_ || sync_button_pressed) {
ESP_LOGI(TAG, "MOPEKA PRO (NRF52) SENSOR FOUND: %s", device.address_str().c_str());
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGI(TAG, "MOPEKA PRO (NRF52) SENSOR FOUND: %s", device.address_str_to(addr_buf));
}
}
@@ -31,7 +31,8 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
return false;
}
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str_to(addr_buf));
const auto &manu_datas = device.get_manufacturer_datas();
@@ -35,15 +35,17 @@ void MopekaStdCheck::dump_config() {
* update the sensor state data.
*/
bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
{
// Validate address.
if (device.address_uint64() != this->address_) {
return false;
}
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
// Validate address.
if (device.address_uint64() != this->address_) {
return false;
}
// Stack buffer for MAC address formatting - reused throughout function
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
const char *addr_str = device.address_str_to(addr_buf);
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", addr_str);
{
// Validate service uuid
const auto &service_uuids = device.get_service_uuids();
@@ -59,7 +61,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
const auto &manu_datas = device.get_manufacturer_datas();
if (manu_datas.size() != 1) {
ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", device.address_str().c_str(), manu_datas.size());
ESP_LOGE(TAG, "[%s] Unexpected manu_datas size (%d)", addr_str, manu_datas.size());
return false;
}
@@ -68,11 +70,11 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
char hex_buf[format_hex_pretty_size(MOPEKA_MAX_LOG_BYTES)];
#endif
ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", device.address_str().c_str(),
ESP_LOGVV(TAG, "[%s] Manufacturer data: %s", addr_str,
format_hex_pretty_to(hex_buf, manu_data.data.data(), manu_data.data.size()));
if (manu_data.data.size() != MANUFACTURER_DATA_LENGTH) {
ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", device.address_str().c_str(), manu_data.data.size());
ESP_LOGE(TAG, "[%s] Unexpected manu_data size (%d)", addr_str, manu_data.data.size());
return false;
}
@@ -82,21 +84,21 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
const u_int8_t hardware_id = mopeka_data->data_1 & 0xCF;
if (static_cast<SensorType>(hardware_id) != STANDARD && static_cast<SensorType>(hardware_id) != XL &&
static_cast<SensorType>(hardware_id) != ETRAILER && static_cast<SensorType>(hardware_id) != STANDARD_ALT) {
ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", device.address_str().c_str(), hardware_id);
ESP_LOGE(TAG, "[%s] Unsupported Sensor Type (0x%X)", addr_str, hardware_id);
return false;
}
ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", device.address_str().c_str(), mopeka_data->slow_update_rate);
ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", device.address_str().c_str(), mopeka_data->sync_pressed);
ESP_LOGVV(TAG, "[%s] Sensor slow update rate: %d", addr_str, mopeka_data->slow_update_rate);
ESP_LOGVV(TAG, "[%s] Sensor sync pressed: %d", addr_str, mopeka_data->sync_pressed);
for (u_int8_t i = 0; i < 3; i++) {
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 1,
mopeka_data->val[i].value_0, mopeka_data->val[i].time_0);
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 2,
mopeka_data->val[i].value_1, mopeka_data->val[i].time_1);
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 3,
mopeka_data->val[i].value_2, mopeka_data->val[i].time_2);
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", device.address_str().c_str(), (i * 4) + 4,
mopeka_data->val[i].value_3, mopeka_data->val[i].time_3);
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 1, mopeka_data->val[i].value_0,
mopeka_data->val[i].time_0);
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 2, mopeka_data->val[i].value_1,
mopeka_data->val[i].time_1);
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 3, mopeka_data->val[i].value_2,
mopeka_data->val[i].time_2);
ESP_LOGVV(TAG, "[%s] %u. Sensor data %u time %u.", addr_str, (i * 4) + 4, mopeka_data->val[i].value_3,
mopeka_data->val[i].time_3);
}
// Get battery level first
@@ -163,12 +165,12 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
}
}
ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", device.address_str().c_str(),
number_of_usable_values, best_value, best_time);
ESP_LOGV(TAG, "[%s] Found %u values with best data %u time %u.", addr_str, number_of_usable_values, best_value,
best_time);
if (number_of_usable_values < 1 || best_value < 2 || best_time < 2) {
// At least two measurement values must be present.
ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", device.address_str().c_str());
ESP_LOGW(TAG, "[%s] Poor read quality. Setting distance to 0.", addr_str);
if (this->distance_ != nullptr) {
this->distance_->publish_state(0);
}
@@ -177,7 +179,7 @@ bool MopekaStdCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
}
} else {
float lpg_speed_of_sound = this->get_lpg_speed_of_sound_(temp_in_c);
ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", device.address_str().c_str(), lpg_speed_of_sound);
ESP_LOGV(TAG, "[%s] Speed of sound in current fluid %f m/s", addr_str, lpg_speed_of_sound);
uint32_t distance_value = lpg_speed_of_sound * best_time / 100.0f;
+10 -2
View File
@@ -163,6 +163,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
}
uint8_t remote_state[WHIRLPOOL_STATE_LENGTH] = {0};
bool skip_footer = false;
// Read all bytes.
for (int i = 0; i < WHIRLPOOL_STATE_LENGTH; i++) {
// Read bit
@@ -170,6 +171,13 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
if (!data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_GAP))
return false;
}
if (i == 14 && !data.is_valid()) {
// Remote control only sent 14 bytes, nothing more to read, not even the footer
ESP_LOGV(TAG, "Remote control only sent %d bytes", i);
skip_footer = true;
break;
}
for (int j = 0; j < 8; j++) {
if (data.expect_item(WHIRLPOOL_BIT_MARK, WHIRLPOOL_ONE_SPACE)) {
remote_state[i] |= 1 << j;
@@ -183,7 +191,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
ESP_LOGVV(TAG, "Byte %d %02X", i, remote_state[i]);
}
// Validate footer
if (!data.expect_mark(WHIRLPOOL_BIT_MARK)) {
if (!data.expect_mark(WHIRLPOOL_BIT_MARK) && !skip_footer) {
ESP_LOGV(TAG, "Footer fail");
return false;
}
@@ -196,7 +204,7 @@ bool WhirlpoolClimate::on_receive(remote_base::RemoteReceiveData data) {
for (uint8_t i = 14; i < 20; i++)
checksum20 ^= remote_state[i];
if (checksum13 != remote_state[13] || checksum20 != remote_state[20]) {
if (checksum13 != remote_state[13] || (!skip_footer && checksum20 != remote_state[20])) {
ESP_LOGVV(TAG, "Checksum fail");
return false;
}
+7 -2
View File
@@ -92,18 +92,23 @@ def run_detailed_analysis(build_dir: str) -> dict | None:
print(f"Build directory not found: {build_dir}", file=sys.stderr)
return None
# Find firmware.elf
# Find firmware.elf (or raw_firmware.elf for LibreTiny)
elf_path = None
for elf_candidate in [
build_path / "firmware.elf",
build_path / ".pioenvs" / build_path.name / "firmware.elf",
# LibreTiny uses raw_firmware.elf
build_path / "raw_firmware.elf",
build_path / ".pioenvs" / build_path.name / "raw_firmware.elf",
]:
if elf_candidate.exists():
elf_path = str(elf_candidate)
break
if not elf_path:
print(f"firmware.elf not found in {build_dir}", file=sys.stderr)
print(
f"firmware.elf/raw_firmware.elf not found in {build_dir}", file=sys.stderr
)
return None
# Find idedata.json - check multiple locations
+9 -5
View File
@@ -89,6 +89,7 @@ class Platform(StrEnum):
ESP32_C6_IDF = "esp32-c6-idf"
ESP32_S2_IDF = "esp32-s2-idf"
ESP32_S3_IDF = "esp32-s3-idf"
BK72XX_ARD = "bk72xx-ard" # LibreTiny BK7231N
# Memory impact analysis constants
@@ -120,6 +121,7 @@ PLATFORM_SPECIFIC_COMPONENTS = frozenset(
# fastest build times, most sensitive to code size changes
# 3. ESP32 IDF - Primary ESP32 platform, most representative of modern ESPHome
# 4-6. Other ESP32 variants - Less commonly used but still supported
# 7. BK72XX - LibreTiny platform (good for detecting LibreTiny-specific changes)
MEMORY_IMPACT_PLATFORM_PREFERENCE = [
Platform.ESP32_C6_IDF, # ESP32-C6 IDF (newest, supports Thread/Zigbee)
Platform.ESP8266_ARD, # ESP8266 Arduino (most memory constrained, fastest builds)
@@ -127,6 +129,7 @@ MEMORY_IMPACT_PLATFORM_PREFERENCE = [
Platform.ESP32_C3_IDF, # ESP32-C3 IDF
Platform.ESP32_S2_IDF, # ESP32-S2 IDF
Platform.ESP32_S3_IDF, # ESP32-S3 IDF
Platform.BK72XX_ARD, # LibreTiny BK7231N
]
@@ -404,7 +407,7 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None:
- wifi_component_esp_idf.cpp, *_idf.h -> ESP32 IDF variants
- wifi_component_esp8266.cpp, *_esp8266.h -> ESP8266_ARD
- *_esp32*.cpp -> ESP32 IDF (generic)
- *_libretiny.cpp, *_retiny.* -> LibreTiny (not in preference list)
- *_libretiny.cpp, *_bk72*.* -> BK72XX (LibreTiny)
- *_pico.cpp, *_rp2040.* -> RP2040 (not in preference list)
Args:
@@ -438,10 +441,11 @@ def _detect_platform_hint_from_filename(filename: str) -> Platform | None:
if "esp32" in filename_lower:
return Platform.ESP32_IDF
# LibreTiny and RP2040 are not in MEMORY_IMPACT_PLATFORM_PREFERENCE
# so we don't return them as hints
# if "retiny" in filename_lower or "libretiny" in filename_lower:
# return None # No specific LibreTiny platform preference
# LibreTiny (via 'libretiny' pattern or BK72xx-specific files)
if "libretiny" in filename_lower or "bk72" in filename_lower:
return Platform.BK72XX_ARD
# RP2040 is not in MEMORY_IMPACT_PLATFORM_PREFERENCE
# if "pico" in filename_lower or "rp2040" in filename_lower:
# return None # No RP2040 platform preference