Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

А не послушать ли нам «радио Судного дня»… с AI? Создаю АПК радиолюбителя для УВБ-76

Дата публикации: 06-08-2026 07:55:11

Я занимаюсь инфраструктурным DevOps в YADRO — кластеры, виртуализация, мониторинг, оптимизация железа. Последнее время на работе был сильный завал. Голова кипела. Захотелось отвлечься на что-то совсем другое: переключить мозги с «как ускорить сборку на три секунды» на «как это вообще работает». Пока думал, как занять подобным образом свободное от работы время, попалась мне статья про УВБ-76.
 
Знаете это чувство? Читаешь про что-то странное и загадочное, и внутри загорается лампочка: «А чего так не системно то?» Тут и родилась идея объединить свою профессиональную деятельность с чем-то новым для меня. Не просто послушать разок жужжание из любопытства, а построить целую станцию наблюдения. Сразу пришло понимание, что сидеть ночами с наушниками и слушать странный эфир я не хочу... Читать далее

Основное содержимое страницы с новостью.

Я занимаюсь инфраструктурным DevOps в YADRO — кластеры, виртуализация, мониторинг, оптимизация железа. Последнее время на работе был сильный завал. Голова кипела. Захотелось отвлечься на что-нибудь совсем другое: переключить мозги с «как ускорить сборку на три секунды» на «как это вообще работает». Пока думал, как занять подобным образом свободное от работы время, попалась мне статья про УВБ-76.
 
Знаете это чувство? Читаешь про что-то странное и загадочное, и внутри загорается лампочка: «А чего так не системно-то?» Тут и родилась идея объединить свою профессиональную деятельность с чем-то новым для меня. Не просто послушать разок жужжание из любопытства, а построить целую станцию наблюдения. Сразу пришло понимание, что сидеть ночами с наушниками и слушать странный эфир я не хочу.

b12a37856676c064b837182611036ae4.png

Если можно автоматизировать, давайте автоматизируем, благо технический бэкграунд позволяет. Последние годы я плотно занимаюсь AI и ML. Дома развернут свой GPU-кластер на Kubernetes — есть на чем гонять инференс. Плюс целый ящик железа, которое ждало своего звездного часа: Raspberry Pi 5 на 8 ГБ, M.2 HAT с SSD на 4 ТБ, какие-то платы, провода, блоки питания. Все это лежало и пылилось после прошлых экспериментов.

Вкратце про УВБ-76

Для тех, кто не в курсе: УВБ-76 — это без преувеличения главная радиозагадка постсоветского пространства. Радиостанция, которая уже несколько десятилетий круглосуточно передает в эфир монотонный жужжащий звук. Двадцать четыре часа в сутки, семь дней в неделю — просто жужжащий радиофон на частоте 4 625 кГц.

Казалось бы, жужжит и жужжит, мало ли чем там военные балуются. Но все становится интереснее, когда жужжание внезапно обрывается и мужской (иногда женский) голос зачитывает загадочные сообщения: «МДЖБ, МДЖБ, БОРИС, ИГЛА, ТОПОЛЬ, 55, 22...». После этого жужжание возвращается. И так — годами.

Станцию слышно по всему миру. Ее принимают от Санкт-Петербурга до Бразилии. Есть фанаты, которые слушают ее сутками, записывают каждое голосовое сообщение и строят теории.

Что это вообще такое, точно не знает никто. Официально — никаких объяснений. Но общественный консенсус таков: это военный объект, а именно канал боевого управления. Скорее всего, принадлежит ВМФ или РВСН (ракетным войскам стратегического назначения).

SDS, RTL, SDR и другая матчасть 

Первый стоппер возник почти сразу. Я много лет работал в телекоме с операторами связи, так что радиоволны, частоты, диапазоны и модуляции для меня не были магией. Техническую базу я понимал. Но именно с приемным оборудованием не сталкивался: моя работа всегда была на стороне инфраструктуры, а не радиоэфира. Про домашние SDR-приемники я не знал вообще. Что такое RTL-SDR? Какой свисток взять? Чем они отличаются? Нужна ли специальная антенна для коротких волн? В этих вопросах я был абсолютным новичком и начал разбираться с нуля.

