Добавить общие графики, декодер KONOR и порт STM32 bxCAN

This commit is contained in:
2026-09-04 12:22:17 +03:00
parent af82687e42
commit e163d9ba55
51 changed files with 5040 additions and 15 deletions

View File

@@ -0,0 +1,80 @@
# Общая работа с графиками Android и SETGUI
Численная логика находится в `include/set_plot.h` и `src/set_plot.c`.
Это модуль C99 без Qt, Android, транспорта, динамической памяти и глобального
состояния. Он собирается в существующую библиотеку SETProtocol; отдельная DLL
для графиков не требуется. Версия ABI графиков — `set_plot_abi_version() == 1`.
| Общее в templates | Адаптер приложения |
|---|---|
| Нормализованная область просмотра, масштаб 1…128, ограничение панорамирования | Размер поля и отступы в пикселях |
| Независимый масштаб X/Y с сохранением координаты под пальцами | События Compose или Qt, колесо, кнопки |
| Выбор преобладающей оси по изменению расстояния между пальцами | Накопление движения до порога; фиксация выбранной оси до конца жеста |
| Перевод значения в долю экрана и обратно, инверсия Y | Canvas/QPainter и оформление шкал |
| Перемещение маркера от начальной координаты, ограничение видимой областью | Захват линии пальцем/мышью, редактор положения |
| Разность BA, DC и множитель единиц, шаги шкалы 1/2/5 | Подписи, цвета, миллисекунды/герцы/единицы сигнала |
| Модели маркеров и их размещение в Kotlin/Python-портах | Жизненный цикл экрана, очистка и выбор источника |
Модель маркеров: A/B — координаты X и вертикальные линии во всю высоту поля;
C/D — координаты Y и горизонтальные линии. Они могут пересекаться: отрицательная
разность сохраняется. Начальные координаты — 1/3 и 2/3 диапазона. При изменении
масштаба уже установленные координаты сохраняются. «Маркеры в видимую область»
заново размещает их. Координаты и область просмотра времени/спектра независимы.
`set_plot_eval()` принимает операцию из `enum set_plot_operation`, точное число
входных `double` и буфер вывода. Контракт каждой операции указан в заголовке.
Возвращает число записанных элементов, 0 — неправильные аргументы. Некорректный
коэффициент масштабирования оставляет корректный viewport неизменным.
NaN/Inf и вырожденные диапазоны не передаются дальше в отрисовку.
FRACTION/VALUE не обрезают координаты: это позволяет правильно отсекать линии
по границе поля, а не создавать ложные горизонтальные участки на его краю.
DRAG, напротив, ограничивает положение маркера видимым диапазоном.
## Порты
- Android/JVM: `ports/android/kotlin/ru/setcorp/setprotocol/trends/NativePlot.kt`,
`PlotViewport.kt`, `TrendMarkers.kt`; тонкий JNI — `ports/android/set_plot_jni.c`.
Android использует `System.loadLibrary("setprotocol")`. Для JVM-тестов можно
передать абсолютный путь DLL/SO системным свойством `setplot.library`.
- Python: `python/protocan/plot.py`: `PlotMath(library)`, `Viewport`, `Bounds`,
`Markers`. Приложение передаёт уже загруженную SETProtocol `ctypes.CDLL`.
Зависимости — стандартная библиотека; Qt в этот модуль не импортируется.
Новые изменения не должны добавлять вторую реализацию численных операций
в Kotlin/Python или в приложение. Состояние и адаптация событий остаются в портах.
Добавление операции требует обновления C ABI, обоих портов и общих эталонов.
## Интеграция приложений
Android: `TrendGraph` использует общие viewport, bounds и маркеры, а Compose
рисует линии и панель. При измерениях время отображается в миллисекундах от
начала выбранного участка; сохранённые X-координаты включают абсолютное начало.
SETGUI: `ui/plot_interaction.py` адаптирует общий модуль к Qt. Его используют
`SignalPlot` (ТМС и датчики), `TrendPlot` (логи) и `SpectrumPlot`.
Колесо по вертикали масштабирует Y, Shift+колесо и горизонтальное колесо — X;
сенсорный pinch выбирает ось по движению. Временные графики хранят метку приёма
каждого отсчёта. Пропущенные значения не растягивают остальные серии:
все сигналы имеют единую шкалу времени. При повторном построении ТМС из журнала
используются исходные метки приёма. Дискретные дорожки имеют общую X-шкалу,
а маркеры уровня Y относятся к аналоговому полю.
Расчёт FFT не является частью этого модуля. Android вычисляет спектр через
`set_spectrum.c`; вкладка спектра SETGUI получает готовые уровни в дБмВ от
прибора. Общая интерактивная часть не меняет эти данные или единицы.
## Проверка и сборка
- CMake/CTest: `shared_plot` проверяет ABI, размеры буферов и координаты.
- `tests/fixtures/plot-v1.json` содержит общие численные эталоны. Их читают
`PlotContractTest` (фактический JNI) и `python/tests/test_plot.py` (ctypes).
- Host DLL/SO для Python: `python tools/build_host.py --output <путь>`.
- Для JVM: та же команда с `--java-home <JDK>` добавляет JNI-порт графиков.
Нужен C-компилятор (MSVC на Windows, cc/clang/gcc на Unix) и JDK с `jni.h`.
- Android-проект автоматически собирает host-библиотеку перед JVM-тестами;
интерпретатор Python можно задать свойством Gradle `setflash.python`.
- SETGUI проверяет события мыши, колеса и двух пальцев через QtTest без
физического прибора; численные тесты не требуют GUI.
Исходники должны приходить из одного коммита подмодуля `templates` в обоих
проектах. Локальная замена файлов без обновления gitlink не закрепляет версию.

