Add /api/library datasheet import and component GET/PUT with no exam. Reorganize pytest into datasheet, library, schematic, PCB, and AF+AI. Document Rust criteria (none chosen; no rustup) and coding conformity.
123 lines
4.3 KiB
Python
123 lines
4.3 KiB
Python
"""LED forward-current check — Ohm's-law over the graph against the LED's rating.
|
|
|
|
Locks in:
|
|
1. An undersized resistor (over-current) is an ERROR with source set.
|
|
2. A properly sized resistor produces nothing.
|
|
3. Resistance strings like "5.6K" parse correctly (not 5.6 ohm).
|
|
4. Unknown rail voltage is not guessed into an ERROR.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from backend.periscopex.models import (
|
|
Component,
|
|
ComponentType,
|
|
DesignGraph,
|
|
Net,
|
|
NetType,
|
|
ResistorSpecs,
|
|
SimpleComponentSpecs,
|
|
PinConnection,
|
|
)
|
|
from backend.periscopex.led_current_check import check_led_current, _parse_resistance
|
|
|
|
|
|
def _led(values, pins, subtype="discrete.led.rgb"):
|
|
return Component(
|
|
reference="D1", value="RGB", footprint="",
|
|
component_type=ComponentType.DISCRETE, component_subtype=subtype,
|
|
mpn="LEDX",
|
|
pins=pins,
|
|
specs=SimpleComponentSpecs(specs_type="discrete", component_subtype=subtype, values=values),
|
|
)
|
|
|
|
|
|
def _res(ref, ohms_str, pins, value_ohms=None):
|
|
specs = ResistorSpecs(value_ohms=value_ohms, value_formatted=ohms_str) if value_ohms is not None else None
|
|
return Component(reference=ref, value=ohms_str, footprint="",
|
|
component_type=ComponentType.RESISTOR, mpn=ref, pins=pins, specs=specs)
|
|
|
|
|
|
def _driver(ref, pins):
|
|
return Component(reference=ref, value="", footprint="",
|
|
component_type=ComponentType.DISCRETE, mpn=ref, pins=pins)
|
|
|
|
|
|
def _graph(components, nets):
|
|
"""nets: {name: (net_type, voltage, [(ref, pin)])}"""
|
|
net_objs = {}
|
|
for name, (ntype, volt, conns) in nets.items():
|
|
net_objs[name] = Net(
|
|
name=name, net_type=ntype, voltage=volt,
|
|
pins=[PinConnection(component_ref=r, pin_number=str(p)) for r, p in conns],
|
|
)
|
|
return DesignGraph(components=components, nets=net_objs)
|
|
|
|
|
|
def _rgb_graph(green_resistor):
|
|
led = _led(
|
|
{"forward_voltage_green_v": "2.8V", "forward_current_per_channel_a": "13mA",
|
|
"common_polarity": 1.0},
|
|
{"A": "+5V", "G": "NetD1_G"},
|
|
)
|
|
q = _driver("Q1", {"3": "NetQ_D"})
|
|
comps = {"D1": led, "R1": green_resistor, "Q1": q}
|
|
nets = {
|
|
"+5V": (NetType.POWER, 5.0, [("D1", "A")]),
|
|
"NetD1_G": (NetType.SIGNAL, None, [("D1", "G"), ("R1", "2")]),
|
|
"NetQ_D": (NetType.SIGNAL, None, [("R1", "1"), ("Q1", "3")]),
|
|
}
|
|
return _graph(comps, nets)
|
|
|
|
|
|
def test_over_current_is_error():
|
|
# 100 ohm from 5 V, Vf 2.8 -> 22 mA > 13 mA rating.
|
|
g = _rgb_graph(_res("R1", "100R", {"2": "NetD1_G", "1": "NetQ_D"}, value_ohms=100.0))
|
|
findings = check_led_current(g)
|
|
assert len(findings) == 1
|
|
f = findings[0]
|
|
assert f.status == "ERROR" and f.source == "led_current_check" and f.source_page is None
|
|
assert f.designator == "D1" and "green channel" in f.finding
|
|
|
|
|
|
def test_proper_resistor_no_finding():
|
|
# 5.6K (string only, no typed value_ohms) -> ~0.4 mA, safe.
|
|
g = _rgb_graph(_res("R1", "5.6K", {"2": "NetD1_G", "1": "NetQ_D"}))
|
|
assert check_led_current(g) == []
|
|
|
|
|
|
def test_unknown_rail_no_error():
|
|
# Anode net has no voltage tag and the resistor far net is untagged -> skip.
|
|
led = _led(
|
|
{"forward_voltage_green_v": "2.8V", "forward_current_per_channel_a": "13mA"},
|
|
{"A": "NetD1_A", "G": "NetD1_G"},
|
|
)
|
|
r = _res("R1", "100R", {"2": "NetD1_G", "1": "NetQ_D"}, value_ohms=100.0)
|
|
q = _driver("Q1", {"3": "NetQ_D"})
|
|
g = _graph(
|
|
{"D1": led, "R1": r, "Q1": q},
|
|
{
|
|
"NetD1_A": (NetType.SIGNAL, None, [("D1", "A")]),
|
|
"NetD1_G": (NetType.SIGNAL, None, [("D1", "G"), ("R1", "2")]),
|
|
"NetQ_D": (NetType.SIGNAL, None, [("R1", "1"), ("Q1", "3")]),
|
|
},
|
|
)
|
|
assert check_led_current(g) == []
|
|
|
|
|
|
def test_no_rating_skipped():
|
|
g = _rgb_graph(_res("R1", "100R", {"2": "NetD1_G", "1": "NetQ_D"}, value_ohms=100.0))
|
|
# Strip the rating off the LED specs.
|
|
g.components["D1"].specs.values = {"forward_voltage_green_v": "2.8V"}
|
|
assert check_led_current(g) == []
|
|
|
|
|
|
def test_parse_resistance():
|
|
assert _parse_resistance("5.6K") == 5600.0
|
|
assert _parse_resistance("5K6") == 5600.0
|
|
assert _parse_resistance("150R") == 150.0
|
|
assert _parse_resistance("4R7") == 4.7
|
|
assert _parse_resistance("1M") == 1_000_000.0
|
|
assert _parse_resistance("0") == 0.0
|
|
assert _parse_resistance("100") == 100.0
|