原理图/PCB 已在 EasyEDA 里做到 12 页跨页 0 漏接,固件刚完成骨架包 A1。 这次是把已有的一切一次性纳入版本控制,没有改动任何设计内容。 纳入: - 设计与需求文档、PROGRESS.md、小米8 参考原理图 PDF 与追脚脚本 - .claude/ 下的 EasyEDA 桥接工具链与几何/网表缓存(eda-parts.json、 pcb-parts.json、pcb-pads.json、p2-ch1.json 等,是重画各页的唯一事实来源) - 117 张验证截图(本项目所有方向问题都是靠人眼看截图发现的,属于证据不是垃圾) - firmware/ 的 A1 骨架与 site/ 文档站源码 .gitignore 排除:构建产物(firmware/build、host/build、sdkconfig)、 site/node_modules、__pycache__、桥接日志 _bridge.log、脚本自标的 *.stale。 .gitattributes 默认 `* -text`:现有 .py/.json 大多是 CRLF,原样存, 不让 git 重写行尾。唯一例外是 *.sh 强制 LF —— 带 \r 的 shell 脚本 bash 跑不了。 Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01QYuExfgyYa6DzRRcUso8M8
260 行
12 KiB
Python
260 行
12 KiB
Python
# -*- coding: utf-8 -*-
|
||
u"""两处接口换件(2026-09-06 用户拍板):
|
||
|
||
1. **J1 12V 输入**:`KF128-5.08-2P` 螺钉端子 → **`XT30PW-M`**(C53373776,90° 板装公头)。
|
||
理由:用户要插拔式。⚠️ 但 **12V 输入是 8 A**(五路 4A×4.2V ÷ 0.92 ÷ 12V ≈ 7.6 A,
|
||
加风扇 0.7 A),而「3.5 mm DC」那一类(DC-002,外径 3.5 / 内径 1.35)**额定只有 0.5~1 A**,
|
||
差了近一个数量级 —— 接上去会烧插座。XT30 主脚额定 **20 A**(温升 <85 °C),余量充足。
|
||
选 2 脚版而不是 (2+2):(2+2) 多出来的两脚是 **1 A 信号脚**,我们用不上,
|
||
而且它的 pin 编号在库里读不出语义;2 脚版的符号**引脚名直接写着 `+` / `−`**,没有歧义。
|
||
|
||
2. **J26~J30 风扇座**:`KF2510-3A`(C2940511)→ **`2510-3A(直针)`**(C2803349)。
|
||
现在这颗名字像直针,**实测是弯针**:本体 7.62 × 12.01,整个甩到 +Y 一侧,进深 11.7 mm
|
||
(§26.6 当年是拿放大截图量出来的)。换成真直针之后本体 7.62 × **5.81**、基本骑在焊盘上,
|
||
**每个座省 6.2 mm 进深**,五个座(左板边 2 / 右板边 2 / 顶板边 1)一起把板边让出来。
|
||
|
||
## 为什么这一轮**一根导线都不用动**
|
||
|
||
| | 旧件引脚(rot 0)| 新件引脚(rot 0)| 做法 |
|
||
|---|---|---|---|
|
||
| J1 | p1(−20,+5) p2(−20,−5) | p1"−"(−20,+5) p2"+"(−20,−5) | 原位 (120,705) rot 180,**p1/p2 位置完全重合** |
|
||
| J26~J30 | p1(−15,+10) p2(−15,0) p3(−15,−10) | p1(−20,+10) p2(−20,0) p3(−20,−10) | 元件**东移 5** 到 x=1085 ⇒ 引脚绝对坐标不变 |
|
||
|
||
极性也天然对上:板上实测 **J1.1 = GND、J1.2 = 12V 进线**,而新符号正好 p1 = `−`、p2 = `+`。
|
||
|
||
⚠️ **J1 新件多出 p3/p4 两个固定脚**(rot 0 时在 (+5,+25) / (+5,−25),rot 180 后落在
|
||
(115,680) / (115,730))。它们是机械固定孔,**不接网络** —— 下发后要把它们加进 NC 清单,
|
||
否则 `verify_sch` 会报未连接。
|
||
|
||
## 按 skill 的四条硬规矩
|
||
|
||
* **先 create 后 delete**(§9b):create 参数写错时删除已经生效,页面会凭空少件。
|
||
* **坐标 5 的倍数**(§8a-2b);1085 / 705 都是。
|
||
* **引脚跨距读回来断言**(§8d),对不上就停手 —— 这一版的判据更硬:
|
||
新件的每一个引脚都必须**落在旧件某个引脚的同一坐标上**(J1 的 p3/p4 除外)。
|
||
* 换件不动版面:导线是独立图元,旧件删掉之后新件引脚落在同一坐标就自动接上。
|
||
|
||
用法: /c/Python314/python .claude/p1p6_conn.py [--push]
|
||
下发完必跑:verify_sch.py --nets(两页)/ port_audit.py / xpage.py
|
||
再 pcb1_import.py --bom → --verify
|
||
"""
|
||
import io
|
||
import json
|
||
import os
|
||
import sys
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
sys.path.insert(0, HERE)
|
||
from eda import run # noqa: E402
|
||
|
||
LIB = '0819f05c4eef4c71ace90d822a990e87'
|
||
P1 = '9a6096762e036e23' # P1-INPUT
|
||
P6 = 'fd00f13e59336392' # P6-FAN-EDL
|
||
|
||
# ── 新器件(按立创编号在库里当场查 uuid,不硬编码)──────────────────────
|
||
NEW_J1 = ('XT30PW-M', 'C53373776') # fp CONN-TH_XT30PW-M 本体 13.30 x 13.95
|
||
NEW_FAN = ('2510-3A(直针)', 'C2803349') # fp CONN-TH_2510-3A 本体 7.62 x 5.81
|
||
|
||
# ── 旧件(primitiveId 是 2026-09-06 从页上读回来的,下发前逐个核存在)──
|
||
OLD = {
|
||
'J1': ('163ed00ced8619b9', P1, (120, 705), 180),
|
||
'J26': ('adc7843724c9f781', P6, (1080, 700), 0),
|
||
'J27': ('4a1abe4be255fb93', P6, (1080, 620), 0),
|
||
'J28': ('1d15c744cd52913d', P6, (1080, 540), 0),
|
||
'J29': ('1202c39a84e9c4fb', P6, (1080, 460), 0),
|
||
'J30': ('68ab7d950e494c11', P6, (1080, 380), 0),
|
||
}
|
||
# 新件放哪(东移 5 抵消引脚偏移;J1 原位不动)
|
||
NEW_XY = {
|
||
'J1': (120, 705, 180),
|
||
'J26': (1085, 700, 0), 'J27': (1085, 620, 0), 'J28': (1085, 540, 0),
|
||
'J29': (1085, 460, 0), 'J30': (1085, 380, 0),
|
||
}
|
||
# 旧件引脚的绝对坐标(p6-pins.json + 本轮从 P1 页读回来的)
|
||
OLD_PINS = {
|
||
'J1': {'1': (140, 700), '2': (140, 710)},
|
||
'J26': {'1': (1065, 710), '2': (1065, 700), '3': (1065, 690)},
|
||
'J27': {'1': (1065, 630), '2': (1065, 620), '3': (1065, 610)},
|
||
'J28': {'1': (1065, 550), '2': (1065, 540), '3': (1065, 530)},
|
||
'J29': {'1': (1065, 470), '2': (1065, 460), '3': (1065, 450)},
|
||
'J30': {'1': (1065, 390), '2': (1065, 380), '3': (1065, 370)},
|
||
}
|
||
# J1 新件多出来的固定脚,不接网络
|
||
J1_NC = ('3', '4')
|
||
|
||
|
||
def selfcheck():
|
||
bad = []
|
||
for des, (x, y, rot) in NEW_XY.items():
|
||
if x % 5 or y % 5:
|
||
bad.append(u'%s 的坐标 (%d,%d) 不是 5 的倍数' % (des, x, y))
|
||
if rot not in (0, 90, 180, 270):
|
||
bad.append(u'%s 的旋转角 %s 不合法' % (des, rot))
|
||
if set(NEW_XY) != set(OLD) or set(OLD_PINS) != set(OLD):
|
||
bad.append(u'三张表的位号对不齐')
|
||
return bad
|
||
|
||
|
||
def dev(name, lcsc):
|
||
r = run("const r = await eda.lib_Device.search(%s, undefined, undefined, undefined, 40, 1);"
|
||
"return (r||[]).map(d => [d.name, d.supplierId||'', d.uuid, d.libraryUuid,"
|
||
" d.footprintName||'']);" % json.dumps(name), timeout=180)
|
||
if not r.get('success'):
|
||
sys.exit(u'查器件失败: ' + json.dumps(r, ensure_ascii=False)[:300])
|
||
for n, s, u, l, fp in r['result']:
|
||
if s == lcsc:
|
||
print(u' 器件 %-16s %-11s uuid=%s fp=%s' % (n, s, u, fp))
|
||
return u, l, fp
|
||
sys.exit(u'库里找不到 %s (%s)' % (name, lcsc))
|
||
|
||
|
||
JS_CREATE = u"""
|
||
await eda.dmt_EditorControl.openDocument('%(page)s');
|
||
await new Promise(r => setTimeout(r, 1800));
|
||
const cur = (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;
|
||
if (cur !== '%(pname)s') return {err: '打错页了: ' + cur};
|
||
|
||
// 要删的旧件必须都还在 —— 不在就说明这个脚本跑过一次了
|
||
const have = new Set();
|
||
for (const c of await eda.sch_PrimitiveComponent.getAll()) have.add(c.getState_PrimitiveId());
|
||
const missing = %(need)s.filter(id => !have.has(id));
|
||
if (missing.length) return {err: '要删的旧件不在页上(已经跑过一次?): ' + missing.join(' ')};
|
||
|
||
const made = [];
|
||
for (const [des, uuid, lib, x, y, rot] of %(plan)s) {
|
||
const c = await eda.sch_PrimitiveComponent.create({libraryUuid: lib, uuid: uuid},
|
||
x, y, undefined, rot, false);
|
||
const pins = [];
|
||
for (const p of await c.getAllPins())
|
||
pins.push([String(p.pinNumber||''), String(p.pinName||''), +p.x, +p.y]);
|
||
made.push({des: des, id: c.getState_PrimitiveId(), pins: pins});
|
||
}
|
||
return {made: made};
|
||
"""
|
||
|
||
JS_FINISH = u"""
|
||
await eda.dmt_EditorControl.openDocument('%(page)s');
|
||
await new Promise(r => setTimeout(r, 1800));
|
||
const cur = (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;
|
||
if (cur !== '%(pname)s') return {err: '打错页了: ' + cur};
|
||
const out = {deleted: [], named: []};
|
||
for (const id of %(dele)s) { await eda.sch_PrimitiveComponent.delete(id); out.deleted.push(id); }
|
||
const byId = {};
|
||
for (const c of await eda.sch_PrimitiveComponent.getAll()) byId[c.getState_PrimitiveId()] = c;
|
||
for (const [id, des] of %(names)s) {
|
||
const c = byId[id];
|
||
if (!c) { out.err = '新建的元件不见了: ' + des; return out; }
|
||
await c.toAsync().setState_Designator(des).done();
|
||
out.named.push(des);
|
||
}
|
||
return out;
|
||
"""
|
||
|
||
|
||
JS_DROP = u"""
|
||
await eda.dmt_EditorControl.openDocument('%(page)s');
|
||
await new Promise(r => setTimeout(r, 1500));
|
||
const cur = (await eda.dmt_Schematic.getCurrentSchematicPageInfo()).name;
|
||
if (cur !== '%(pname)s') return {err: '打错页了: ' + cur};
|
||
let n = 0;
|
||
for (const id of %(ids)s) { await eda.sch_PrimitiveComponent.delete(id); n++; }
|
||
return {n: n};
|
||
"""
|
||
|
||
|
||
def do_page(page, pname, deslist, devmap, push):
|
||
plan, need, dele, expect = [], [], [], {}
|
||
for des in deslist:
|
||
x, y, rot = NEW_XY[des]
|
||
u, l, _fp = devmap[des]
|
||
plan.append([des, u, l, x, y, rot])
|
||
need.append(OLD[des][0])
|
||
dele.append(OLD[des][0])
|
||
expect[des] = OLD_PINS[des]
|
||
r = run(JS_CREATE % dict(page=page, pname=pname, need=json.dumps(need),
|
||
plan=json.dumps(plan)), timeout=400)
|
||
if not r.get('success'):
|
||
sys.exit(u'建新件失败: ' + json.dumps(r, ensure_ascii=False)[:500])
|
||
d = r['result']
|
||
if d.get('err'):
|
||
sys.exit(u'⛔ ' + d['err'])
|
||
|
||
# ── 硬断言:新件每个引脚都要落在旧件同号引脚的坐标上 ──
|
||
bad, names = [], []
|
||
for m in d['made']:
|
||
des = m['des']
|
||
got = {p[0]: (p[2], p[3]) for p in m['pins']}
|
||
for num, xy in expect[des].items():
|
||
if num not in got:
|
||
bad.append(u'%s 少了 p%s' % (des, num))
|
||
elif got[num] != tuple(xy):
|
||
bad.append(u'%s p%s 在 %s,旧件在 %s' % (des, num, got[num], tuple(xy)))
|
||
extra = sorted(set(got) - set(expect[des]))
|
||
if des == 'J1':
|
||
if extra != list(J1_NC):
|
||
bad.append(u'J1 多出来的脚是 %s,预期 %s' % (extra, list(J1_NC)))
|
||
elif extra:
|
||
bad.append(u'%s 多出来的脚 %s(旧件没有)' % (des, extra))
|
||
names.append([m['id'], des])
|
||
for m in d['made']:
|
||
print(u' 新件 %-4s id=%s %s' % (m['des'], m['id'],
|
||
' '.join(u'p%s"%s"@%d,%d' % tuple(p) for p in m['pins'])))
|
||
if bad:
|
||
print(u'\n⛔ 引脚对不上,**新件已经建出来了但没删旧件** —— 请手工撤销:')
|
||
for b in bad:
|
||
print(u' ' + b)
|
||
sys.exit(1)
|
||
print(u' ✔ %d 个新件的引脚逐个落在旧件同号引脚的坐标上' % len(d['made']))
|
||
if not push:
|
||
# ⚠️ **试跑必须自己收尾。** 「先 create 后 delete」(skill §9b)意味着
|
||
# 断言那一步已经把新件建到页上了 —— 不删掉的话,一次「只看看」的试跑
|
||
# 就在页面上留下 6 个和旧件完全重叠的元件,而且位号还是自动分配的,
|
||
# 下一次跑又会再建一批。2026-09-06 第一次跑就是这么留下 6 个的。
|
||
r3 = run(JS_DROP % dict(page=page, pname=pname,
|
||
ids=json.dumps([m['id'] for m in d['made']])), timeout=300)
|
||
if not r3.get('success'):
|
||
sys.exit(u'⛔ 试跑之后没能删掉试建的新件,页面现在是重叠状态,手工清理: '
|
||
+ json.dumps(r3, ensure_ascii=False)[:400])
|
||
print(u' (试跑:断言过了,刚建的 %d 个新件已经删回去,页面原样未动)'
|
||
% r3['result'].get('n', 0))
|
||
return d
|
||
r2 = run(JS_FINISH % dict(page=page, pname=pname, dele=json.dumps(dele),
|
||
names=json.dumps(names)), timeout=400)
|
||
if not r2.get('success'):
|
||
sys.exit(u'删旧件/改位号失败: ' + json.dumps(r2, ensure_ascii=False)[:500])
|
||
x = r2['result']
|
||
if x.get('err'):
|
||
sys.exit(u'⛔ ' + x['err'])
|
||
print(u' 删掉旧件 %d 个,位号设好 %s' % (len(x['deleted']), ' '.join(x['named'])))
|
||
return d
|
||
|
||
|
||
def main():
|
||
push = '--push' in sys.argv[1:]
|
||
bad = selfcheck()
|
||
if bad:
|
||
for b in bad:
|
||
print(u'⛔ ' + b)
|
||
sys.exit(1)
|
||
print(u'两处接口换件:J1 → XT30PW-M(20 A) / J26~J30 → 2510-3A 直针座')
|
||
u1 = dev(*NEW_J1)
|
||
u6 = dev(*NEW_FAN)
|
||
devmap = {'J1': u1}
|
||
for d in ('J26', 'J27', 'J28', 'J29', 'J30'):
|
||
devmap[d] = u6
|
||
print(u'\n[P1-INPUT] J1')
|
||
do_page(P1, 'P1-INPUT', ['J1'], devmap, push)
|
||
print(u'\n[P6-FAN-EDL] J26~J30')
|
||
do_page(P6, 'P6-FAN-EDL', ['J26', 'J27', 'J28', 'J29', 'J30'], devmap, push)
|
||
if push:
|
||
print(u'\n下一步(缺一不可):')
|
||
print(u' 1. J1 的 p3/p4 是机械固定脚,加进 P1-INPUT 的 NC 清单')
|
||
print(u' 2. verify_sch.py --nets(两页)/ port_audit.py / xpage.py')
|
||
print(u' 3. pcb1_import.py --bom → --verify(封装换了,pcb1-parts.json 必须重抓)')
|
||
print(u' 4. pcb1_place.FP_BODY / H_FP 加两条新封装,再重推摆位')
|
||
return 0
|
||
|
||
|
||
if __name__ == '__main__':
|
||
sys.exit(main())
|