from pathlib import Path
import json,hashlib,zipfile,shutil,xml.etree.ElementTree as E
R=Path(__file__).resolve().parent;B=R.parent;H=B/'engineering_handoff_local';sha=lambda p:hashlib.sha256(p.read_bytes()).hexdigest();D=R/'firmware/固件/vacuum_target'
build=json.loads((D/'build/build_verification.json').read_text());assert not build['fan_power_output_enabled']
vp=B/'logic_fet_A29/vacuum/vacuum_control_evt_a5_logic_fet.kicad_pcb';lp=B/'lamp_IR_PCB_A39/board/netlist.xml';shutil.copy2(lp,R/'lamp_A39_analog_netlist_reference.xml')
root=E.parse(lp).getroot();pins={(n.get('ref'),n.get('pin')):net.get('name') for net in root.findall('./nets/net') for n in net.findall('node')}
for c in ['C3','C4','C5']:assert pins[c,'2']=='/GND'
checks={'status':'A41_VACUUM_ADC_CCRDY_LOCAL_PASS_LAMP_ANALOG_AUDITED_FABRICATION_HOLD','actual_read_adc_success_cases':72,'negative_control':'Original A30 read_adc fails delayed-channel-configuration success assertion','sanitizers':'ASAN_UBSAN_PASS','vacuum_code_outside_read_adc':'UNCHANGED','vacuum_CAD_changed':False,'vacuum_board_pair':'A29 vacuum_control_evt_a5_logic_fet','vacuum_board_sha256':sha(vp),'target_build':build,'timeout_policy':'Channel-config timeout/conversion timeout/overrun latches g_adc_ready=false; explicit reinitialization/reset needed','loop_timeout_count':100000,'measured_timeout_ms':None,'lamp_ADC_target_binding':'UNIMPLEMENTED; A40 remains latest lamp target','lamp_analog_netlist_sha256':sha(lp),'physical_verified_requirements':0,'reference_requirements':20,'physical_validation':'NOT_RUN_NO_SAMPLE'}
(R/'checks.json').write_text(json.dumps(checks,ensure_ascii=False,indent=2)+'\n')
report=f'''# ADC通道配置就绪修订与灯具模拟接口核对 A41

状态：吸尘器ADC驱动的实际代码已修订并通过主机/目标编译验证；灯具模拟接口完成网表核对，尚未绑定ADC目标采样。FABRICATION_HOLD；三款整机20项功能效果仍0项实测验证。

## 本轮实际修复

A30 吸尘器 `read_adc` 在写 CHSELR 后立即设置 ADSTART。STM32G0 的通道配置需若干ADC时钟生效，未就绪时可忽略启动命令。A41先清旧转换/配置完成标志，写新通道，带有限循环等待 CCRDY，再启动转换。配置超时、转换超时或OVR会将内部ADC就绪标志锁定false，后续不再返回成功采样；正在转换时请求停止。未证明ADSTP在硬件上已完成，错误后依赖原main的关断并进入等待路径，需显式重新初始化/复位恢复。

依据：[ST RM0454 ADC_CR / CCRDY](https://www.st.com/resource/en/reference_manual/rm0454-stm32g0x0-advanced-armbased-32bit-mcus-stmicroelectronics.pdf)，[ST 官方HAL驱动关于通道配置需要ADC时钟生效的说明](https://github.com/STMicroelectronics/stm32g0xx-hal-driver/blob/master/Src/stm32g0xx_hal_adc.c)。ADC由当前单一串行采样路径独占，不能由未协调的ISR或另一调用者改CHSELR。

吸尘器 `vacuum_board.c` 除 `read_adc` 函数外的源文本逐字不变，PA12物理脚17输出关断修订保留；PCB/BOM没有修改。共享MCU依赖移至 `stm32_shared`，避免将旧灯具目标源混入本吸尘器发布包，寄存器/CMSIS/启动/链接/runtime源字节保留。

## 证据与边界

- 72组实际read_adc函数测试：8个通道 × 3种通道配置延迟 × 3种转换延迟，检查返回数据的通道身份、旧EOC/EOS/OVR和旧DR不被当作新值。
- 配置超时、转换超时、OVR、后续调用锁定及null/非法通道/忙状态拒绝通过；输出缓冲区失败时不被改写。W1C、硬件延迟配置、未就绪时忽略ADSTART用独立MMIO事件模型模拟，不是通电采样。
- 原A30实际函数在延迟配置场景下失败，负对照成立。
- 原有风机输出锁、VREFINT/VDDA与电压电流数值转换、风机策略回归通过，ASan/UBSan通过。
- 完整Cortex-M0+编译链接/已有映像静态检查通过，FLASH {build['flash_binary_bytes']} B，BIN SHA256 `{build['sha256_bin']}`。风机供电/PWM回调仍硬锁关断，未实现运行效果。

等待上限仍为100000次循环，未测墙钟时间，不是“100000ms”或保证毫秒级实时响应。这仍是已有吸尘器诊断采样结构，不能直接作为灯具5ms输入采样调度保证；灯具应采用有调度界限的采样实现。全ADC初始化、时钟/校准/模拟准确度和停止转换波形仍未实机验证。原main错误后停止，保留的旧调试快照带旧时间戳，不能将其当作继续实时采样。

配对吸尘器A29板 SHA256 `{sha(vp)}`。本轮没有新跑CAD检查，也不新增其制造资格结论。

## 灯具模拟接口核对

A39网表确认：U1.8=PA1/ADC_IN1/NTC_ADC，R10=10k上拉到CTRL_3V3，C3=100n到GND，外部NTC由J4.4接入。NTC精确曲线/公差/安装温度位置未选定，不能把一个假设的10k阻值转换成已校准温度或热保护阈值。

U1.12=PA5/ADC_IN5/LED_V_SENSE，经R13=1k和C4=100n；U1.13=PA6/ADC_IN6/LED_I_SENSE，经R14=1k和C5=100n。这两路依赖J3外部约定的0–3V调理信号，上游分压/分流电阻/增益/偏移/限幅未绑定到已完成的实际电路。不能将LED主电压直接送入这些MCU输入，也不能由原始count宣称已测得整机LED功率。

接口映射CSV及参考网表随包提供。灯具当前仍为A40：只有数字温控触点、联锁、按键、PC15 IR采样，NTC/供电ADC和驱动健康反馈未绑定，输出关断。下一步补灯具ADC采样及实际模拟调理/校准链，再完成输出闭环和三款物理验证。

## 交付

包内包括可编辑吸尘器完整所需源、ELF/BIN/MAP、主机模型与负对照、回归与构建日志、灯具模拟映射及参考网表、SHA清单。运行 `sh run_checks.sh`，需要cc/Python3/arm-none-eabi工具链，依赖均在包内。不增加BOM器件，不宣称千台整机成本达标。未采购、联系供应商、烧录或通电。
'''
(R/'README.md').write_text(report);(H/'A41-ADC-channel-ready-and-lamp-analog.md').write_text(report)
files=[p for p in R.rglob('*') if p.is_file() and 'lamp_target' not in p.relative_to(R).parts and p.name not in ['prepare.py','package.py'] and not p.name.startswith('.') and p.suffix in ['.c','.h','.s','.ld','.sh','.py','.md','.json','.log','.txt','.csv','.xml','.elf','.bin','.map']]
manifest=''.join(f'{sha(p)}  {p.relative_to(R).as_posix()}\n' for p in sorted(files));(H/'A41-SHA256SUMS.txt').write_text(manifest)
z=H/'ADC-channel-ready-and-lamp-analog-review-A41.zip'
with zipfile.ZipFile(z,'w',zipfile.ZIP_DEFLATED) as a:
 for p in files:a.write(p,p.relative_to(R).as_posix())
 a.writestr('SHA256SUMS.txt',manifest)