SDR (Software-Defined Radio) — это программно-определяемое радио. Звучит сложно, но на деле все просто. Обычный радиоприемник — это «железная» коробка, в которой все настроено на заводе: фильтры, демодуляторы, декодеры. SDR — это маленький USB-донгл, который оцифровывает радиосигнал и отдает его в компьютер. А компьютер уже делает остальное: фильтрует, демодулирует, декодирует. Таким образом, радио становится программой.

RTL-SDR — это семейство дешевых USB-приемников, построенных на чипе RTL2832U. Изначально они создавались для приема цифрового телевидения (DVB-T), но энтузиасты обнаружили, что чип можно перевести в режим «сырого» приема и получить доступ к радиоспектру от 500 кГц до 1,7 ГГц. Стоит такой свисток копейки — от 500 до 5 000 рублей в зависимости от модели.

Блок-схема Realtek RTL2832U

Блок-схема Realtek RTL2832U

Почему не любой свисток подходит? Дешевые донглы за 500 рублей отлично работают на УКВ (FM-радио, авиадиапазон), но для коротких волн, где живет УВБ-76, им не хватает чувствительности. Нужна модель с поддержкой Direct Sampling — режима, в котором антенна подключается напрямую к чипу, минуя тюнер. Я взял RTL-SDR Blog V4 — у него есть встроенный фильтр помех и термокомпенсированный генератор (TCXO).

Чем хорош термокомпенсированный генератор? Обычные кварцевые генераторы в дешевых приемниках «плывут» при нагреве. Ты настроился на 4 625 кГц, но через полчаса работы частота сдвинулась на 4626 или 4624 — и ты слушаешь пустоту. TCXO держит частоту с точностью до миллионной доли (1 PPM). За 12 суток непрерывной работы Buzzer-1 не зафиксировал ни одного ухода частоты. Так я могу быть уверен: если жужжание пропадет — это событие на станции, а не дрейф приемника.

Пока пересматривал все полезные форумы и обзоры, понял: домашнее радио — это отдельная вселенная со своим сленгом, своими гуру и религиозными войнами на тему «какой приемник лучше».

BOM, Ozon и бесконечное ожидание

С чего начинается любой инженерный проект? Правильно: со списка покупок. Открыл маркетплейс и начал собирать корзину. Это был мой первый BOM (Bill of Materials) для радио, и я чувствовал себя немного странно: обычно я заказываю серверное железо или кабели для дата-центра, а тут — какие-то свистки, антенны-петли и ферритовые защелки.
 
Сразу скажу: без помощи разобраться было бы сложно. Мой напарник, с которым мы обсуждали проект, как раз разбирался в радио и направлял меня. Именно он объяснил, почему не стоит брать дешевый синий донгл за 500 рублей, а лучше взять RTL-SDR Blog V4 — с нормальным КВ-трактом, фильтрами и термокомпенсированным генератором. Причины я описал выше. Напарник также посоветовал вместо комплектного телескопа антенну YouLoop, потому что она не собирает помехи от роутеров и зарядок.
 
Один за другим я определился с приемником, антенной, кабелями, переходниками. Отдельная история вышла с GPS-модулем: в перспективе он бы пригодился, чтобы развить проект до радиотелескопа или портативного радара. Но увидев, что подходящая мне модель стоит в районе 11 тысяч, решил: пока без GPS.
 
Пока приехало еще не все. RTL-SDR был у меня уже через три дня, а вот антенну жду до сих пор. Мой Raspberry Pi 5 сам лежит без дела уже года три. Комплектная антенна-телескоп RTL-SDR выглядела игрушечной, но для старта ее оказалось достаточно. Придет YouLoop — заменю и усилю.

3b9b77e13f1247a82fec80f4243bb259.jpegПервый запуск: как я пытался услышать жужжание на комплектную антенну и что из этого вышлоРаспаковка и первый старт

Первым делом я записал образ Raspberry Pi OS Lite на microSD, настроил Wi-Fi и SSH, подключил приемник. Признаюсь честно: когда впервые запустил rtl_test -t и увидел в выводе RTLSDRBlog, Blog V4, SN: 00000001, испытал детскую радость. Железо работает! Мой первый в жизни SDR-приемник ожил.

