Files
templates/python/set_devices/spectrum_demo.py

120 lines
4.9 KiB
Python
Raw 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.
"""@file spectrum_demo.py
@brief Демонстрационный источник спектра для проверки вкладки без прибора.
Повторяет то, что передаёт прошивка: основной тон, несколько гармоник
и шумовая подложка. Нужен, чтобы вкладку можно было открыть и убедиться
в правильности отрисовки, не имея под рукой ни платы, ни датчика.
Источник намеренно живёт рядом с разбором потока, а не внутри вкладки:
он выдаёт те же самые строки, что и прибор, и проходит через тот же
разбор. Иначе демонстрация проверяла бы отрисовку, но не формат.
"""
from __future__ import annotations
import math
import random
from set_devices.spectrum_stream import (
Harmonic,
HarmonicsFrame,
build_harmonics_line,
build_spectrum_line,
)
#: Число элементов спектра в демонстрационном кадре.
DEMO_BINS = 128
#: Частота выборки демонстрационного источника, Гц.
DEMO_SAMPLE_RATE = 20000
#: Размер преобразования, которому соответствует спектр.
DEMO_FFT_SIZE = 512
#: Верхняя граница шкалы уровней, дБ относительно милливольта.
DEMO_TOP_DBMV = 60.0
#: Размах шкалы уровней, дБ.
DEMO_RANGE_DB = 60.0
class SpectrumDemoSource:
"""@brief Выдаёт строки спектра и гармоник, как это делал бы прибор."""
def __init__(self, seed: int = 1) -> None:
self._seq = 0
self._random = random.Random(seed)
self._phase = 0.0
def next_lines(self) -> str:
"""@brief Формирует очередную пару строк.
@return Строка спектра и строка гармоник, склеенные подряд.
Основной тон медленно плывёт по частоте, чтобы на экране было
видно движение и стало заметно, если отрисовка где-то подвисает.
"""
self._seq += 1
self._phase += 0.05
fundamental_hz = 1000.0 + 200.0 * math.sin(self._phase)
fundamental_mv = 400.0
bin_width = (DEMO_SAMPLE_RATE / 2.0) / DEMO_BINS
# Уровни: шумовая подложка около 3 мВ плюс узкие пики на основном
# тоне и его гармониках.
amplitudes = [self._random.uniform(1.5, 4.0) for _ in range(DEMO_BINS)]
harmonics: list[Harmonic] = []
for order in range(1, 5):
frequency = fundamental_hz * order
if frequency >= DEMO_SAMPLE_RATE / 2.0:
break
amplitude = fundamental_mv / (order * order)
index = int(round(frequency / bin_width))
if 0 <= index < DEMO_BINS:
amplitudes[index] = amplitude
# Соседние элементы приподняты: у реального окна энергия
# тона всегда занимает несколько элементов, и совсем узкий
# пик выглядел бы неправдоподобно.
for neighbour in (index - 1, index + 1):
if 0 <= neighbour < DEMO_BINS:
amplitudes[neighbour] = max(
amplitudes[neighbour], amplitude * 0.45
)
if order >= 2:
harmonics.append(
Harmonic(
index=order,
frequency_hz=frequency,
amplitude_mv=amplitude,
relative_db=20.0 * math.log10(amplitude / fundamental_mv),
)
)
levels = [20.0 * math.log10(max(value, 1e-3)) for value in amplitudes]
spectrum = build_spectrum_line(
seq=self._seq,
sample_rate_hz=DEMO_SAMPLE_RATE,
fft_size=DEMO_FFT_SIZE,
levels_db=levels,
top_dbmv=DEMO_TOP_DBMV,
range_db=DEMO_RANGE_DB,
)
total = math.sqrt(sum(h.amplitude_mv**2 for h in harmonics))
frame = HarmonicsFrame(
seq=self._seq,
fundamental_hz=fundamental_hz,
fundamental_mv=fundamental_mv,
thd_percent=100.0 * total / fundamental_mv,
snr_db=42.0,
rms_mv=fundamental_mv / math.sqrt(2.0),
harmonics=tuple(harmonics),
)
return spectrum + build_harmonics_line(frame)