with zipfile.ZipFile(z) as a:
 assert a.testzip() is None
 for line in manifest.splitlines():
  digest,name=line.split('  ',1);assert hashlib.sha256(a.read(name)).hexdigest()==digest
 count=len(a.namelist())
ip=H/'LOCAL-PACKAGES.json';idx=json.loads(ip.read_text());idx['status']=checks['status'];idx['latest_report']='A41-ADC-channel-ready-and-lamp-analog.md';idx['latest_machine_report']='../ADC_channel_ready_A41/checks.json';idx['latest_manifest']='A41-SHA256SUMS.txt'
idx['packages'].append({'archive':z.name,'sha256':sha(z),'bytes':z.stat().st_size,'zip_entries':count,'manifest_entries':len(files),'zip_crc':'PASS','manifest_readback':'PASS','manufacturing_status':'FABRICATION_HOLD'})
idx['latest_vacuum_diagnostic_firmware']={'source':'../ADC_channel_ready_A41/firmware/固件/vacuum_target','binary':'../ADC_channel_ready_A41/firmware/固件/vacuum_target/build/vacuum_control_g030f6.bin','sha256':build['sha256_bin'],'output_enabled':False,'physical_test_release':False,'board_pair':'A29 vacuum_control_evt_a5_logic_fet'}
idx['ADC_CCRDY_gate_A41']={'source':'../ADC_channel_ready_A41/firmware/固件/vacuum_target/src/vacuum_board.c','host_cases':72,'original_negative_control':'REJECTED','physical_validation':'NOT_RUN','lamp_ADC_binding':'NOT_IMPLEMENTED'}
idx['manufacturing_exclusions']['vacuum_prior_A30_ADC']='A30 starts ADC before CCRDY acknowledgement; A41 supersedes diagnostic image. Both physically unqualified; fan outputs locked off.'
ip.write_text(json.dumps(idx,ensure_ascii=False,indent=2)+'\n');print(json.dumps(idx['packages'][-1],indent=2))
