AnasSarkiz/magnetic-shutter-remote-r8

A USB‑C rechargeable battery-powered ESP32‑C3 wireless controller with Li‑ion charging, protected 3.3‑V buck‑boost regulation, thermal/safety monitoring, status LEDs, buttons, programming UART, crystal clock, and 2.4‑GHz antenna matching network.

Version
0.4.0-prototype
License
unset
Stars
0

tests/cam/test_power_width_budgets.py

"""Detect switching-ripple width failures missed by a DC-only check."""
import importlib.util
import math
from pathlib import Path
import sys
import unittest

ROOT = Path(__file__).resolve().parents[2]
SPEC = importlib.util.spec_from_file_location('power_width_review',ROOT/'scripts/review-r8-power.py')
REVIEW = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = REVIEW
SPEC.loader.exec_module(REVIEW)


class PowerWidthBudgetTest(unittest.TestCase):
    def test_old_neck_passes_dc_but_fails_switching_rms(self):
        switching = REVIEW.switching_current_budget({'input_floor_v':2.971566710888736,
                                                      'input_amps':0.7430844449524722})
        old_capacity = 0.048*10**0.44*((0.2/0.0254)*(0.035/0.0254))**0.725
        self.assertGreater(old_capacity,0.7430844449524722)
        self.assertGreater(switching['rms_design_amps'],old_capacity)
        paths = {'L1':{'ipc2221_external_estimate_10c_amps':old_capacity}}
        self.assertEqual(len(REVIEW.width_budget_failures(paths,{'L1':switching['rms_design_amps']})),1)
        paths['L1']['ipc2221_external_estimate_10c_amps'] = 0.048*10**0.44*((0.3/0.0254)*(0.035/0.0254))**0.725
        self.assertEqual(REVIEW.width_budget_failures(paths,{'L1':switching['rms_design_amps']}),[])

    def test_ripple_uses_minimum_frequency_and_inductance(self):
        result = REVIEW.switching_current_budget({'input_floor_v':3,'input_amps':0.74})
        # Exact manufacturer row is 1.5uH +/-30% (minimum1.05uH).
        self.assertAlmostEqual(result['minimum_inductance_h'],1.05e-6,delta=1e-12)
        ripple = 3*(1-3/3.533)/(2_200_000*1.05e-6)
        self.assertAlmostEqual(result['boost_ripple_peak_to_peak_amps'],ripple)
        self.assertAlmostEqual(result['rms_design_amps'],math.hypot(0.74,ripple/math.sqrt(12)))
        self.assertAlmostEqual(result['peak_design_amps'],0.74+ripple/2)

    def test_saturation_rating_requires_recommended_headroom(self):
        # A2.0A peak is below the2.3A rating but needs2.4A with TI margin.
        failures = REVIEW.inductor_rating_failures({'rms_design_amps':1.0,'peak_design_amps':2.0})
        self.assertEqual(len(failures),1)
        self.assertIn('20%',failures[0])
        self.assertEqual(REVIEW.inductor_rating_failures({'rms_design_amps':1.0,'peak_design_amps':1.8}),[])

    def test_rms_rating_is_checked_independently_of_peak_headroom(self):
        failures = REVIEW.inductor_rating_failures({'rms_design_amps':1.8,'peak_design_amps':1.8})
        self.assertEqual(len(failures),1)
        self.assertIn('RMS',failures[0])