Оставалось главное — услышать ту самую «жужжалку».

Битва с антенной

Я настроил rtl_fm на частоту 4 625 кГц, включил прямой режим для коротких волн, запустил и услышал… ничего. Только шум. Белый шум, как из ненастроенного телевизора. Первая мысль: «Приемник сломан?» Вторая: «Я что-то не так настроил?» Третья: «Может, УВБ-76 вообще не вещает?»

Но нет, проблема была в антенне. Телескопический штырь из комплектации RTL-SDR рассчитан на УКВ-диапазон — FM-радио, авиация. Для коротких волн на 4,6 МГц нужна была антенна длиной около 16 метров — то есть в 16 раз длиннее моей.

Но я решил не сдаваться раньше времени. Поставил антенну на подоконник, направил в сторону предполагаемого передатчика — и, к моему удивлению, в наушниках раздалось монотонное жужжание. Слабое, с помехами, но совершенно узнаваемое. То самое, легендарное. УВБ-76 была поймана на комплектную антенну без каких-либо модификаций.

Позже, когда приедет заказанная антенна YouLoop, прием станет еще чище и громче. Но главное, что уже на этом этапе я понял: система работает. Можно строить дальше.

e3c9f0f1d8c55679385c863b64ec019d.jpegПервая архитектура

Уже на следующий день после первого приема у меня был работающий прототип:

  1. Rtl_fm захватывает эфир.

  2. Python-скрипт читает аудиопоток и отправляет его в браузер через WebSocket.

  3. Браузер воспроизводит звук через Web Audio API.

Это была минимальная версия, просто «слушалка» с веб-интерфейсом. Но уже на этом этапе я заложил основу для будущего расширения: весь код был разбит на модули, каждый отвечал за свою задачу.

Позже, когда я добавил Whisper для распознавания речи и Ollama для анализа кодовых слов, модульная архитектура спасла мне жизнь. Я мог менять AI-часть, не трогая SDR-захват, и наоборот.

Первые метрики

Почти сразу я начал собирать метрики жужжания, просто чтобы понять, насколько сигнал стабилен. Каждые 60 секунд скрипт записывал в PostgreSQL частоту, RMS (среднеквадратичное значение) и ширину полосы.

Через четыре часа у меня уже было 228 записей, и я мог сделать первые выводы:

  • Частота стабильна как часы: 4 625 кГц без дрейфа.

  • RMS не меняется: передатчик работает с постоянной мощностью.

  • Ширина полосы — около 900 Гц, что широко для чистого тона (должно быть 50–100 Гц).

