from pathlib import Path
import shutil,re,subprocess,json,sys,hashlib
ROOT=Path(__file__).resolve().parent;BASE=ROOT.parent;SRC=BASE/'vacuum_vref_calibration_a0/固件';DST=ROOT/'firmware/固件'
for n in ['vacuum_target','vacuum_control','lamp_target']:
 shutil.copytree(SRC/n,DST/n,dirs_exist_ok=True,ignore=shutil.ignore_patterns('build','__pycache__'))
p=DST/'vacuum_target/src/vacuum_board.c';s=p.read_text();assert '#define FAN_POWER_EN_PIN 10u' in s
s=s.replace('#define FAN_POWER_EN_PIN 10u','#define FAN_POWER_EN_PIN 12u\n#define FAN_POWER_REMAPPED_PIN 10u')
a=s.index('void vacuum_board_force_off(void)');b=s.index('int vacuum_board_set_fan_power',a)
s=s[:a]+'''/* U5 TSSOP20 pin17 is PA12 unless SYSCFG remaps PA10 onto that pad.
 * Stage both outputs low before selecting PA12, so a retained remap cannot
 * leave the physical fan-power pad enabled during the mapping transition. */
static void force_power_pad_low(void)
{
    RCC->IOPENR |= RCC_IOPENR_GPIOAEN;
    RCC->APBENR2 |= RCC_APBENR2_SYSCFGEN;
    (void)RCC->IOPENR;
    (void)RCC->APBENR2;
    gpio_output_low(GPIOA, FAN_POWER_REMAPPED_PIN);
    gpio_output_low(GPIOA, FAN_POWER_EN_PIN);
    SYSCFG->CFGR1 &= ~SYSCFG_CFGR1_PA12_RMP;
    __DSB();
}

void vacuum_board_force_off(void)
{
    force_power_pad_low();
    GPIOA->BSRR = (1u << (FAN_POWER_EN_PIN + 16u)) |
                  (1u << (FAN_PWM_PIN + 16u));
    __DSB();
    gpio_input(GPIOA, FAN_PWM_PIN, 2u);
}

void vacuum_board_force_fan_disabled(void)
{
    force_power_pad_low();
    if ((RCC->APBENR2 & RCC_APBENR2_TIM1EN) != 0u) {
        TIM1->CCR1 = 0u;
    }
    __DSB();
}

'''+s[b:];p.write_text(s)
r=DST/'vacuum_target/README.md';s=r.read_text().replace('active-high PA10 power gate','active-high PA12 power gate on U5 physical pin17');s+='\nA30 corrects the physical power pad mapping: DS12991 Rev6 Table12 identifies TSSOP20 pin17 as PA12, with optional PA10 remap. Both PA10/PA12 are staged low before clearing PA12_RMP. The output lock remains in force. No board has been flashed or powered.\n';r.write_text(s)
# Use the pinned manufacturer's register layouts and masks, replacing only
# Cortex core intrinsics and MMIO base-pointer macros for host execution.
T=ROOT/'host_test';T.mkdir(exist_ok=True)
h=(DST/'lamp_target/Device/Include/stm32g030xx.h').read_text();h=re.sub(r'^#include "(?:core_cm0plus|system_stm32g0xx)\.h"[^\n]*','',h,flags=re.M)
(T/'device_registers.h').write_text('#include <stdint.h>\n#define __IO volatile\n#define __I volatile const\n#define __O volatile\n'+h)
(T/'stm32g0xx.h').write_text('''#include "device_registers.h"
#undef RCC
#undef GPIOA
#undef TIM1
#undef SYSCFG
#undef EXTI
#undef ADC1
#undef ADC1_COMMON
extern RCC_TypeDef host_rcc;
extern GPIO_TypeDef host_gpio;
extern TIM_TypeDef host_tim;
extern SYSCFG_TypeDef host_syscfg;
extern EXTI_TypeDef host_exti;
extern ADC_TypeDef host_adc;
extern ADC_Common_TypeDef host_adc_common;
extern uint32_t SystemCoreClock;
#define RCC (&host_rcc)
#define GPIOA (&host_gpio)
#define TIM1 (&host_tim)
#define SYSCFG (&host_syscfg)
#define EXTI (&host_exti)
#define ADC1 (&host_adc)
#define ADC1_COMMON (&host_adc_common)
#define __DSB() ((void)0)
#define __NOP() ((void)0)
static inline void NVIC_ClearPendingIRQ(IRQn_Type x) {(void)x;}
static inline void NVIC_EnableIRQ(IRQn_Type x) {(void)x;}
''')
(T/'test_power_pad.c').write_text('''#include "stm32g0xx.h"
#include "vacuum_board.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
RCC_TypeDef host_rcc;
GPIO_TypeDef host_gpio;
TIM_TypeDef host_tim;
SYSCFG_TypeDef host_syscfg;
EXTI_TypeDef host_exti;
ADC_TypeDef host_adc;
ADC_Common_TypeDef host_adc_common;
uint32_t SystemCoreClock = 16000000u;
static void seed(unsigned remap, unsigned timer)
{
 memset(&host_rcc,0,sizeof host_rcc);memset(&host_gpio,0,sizeof host_gpio);
 memset(&host_syscfg,0,sizeof host_syscfg);memset(&host_tim,0,sizeof host_tim);
 host_gpio.ODR = (1u<<10)|(1u<<12); /* Prior logical outputs high. */
 host_gpio.MODER = ~0u;host_gpio.OTYPER=~0u;host_gpio.PUPDR=0;
 host_syscfg.CFGR1=SYSCFG_CFGR1_PA11_RMP|(remap?SYSCFG_CFGR1_PA12_RMP:0);
 host_rcc.APBENR2 = timer?RCC_APBENR2_TIM1EN:0;host_tim.CCR1=500;
}
static void check_pad(void)
{
 assert(((host_gpio.MODER>>(12*2))&3)==1); /* Physical pin17 must be an output. */
 assert((host_rcc.IOPENR&RCC_IOPENR_GPIOAEN)!=0);
 assert((host_rcc.APBENR2&RCC_APBENR2_SYSCFGEN)!=0);
 assert((host_syscfg.CFGR1&SYSCFG_CFGR1_PA12_RMP)==0);
 assert((host_syscfg.CFGR1&SYSCFG_CFGR1_PA11_RMP)!=0); /* Other mapping preserved. */
 assert(((host_gpio.MODER>>(12*2))&3)==1); /* Package pin17, not unbonded PA10. */
 assert((host_gpio.OTYPER&(1u<<12))==0);
 assert(((host_gpio.PUPDR>>(12*2))&3)==2);
 assert((host_gpio.BSRR&(1u<<(12+16)))!=0);
 assert((host_gpio.BSRR&(1u<<12))==0);
}
int main(void)
{
 for(unsigned remap=0;remap<2;remap++) for(unsigned timer=0;timer<2;timer++) {
  seed(remap,timer);vacuum_board_force_off();check_pad();
  assert(((host_gpio.MODER>>(8*2))&3)==0);
  assert((host_gpio.BSRR&(1u<<(8+16)))!=0);
  seed(remap,timer);vacuum_board_force_fan_disabled();check_pad();
  assert(host_tim.CCR1==(timer?0:500));
  seed(remap,timer);assert(vacuum_board_set_fan_power(0,true)==-1);check_pad();
  seed(remap,timer);assert(vacuum_board_set_fan_duty(0,1000)==-1);check_pad();
 }
 puts("PASS: physical pin17 PA12 off commands, both prior remap states, timer on/off, output lock rejection; MMIO writes only, no measured voltage.");
}
''')
flags=['cc','-std=c11','-Wall','-Wextra','-Werror','-ffunction-sections','-fdata-sections','-Wl,-dead_strip','-I'+str(T),'-I'+str(DST/'vacuum_target/src')]
def test(source,exe):
 c=subprocess.run(flags+[str(T/'test_power_pad.c'),str(source),str(DST/'vacuum_target/src/vacuum_output_lock.c'),'-o',str(T/exe)],capture_output=True,text=True)
 if c.returncode:print(c.stderr);raise SystemExit(c.returncode)
 return subprocess.run([str(T/exe)],capture_output=True,text=True)
x=test(p,'test_fixed');assert x.returncode==0;print(x.stdout);(ROOT/'host_test.log').write_text(x.stdout)
x=test(SRC/'vacuum_target/src/vacuum_board.c','test_original');assert x.returncode!=0
(ROOT/'original_negative_control.log').write_text('Original firmware failed pin17/remap expectations as required.\n'+x.stderr)
# Original negative control demonstrates why the revised test is necessary.
print('PASS: original source rejected by physical-pad regression')
