182 lines
7.7 KiB
Python
182 lines
7.7 KiB
Python
#!/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 <assert.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
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()
|