Код скрипта на Python
#!/usr/bin/env python3
"""
Buzzer-1 Metrics Collector
Каждые 60 секунд захватывает 3-секундный фрагмент эфира,
считает частоту, RMS и ширину полосы, сохраняет в PostgreSQL.
"""
import os, sys, time, logging, subprocess, numpy as np
from pathlib import Path
from datetime import datetime, timezone
BUZZER_FREQ = os.environ.get('BUZZER_FREQ', '4625000')
SAMPLE_RATE = int(os.environ.get('SAMPLE_RATE', '12000'))
GAIN = os.environ.get('SDR_GAIN', '30')
INTERVAL_SEC = int(os.environ.get('METRICS_INTERVAL', '60'))
CAPTURE_SEC = 3  # длительность захвата для анализа
PG_HOST = os.environ.get('PG_HOST', 'localhost')
PG_DB = os.environ.get('PG_DB', 'buzzer_db')
PG_USER = os.environ.get('PG_USER', 'buzzer')
PG_PASS = os.environ.get('PG_PASS', 'buzzer123')
LOG_DIR = Path(os.environ.get('LOG_DIR', '/opt/buzzer/logs'))
LOG_DIR.mkdir(parents=True, exist_ok=True)
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s [%(levelname)s] %(message)s',
    handlers=[
        logging.FileHandler(LOG_DIR / 'metrics.log'),
        logging.StreamHandler(sys.stdout)
    ]
)
log = logging.getLogger(__name__)
def capture_and_analyze():
    """
    Захватывает CAPTURE_SEC секунд аудио через rtl_fm,
    вычисляет метрики жужжания:
    - доминирующую частоту
    - RMS (среднеквадратичное значение)
    - ширину полосы по уровню -10 дБ
    """
    cmd = [
        'rtl_fm',
        '-f', BUZZER_FREQ,
        '-M', 'usb',
        '-s', str(SAMPLE_RATE),
        '-g', GAIN,
        '-E', 'direct',
        '-'
    ]

    try:
        proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
        raw = proc.stdout.read(SAMPLE_RATE * CAPTURE_SEC)  # 1 байт на семпл (8-bit)
        proc.terminate()
        proc.wait()

        if len(raw) < 1000:
            log.warning("Слишком мало данных — пропускаем")
            return None

        # Преобразуем 8-bit unsigned → float32 [-1.0, 1.0]
        data = np.frombuffer(raw, dtype=np.uint8).astype(np.float32)
        data = (data - 128.0) / 128.0
        data = data - np.mean(data)  # убираем DC-смещение
        rms = float(np.sqrt(np.mean(data ** 2)))
        windowed = data * np.hanning(len(data))
        spectrum = np.abs(np.fft.rfft(windowed))
        freqs = np.fft.rfftfreq(len(data), 1.0 / SAMPLE_RATE)
        peak_idx = np.argmax(spectrum[1:]) + 1  # +1 потому что пропустили нулевой бин
        peak_freq = float(freqs[peak_idx])
        threshold = np.max(spectrum) * 0.316  # -10 дБ = 0.316 по мощности
        above_threshold = spectrum > threshold
        bandwidth = float(np.sum(above_threshold) * (SAMPLE_RATE / len(data)))
        harmonic_threshold = np.max(spectrum) * 0.1  # -20 дБ
        harmonic_region = (spectrum > harmonic_threshold) & (freqs > peak_freq * 1.5)
        harmonics = int(np.sum(np.diff(harmonic_region.astype(int)) == 1))

        return {
            'freq_hz': peak_freq,
            'bandwidth_hz': bandwidth,
            'rms': rms,
            'peak': float(np.max(np.abs(data))),
            'harmonics': harmonics
        }

    except Exception as e:
        log.error(f"Ошибка захвата: {e}")
        return None

def save_metrics(metrics):
    """Сохраняет метрики в PostgreSQL."""
    try:
        import psycopg2
        conn = psycopg2.connect(
            host=PG_HOST,
            database=PG_DB,
            user=PG_USER,
            password=PG_PASS
        )
        cur = conn.cursor()
        cur.execute(
            """INSERT INTO metrics (freq_hz, bandwidth_hz, rms, peak, harmonics)
               VALUES (%s, %s, %s, %s, %s)""",
            (
                metrics['freq_hz'],
                metrics['bandwidth_hz'],
                metrics['rms'],
                metrics['peak'],
                metrics['harmonics']
            )
        )
        conn.commit()
        cur.close()
        conn.close()

        log.info(
            f"✓ f={metrics['freq_hz']:7.0f} Гц | "
            f"BW={metrics['bandwidth_hz']:5.0f} Гц | "
            f"RMS={metrics['rms']:.6f} | "
            f"гармоник={metrics['harmonics']}"
        )

    except Exception as e:
        log.error(f"Ошибка сохранения в БД: {e}")


def main():
    log.info(f"Запуск сборщика метрик | Частота: {BUZZER_FREQ} Гц | Интервал: {INTERVAL_SEC} сек")

    try:
        import psycopg2
    except ImportError:
        log.info("Установка psycopg2...")
        subprocess.run([sys.executable, '-m', 'pip', 'install', 'psycopg2-binary'], check=True)
        import psycopg2

    while True:
        try:
            metrics = capture_and_analyze()
            if metrics:
                save_metrics(metrics)
        except Exception as e:
            log.error(f"Ошибка в главном цикле: {e}")

        time.sleep(INTERVAL_SEC)


if __name__ == '__main__':
    main()

Жужжание с моей антенной выглядит очень похожим на белый шум, причем регулярно повторяющийся. Это натолкнуло меня на мысль: а нет ли внутри жужжания цифровой модуляции? Может быть, FSK или PSK, кодирующая данные?