View File

@@ -0,0 +1,53 @@
# Shared GUI spectrum
`include/set_spectrum.h` / `src/set_spectrum.c` implement the same numerical
pipeline for desktop and Android. CMake, `tools/build_host.py`, and the Android
NDK build include it. The wire protocol is unchanged. The host-only analyzer
allocates at most two FFT arrays (2×N doubles), releasing them before return.
The last N samples are used: the largest power of two within available data and
`max_size`, 16…16384. Timestamps are seconds; inputs are immutable. Fs is measured
from the selected endpoints. Non-monotonic/duplicate times and interval deviations
over 50% of the mean are rejected. Lesser jitter is linearly interpolated onto an
even grid (this can affect high-frequency amplitude; inspect returned jitter).
Processing order: interpolation, optional mean subtraction, filter, periodic
window, forward FFT, one-sided peak amplitude. Windows: rectangular, Hann,
Hamming, Blackman, Flat Top. Amplitudes are divided by window sum, doubled except
DC and Nyquist. Output is not RMS/PSD/dB. Coherent gain correction is exact for
bin-centered tones away from DC/Nyquist; off-bin tones still have scalloping.
Filters run before the window, on the unmodified sample copy. LP/HP are second
order Butterworth; band pass cascades HP and LP; notch uses Q=30.
Coefficients: [W3C Audio EQ Cookbook](https://www.w3.org/TR/audio-eq-cookbook/).
Initial state is the first sample's steady state, not a carried streaming state;
block-start transients may occur. Cutoffs must be strictly within (0, Fs/2).
## Python
```python
from protocan.spectrum import NativeSpectrum, Window, Filter
# lib = an existing ctypes.CDLL from protocan.native.NativeProtocol
spectrum = NativeSpectrum(lib).analyze(times_seconds, values,
window=Window.HANN, filter=Filter.LOW_PASS, high_hz=100,
max_size=4096, remove_mean=True)
plot(spectrum.frequencies, spectrum.amplitudes)
```
Adapter errors are ValueError, not empty/misleading arrays. No NumPy/Qt dependency.
## Android
`SpectrumAnalyzer.analyze(List<TrendPoint>, SpectrumOptions)` calls JNI using
relative seconds from millisecond timestamps. `TrendSpectrum` contains N, Fs,
maximum relative jitter, amplitudes or a displayable error. Call on a worker
dispatcher. `PlotViewport` provides axis-independent zoom/pan, focus anchoring,
128× limit and bounds clamping without any Compose dependency.
## Checks
Build CMake with tests and run CTest in Debug (assertions enabled). Python
`tests/test_spectrum.py` runs against an explicitly supplied `SETPROTOCOL_LIBRARY`:
independent direct DFT, amplitude correction, DC/Nyquist, off-bin leakage, filter
bands, timing rejection/interpolation, bounded tail selection and immutable inputs.

View File

@@ -0,0 +1,78 @@
# Общие тренды для Android GUI и SETGUI
Модуль задаёт совместимые настройки и числовые источники графиков. Он не
рисует экран, не открывает файлы или порты и не управляет прошивкой устройства.
Слои: GUI → Kotlin/Python adapter → `set_trends.c` → уже разобранные кадры.
Транспорт, таймер опроса, выбор файла, хранение настроек и Canvas/Qt остаются
в приложении. У ядра нет callbacks, динамической памяти или глобального состояния.
| Файл | Назначение и зависимости |
|---|---|
| `include/set_trends.h`, `src/set_trends.c` | C99: фильтрация GAS/raw CAN, signed/unsigned word, payload подписки SET GUI; использует `pcan_id` и ABI export macro |
| `ports/android/kotlin/ru/setcorp/setprotocol/trends/` | Модель, валидация JSON, ограниченная история, GAS_WATCH; JVM + org.json, без Android/Compose |
| `ports/android/setprotocol_jni.c` | Только преобразование JNI-аргументов |
| `python/protocan/trends.py` | Модель, JSON, история и `NativeTrends`; Python 3.9+, stdlib, без Qt |
| `tests/fixtures/trends-v1.json` | Один образец для тестов обоих GUI и обмена настройками |
## Формат файла
UTF-8 JSON: `format = "setflash-trends"`, `version = 1`, `profiles` — словарь.
Ключи профилей стабильны: `TMS2812`, `SET_V1`, `CAN_BRIDGE`, `GS_USB_CAN`,
`SLCAN`, `CANGAROO_SLCAN`, `BALZAM_CAN`. Приложение явно сопоставляет своё
подключение с этим ключом; импорт не меняет подключение.
Поля сигнала обязательны: `id`, `order`, `name`, `source`, `address`, `color`,
`visible`, `valueType`, `deviceType`, `device`, `byteOffset`, `extended`.
`id` уникален во всём файле. `order` — 1…9999, уникален внутри профиля.
`name` — непустое изменяемое имя (до 100 символов), `color` — `#RRGGBB`.
`visible` управляет только отрисовкой, не сбором. История и соединение в файл
не входят. Максимум 64 сигнала на профиль и 1 МиБ на файл. Неизвестные версии,
профили, источники и неверные значения отклоняются до замены настроек.
| Профиль / источник | Адрес и значение |
|---|---|
| TMS2812 / TMS_MEMORY | 32-битный адрес слова, CMD_PEEK; контроллер из подключения |
| SET_V1 / SET_GAS | 16-битный GAS; GAS_WATCH_SET `0x12`, DATA `0x13` |
| SET_V1 / SET_SENSOR | ROM DS18B20, 16 HEX-цифр (дефисы допустимы), °C |
| Современные CAN / CAN_GAS | GAS 0…65535, DeviceType 0…7, DeviceID 0…15 |
| Все CAN / CAN_RAW | CAN ID, STD/EXT, byteOffset 0…6; слово Little-endian |
Числовые адреса — десятичные либо HEX с `0x`. Регистры трактуются как
`UINT16` или `INT16`. Для RAW/CAN_GAS C-функция возвращает значение либо
`SET_TREND_NO_VALUE`, никогда не подставляет ноль вместо отсутствующего кадра.
CAN — пассивный приём, без записи GAS и автоматической отправки запросов.
GAS принимает только входящие FROM_DEVICE; TX/RTR/ошибки исключены.
SET GUI оформляет подписку в порядке адресов. ACK должен подтвердить весь
список: при частичном принятии нельзя определить пропущенные адреса, поэтому
строить график по смещённым индексам запрещено. При паузе порт приложения
отправляет подписку с периодом 0, когда транспорт доступен и не занят прошивкой.
## Быстрый старт SETGUI
```python
from protocan.native import NativeProtocol
from protocan.trends import TrendSignal, NativeTrends, encode_settings
core = NativeProtocol() # библиотека, пересобранная с set_trends.c
decoder = NativeTrends(core.lib)
signal = TrendSignal("current", source="CAN_GAS", address="0x1235")
text = encode_settings({"CAN_BRIDGE": [signal]})
value = decoder.can_value(signal, 0x1FD31234, 1, bytes([1, 0, 254, 255]))
assert value == 65534.0
# GUI сохраняет text выбранным способом и передаёт value своему графику.
```
Android подключает Kotlin-каталог напрямую через Gradle и C-файлы через NDK.
Приложение SETGUI может импортировать модуль через уже используемый путь
`third_party/templates/python`; в этом изменении его экран не изменяется.
После публикации новой ревизии templates оба проекта должны обновить сабмодуль
и пересобрать собственные native-библиотеки — вручную копировать реализации не нужно.
## Проверки
- CMake/CTest: `shared_trends`, плюс существующие protocol/ABI тесты.
- JVM: `TrendSettingsJsonTest`, `GuiGasWatchTest`; один JSON fixture с Python.
- Python: `PYTHONPATH=python python -m unittest discover -s python/tests`.
Для проверки настоящего C-декодера задайте `SETPROTOCOL_LIBRARY` путём DLL/SO.

View File

@@ -48,7 +48,7 @@ uint16_t n = pcan_gas_read_block(&map, 0x0000, block, 4);
Тип сообщения `PCAN_MSG_GAS` (`0b0011`). Адрес первого регистра лежит
в `MsgBody`, данные — до 4 регистров подряд, младшим байтом вперёд.
Это ровно то, что делает `PROTOCAN_SEND_GENERAL_ADDRESS_SPACE()`
в `SETCAN/Src/protocan.c`, поэтому обмен совместим с существующими
в `ports/stm32-bxcan/protocan.c` (бывший SETCAN), поэтому обмен совместим с существующими
устройствами.
| Кадр | DLC | Смысл |