#!/usr/bin/env python3 """Run the production battery initializer with ADC1/ADC2 and failure stubs.""" from pathlib import Path import re import subprocess import tempfile ROOT = Path(__file__).resolve().parents[1] SOURCE = ROOT / "components/platform/src/platform_battery_driver.c" def initializer(): source = SOURCE.read_text() parts = [] for signature in ["static esp_err_t configure_charge_inputs_locked(void)", "esp_err_t platform_battery_read_charge_inputs(", "esp_err_t platform_battery_init(void)"]: start = source.index(signature) body = source.index("{", start) end, depth = body + 1, 1 while depth: depth += (source[end] == "{") - (source[end] == "}") end += 1 parts.append(source[start:end]) return "\n".join(parts) STUBS = r''' #include #include #include #include #include typedef int esp_err_t; typedef int adc_unit_t; typedef int adc_channel_t; typedef void *adc_oneshot_unit_handle_t; #define ESP_OK 0 #define ESP_ERR_TIMEOUT 1 #define ESP_ERR_INVALID_ARG 2 #define ESP_ERR_NO_MEM 3 #define ESP_ERR_NOT_SUPPORTED 4 #define ADC_UNIT_1 0 #define ADC_UNIT_2 1 #define ADC_CHANNEL_0 0 #define GPIO_NUM_MAX 49 #define GPIO_MODE_INPUT 1 #define GPIO_PULLUP_ENABLE 1 #define GPIO_PULLDOWN_DISABLE 0 #define GPIO_INTR_DISABLE 0 #define ADC_ULP_MODE_DISABLE 0 #define ADC_ATTEN_DB_12 3 #define ADC_BITWIDTH_DEFAULT 0 #define CONFIG_TQ_BATTERY_DIVIDER_HIGH_OHM 100000 #define CONFIG_TQ_BATTERY_DIVIDER_LOW_OHM 100000 #define ESP_LOGE(...) ((void)0) #define ESP_LOGI(...) ((void)0) typedef struct { uint64_t pin_bit_mask; int mode, pull_up_en, pull_down_en, intr_type; } gpio_config_t; typedef struct { int unit_id, ulp_mode; } adc_oneshot_unit_init_cfg_t; typedef struct { int atten, bitwidth; } adc_oneshot_chan_cfg_t; static adc_oneshot_unit_handle_t s_adc_handle; static adc_channel_t s_adc_channel; static bool s_initialized, s_inputs_initialized, locked; #define GPIO_IS_VALID_GPIO(n) ((n) >= 0 && (n) < GPIO_NUM_MAX) typedef struct { int charge_level, full_level; bool valid; } platform_battery_charge_inputs_t; static int gpio_get_level(int gpio) { return gpio == CONFIG_TQ_BATTERY_FULL_GPIO; } static int fail_lock, gpio_error, mapping_error, unit_error, channel_error, calibration_error; static int gpio_calls, unit_calls, delete_calls, calibration_calls; static int actual_unit, actual_channel; static uint64_t charge_mask; static bool lock_adc(void) { if (fail_lock) return false; assert(!locked); locked = true; return true; } static void unlock_adc(void) { assert(locked); locked = false; } static uint64_t gpio_mask(int gpio) { return UINT64_C(1) << gpio; } static esp_err_t gpio_config(const gpio_config_t *cfg) { ++gpio_calls; charge_mask = cfg->pin_bit_mask; return gpio_error; } static esp_err_t adc_oneshot_io_to_channel(int gpio, adc_unit_t *unit, adc_channel_t *channel) { if (mapping_error) return mapping_error; if (gpio == 17) { *unit = ADC_UNIT_2; *channel = 6; return ESP_OK; } if (gpio == 9) { *unit = ADC_UNIT_1; *channel = 8; return ESP_OK; } return ESP_ERR_INVALID_ARG; } static esp_err_t adc_oneshot_new_unit(const adc_oneshot_unit_init_cfg_t *cfg, adc_oneshot_unit_handle_t *handle) { ++unit_calls; actual_unit = cfg->unit_id; if (unit_error) return unit_error; *handle = (void *)(uintptr_t)1; return ESP_OK; } static esp_err_t adc_oneshot_config_channel(adc_oneshot_unit_handle_t handle, adc_channel_t channel, const adc_oneshot_chan_cfg_t *cfg) { assert(handle && cfg->atten == ADC_ATTEN_DB_12); actual_channel = channel; return channel_error; } static esp_err_t create_calibration(adc_unit_t unit, adc_channel_t channel) { ++calibration_calls; assert(unit == actual_unit && channel == actual_channel); return calibration_error; } static esp_err_t adc_oneshot_del_unit(adc_oneshot_unit_handle_t handle) { assert(handle); ++delete_calls; return ESP_OK; } ''' TESTS = r''' static void reset(void) { assert(!locked); s_initialized = s_inputs_initialized = false; s_adc_handle = NULL; fail_lock = gpio_error = mapping_error = unit_error = channel_error = calibration_error = 0; gpio_calls = unit_calls = delete_calls = calibration_calls = 0; } int main(void) { reset(); assert(platform_battery_init() == ESP_OK); assert(s_initialized && !locked); assert(actual_unit == EXPECTED_UNIT && actual_channel == EXPECTED_CHANNEL); assert(s_adc_channel == EXPECTED_CHANNEL && calibration_calls == 1); assert(charge_mask == ((UINT64_C(1) << CONFIG_TQ_BATTERY_CHARGE_GPIO) | (UINT64_C(1) << CONFIG_TQ_BATTERY_FULL_GPIO))); assert(platform_battery_init() == ESP_OK && unit_calls == 1 && gpio_calls == 1); reset(); fail_lock = 1; assert(platform_battery_init() == ESP_ERR_TIMEOUT && !s_initialized && !unit_calls); reset(); gpio_error = ESP_ERR_INVALID_ARG; assert(platform_battery_init() == gpio_error && !locked && !unit_calls); reset(); mapping_error = ESP_ERR_INVALID_ARG; assert(platform_battery_init() == mapping_error && !locked && !unit_calls); reset(); unit_error = ESP_ERR_NO_MEM; assert(platform_battery_init() == unit_error && !locked && !s_initialized && !delete_calls); reset(); channel_error = ESP_ERR_INVALID_ARG; assert(platform_battery_init() == channel_error && !locked && !s_initialized); assert(delete_calls == 1 && s_adc_handle == NULL && !calibration_calls); channel_error = ESP_OK; assert(platform_battery_init() == ESP_OK && s_initialized); reset(); calibration_error = ESP_ERR_NOT_SUPPORTED; assert(platform_battery_init() == calibration_error && !locked && !s_initialized); assert(delete_calls == 1 && s_adc_handle == NULL); platform_battery_charge_inputs_t inputs = {0}; assert(platform_battery_read_charge_inputs(&inputs) == ESP_OK); assert(inputs.valid && inputs.charge_level == 0 && inputs.full_level == 1); assert(!s_initialized); /* 数字输入不依赖ADC校准 */ calibration_error = ESP_OK; assert(platform_battery_init() == ESP_OK && s_initialized); assert(platform_battery_read_charge_inputs(NULL) == ESP_ERR_INVALID_ARG); fail_lock = 1; inputs.valid = true; assert(platform_battery_read_charge_inputs(&inputs) == ESP_ERR_TIMEOUT && !inputs.valid); puts("PASS: battery ADC unit/channel propagation, idempotence, bounded lock failure, cleanup and retry"); } ''' def main(): defaults = dict(re.findall(r"^CONFIG_(\w+)=(.*)$", (ROOT / "sdkconfig.defaults").read_text(), re.M)) adc_gpio = int(defaults["TQ_BATTERY_ADC_GPIO"]) charge_gpio = int(defaults["TQ_BATTERY_CHARGE_GPIO"]) assert (adc_gpio, charge_gpio) == (17, 18), "new board must use ADC GPIO17 / charge GPIO18" with tempfile.TemporaryDirectory(prefix="battery-adc-init-") as tmp: path = Path(tmp) source = path / "init.c" source.write_text(STUBS + initializer() + TESTS) for gpio, unit, channel in [(adc_gpio, 1, 6), (9, 0, 8)]: binary = path / f"init-{gpio}" subprocess.run([ "clang", "-std=c11", "-Wall", "-Wextra", "-Werror", "-fsanitize=address,undefined", "-fno-omit-frame-pointer", f"-DCONFIG_TQ_BATTERY_ADC_GPIO={gpio}", f"-DCONFIG_TQ_BATTERY_CHARGE_GPIO={charge_gpio}", "-DCONFIG_TQ_BATTERY_FULL_GPIO=8", f"-DEXPECTED_UNIT={unit}", f"-DEXPECTED_CHANNEL={channel}", str(source), "-o", str(binary), ], check=True) subprocess.run([str(binary)], check=True) if __name__ == "__main__": main()