Я провел спектральный анализ и автокорреляцию. Результат: никакой модуляции здесь нет. Жужжание — это чистый тон. Единственная его функция — маркер занятости канала.

Whisper на Raspberry Pi, Ollama в кластере, и почему восьмибитный звук чуть не свел меня с ума

Когда базовая система заработала — прием, веб-интерфейс, метрики — встал вопрос: а что дальше? Сидеть и слушать жужжание сутками в надежде поймать голосовое сообщение? Ну нет, я слишком стар и ленив для этого.

План был такой:

  1. Whisper распознает речь из аудиопотока и превращает ее в текст.

  2. Ollama с моделью Qwen 2.5 7B анализирует текст: определяет тип сообщения, срочность, ключевые слова.

  3. Telegram-бот отправляет мне уведомление, если что-то обнаружено.

Звучит просто. На практике все оказалось несколько сложнее.

Whisper на Raspberry Pi: зачем мучить «малину»

Изначально я хотел запустить Whisper на своем K8s-кластере — там есть GPU, там ему самое место. Но выяснилось, что единственная видеокарта в кластере уже занята Ollama, которая обслуживает несколько проектов.

Пришлось запускать Whisper прямо на Raspberry Pi 5. Да, на CPU. Да, без GPU. И знаете что? Он справляется.

Самая маленькая модель tiny на 80 МБ обрабатывает пятисекундный чанк аудио за две-три секунды. Этого достаточно, чтобы успевать за потоком. Да, качество распознавания ниже, чем у больших моделей. Но для задачи «отличить голос от жужжания и разобрать кодовые слова» хватает.

Позже, когда появится вторая GPU в кластере, я перенесу Whisper туда и включу модель побольше. Но пока работает так.

7c8444ea227a357bd13f44d9fc42ae5b.jpegOllama и Qwen: анализ кодовых слов

С этой частью проще. Ollama с моделью Qwen 2.5 7B (7 миллиардов параметров) уже работает в кластере на паре проектов. Я добавил еще одного клиента — свой Pi 5. Схема такая:

  1. Whisper распознал текст на Pi 5.

  2. Текст отправляется в Ollama на K8s.

  3. Qwen анализирует: «Это шум? Или голосовое сообщение? Если голос — какой тип? Какая важность? Какие ключевые слова?».

  4. Ответ возвращается на Pi 5 и пишется в базу.

Дополнительно объясню про важность. Голосовые сообщения УВБ-76 бывают трех типов, и по ним можно косвенно судить о важности передачи.

Проверка связи (срочность 1–3). Самый частый тип. Звучит примерно так: «МДЖБ, МДЖБ, ЖУЖЖАЛКА, 38 929, БОРИС, ИГЛА, 14 83 51». Это рутинная проверка канала. Оператор просто убеждается, что связь работает. Такие сообщения могут идти несколько раз в день или раз в несколько дней.

Учебная тревога (срочность 4–6). Отличается набором кодовых слов. Например, появление слова «ТОПОЛЬ» или «НАНОС» в определенных комбинациях может указывать на учебные маневры. Радиолюбители замечали корреляцию между такими сообщениями и известными датами военных учений.

Боевое управление (срочность 7–10). Самый редкий и самый тревожный тип. Длинные сообщения с большим количеством чисел и кодовых слов. По одной из теорий, именно такие передачи содержат команды для флота или ракетных войск. К счастью, за всю историю наблюдений таких сообщений были единицы, и ни одно из них не привело к реальным пускам (мы бы заметили).

Все сообщения сохраняются в PostgreSQL. Buzzer-1 умеет переключаться и на другие каналы: «пищалка» (5 448 кГц), «скрипящее колесо» (5 473 кГц), авиадиапазон Пулково (118,1 МГц). Так можно проверять, что приемник работает: при переключении характер сигнала меняется. Если сообщение получено именно по каналу УВБ-76, дополнительно уходит уведомление в Telegram. 

Восьмибитная битва, или как я спорил с тремя нейросетями одновременно

На этапе отладки я столкнулся с тем, что звук в браузере был ужасным — треск, шум, искажения. При этом rtl_fm работал, данные шли. Я начал копать.

