component
LTC2945CMS#PBF - Wide Range I2C Power Monitor
Public Made by Adomby adom
Analog Devices (Linear Technology) LTC2945CMS#PBF: wide range I2C power monitor, 0 V to 80 V current and voltage sensing, 12-bit ADC, 24-bit power, min/max and alerts, split SDA for opto-isolation, 0 C to 70 C, MSOP-12. KiCad, Fusion and Altium libraries, 3D model and datasheet.
main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
#!/usr/bin/env python3
"""Read the pin records straight out of an Altium .SchLib (OLE2/CFB container, binary pin records), independent of
adom-lbr's own reader, so a writer bug cannot hide behind a matching reader. Prints, per pin: designator, name,
electrical type and the conglomerate flags (hidden, name shown, designator shown).
Pin record layout per the public format notes (AltiumSharp / pyAltiumLib): int32 record=2, byte, int16 owner part,
byte display mode, 4 symbol bytes, pascal description, byte formal type, byte electrical, byte conglomerate,
int16 length, int16 x, int16 y, int32 colour, pascal name, pascal designator.
Conglomerate bits: 0x01/0x02 orientation, 0x04 hide, 0x08 name visible, 0x10 designator visible.
usage: schlib_pins.py file.SchLib [out.json]"""
import struct, sys, json
ET = {0: "input", 1: "io", 2: "output", 3: "open_collector", 4: "passive", 5: "hiz", 6: "open_emitter", 7: "power"}
def cfb_streams(path):
b = open(path, "rb").read()
assert b[:8] == bytes.fromhex("d0cf11e0a1b11ae1"), "not an OLE2 file"
ss = 1 << struct.unpack_from("<H", b, 30)[0]; mss = 1 << struct.unpack_from("<H", b, 32)[0]
nfat, dir0, _, mini_cut, minifat0, nminifat, difat0, ndifat = struct.unpack_from("<IIIIIIII", b, 44)
hdr = max(512, ss) # the header fills a whole sector: 512 bytes in CFB v3, 4096 in v4
sec = lambda i: b[hdr + i * ss: hdr + (i + 1) * ss]
difat = list(struct.unpack_from("<109I", b, 76)); d = difat0
for _ in range(ndifat):
s = sec(d); difat += list(struct.unpack_from("<%dI" % (ss // 4 - 1), s)); d = struct.unpack_from("<I", s, ss - 4)[0]
fat = []
for f in difat[:nfat]: fat += list(struct.unpack_from("<%dI" % (ss // 4), sec(f)))
def chain(i):
# a sector chain visits each sector once; a cycle or an out-of-range index means a bad file or a misread
# header, so stop instead of growing the list until the machine runs out of memory
out = []; seen = set()
while i < 0xFFFFFFFA:
if i in seen or i >= len(fat): raise ValueError(f"bad FAT chain at sector {i} after {len(out)} sectors")
seen.add(i); out.append(i); i = fat[i]
return out
read = lambda i: b"".join(sec(j) for j in chain(i))
dirs = read(dir0); ents = []
for k in range(len(dirs) // 128):
e = dirs[k * 128:(k + 1) * 128]; nl = struct.unpack_from("<H", e, 64)[0]
left, right, child = struct.unpack_from("<III", e, 68)
ents.append(dict(name=e[:max(nl - 2, 0)].decode("utf-16-le"), type=e[66], left=left, right=right, child=child,
start=struct.unpack_from("<I", e, 116)[0], size=struct.unpack_from("<Q", e, 120)[0]))
root = ents[0]; ministream = read(root["start"]) if root["start"] < 0xFFFFFFFA else b""
minifat = []
if nminifat:
mf = read(minifat0); minifat = list(struct.unpack_from("<%dI" % (len(mf) // 4), mf))
def data(e):
if e["size"] < mini_cut:
out = []; i = e["start"]
while i < 0xFFFFFFFA: out.append(ministream[i * mss:(i + 1) * mss]); i = minifat[i]
return b"".join(out)[:e["size"]]
return read(e["start"])[:e["size"]]
paths = {}
def walk(idx, prefix):
if idx == 0xFFFFFFFF: return
e = ents[idx]; walk(e["left"], prefix); walk(e["right"], prefix)
p = prefix + "/" + e["name"]
if e["type"] == 2: paths[p] = data(e)
if e["type"] == 1: walk(e["child"], p)
walk(root["child"], "")
return paths
def pascal(b, o):
n = b[o]; return b[o + 1:o + 1 + n].decode("latin-1"), o + 1 + n
def pins(stream):
o = 0; out = []
while o + 4 <= len(stream):
hdr = struct.unpack_from("<I", stream, o)[0]; ln = hdr & 0xFFFFFF; kind = hdr >> 24; rec = stream[o + 4:o + 4 + ln]; o += 4 + ln
if kind == 1 and len(rec) > 4 and struct.unpack_from("<i", rec, 0)[0] == 2:
p = 4 + 1 + 2 + 1 + 4
desc, p = pascal(rec, p)
formal, elec, cong = rec[p], rec[p + 1], rec[p + 2]; p += 3
length, x, y = struct.unpack_from("<hhh", rec, p); p += 6 + 4
name, p = pascal(rec, p); des, p = pascal(rec, p)
out.append(dict(designator=des, name=name, electrical=ET.get(elec, elec), electrical_code=elec,
hidden=bool(cong & 0x04), name_visible=bool(cong & 0x08), designator_visible=bool(cong & 0x10),
conglomerate=cong, length=length, x=x, y=y))
return out
res = {}
for path, blob in cfb_streams(sys.argv[1]).items():
if path.endswith("/Data") and path.count("/") == 2:
ps = pins(blob)
if ps: res[path.split("/")[1]] = ps
print(json.dumps(res, indent=1))
if len(sys.argv) > 2: json.dump(res, open(sys.argv[2], "w"), indent=1)