added generator mode

This commit is contained in:
Ayzen
2026-04-01 13:00:01 +03:00
parent 43d1c226a1
commit 4abc95c372
16 changed files with 936 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
"""Hardware-PWM backend for Raspberry Pi 5 generator sweep gating."""
from __future__ import annotations
from dataclasses import dataclass, field
from pathlib import Path
_PWM_CHANNEL_BY_PIN = {
12: 0,
13: 1,
18: 2,
19: 3,
}
@dataclass(slots=True)
class HardwarePwmGate:
"""Thin wrapper around Raspberry Pi hardware PWM output."""
pin: int
frequency_hz: int
duty_cycle: float
_pwm: object | None = field(init=False, default=None, repr=False)
_running: bool = field(init=False, default=False, repr=False)
def __post_init__(self) -> None:
"""Validate static PWM configuration."""
if self.pin not in _PWM_CHANNEL_BY_PIN:
raise ValueError(
"generator_sweep.pwm_pin must be one of GPIO12, GPIO13, GPIO18, or GPIO19 on Raspberry Pi 5"
)
if self.frequency_hz <= 0:
raise ValueError("generator_sweep.pwm_frequency_hz must be > 0")
if not (0.0 < self.duty_cycle <= 1.0):
raise ValueError("generator_sweep.pwm_duty_cycle must be within (0, 1]")
def open(self) -> None:
"""Initialize hardware PWM handle."""
if self._pwm is not None:
return
try:
from rpi_hardware_pwm import HardwarePWM
except ImportError as exc:
raise RuntimeError(
"rpi_hardware_pwm is required for generator_sweep PWM output. "
"Install project dependencies on the Raspberry Pi before running this script."
) from exc
channel = _PWM_CHANNEL_BY_PIN[self.pin]
try:
self._pwm = HardwarePWM(pwm_channel=channel, hz=self.frequency_hz, chip=0)
except Exception as exc:
config_hint = _boot_config_hint()
raise RuntimeError(
"Failed to initialize Raspberry Pi hardware PWM. "
f"Enable the PWM overlay in {config_hint} by adding "
"'dtoverlay=pwm-2chan', then reboot the Raspberry Pi."
) from exc
self._running = False
def enable(self) -> None:
"""Start PWM output when not already running."""
if self._pwm is None:
raise RuntimeError("Hardware PWM gate is not open")
if self._running:
return
duty_percent = self.duty_cycle * 100.0
self._pwm.start(duty_percent)
self._running = True
def disable(self) -> None:
"""Stop PWM output when active."""
if self._pwm is None or not self._running:
return
self._pwm.stop()
self._running = False
def close(self) -> None:
"""Stop PWM and release runtime state."""
self.disable()
self._pwm = None
def _boot_config_hint() -> str:
"""Return the most likely active Raspberry Pi boot config path."""
firmware_config = Path("/boot/firmware/config.txt")
if firmware_config.exists():
return str(firmware_config)
return "/boot/config.txt"