Files
2026-07-17 12:57:18 +03:00

108 lines
3.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import threading
import time
from typing import Optional
class GPIOController:
"""Thread-safe singleton controller for GPIO output pin."""
_instance = None
_instance_lock = threading.Lock()
def __init__(self, pin: int, simulation_mode: bool = False):
self.pin = pin
self.simulation_mode = simulation_mode
self._lock = threading.Lock()
self._blink_stop_event = threading.Event()
self._blink_thread: Optional[threading.Thread] = None
self._state = False
self._gpio = None
self._gpio_available = False
if not self.simulation_mode:
try:
import RPi.GPIO as GPIO # type: ignore[import-not-found]
self._gpio = GPIO
self._gpio.setmode(GPIO.BCM)
self._gpio.setup(self.pin, GPIO.OUT)
self._gpio.output(self.pin, GPIO.LOW)
self._gpio_available = True
except Exception:
self._gpio = None
self._gpio_available = False
@classmethod
def get_instance(cls, pin: int = 18, simulation_mode: bool = False):
with cls._instance_lock:
if cls._instance is None:
cls._instance = cls(pin=pin, simulation_mode=simulation_mode)
return cls._instance
@classmethod
def _reset_for_tests(cls):
with cls._instance_lock:
if cls._instance is not None:
cls._instance.cleanup()
cls._instance = None
def _write_locked(self, state: bool) -> None:
self._state = state
if self._gpio_available and self._gpio is not None:
self._gpio.output(self.pin, self._gpio.HIGH if state else self._gpio.LOW)
def on(self) -> None:
with self._lock:
self._write_locked(True)
def off(self) -> None:
with self._lock:
self._write_locked(False)
def is_on(self) -> bool:
with self._lock:
return bool(self._state)
@property
def rpi_gpio_available(self) -> bool:
"""True, если поднят настоящий RPi.GPIO (Raspberry Pi). В симуляции всегда False."""
return bool(self._gpio_available)
def stop_blink(self) -> None:
self._blink_stop_event.set()
if self._blink_thread and self._blink_thread.is_alive():
self._blink_thread.join(timeout=2)
self._blink_thread = None
self._blink_stop_event.clear()
def blink(self, times: int = 3, delay: float = 0.2) -> None:
if times <= 0:
return
self.stop_blink()
def _worker():
for _ in range(times):
if self._blink_stop_event.is_set():
break
self.on()
time.sleep(delay)
if self._blink_stop_event.is_set():
break
self.off()
time.sleep(delay)
self.off()
self._blink_thread = threading.Thread(target=_worker, daemon=True, name="gpio-blink")
self._blink_thread.start()
def cleanup(self) -> None:
self.stop_blink()
self.off()
if self._gpio_available and self._gpio is not None:
try:
self._gpio.cleanup(self.pin)
except Exception:
pass