Проблема оказалась в формате аудиоданных. Rtl_fm по умолчанию выдает восьмибитный unsigned PCM (uint8, диапазон 0–255). Мой код читал его как 16-битный signed PCM (int16, диапазон -32768...+32767). Байты сдвигались, семплы склеивались, и на выходе получалась каша.

Я попросил помощи у трех нейросетей одновременно: DeepSeek, Qwen 2.5 и KIMI. И начался цирк.

Qwen трижды настаивал, что нужно добавить флаг -f S16_LE в команду rtl_fm. Проблема в том, что rtl_fm не поддерживает такой флаг: -f означает frequency (частота), а не format. Qwen галлюцинировал.

DeepSeek помог с архитектурой, но тоже путался в форматах.

KIMI дал самый точный разбор архитектурных проблем, но тоже ошибался насчет формата, предлагая то int16, то S16_LE.

В итоге истина была установлена эмпирически: прямыми тестами на реальном железе. Оказалось, что rtl_fm на моей версии выдает восьмибитный unsigned PCM. Решение — конвертировать uint8 → int16 для браузера и uint8 → float32 для Whisper.

Формулы конвертации:

  • для веба: (uint8 - 128) × 256 — дает диапазон -32768..32512, который ждет JavaScript;

  • для Whisper: (uint8 - 128) / 128 — дает диапазон -1.0..1.0, с которым работает модель.

Давайте их разберем. Формула для браузера: (uint8 - 128) × 256. Браузерный Web Audio API ожидает 16-битные знаковые целые числа — формат Int16. Это числа в диапазоне от –32768 до +32767.

Что нам нужно сделать с uint8 (0–255):

  1. Сдвинуть диапазон, чтобы тишина оказалась в 0. В uint8 тишина — это значение около 128 (середина диапазона). Вычитаем 128: теперь тишина = 0, минимальный сигнал = –128, максимальный = 127.

  2. Растянуть в 16-битный диапазон. У нас есть числа от –128 до 127, а нужно от –32768 до 32767. Умножаем на 256 — и вот почему:

    • –128 × 256 = –32768 (нижняя граница Int16),

    • 0 × 256 = 0 (тишина),

    • 127 × 256 = 32512 (почти верхняя граница Int16, 32767).

Почему не ровно +32767? Потому что 127.5 × 256 = 32640, но у нас целые числа. Разница в 0.4% не имеет значения — человеческое ухо не заметит.

Формула для Whisper такова: (uint8 - 128) / 128. Модель Whisper, как и большинство нейросетей для обработки звука, ожидает числа с плавающей точкой в диапазоне от –1.0 до 1.0 — формат float32.

Что нам нужно сделать:

  1. Снова сдвинуть диапазон. Вычитаем 128, получаем числа от –128 до 127, где 0 = тишина.

  2. Сжать в диапазон -1.0...1.0. Делим на 128:

    • –128 / 128 = –1.0 (нижняя граница).

    • 0 / 128 = 0.0 (тишина).

    • 127 / 128 ≈ +0.992 (почти +1.0).

После этого звук стал чистым, Whisper перестал галлюцинировать. В очередной раз убедился: за нейронками стоит все перепроверять вручную.

AI-стек работает. Whisper слушает эфир, Ollama анализирует, Telegram ждет. За 141 час наблюдения ни одного голосового сообщения, но система к ним готова. Когда УВБ-76 заговорит, я увижу это первым.

eada9f150e2e3a0d8c49bf213804b5a5.jpegЧто мы узнали об УВБ-76

Прошла почти неделя непрерывной работы системы. За это время собрано 8 447 метрик — это больше 141 часа наблюдений. По ним можно довольно уверенно сделать некоторые выводы.

Жужжание — это чистый тон. Никакой цифровой модуляции внутри нет. Я проверял это дважды: спектральным анализом и автокорреляцией. Результат однозначный: FSK, PSK или другие методы передачи данных внутри жужжания не используются. Это просто маркер занятости канала. Станция как бы говорит: «Эта частота занята, не лезьте».

Частота стабильна как атомные часы. За 141 час — ни одного дрейфа. Передатчик, скорее всего, использует прецизионный опорный генератор с GPS-дисциплиной. Никаких «плаваний» частоты, характерных для старого лампового оборудования.

