from pathlib import Path
import shutil
R=Path(__file__).resolve().parent;B=R.parent;shutil.copytree(B/'lamp_input_A37/firmware',R/'firmware',ignore=shutil.ignore_patterns('build','__pycache__'))
T=R/'firmware/固件/lamp_target'
(T/'include/lamp_ir_filter.h').write_text('''#ifndef LAMP_IR_FILTER_H
#define LAMP_IR_FILTER_H
#include <stdbool.h>
#include <stdint.h>
#define LAMP_IR_ASSERT_MS 10u
#define LAMP_IR_MAX_SAMPLE_GAP_MS 5u
typedef struct {bool high_seen;bool sample_seen;uint32_t high_since_ms;uint32_t last_ms;} lamp_ir_filter;
/* Provisional timing, requires optical and physical response validation.
 * High must persist across timely samples. Low/unavailable/stall resets
 * qualification immediately. Repeated timestamps cannot advance elapsed time. */
bool lamp_ir_filter_update(lamp_ir_filter *f,bool available,bool high,uint32_t now);
#endif
''')
(T/'src/lamp_ir_filter.c').write_text('''#include "lamp_ir_filter.h"
bool lamp_ir_filter_update(lamp_ir_filter *f,bool available,bool high,uint32_t now) {
    if (!f) return false;
    const bool stalled = f->sample_seen &&
        (uint32_t)(now-f->last_ms)>LAMP_IR_MAX_SAMPLE_GAP_MS;
    f->sample_seen=true;f->last_ms=now;
    if (!available || !high || stalled) f->high_seen=false;
    if (!available || !high) return false;
    if (!f->high_seen) {f->high_seen=true;f->high_since_ms=now;return false;}
    return (uint32_t)(now-f->high_since_ms)>=LAMP_IR_ASSERT_MS;
}
''')
(T/'include/lamp_board_inputs.h').write_text('''#ifndef LAMP_BOARD_INPUTS_H
#define LAMP_BOARD_INPUTS_H
#include "lamp_guard.h"
typedef struct {
    bool thermal_contact_closed;
    bool hardware_interlock_closed;
    bool user_key_pressed;
    bool infrared_gpio_available;
    bool infrared_raw_high;
    lg_inputs guard_inputs;
} lamp_board_input_snapshot;
/* A39 PCA IR board only: U1 pin3 PC15, active-high 3.3V input with external
 * 47k pulldown. No RTC reset or LSE enable/disable side effect. */
bool lamp_board_ir_init(void);
lamp_board_input_snapshot lamp_board_inputs_read(uint32_t now_ms);
#endif
''')
(T/'src/lamp_board_inputs.c').write_text('''#include "lamp_board_inputs.h"
#include "lamp_ir_filter.h"
#include "stm32g0xx.h"
static lamp_ir_filter ir_filter;
static bool ir_initialized;
static bool clock_allows_ir(void) {
    return (RCC->BDCR & (RCC_BDCR_LSEON|RCC_BDCR_LSERDY|RCC_BDCR_LSEBYP))==0u;
}
static bool configuration_allows_ir(void) {
    return (RCC->IOPENR & RCC_IOPENR_GPIOCEN)!=0u && clock_allows_ir() &&
           (GPIOC->LCKR & ((1u<<16u)|(1u<<15u)))!=((1u<<16u)|(1u<<15u)) &&
           (GPIOC->MODER & (3u<<30u))==0u &&
           (GPIOC->PUPDR & (3u<<30u))==0u &&
           (GPIOC->AFR[1] & (15u<<28u))==0u;
}
bool lamp_board_ir_init(void) {
    ir_initialized=false;ir_filter=(lamp_ir_filter){0};
    if (!clock_allows_ir()) return false;
    RCC->IOPENR |= RCC_IOPENR_GPIOCEN;
    (void)RCC->IOPENR;
    if ((GPIOC->LCKR & ((1u<<16u)|(1u<<15u)))==((1u<<16u)|(1u<<15u))) return false;
    GPIOC->MODER &= ~(3u<<30u);
    GPIOC->PUPDR &= ~(3u<<30u); /* Use A39 external 47k pulldown. */
    GPIOC->AFR[1] &= ~(15u<<28u);
    __DSB();
    ir_initialized=configuration_allows_ir();
    return ir_initialized;
}
lamp_board_input_snapshot lamp_board_inputs_read(uint32_t now_ms) {
    lamp_board_input_snapshot s={0};
    const uint32_t pa=GPIOA->IDR;
    const uint32_t pb=GPIOB->IDR;
    s.thermal_contact_closed=(pa & (1u<<12u))!=0u;
    s.hardware_interlock_closed=(pa & (1u<<0u))!=0u;
    s.user_key_pressed=(pb & (1u<<0u))==0u;
    s.infrared_gpio_available=ir_initialized && configuration_allows_ir();
    s.infrared_raw_high=s.infrared_gpio_available && (GPIOC->IDR & (1u<<15u))!=0u;
    s.guard_inputs.hardware_interlock_ok=s.hardware_interlock_closed;
    s.guard_inputs.infrared_hand_present=lamp_ir_filter_update(&ir_filter,
        s.infrared_gpio_available,s.infrared_raw_high,now_ms);
    /* NTC/rail calibration and driver health are not bound yet; those fields
       deliberately remain false. A closed thermal contact is raw evidence. */
    return s;
}
''')
p=T/'src/main.c';s=p.read_text().replace('volatile uint32_t lamp_debug_raw_input_bits;','volatile uint32_t lamp_debug_raw_input_bits;\nvolatile uint32_t lamp_debug_ir_init_ok;')
s=s.replace('lamp_board_inputs_read();','lamp_board_inputs_read(system_tick_ms);').replace('(sample.user_key_pressed ? 4u : 0u);','(sample.user_key_pressed ? 4u : 0u) |\n                               (sample.infrared_gpio_available ? 8u : 0u) |\n                               (sample.infrared_raw_high ? 16u : 0u) |\n                               (sample.guard_inputs.infrared_hand_present ? 32u : 0u);')
s=s.replace('    board_safe_boot();','    board_safe_boot();\n    lamp_debug_ir_init_ok=lamp_board_ir_init() ? 1u : 0u;');p.write_text(s)
p=T/'build.sh';s=p.read_text().replace('$CC $FLAGS $INCLUDES -c "$TARGET/src/lamp_board_inputs.c" -o "$OUT/lamp_board_inputs.o"','$CC $FLAGS $INCLUDES -c "$TARGET/src/lamp_board_inputs.c" -o "$OUT/lamp_board_inputs.o"\n$CC $FLAGS $INCLUDES -c "$TARGET/src/lamp_ir_filter.c" -o "$OUT/lamp_ir_filter.o"').replace('"$OUT/lamp_board_inputs.o" \\','"$OUT/lamp_board_inputs.o" "$OUT/lamp_ir_filter.o" \\');p.write_text(s)
p=T/'verify_image.py';s=p.read_text().replace('"lg_poll", "main")','"lg_poll", "lamp_board_ir_init", "lamp_ir_filter_update", "main")')
s=s.replace('for forbidden in (', '''if "lamp_board_ir_init" not in main_body:
    raise SystemExit("main has no physical IR initializer call")
if "lamp_ir_filter_update" not in function_body("lamp_board_inputs_read"):
    raise SystemExit("GPIO sampling path has no IR filter call")
for forbidden in (''').replace('"IR_GPIO_bound": False','"IR_GPIO_bound": True');p.write_text(s)
print('A40 PC15 clock/config-checked physical sampling and IR filter prepared')
