Extract reusable device protocols, ports and logic analyzers from SETGUI

This commit is contained in:
2026-09-23 20:11:37 +03:00
parent 80ba17d77d
commit 795a1279b1
63 changed files with 10181 additions and 6 deletions

View File

@@ -0,0 +1,119 @@
"""@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)