Мощность не меняется. RMS держится на уровне 0,97 с колебаниями в пятом знаке после запятой. Передатчик работает 24/7 с постоянной мощностью — нет ни дневных, ни ночных переключений режима.

Голосовых сообщений не было, ни одного за всю неделю. Детектор пауз молчал. Это нормально: по опыту других наблюдателей, УВБ-76 может молчать неделями.

Архитектурные решения, которыми я горжусь

Когда я начинал этот проект, то думал, что это будет «скрипт на коленке» — запустил и забыл. Но инженерное мышление взяло свое, и в итоге получилась довольно продуманная система:

Модульная архитектура. Код разбит на восемь файлов: config.py, state.py, sdr.py, audio_proc.py, db.py, scan.py, html.py, web_server.py. Каждый модуль отвечает за свою задачу. Можно менять AI-часть, не трогая SDR-захват. Можно обновить веб-интерфейс, не затронув базу данных.

Queue для аудиопотока. Чтобы избежать гонки потоков между SDR-захватом и отправкой аудио в браузер, я использовал queue.Queue. Один поток читает данные из rtl_fm, складывает в очередь. Второй поток забирает данные из очереди и отправляет через WebSocket. Никаких блокировок, никаких потерянных байтов.

CI/CD для хобби-проекта. Да, я настроил пайплайн в Jenkins. Код хранится в Bitbucket. При пуше в main автоматически происходит синтаксическая проверка, копирование файлов на Raspberry Pi и перезапуск сервиса. Это дисциплина. И защита от дурака: править код на проде запрещено. Всё только через репозиторий.

611695a795867706ef848568f36e5b2a.jpegЧто дальше: планы по развитию

Проект далек от завершения. В ближайшее время планирую подключить YouLoop. Эта антенна должна улучшить прием коротких волн. Сейчас комплектный штырь на подоконнике справляется, но с хорошей антенной сигнал будет чище.

В перспективе было бы здорово сделать на базе этого решения радиотелескоп для приема радиоизлучения Юпитера и Солнца. Думаю сначала получить цифровую картинку, а потом с помощью VLLM уже вывести графическое изображение. Звучит безумно, но с RTL-SDR это реально. А я с детства интересовался астрономией.

eb35e78b2865d7c3e78a65f6ec9ffdf1.jpegПочему я это сделал

Этот проект начался как способ отвлечься от рабочей рутины. Я DevOps-инженер, и мне хотелось попробовать что-то совсем иное. Но в итоге получился целый проект на стыке радио, программирования и искусственного интеллекта. Я пришел в радио полным нулем, а через две недели у меня была работающая станция радиомониторинга с CI/CD и AI-анализом.

Если у вас есть опыт наблюдения за УВБ-76, идеи по архитектуре или вы просто хотите обсудить проект — пишите в комментариях. А если вы слышали «Бориса» или «иглу» в эфире за последнюю неделю — дайте знать. Возможно, моя система просто спала в этот момент.

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Пятое сообщение за день прозвучало в эфире "Радиостанции Судного дня"05.3106-08-2026
2«Радиостанция Судного дня» передала три сообщения за день08.6106-08-2026
3"Радиостанция Судного дня" УВБ-76 передала двойную шифровку015.6206-08-2026
4В эфире "Радиостанции Судного дня" прозвучали три сообщения05.7306-08-2026
5Новое облако для умных устройств Яндекса: IoT без хаба, без лишних соединений и без надежды на хороший Wi‑Fi08.4628-07-2026
67 месяцев вайбкодинга: как в одиночку делать то, что раньше требовало команду0901-08-2026
7Анти‑кейс. Как создать технически идеальный сайт на Next.js про ИИ и нейросети и остаться без поискового трафика0528-06-2026
8Как управлять командой в 2026 году: зумеры, удаленщики и методы, которые не раздражают010.6722-07-2026
9Как я собрал корпоративного AI‑ассистента в Mattermost для девопс‑задач08.2922-07-2026

Классификация: Мнения. Схожих патентов: 0. Схожих новостей: 9. Тональность: 0. Информативность: 8.35. Источник: habr.com.