120 lines
4.9 KiB
Python
120 lines
4.9 KiB
Python
"""@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)
|