initial commit
This commit is contained in:
447
Projects/SDR_Rover/protocol/link_controller.py
Normal file
447
Projects/SDR_Rover/protocol/link_controller.py
Normal file
@@ -0,0 +1,447 @@
|
||||
"""
|
||||
Устойчивый контроллер режима радиоканала SDR Rover Link.
|
||||
|
||||
Контроллер устраняет частое переключение режимов
|
||||
из-за шума измерения Eb/N0.
|
||||
|
||||
Реализованы:
|
||||
|
||||
1. Экспоненциальное усреднение Eb/N0.
|
||||
2. Гистерезис порогов.
|
||||
3. Подтверждение повышения режима.
|
||||
4. Минимальное время удержания режима.
|
||||
5. Быстрое аварийное понижение режима.
|
||||
|
||||
Доступные режимы:
|
||||
|
||||
0 — изображения отключены;
|
||||
128 — фрагменты по 128 байт;
|
||||
512 — фрагменты по 512 байт;
|
||||
1024 — фрагменты по 1024 байта.
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Допустимые режимы
|
||||
# ============================================================
|
||||
|
||||
IMAGE_OFF = 0
|
||||
|
||||
FRAGMENT_MODE_128 = 128
|
||||
FRAGMENT_MODE_512 = 512
|
||||
FRAGMENT_MODE_1024 = 1024
|
||||
|
||||
VALID_MODES = (
|
||||
IMAGE_OFF,
|
||||
FRAGMENT_MODE_128,
|
||||
FRAGMENT_MODE_512,
|
||||
FRAGMENT_MODE_1024,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Результат одного шага контроллера
|
||||
# ============================================================
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LinkControllerResult:
|
||||
"""
|
||||
Состояние контроллера после нового измерения Eb/N0.
|
||||
"""
|
||||
|
||||
step: int
|
||||
|
||||
measured_eb_n0_db: float
|
||||
filtered_eb_n0_db: float
|
||||
|
||||
previous_mode: int
|
||||
selected_mode: int
|
||||
|
||||
mode_changed: bool
|
||||
|
||||
pending_upgrade_mode: int | None
|
||||
pending_upgrade_count: int
|
||||
|
||||
reason: str
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Контроллер
|
||||
# ============================================================
|
||||
|
||||
class LinkModeController:
|
||||
"""
|
||||
Устойчивый адаптивный контроллер режима изображения.
|
||||
|
||||
Пороговые значения выбраны по результатам Lab015 и Lab016.
|
||||
|
||||
Гистерезис означает, что повышение и понижение режима
|
||||
происходят при разных значениях Eb/N0.
|
||||
|
||||
Пример для режима 512 байт:
|
||||
|
||||
повышение 128 → 512: Eb/N0 >= 8,7 дБ
|
||||
понижение 512 → 128: Eb/N0 < 8,1 дБ
|
||||
|
||||
Благодаря разнице 0,6 дБ небольшие колебания около
|
||||
порога не вызывают постоянного переключения.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
initial_mode: int = IMAGE_OFF,
|
||||
filter_alpha: float = 0.35,
|
||||
upgrade_confirmation_count: int = 3,
|
||||
minimum_hold_steps: int = 2,
|
||||
) -> None:
|
||||
"""
|
||||
Создать контроллер.
|
||||
|
||||
Параметры
|
||||
----------
|
||||
initial_mode:
|
||||
Начальный режим.
|
||||
|
||||
filter_alpha:
|
||||
Коэффициент экспоненциального фильтра.
|
||||
|
||||
Большое значение:
|
||||
быстрее реакция;
|
||||
слабее фильтрация шума.
|
||||
|
||||
Малое значение:
|
||||
медленнее реакция;
|
||||
сильнее фильтрация.
|
||||
|
||||
upgrade_confirmation_count:
|
||||
Сколько последовательных измерений требуется
|
||||
для повышения режима.
|
||||
|
||||
minimum_hold_steps:
|
||||
Минимальное число шагов удержания режима
|
||||
перед очередным повышением.
|
||||
"""
|
||||
|
||||
if initial_mode not in VALID_MODES:
|
||||
raise ValueError(
|
||||
f"Недопустимый начальный режим: {initial_mode}"
|
||||
)
|
||||
|
||||
if not 0.0 < filter_alpha <= 1.0:
|
||||
raise ValueError(
|
||||
"filter_alpha должен находиться в диапазоне 0...1"
|
||||
)
|
||||
|
||||
if upgrade_confirmation_count <= 0:
|
||||
raise ValueError(
|
||||
"upgrade_confirmation_count должен быть положительным"
|
||||
)
|
||||
|
||||
if minimum_hold_steps < 0:
|
||||
raise ValueError(
|
||||
"minimum_hold_steps не может быть отрицательным"
|
||||
)
|
||||
|
||||
self.current_mode = initial_mode
|
||||
|
||||
self.filter_alpha = filter_alpha
|
||||
|
||||
self.upgrade_confirmation_count = (
|
||||
upgrade_confirmation_count
|
||||
)
|
||||
|
||||
self.minimum_hold_steps = (
|
||||
minimum_hold_steps
|
||||
)
|
||||
|
||||
self.filtered_eb_n0_db: float | None = None
|
||||
|
||||
self.step = 0
|
||||
self.steps_in_mode = 0
|
||||
|
||||
self.pending_upgrade_mode: int | None = None
|
||||
self.pending_upgrade_count = 0
|
||||
|
||||
# ========================================================
|
||||
# Внутренние функции
|
||||
# ========================================================
|
||||
|
||||
def _update_filter(
|
||||
self,
|
||||
measured_eb_n0_db: float,
|
||||
) -> float:
|
||||
"""
|
||||
Обновить экспоненциальное среднее.
|
||||
|
||||
Формула:
|
||||
|
||||
filtered =
|
||||
alpha * measured
|
||||
+ (1 - alpha) * previous_filtered
|
||||
"""
|
||||
|
||||
if self.filtered_eb_n0_db is None:
|
||||
self.filtered_eb_n0_db = measured_eb_n0_db
|
||||
|
||||
else:
|
||||
self.filtered_eb_n0_db = (
|
||||
self.filter_alpha
|
||||
* measured_eb_n0_db
|
||||
+ (
|
||||
1.0
|
||||
- self.filter_alpha
|
||||
)
|
||||
* self.filtered_eb_n0_db
|
||||
)
|
||||
|
||||
return self.filtered_eb_n0_db
|
||||
|
||||
def _emergency_safe_mode(
|
||||
self,
|
||||
measured_eb_n0_db: float,
|
||||
) -> int:
|
||||
"""
|
||||
Определить максимально безопасный режим
|
||||
по мгновенному измерению.
|
||||
|
||||
Используется для быстрого понижения режима,
|
||||
не дожидаясь усреднения.
|
||||
"""
|
||||
|
||||
if measured_eb_n0_db < 5.8:
|
||||
return IMAGE_OFF
|
||||
|
||||
if measured_eb_n0_db < 7.7:
|
||||
return FRAGMENT_MODE_128
|
||||
|
||||
if measured_eb_n0_db < 8.7:
|
||||
return FRAGMENT_MODE_512
|
||||
|
||||
return FRAGMENT_MODE_1024
|
||||
|
||||
def _desired_mode_from_filtered(
|
||||
self,
|
||||
filtered_eb_n0_db: float,
|
||||
) -> int:
|
||||
"""
|
||||
Определить желаемый режим с учётом гистерезиса.
|
||||
|
||||
Повышение выполняется только на один уровень за шаг.
|
||||
"""
|
||||
|
||||
if self.current_mode == IMAGE_OFF:
|
||||
|
||||
if filtered_eb_n0_db >= 6.8:
|
||||
return FRAGMENT_MODE_128
|
||||
|
||||
return IMAGE_OFF
|
||||
|
||||
if self.current_mode == FRAGMENT_MODE_128:
|
||||
|
||||
if filtered_eb_n0_db < 6.2:
|
||||
return IMAGE_OFF
|
||||
|
||||
if filtered_eb_n0_db >= 8.7:
|
||||
return FRAGMENT_MODE_512
|
||||
|
||||
return FRAGMENT_MODE_128
|
||||
|
||||
if self.current_mode == FRAGMENT_MODE_512:
|
||||
|
||||
if filtered_eb_n0_db < 8.1:
|
||||
return FRAGMENT_MODE_128
|
||||
|
||||
if filtered_eb_n0_db >= 9.8:
|
||||
return FRAGMENT_MODE_1024
|
||||
|
||||
return FRAGMENT_MODE_512
|
||||
|
||||
if self.current_mode == FRAGMENT_MODE_1024:
|
||||
|
||||
if filtered_eb_n0_db < 9.2:
|
||||
return FRAGMENT_MODE_512
|
||||
|
||||
return FRAGMENT_MODE_1024
|
||||
|
||||
raise RuntimeError(
|
||||
f"Неизвестный текущий режим: {self.current_mode}"
|
||||
)
|
||||
|
||||
def _reset_pending_upgrade(
|
||||
self,
|
||||
) -> None:
|
||||
"""
|
||||
Сбросить накопленное подтверждение повышения.
|
||||
"""
|
||||
|
||||
self.pending_upgrade_mode = None
|
||||
self.pending_upgrade_count = 0
|
||||
|
||||
def _change_mode(
|
||||
self,
|
||||
new_mode: int,
|
||||
) -> None:
|
||||
"""
|
||||
Немедленно установить новый режим.
|
||||
"""
|
||||
|
||||
if new_mode not in VALID_MODES:
|
||||
raise ValueError(
|
||||
f"Недопустимый режим: {new_mode}"
|
||||
)
|
||||
|
||||
self.current_mode = new_mode
|
||||
self.steps_in_mode = 0
|
||||
|
||||
self._reset_pending_upgrade()
|
||||
|
||||
# ========================================================
|
||||
# Основной шаг
|
||||
# ========================================================
|
||||
|
||||
def update(
|
||||
self,
|
||||
measured_eb_n0_db: float,
|
||||
) -> LinkControllerResult:
|
||||
"""
|
||||
Обработать новое измерение Eb/N0.
|
||||
|
||||
Логика:
|
||||
|
||||
1. Обновить фильтр.
|
||||
2. Проверить аварийное понижение.
|
||||
3. Проверить обычное понижение по фильтру.
|
||||
4. Для повышения потребовать несколько подтверждений.
|
||||
5. Вернуть результат.
|
||||
"""
|
||||
|
||||
measured_eb_n0_db = float(
|
||||
measured_eb_n0_db
|
||||
)
|
||||
|
||||
previous_mode = self.current_mode
|
||||
|
||||
filtered_eb_n0_db = self._update_filter(
|
||||
measured_eb_n0_db
|
||||
)
|
||||
|
||||
reason = "Режим удерживается"
|
||||
|
||||
# ----------------------------------------------------
|
||||
# Аварийное быстрое понижение
|
||||
# ----------------------------------------------------
|
||||
|
||||
emergency_mode = self._emergency_safe_mode(
|
||||
measured_eb_n0_db
|
||||
)
|
||||
|
||||
if emergency_mode < self.current_mode:
|
||||
|
||||
self._change_mode(
|
||||
emergency_mode
|
||||
)
|
||||
|
||||
reason = (
|
||||
"Аварийное понижение по мгновенному Eb/N0"
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
desired_mode = self._desired_mode_from_filtered(
|
||||
filtered_eb_n0_db
|
||||
)
|
||||
|
||||
# ------------------------------------------------
|
||||
# Обычное понижение
|
||||
# ------------------------------------------------
|
||||
|
||||
if desired_mode < self.current_mode:
|
||||
|
||||
self._change_mode(
|
||||
desired_mode
|
||||
)
|
||||
|
||||
reason = (
|
||||
"Понижение по фильтрованному Eb/N0"
|
||||
)
|
||||
|
||||
# ------------------------------------------------
|
||||
# Возможное повышение
|
||||
# ------------------------------------------------
|
||||
|
||||
elif desired_mode > self.current_mode:
|
||||
|
||||
if (
|
||||
self.steps_in_mode
|
||||
< self.minimum_hold_steps
|
||||
):
|
||||
self._reset_pending_upgrade()
|
||||
|
||||
reason = (
|
||||
"Повышение запрещено: "
|
||||
"не выдержано минимальное время режима"
|
||||
)
|
||||
|
||||
else:
|
||||
|
||||
if (
|
||||
self.pending_upgrade_mode
|
||||
== desired_mode
|
||||
):
|
||||
self.pending_upgrade_count += 1
|
||||
|
||||
else:
|
||||
self.pending_upgrade_mode = (
|
||||
desired_mode
|
||||
)
|
||||
|
||||
self.pending_upgrade_count = 1
|
||||
|
||||
reason = (
|
||||
"Накопление подтверждений "
|
||||
"для повышения режима"
|
||||
)
|
||||
|
||||
if (
|
||||
self.pending_upgrade_count
|
||||
>= self.upgrade_confirmation_count
|
||||
):
|
||||
self._change_mode(
|
||||
desired_mode
|
||||
)
|
||||
|
||||
reason = (
|
||||
"Режим повышен после "
|
||||
"нескольких подтверждений"
|
||||
)
|
||||
|
||||
else:
|
||||
self._reset_pending_upgrade()
|
||||
|
||||
mode_changed = (
|
||||
self.current_mode != previous_mode
|
||||
)
|
||||
|
||||
result = LinkControllerResult(
|
||||
step=self.step,
|
||||
measured_eb_n0_db=measured_eb_n0_db,
|
||||
filtered_eb_n0_db=filtered_eb_n0_db,
|
||||
previous_mode=previous_mode,
|
||||
selected_mode=self.current_mode,
|
||||
mode_changed=mode_changed,
|
||||
pending_upgrade_mode=(
|
||||
self.pending_upgrade_mode
|
||||
),
|
||||
pending_upgrade_count=(
|
||||
self.pending_upgrade_count
|
||||
),
|
||||
reason=reason,
|
||||
)
|
||||
|
||||
self.step += 1
|
||||
self.steps_in_mode += 1
|
||||
|
||||
return result
|
||||
|
||||
Reference in New Issue
Block a user