#!/usr/bin/env python3 """Compile production battery domain with fake clock/GPIO/ADC; no copied state machine.""" from pathlib import Path import re import subprocess import tempfile ROOT = Path(__file__).resolve().parents[1] def facade_type(path, name): text = (ROOT / path).read_text() return next(m.group() for m in re.finditer(r'typedef\s+(?:enum|struct)\s*\{[^}]*\}\s*\w+;', text) if re.search(r'\}\s*' + name + ';', m.group())) STUBS = r''' #include #include #include #include #include #include #include #define CONFIG_TQ_BATTERY_ENABLE 1 #define CONFIG_TQ_BATTERY_CONTROLLED_SHUTDOWN_ENABLE 0 #define CONFIG_TQ_BATTERY_LOW_ENTER_PERCENT 20 #define CONFIG_TQ_BATTERY_LOW_EXIT_PERCENT 25 #define CONFIG_TQ_BATTERY_CRITICAL_ENTER_PERCENT 5 #define CONFIG_TQ_BATTERY_CRITICAL_EXIT_PERCENT 10 #define CONFIG_TQ_BATTERY_CHARGE_ACTIVE_LEVEL 0 #define CONFIG_TQ_BATTERY_FULL_ACTIVE_LEVEL 0 #define ESP_OK 0 #define ESP_ERR_TIMEOUT 1 #define ESP_ERR_INVALID_ARG 2 #define ESP_ERR_INVALID_STATE 3 #define ESP_ERR_NOT_SUPPORTED 4 #define ESP_ERR_INVALID_RESPONSE 5 #define pdTRUE 1 #define pdMS_TO_TICKS(x) (x) #define portMUX_INITIALIZER_UNLOCKED 0 #define portENTER_CRITICAL(x) ((void)(x)) #define portEXIT_CRITICAL(x) ((void)(x)) typedef int esp_err_t; typedef int StaticSemaphore_t; typedef int *SemaphoreHandle_t; typedef int portMUX_TYPE; static bool lock_failure; static int64_t fake_ms; static int gpio_error, adc_error; static unsigned adc_calls; static platform_battery_charge_inputs_t fake_inputs = {1, 1, true}; static platform_battery_sample_t fake_sample = {.voltage_mv=4180, .voltage_valid=true}; static int64_t esp_timer_get_time(void) { return fake_ms * 1000; } static SemaphoreHandle_t xSemaphoreCreateMutexStatic(StaticSemaphore_t *p) { return p; } static int xSemaphoreTake(SemaphoreHandle_t p, int ms) { assert(p && ms==100); return !lock_failure; } static int xSemaphoreGive(SemaphoreHandle_t p) { assert(p); return 1; } static esp_err_t platform_battery_read_charge_inputs(platform_battery_charge_inputs_t *out) { *out = fake_inputs; return gpio_error; } static esp_err_t platform_battery_read_sample(platform_battery_sample_t *out) { ++adc_calls; *out = fake_sample; return adc_error; } static bool platform_power_woke_from_deep_sleep(void) { return false; } static void platform_power_release_deep_sleep_holds(void) {} ''' def production_function(text, signature): start = text.index(signature) body = text.index('{', start) end, depth = body + 1, 1 while depth: depth += (text[end] == '{') - (text[end] == '}') end += 1 return text[start:end] WORKER_STUBS = r''' #define CONFIG_TQ_BATTERY_CHARGE_GPIO 18 #define CONFIG_TQ_BATTERY_FULL_GPIO 8 #define CONFIG_TQ_BATTERY_SAMPLE_INTERVAL_SEC 10 #define CONFIG_TQ_LIGHT_STANDBY_BATTERY_INTERVAL_SEC 300 #define BATTERY_CHARGE_POLL_MS 100 #define BATTERY_SUPPRESSED_RETRY_MS 1000 #define BATTERY_CONFIRM_RETRY_MS 1000 #define portMAX_DELAY UINT32_MAX #define ESP_LOGI(tag, ...) do { if(0) printf(__VA_ARGS__); (void)(tag); } while(0) #define ESP_LOGW ESP_LOGI #define ESP_LOGE ESP_LOGI #define ESP_LOGD ESP_LOGI typedef uint32_t TickType_t; static const char *TAG = "test"; static uint8_t s_shutdown_confirmations, s_emergency_confirmations; static unsigned full_prompts, shutdown_checks, last_shutdown_adc_calls, worker_finishes; static int scenario; static int64_t worker_end_ms; static const char *esp_err_to_name(esp_err_t err) { (void)err; return "test"; } static TickType_t xTaskGetTickCount(void) { return (TickType_t)fake_ms; } static bool battery_light_standby(void) { return scenario == 4 && fake_ms >= 1000 && fake_ms < 3000; } static bool battery_stop_requested(void) { return fake_ms >= worker_end_ms; } static void battery_worker_finish(void) { ++worker_finishes; } static void vTaskDelete(void *p) { (void)p; } static uint32_t ulTaskNotifyTake(int clear, TickType_t ticks) { assert(clear); if(ticks == portMAX_DELAY) return 1; assert(ticks == 100); fake_ms += ticks; if(scenario == 1) { fake_inputs.charge_level = 1; fake_inputs.full_level = 0; } else if(scenario == 2) { fake_inputs.charge_level = fake_ms < 2000 ? 0 : 1; fake_inputs.full_level = fake_ms < 2000 ? 1 : 0; } else if(scenario == 3) { fake_inputs.charge_level = fake_ms < 2000 ? 0 : 1; fake_inputs.full_level = fake_ms < 4000 ? 1 : 0; } return 0; } static esp_err_t domain_runtime_update_battery_overlay(const domain_battery_status_t *s) { assert(s); return ESP_OK; } static void control_plane_battery_low_hook(const domain_battery_status_t *s) { assert(s); } static void control_plane_battery_full_hook(const domain_battery_status_t *s) { assert(s->charge_state == DOMAIN_BATTERY_CHARGE_FULL); ++full_prompts; } static void battery_consider_controlled_shutdown(const domain_battery_status_t *s) { assert(s && adc_calls > last_shutdown_adc_calls); last_shutdown_adc_calls = adc_calls; ++shutdown_checks; } ''' TESTS = r''' static void poll(int charging_level, int full_level, unsigned ms) { fake_inputs.charge_level = charging_level; fake_inputs.full_level = full_level; for (unsigned i=0; i\n#include \n' + types + '\n' + STUBS + source + WORKER_STUBS + worker_source + TESTS) subprocess.run(['clang', '-std=c11', '-Wall', '-Wextra', '-Werror', '-fsanitize=address,undefined', '-fno-omit-frame-pointer', str(cfile), '-o', str(path / 'test')], check=True) subprocess.run([str(path / 'test')], check=True) if __name__ == '__main__': main()