Объединить разработки CAN и STM32 в master

This commit is contained in:
2026-09-15 15:57:48 +03:00
68 changed files with 3585 additions and 122 deletions

View File

@@ -18,6 +18,9 @@ set(SETPROTOCOL_V2_SOURCES
# Совместимые ProtoCAN/SETGUI v1 форматы переходного периода.
set(SETPROTOCOL_LEGACY_SOURCES
src/balsam_can.c
src/set_crc.c
src/periph28335.c
src/tms2812.c
src/gui_catalog.c
src/gui_frame.c
src/pcan_abi.c
@@ -103,6 +106,19 @@ if(SETP_BUILD_TESTS)
add_executable(test_balsam_can tests/test_balsam_can.c)
target_link_libraries(test_balsam_can PRIVATE setprotocol_static)
add_test(NAME legacy_balsam_can COMMAND test_balsam_can)
add_executable(test_periph28335 tests/test_periph28335.c)
target_link_libraries(test_periph28335 PRIVATE setprotocol_static)
add_test(NAME shared_periph28335 COMMAND test_periph28335)
add_executable(test_tms2812 tests/test_tms2812.c)
target_link_libraries(test_tms2812 PRIVATE setprotocol_static)
add_test(NAME shared_tms2812 COMMAND test_tms2812)
add_executable(test_tms2812_boot
tests/test_tms2812_boot.c
ports/tms320f2812/setp_tms2812_boot.c
)
target_include_directories(test_tms2812_boot PRIVATE ports/tms320f2812)
target_link_libraries(test_tms2812_boot PRIVATE setprotocol_static)
add_test(NAME setp_tms2812_boot COMMAND test_tms2812_boot)
add_executable(test_trends tests/test_trends.c)
target_link_libraries(test_trends PRIVATE setprotocol_static)
add_test(NAME shared_trends COMMAND test_trends)

View File

@@ -43,6 +43,11 @@ multicast запрещены WRITE, FW_BEGIN и FW_ACTIVATE.
В проект устройства добавляются `ports/stm32-bxcan/protocan.c` и `src/pcan_id.c`,
а в include paths — `ports/stm32-bxcan` и `include`.
Для TMS320F2812 доступен SETProtocol v2 firmware service
[`ports/tms320f2812`](ports/tms320f2812/README.md). Он использует общую
сегментацию `set_can`, собирается без TI headers и получает eCAN, Flash и reset
через callbacks проекта платы.
Не вызывайте прикладной router из ISR. ISR принимает CAN-кадры в очередь,
сегментация собирается в главном цикле, и только полный SETP-кадр передаётся
parser/router. Сборка должна иметь timeout, контроль номера сегмента и одного

View File

@@ -21,6 +21,12 @@ TCP, UART/DMA и аппаратный CAN подключаются портам
Масштабирование, координаты и измерительные маркеры Android/SETGUI используют
общий `set_plot.c`: [границы модулей, ABI и проверки](docs/GUI_PLOT.md).
Прямой терминал ПМ35/TMS320F28335 использует единый wire contract
`Set_Terminal_28335`: функции MODBUS RTU `03/06`, 128 регистров и CRC16.
Эталонные адаптеры находятся в
[`python/protocan/periph28335.py`](../../python/protocan/periph28335.py) и
[`ports/android/kotlin/.../periph28335`](ports/android/kotlin/ru/setcorp/setprotocol/periph28335).
## Структура
| Каталог | Назначение |

View File

@@ -3,7 +3,8 @@
Численная логика находится в `include/set_plot.h` и `src/set_plot.c`.
Это модуль C99 без Qt, Android, транспорта, динамической памяти и глобального
состояния. Он собирается в существующую библиотеку SETProtocol; отдельная DLL
для графиков не требуется. Версия ABI графиков — `set_plot_abi_version() == 1`.
для графиков не требуется. Версия ABI графиков — `set_plot_abi_version() == 1`;
новые операции добавляются без изменения существующих значений и сигнатур.
| Общее в templates | Адаптер приложения |
|---|---|
@@ -13,6 +14,7 @@
| Перевод значения в долю экрана и обратно, инверсия Y | Canvas/QPainter и оформление шкал |
| Перемещение маркера от начальной координаты, ограничение видимой областью | Захват линии пальцем/мышью, редактор положения |
| Разность BA, DC и множитель единиц, шаги шкалы 1/2/5 | Подписи, цвета, миллисекунды/герцы/единицы сигнала |
| Проверка абсолютных границ X/Y для фиксации осей | Диалог ввода и хранение отдельных границ времени/FFT |
| Модели маркеров и их размещение в Kotlin/Python-портах | Жизненный цикл экрана, очистка и выбор источника |
Модель маркеров: A/B — координаты X и вертикальные линии во всю высоту поля;
@@ -59,8 +61,9 @@ SETGUI: `ui/plot_interaction.py` адаптирует общий модуль к
используются исходные метки приёма. Дискретные дорожки имеют общую X-шкалу,
а маркеры уровня Y относятся к аналоговому полю.
Расчёт FFT не является частью этого модуля. Android вычисляет спектр через
`set_spectrum.c`; вкладка спектра SETGUI получает готовые уровни в дБмВ от
Расчёт FFT не является частью этого модуля. Android и SETGUI вычисляют спектр через
`set_spectrum.c`; там же находится общий поиск доминирующего узкополосного пика,
а трёхсекундный таймер его отображения остаётся состоянием GUI. Вкладка спектра SETGUI получает готовые уровни в дБмВ от
прибора. Общая интерактивная часть не меняет эти данные или единицы.
## Проверка и сборка

View File

@@ -12,7 +12,7 @@
| `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 |
| `python/protocan/trends.py` | Модель, JSON, история и `NativeTrends` (word/CAN/GAS request/ack/data); Python 3.9+, stdlib, без Qt |
| `tests/fixtures/trends-v1.json` | Один образец для тестов обоих GUI и обмена настройками |
## Формат файла
@@ -44,6 +44,8 @@ UTF-8 JSON: `format = "setflash-trends"`, `version = 1`, `profiles` — слов
CAN — пассивный приём, без записи GAS и автоматической отправки запросов.
GAS принимает только входящие FROM_DEVICE; TX/RTR/ошибки исключены.
Полный `GAS_WATCH_DATA`, включая 32-битную метку прибора, разбирается функцией
`set_trend_watch_decode`; UI не знает смещений полей и endian.
SET GUI оформляет подписку в порядке адресов. ACK должен подтвердить весь
список: при частичном принятии нельзя определить пропущенные адреса, поэтому
строить график по смещённым индексам запрещено. При паузе порт приложения

View File

@@ -1,8 +1,8 @@
/**
* @file balsam_can.h
* @brief Legacy Balsam 167 extended-CAN register frames.
* @brief CAN_Bal_2812 extended-CAN register space.
*
* The wire layout is taken from Balsam_167_periph/Source/Internal/ecan.c:
* The wire layout is used by BALZAM Src/balzam_7/CanSetupBalzam7.c:
* one big-endian address/mask word followed by three big-endian register words.
*/
#ifndef BALSAM_CAN_H
@@ -34,7 +34,7 @@ typedef struct {
uint16_t values[BALSAM_CAN_REGISTER_COUNT];
} balsam_can_frame_t;
/** Return non-zero for the command/data/terminal IDs used by Balsam 167. */
/** Return non-zero for the command/data/terminal IDs used by CAN_Bal_2812. */
int balsam_can_is_id(uint32_t can_id);
/**
@@ -44,11 +44,11 @@ int balsam_can_is_id(uint32_t can_id);
int balsam_can_decode(uint32_t can_id, const uint8_t *data, size_t size,
balsam_can_frame_t *output);
/** Human-readable Russian name of a Balsam device. */
/** Human-readable Russian name of a CAN_Bal_2812 device. */
const char *balsam_can_device_name(uint8_t device);
/**
* Format the register name from the Balsam 167 data table into output.
* Format the register name from the CAN_Bal_2812 data table into output.
* Returns the required length (excluding NUL); an empty string means unknown.
*/
size_t balsam_can_register_name(uint8_t device, uint16_t address,

View File

@@ -73,6 +73,49 @@ PCAN_ABI_API const char *pcan_abi_balsam_device_name(uint8_t device);
PCAN_ABI_API size_t pcan_abi_balsam_register_name(
uint8_t device, uint16_t address, char *output, size_t output_size);
/* PM35/TMS320F28335 direct RS232/485 register terminal. */
PCAN_ABI_API uint16_t pcan_abi_periph28335_crc16(
const uint8_t *data, size_t size);
PCAN_ABI_API size_t pcan_abi_periph28335_append_crc(
const uint8_t *payload, size_t payload_size,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_periph28335_build_read(
uint8_t controller, uint16_t start, uint16_t count,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_periph28335_build_write(
uint8_t controller, uint16_t address, uint16_t value,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_periph28335_build_command(
uint8_t controller, uint8_t command_index,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_periph28335_expected_read_size(uint16_t count);
PCAN_ABI_API int pcan_abi_periph28335_decode_read(
const uint8_t *data, size_t size, uint8_t controller, uint16_t count,
uint16_t *output, size_t output_count);
PCAN_ABI_API int pcan_abi_periph28335_validate_write(
const uint8_t *response, size_t response_size,
const uint8_t *request, size_t request_size);
PCAN_ABI_API size_t pcan_abi_periph28335_project_count(void);
PCAN_ABI_API const char *pcan_abi_periph28335_project_name(
size_t project_index);
PCAN_ABI_API const char *pcan_abi_periph28335_command_name(
size_t project_index, size_t command_index);
/* PM67/TMS320F2812 legacy memory upload. */
PCAN_ABI_API uint16_t pcan_abi_tms2812_crc16(
const uint8_t *data, size_t size);
PCAN_ABI_API size_t pcan_abi_tms2812_build_upload(
uint8_t controller, uint32_t word_address, uint32_t byte_count,
uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_tms2812_expected_upload_size(
uint32_t byte_count);
PCAN_ABI_API int pcan_abi_tms2812_validate_upload(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count);
PCAN_ABI_API int pcan_abi_tms2812_decode_upload(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count, uint8_t *output, size_t output_size);
PCAN_ABI_API size_t pcan_abi_frame_encode(uint8_t sequence, uint8_t flags,
uint32_t can_id,
const uint8_t *data, uint8_t dlc,

View File

@@ -0,0 +1,68 @@
/**
* @file periph28335.h
* @brief Shared PM35/TMS320F28335 register protocol.
*
* The protocol is independent from PM67/TMS320F2812 CAN and RS channels.
* It implements the direct RS232/485 register terminal used by PM35.
*/
#ifndef PERIPH28335_H
#define PERIPH28335_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define PERIPH28335_REGISTER_COUNT 128U
#define PERIPH28335_REQUEST_SIZE 8U
#define PERIPH28335_COMMAND_COUNT 17U
typedef enum {
PERIPH28335_OK = 0,
PERIPH28335_ERROR_ARGUMENT = -1,
PERIPH28335_ERROR_RANGE = -2,
PERIPH28335_ERROR_LENGTH = -3,
PERIPH28335_ERROR_CRC = -4,
PERIPH28335_ERROR_HEADER = -5,
PERIPH28335_ERROR_CAPACITY = -6
} periph28335_status_t;
uint16_t periph28335_crc16_modbus(const uint8_t *data, size_t size);
size_t periph28335_append_crc(const uint8_t *payload, size_t payload_size,
uint8_t *output, size_t output_size);
size_t periph28335_build_read_registers(
uint8_t controller, uint16_t start, uint16_t count,
uint8_t *output, size_t output_size);
size_t periph28335_build_write_register(
uint8_t controller, uint16_t address, uint16_t value,
uint8_t *output, size_t output_size);
size_t periph28335_build_command(
uint8_t controller, uint8_t command_index,
uint8_t *output, size_t output_size);
size_t periph28335_expected_read_response_size(uint16_t count);
int periph28335_decode_read_response(
const uint8_t *data, size_t size, uint8_t controller, uint16_t count,
uint16_t *output, size_t output_count);
int periph28335_validate_write_response(
const uint8_t *response, size_t response_size,
const uint8_t *request, size_t request_size);
size_t periph28335_project_count(void);
const char *periph28335_project_name(size_t project_index);
const char *periph28335_command_name(size_t project_index,
size_t command_index);
#ifdef __cplusplus
}
#endif
#endif /* PERIPH28335_H */

View File

@@ -0,0 +1,19 @@
/** @file set_crc.h @brief Shared checksums used by legacy SET controllers. */
#ifndef SET_CRC_H
#define SET_CRC_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/** CRC-16/Modbus: reflected polynomial 0xA001, initial value 0xFFFF. */
uint16_t set_crc16_modbus(const uint8_t *data, size_t size);
#ifdef __cplusplus
}
#endif
#endif /* SET_CRC_H */

View File

@@ -17,10 +17,11 @@ enum set_plot_operation {
SET_PLOT_DRAG = 4, /* initial,deltaPixels,length,low,high,inverted -> clamped value */
SET_PLOT_TICK_STEP = 5, /* range,lengthPixels -> nice step */
SET_PLOT_DELTA = 6, /* A,B,multiplier -> (B-A)*multiplier */
SET_PLOT_DB_DELTA = 7 /* A,B -> 20*log10(abs(B/A)); zero is invalid */
SET_PLOT_DB_DELTA = 7, /* A,B -> 20*log10(abs(B/A)); zero is invalid */
SET_PLOT_LIMITS = 8 /* xMin,xMax,yMin,yMax -> validated unchanged limits */
};
/** Version of this plot ABI, independently of the transport ABI. */
/** Version of this additive plot ABI, independently of the transport ABI. */
PCAN_ABI_API uint32_t set_plot_abi_version(void);
/** Evaluates one operation. Returns output count, or 0 for invalid arguments.
* TRANSFORM rejects malformed viewports; invalid gesture values return the

View File

@@ -28,6 +28,15 @@ PCAN_ABI_API int set_spectrum_analyze(const double *times, const double *values,
size_t max_size, int window, int filter, double low_hz, double high_hz, int remove_mean,
double *amplitudes, size_t capacity, double *meta);
/** Find the strongest non-DC local maximum above both the absolute floor and
* relative_threshold * median(non-DC amplitudes). The caller supplies scratch
* storage of at least count-1 doubles. peak = {frequency_hz, amplitude}.
* Returns 1 when found, 0 when no narrow-band peak exists, -1 on invalid input.
*/
PCAN_ABI_API int set_spectrum_dominant_peak(const double *amplitudes, size_t count,
double bin_hz, double relative_threshold, double absolute_floor,
double *scratch, size_t scratch_capacity, double *peak, size_t peak_capacity);
#ifdef __cplusplus
}
#endif

View File

@@ -48,6 +48,13 @@ PCAN_ABI_API int set_trend_watch_ack(
PCAN_ABI_API int set_trend_watch_values(
const uint8_t *payload, size_t size, uint16_t *words, size_t capacity);
/** Decode the complete GAS_WATCH_DATA payload including its device timestamp.
* This is the preferred ABI for GUI ports; the older values-only symbol remains
* available for binary compatibility.
*/
PCAN_ABI_API int set_trend_watch_decode(const uint8_t *payload, size_t size,
uint32_t *timestamp_ms, uint16_t *words, size_t capacity);
#ifdef __cplusplus
}
#endif

View File

@@ -6,6 +6,8 @@
#define SETPROTOCOL_H
#include "balsam_can.h"
#include "periph28335.h"
#include "tms2812.h"
/* Основной SET protocol v2. */
#include "set_protocol.h"

View File

@@ -0,0 +1,49 @@
/**
* @file tms2812.h
* @brief Shared PM67/TMS320F2812 legacy upload protocol.
*/
#ifndef TMS2812_H
#define TMS2812_H
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define TMS2812_CMD_UPLOAD 52U
#define TMS2812_UPLOAD_REQUEST_SIZE 12U
#define TMS2812_UPLOAD_RESPONSE_OVERHEAD 8U
typedef enum {
TMS2812_OK = 0,
TMS2812_ERROR_ARGUMENT = -1,
TMS2812_ERROR_RANGE = -2,
TMS2812_ERROR_LENGTH = -3,
TMS2812_ERROR_CRC = -4,
TMS2812_ERROR_HEADER = -5,
TMS2812_ERROR_CAPACITY = -6
} tms2812_status_t;
uint16_t tms2812_crc16(const uint8_t *data, size_t size);
size_t tms2812_build_upload_request(
uint8_t controller, uint32_t word_address, uint32_t byte_count,
uint8_t *output, size_t output_size);
size_t tms2812_expected_upload_response_size(uint32_t byte_count);
int tms2812_validate_upload_response(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count);
int tms2812_decode_upload_response(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count, uint8_t *output, size_t output_size);
#ifdef __cplusplus
}
#endif
#endif /* TMS2812_H */

View File

@@ -13,6 +13,9 @@ LOCAL_SRC_FILES := \
../../src/set_trends.c \
../../src/set_spectrum.c \
../../src/balsam_can.c \
../../src/set_crc.c \
../../src/periph28335.c \
../../src/tms2812.c \
../../src/gui_catalog.c \
../../src/gui_frame.c \
../../src/pcan_abi.c \

View File

@@ -175,7 +175,9 @@ object CanBridgeProtocol {
stats = Stats(
frames = values[0],
crcErrors = values[1],
resyncBytes = values[2] + values[3],
// The former Kotlin parser counted one resync step
// for a rejected CRC frame in addition to stray bytes.
resyncBytes = values[1] + values[2] + values[3],
sequenceLost = values[4],
)
}
@@ -263,31 +265,14 @@ data class ProtoCanId(
val messageType: Int,
val body: Int,
) {
val messageTypeName: String get() = MESSAGE_TYPES[messageType] ?: "Reserved 0x${messageType.toString(16).uppercase()}"
val messageTypeName: String get() = ProtoCanMessageType.fromCode(messageType)?.title
?: "Reserved 0x${messageType.toString(16).uppercase()}"
val deviceName: String? get() = DEVICE_NAMES[device]
fun summary(): String = "P$priority · PM$pm · Type $deviceType · Dev $device" +
(deviceName?.let { " ($it)" } ?: "") + " · $messageTypeName · Body 0x%04X".format(body)
companion object {
private val MESSAGE_TYPES = mapOf(
0x0 to "Broadcast",
0x1 to "Discrete",
0x2 to "Analog",
0x3 to "General Address Space",
0x4 to "Modbus Coil",
0x5 to "Modbus Discrete",
0x6 to "Modbus Holding",
0x7 to "Modbus Input",
0x8 to "Error",
0x9 to "Boot Control",
0xA to "Boot Data A",
0xB to "Boot Data B",
0xC to "Boot Status",
0xD to "Boot Discovery",
0xE to "Settings",
0xF to "Pulse",
)
private val DEVICE_NAMES = mapOf(
0xD to "DS_CONTROL",
0xE to "Android GUI",

View File

@@ -0,0 +1,67 @@
package ru.setcorp.setflash.core
/** Canonical ProtoCAN message-type registry shared by Android protocol clients. */
enum class ProtoCanMessageType(val code: Int, val title: String) {
BROADCAST(0x0, "Broadcast"),
DISCRETE(0x1, "Discrete"),
ANALOG(0x2, "Analog"),
GENERAL_ADDRESS_SPACE(0x3, "General Address Space"),
MODBUS_COIL(0x4, "Modbus Coil"),
MODBUS_DISCRETE(0x5, "Modbus Discrete"),
MODBUS_HOLDING(0x6, "Modbus Holding"),
MODBUS_INPUT(0x7, "Modbus Input"),
ERROR(0x8, "Error"),
BOOT_CONTROL(0x9, "Boot Control"),
BOOT_DATA_A(0xA, "Boot Data A"),
BOOT_DATA_B(0xB, "Boot Data B"),
BOOT_STATUS(0xC, "Boot Status"),
BOOT_DISCOVERY(0xD, "Boot Discovery"),
SETTINGS(0xE, "Settings"),
PULSE(0xF, "Pulse");
companion object {
fun fromCode(code: Int): ProtoCanMessageType? = entries.firstOrNull { it.code == code }
}
}
enum class ProtoCanBroadcastType(val code: Int, val title: String) {
STATUS(0x0, "STATUS · запрос статуса"),
ONOFF(0x1, "ONOFF · вкл/выкл пульса"),
RESTART_DEVICE(0x2, "RESTARTDEVICE · перезапуск"),
RTC_SETUP(0x3, "RTCSETUP · установка RTC"),
END(0xFFF, "END · конец диапазона"),
}
enum class ProtoCanDiscreteType(val code: Int, val title: String) {
ACCIDENT(0x0, "ACCIDENT · авария"),
WARNING(0x1, "WARNING · предупреждение"),
CONTROL_SIGNALS(0x2, "CONTROL_SIGNALS · управление"),
FLAGS(0x3, "FLAGS · флаги"),
RESET(0x4, "RESET · сброс"),
CHANGE_MODE(0x5, "CHANGE_MODE · смена режима"),
REQUEST_PARAMETERS(0x6, "REQUEST_LIST_OF_PARAMETERS"),
END(0xF, "END · конец диапазона"),
}
enum class ProtoCanAnalogType(val code: Int, val title: String) {
UNIVERSAL(0x0, "UNIVERSAL · универсальный"),
SETTINGS(0x1, "SETTINGS · уставки"),
U(0x2, "U · напряжение"),
I(0x3, "I · ток"),
T(0x4, "T · температура"),
END(0xF, "END · конец диапазона"),
}
/** Portable MsgBody layouts matching `pcan_id.h` and SETGUI's frame builder. */
object ProtoCanBody {
fun broadcast(type: Int, body: Int): Int = ((type and 0xFFF) shl 4) or (body and 0xF)
fun typed(type: Int, body: Int): Int = ((type and 0xF) shl 12) or (body and 0xFFF)
fun modbus(address: Int, registerCount: Int): Int =
((address and 0xFFF) shl 4) or (registerCount and 0xF)
fun error(info: Int, code: Int): Int = ((info and 0xFF) shl 8) or (code and 0xFF)
fun settings(z: Int, y: Int): Int = ((z and 0xFF) shl 8) or (y and 0xFF)
}

View File

@@ -4,7 +4,10 @@ package ru.setcorp.setprotocol
object NativeSetProtocol {
val available: Boolean by lazy {
runCatching {
System.loadLibrary("setprotocol")
val hostLibrary = System.getProperty("setprotocol.library")
?: System.getProperty("setplot.library")
if (hostLibrary != null) System.load(hostLibrary)
else System.loadLibrary("setprotocol")
nativeAbiVersion() == 1
}.getOrDefault(false)
}
@@ -13,12 +16,18 @@ object NativeSetProtocol {
/** {status, N, Fs, jitter, amplitudes...}; status != 0 has no amplitudes. */
external fun nativeSpectrum(times: DoubleArray, values: DoubleArray, maxSize: Int,
window: Int, filter: Int, lowHz: Double, highHz: Double, removeMean: Boolean): DoubleArray?
/** {frequencyHz, amplitude}, empty when no narrow-band peak is present. */
external fun nativeSpectrumPeak(amplitudes: DoubleArray, binHz: Double,
relativeThreshold: Double, absoluteFloor: Double): DoubleArray?
external fun nativeTrendCanValue(
source: Int, address: Long, deviceType: Int, device: Int, byteOffset: Int,
extended: Boolean, signed: Boolean, canId: Long, flags: Int, input: ByteArray,
): Int
external fun nativeTrendWatchRequest(period: Int, addresses: IntArray): ByteArray?
external fun nativeTrendWatchAck(input: ByteArray, period: Int, count: Int): Boolean
external fun nativeTrendWatchValues(input: ByteArray): IntArray?
/** {unsigned timestampMs, word0, ...}. */
external fun nativeTrendWatchDecode(input: ByteArray): LongArray?
external fun nativePackId(
priority: Int,
route: Int,
@@ -32,6 +41,22 @@ object NativeSetProtocol {
external fun nativeBalsamDecode(canId: Long, input: ByteArray): IntArray?
external fun nativeBalsamDeviceName(device: Int): String
external fun nativeBalsamRegisterName(device: Int, address: Int): String
external fun nativePeriph28335Crc16(input: ByteArray): Int
external fun nativePeriph28335AppendCrc(input: ByteArray): ByteArray?
external fun nativePeriph28335BuildRead(controller: Int, start: Int, count: Int): ByteArray?
external fun nativePeriph28335BuildWrite(controller: Int, address: Int, value: Int): ByteArray?
external fun nativePeriph28335BuildCommand(controller: Int, commandIndex: Int): ByteArray?
external fun nativePeriph28335ExpectedReadSize(count: Int): Int
external fun nativePeriph28335DecodeRead(input: ByteArray, controller: Int, count: Int): IntArray?
external fun nativePeriph28335ValidateWrite(response: ByteArray, request: ByteArray): Int
external fun nativePeriph28335ProjectCount(): Int
external fun nativePeriph28335ProjectName(projectIndex: Int): String?
external fun nativePeriph28335CommandName(projectIndex: Int, commandIndex: Int): String?
external fun nativeTms2812Crc16(input: ByteArray): Int
external fun nativeTms2812BuildUpload(controller: Int, wordAddress: Long, byteCount: Long): ByteArray?
external fun nativeTms2812ExpectedUploadSize(byteCount: Long): Int
external fun nativeTms2812ValidateUpload(input: ByteArray, controller: Int, byteCount: Int): Boolean
external fun nativeTms2812DecodeUpload(input: ByteArray, controller: Int, byteCount: Int): ByteArray?
external fun nativeEncodeFrame(
sequence: Int,
flags: Int,

View File

@@ -7,6 +7,15 @@ data class BalsamRegister(val address: Int, val value: Int, val name: String) {
val signedValue: Int get() = if (value < 0x8000) value else value - 0x10000
}
data class BalsamCanNode(
val device: Int,
val name: String,
val commandId: Long,
val dataId: Long,
)
data class BalsamCanWireFrame(val canId: Long, val data: ByteArray)
data class BalsamFrame(
val canId: Long,
val device: Int,
@@ -20,18 +29,31 @@ data class BalsamFrame(
val direction = if (fromDevice) "данные" else "команда"
val values = registers.joinToString { "${it.displayName}=0x%04X (%d)".format(it.value, it.signedValue) }
.ifEmpty { "нет отмеченных регистров" }
return "BALZAM · $deviceName · $direction · $values"
return "CAN_Bal_2812 · $deviceName · $direction · $values"
}
}
/** Shared parser for Balsam_167_periph eCAN frames. */
/** Shared CAN_Bal_2812 register protocol used by the BALZAM F2812 firmware. */
object BalsamCanProtocol {
const val BASE_ID = 0x00BA_0000L
const val NODE_COUNT = 13
const val REGISTER_BANK_SIZE = 128
const val TERMINAL_REQUEST_ID = 0x00BA_001CL
const val TERMINAL_RESPONSE_ID = 0x00BA_000CL
const val PULT_REQUEST_ID = 0x0074_5019L
const val PULT_RESPONSE_ID = 0x0074_5009L
val nodes: List<BalsamCanNode> by lazy {
(1..NODE_COUNT).map { device ->
BalsamCanNode(
device = device,
name = deviceName(device),
commandId = BASE_ID + device - 1L,
dataId = BASE_ID + 0x0FL + device,
)
}
}
fun isLegacyId(canId: Long): Boolean {
val relative = (canId and 0x1FFF_FFFFL) - BASE_ID
return relative in 0L..12L || relative in 0x10L..0x1CL ||
@@ -62,17 +84,44 @@ object BalsamCanProtocol {
)
}
/** Encode one CAN_Bal_2812 register write/data frame with one to three words. */
fun encodeWrite(
device: Int,
fromDevice: Boolean,
startAddress: Int,
values: List<Int>,
): BalsamCanWireFrame {
require(device in 1..NODE_COUNT) { "Номер узла должен быть в диапазоне 1..$NODE_COUNT" }
require(startAddress in 0 until REGISTER_BANK_SIZE) { "Адрес регистра должен быть в диапазоне 0..127" }
require(values.size in 1..3) { "Нужно от 1 до 3 слов данных" }
require(startAddress + values.size <= REGISTER_BANK_SIZE) { "Запись выходит за границу регистрового пространства" }
values.forEach { require(it in 0..0xFFFF) { "Значение должно быть в диапазоне 0..65535" } }
val mask = when (values.size) {
1 -> 4
2 -> 6
else -> 7
}
val padded = values + List(3 - values.size) { 0 }
val header = (mask shl 13) or startAddress
val data = listOf(header, padded[0], padded[1], padded[2])
.flatMap { listOf((it ushr 8).toByte(), it.toByte()) }
.toByteArray()
val node = nodes[device - 1]
return BalsamCanWireFrame(if (fromDevice) node.dataId else node.commandId, data)
}
fun summary(canId: Long, data: ByteArray? = null): String =
data?.let { decode(canId, it)?.summary() } ?: when (canId) {
TERMINAL_REQUEST_ID -> "BALZAM legacy · запрос терминала"
TERMINAL_RESPONSE_ID -> "BALZAM legacy · ответ терминалу"
PULT_REQUEST_ID -> "BALZAM legacy · данные пульта"
PULT_RESPONSE_ID -> "BALZAM legacy · команда пульту"
TERMINAL_REQUEST_ID -> "CAN_Bal_2812 · запрос терминала"
TERMINAL_RESPONSE_ID -> "CAN_Bal_2812 · ответ терминалу"
PULT_REQUEST_ID -> "CAN_Bal_2812 · данные пульта"
PULT_RESPONSE_ID -> "CAN_Bal_2812 · команда пульту"
in (BASE_ID + 0x10L)..(BASE_ID + 0x1BL) ->
"BALZAM legacy · данные · ${deviceName((canId - BASE_ID - 0x0FL).toInt())}"
"CAN_Bal_2812 · данные · ${deviceName((canId - BASE_ID - 0x0FL).toInt())}"
in BASE_ID..(BASE_ID + 0x0BL) ->
"BALZAM legacy · команда · ${deviceName((canId - BASE_ID + 1L).toInt())}"
else -> "BALZAM legacy · неизвестный ID"
"CAN_Bal_2812 · команда · ${deviceName((canId - BASE_ID + 1L).toInt())}"
else -> "CAN_Bal_2812 · неизвестный ID"
}
private fun isRegisterId(canId: Long): Boolean {
@@ -95,7 +144,7 @@ object BalsamCanProtocol {
private fun u16be(data: ByteArray, offset: Int): Int =
((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
private fun deviceName(device: Int): String = if (NativeSetProtocol.available) {
fun deviceName(device: Int): String = if (NativeSetProtocol.available) {
NativeSetProtocol.nativeBalsamDeviceName(device)
} else listOf(
"Трансформатор 1", "Трансформатор 2", "Силовой блок 1", "Силовой блок 2",
@@ -103,7 +152,7 @@ object BalsamCanProtocol {
"Узел 12", "Терминал",
).getOrElse(device - 1) { "Неизвестный узел" }
private fun registerName(device: Int, address: Int): String =
fun registerName(device: Int, address: Int): String =
if (NativeSetProtocol.available) NativeSetProtocol.nativeBalsamRegisterName(device, address)
else when {
device in 1..2 && address in 0x18..0x2B -> "Показания T° ${address - 0x17}"

View File

@@ -0,0 +1,117 @@
package ru.setcorp.setprotocol.periph28335
import ru.setcorp.setprotocol.NativeSetProtocol
/** Kotlin UI adapter; all PM35 wire logic and the command catalog live in C99. */
object Periph28335Protocol {
const val REGISTER_COUNT = 128
const val DEFAULT_CONTROLLER = 16
const val DEFAULT_BAUD_RATE = 115_200
val projectCommands: Map<String, List<String>> by lazy {
requireNative()
buildMap {
repeat(NativeSetProtocol.nativePeriph28335ProjectCount()) { project ->
val name = requireNotNull(
NativeSetProtocol.nativePeriph28335ProjectName(project),
) { "Повреждён каталог проектов ПМ35" }
put(name, List(17) { command ->
requireNotNull(
NativeSetProtocol.nativePeriph28335CommandName(project, command),
) { "Повреждён каталог команд ПМ35" }
})
}
}
}
fun crc16Modbus(data: ByteArray, initial: Int = 0xFFFF): Int {
require(initial == 0xFFFF) {
"Произвольное начальное значение CRC не входит в протокол ПМ35"
}
requireNative()
return NativeSetProtocol.nativePeriph28335Crc16(data)
}
fun withCrc(payload: ByteArray): ByteArray {
requireNative()
return requireNotNull(NativeSetProtocol.nativePeriph28335AppendCrc(payload)) {
"SETProtocol отклонил данные ПМ35"
}
}
fun buildReadRegisters(controller: Int, start: Int, count: Int): ByteArray {
requireRange("Адрес контроллера", controller, 0xFF)
requireRange("Начальный регистр", start, 0xFFFF)
require(count in 1..REGISTER_COUNT && start + count <= REGISTER_COUNT) {
"Диапазон регистров должен находиться в 0..127"
}
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335BuildRead(controller, start, count),
) { "SETProtocol отклонил запрос чтения ПМ35" }
}
fun buildWriteRegister(controller: Int, address: Int, value: Int): ByteArray {
requireRange("Адрес контроллера", controller, 0xFF)
requireRange("Адрес регистра", address, REGISTER_COUNT - 1)
requireRange("Значение", value, 0xFFFF)
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335BuildWrite(controller, address, value),
) { "SETProtocol отклонил запрос записи ПМ35" }
}
fun buildCommand(controller: Int, commandIndex: Int): ByteArray {
require(commandIndex in 0..16) { "Номер команды должен быть в диапазоне 0..16" }
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335BuildCommand(controller, commandIndex),
) { "SETProtocol отклонил команду ПМ35" }
}
fun expectedReadResponseSize(count: Int): Int {
require(count in 1..REGISTER_COUNT)
requireNative()
return NativeSetProtocol.nativePeriph28335ExpectedReadSize(count)
}
fun decodeReadResponse(data: ByteArray, controller: Int, count: Int): List<Int> {
val expected = expectedReadResponseSize(count)
require(data.size == expected) { "Ожидалось $expected байт, получено ${data.size}" }
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335DecodeRead(data, controller, count),
) { "Повреждён ответ ПМ35: заголовок, длина или CRC" }.toList()
}
fun validateWriteResponse(data: ByteArray, request: ByteArray): Boolean {
requireNative()
return NativeSetProtocol.nativePeriph28335ValidateWrite(data, request) == 0
}
// Presentation-only conversions stay in the GUI port; they do not define wire bytes.
fun bitsLsbFirst(value: Int): List<Boolean> {
requireRange("Значение", value, 0xFFFF)
return (0 until 16).map { bit -> value and (1 shl bit) != 0 }
}
fun wordFromBits(bits: List<Boolean>): Int {
require(bits.size == 16) { "Должно быть ровно 16 бит" }
return bits.foldIndexed(0) { bit, value, checked ->
if (checked) value or (1 shl bit) else value
}
}
fun signedWord(value: Int): Int {
requireRange("Значение", value, 0xFFFF)
return if (value < 0x8000) value else value - 0x10000
}
private fun requireNative() {
check(NativeSetProtocol.available) { "Нативное ядро SETProtocol недоступно" }
}
private fun requireRange(name: String, value: Int, maximum: Int) {
require(value in 0..maximum) { "$name вне диапазона 0..$maximum" }
}
}

View File

@@ -10,37 +10,28 @@ object GuiGasWatch {
fun request(periodMs: Int, addresses: List<Int>): ByteArray {
require(periodMs in 0..65535 && addresses.size <= 64 && addresses.all { it in 0..65535 })
if (NativeSetProtocol.available) return requireNotNull(NativeSetProtocol.nativeTrendWatchRequest(periodMs, addresses.toIntArray()))
return ByteArray(4 + addresses.size * 2).also { output ->
put16(output, 0, periodMs)
put16(output, 2, addresses.size)
addresses.forEachIndexed { index, address -> put16(output, 4 + index * 2, address) }
}
check(NativeSetProtocol.available) { "Общая библиотека SETProtocol недоступна" }
return requireNotNull(NativeSetProtocol.nativeTrendWatchRequest(periodMs, addresses.toIntArray()))
}
fun validateAck(payload: ByteArray, periodMs: Int, count: Int) {
require(payload.size == 4 && read16(payload, 0) == periodMs && read16(payload, 2) == count) {
require(NativeSetProtocol.available && NativeSetProtocol.nativeTrendWatchAck(payload, periodMs, count)) {
"Прибор принял не все адреса GAS. Проверьте карту регистров; отображение по неполной подписке невозможно"
}
}
fun values(payload: ByteArray, expectedCount: Int): List<Int> {
val values = if (NativeSetProtocol.available) {
requireNotNull(NativeSetProtocol.nativeTrendWatchValues(payload)) { "Повреждён GAS_WATCH_DATA" }.toList()
} else {
require(payload.size >= 6) { "GAS_WATCH_DATA короче заголовка" }
val count = read16(payload, 4)
require(count <= 64 && payload.size == 6 + count * 2) { "Неверная длина GAS_WATCH_DATA" }
List(count) { read16(payload, 6 + it * 2) }
}
check(NativeSetProtocol.available) { "Общая библиотека SETProtocol недоступна" }
val values = requireNotNull(NativeSetProtocol.nativeTrendWatchValues(payload)) { "Повреждён GAS_WATCH_DATA" }.toList()
require(values.size == expectedCount) { "Число значений GAS не соответствует подписке" }
return values
}
private fun read16(data: ByteArray, offset: Int): Int =
(data[offset].toInt() and 0xFF) or ((data[offset + 1].toInt() and 0xFF) shl 8)
private fun put16(data: ByteArray, offset: Int, value: Int) {
data[offset] = value.toByte()
data[offset + 1] = (value ushr 8).toByte()
data class Sample(val timestampMs: Long, val values: List<Int>)
fun decode(payload: ByteArray, expectedCount: Int): Sample {
check(NativeSetProtocol.available) { "Общая библиотека SETProtocol недоступна" }
val decoded = requireNotNull(NativeSetProtocol.nativeTrendWatchDecode(payload)) { "Повреждён GAS_WATCH_DATA" }
require(decoded.size == expectedCount + 1) { "Число значений GAS не соответствует подписке" }
return Sample(decoded[0], decoded.drop(1).map(Long::toInt))
}
}

View File

@@ -32,3 +32,10 @@ fun plotTickStep(range: Double, pixels: Double): Double = NativePlot.call(5, ran
fun plotDelta(a: Double, b: Double, multiplier: Double = 1.0): Double = NativePlot.call(6, a, b, multiplier)[0]
fun plotDbDelta(a: Double, b: Double): Double? =
runCatching { NativePlot.call(7, a, b)[0] }.getOrNull()
data class PlotLimits(val xMin: Double, val xMax: Double, val yMin: Double, val yMax: Double) {
fun validated(): PlotLimits {
val values = NativePlot.call(8, xMin, xMax, yMin, yMax)
return PlotLimits(values[0], values[1], values[2], values[3])
}
}

View File

@@ -6,7 +6,7 @@ enum class TrendSection { SIGNALS, CHART }
/** File-format IDs; host applications map their connection profiles to these IDs. */
enum class TrendProfile(val title: String) {
SET_V1("SET GUI v1"), TMS2812("TMS320F2812 / BALZAM"), CAN_BRIDGE("CAN ↔ RS485"),
SET_V1("SET GUI v1"), TMS2812("TMS320F2812 / BALZAM"), TMS28335("TMS320F28335 / ПМ35"), CAN_BRIDGE("CAN ↔ RS485"),
GS_USB_CAN("CANgaroo / gs_usb"), SLCAN("SKLab SLCAN"), CANGAROO_SLCAN("CANgaroo / SLCAN"),
BALZAM_CAN("Старый CAN BALZAM"),
}
@@ -29,7 +29,7 @@ enum class TrendValueType(val title: String) {
}
fun TrendProfile.trendSources(): List<TrendSource> = when (this) {
TrendProfile.TMS2812 -> listOf(TrendSource.TMS_MEMORY)
TrendProfile.TMS2812, TrendProfile.TMS28335 -> listOf(TrendSource.TMS_MEMORY)
TrendProfile.SET_V1 -> listOf(TrendSource.SET_GAS, TrendSource.SET_SENSOR)
TrendProfile.BALZAM_CAN -> listOf(TrendSource.CAN_RAW)
else -> listOf(TrendSource.CAN_GAS, TrendSource.CAN_RAW)

View File

@@ -34,6 +34,8 @@ data class TrendSpectrum(
val binHz: Double get() = if (size > 0) sampleRate / size else 0.0
}
data class SpectrumPeak(val frequencyHz: Double, val amplitude: Double)
/** Math is implemented once in C and used unchanged by JNI and ctypes. */
object SpectrumAnalyzer {
fun analyze(points: List<TrendPoint>, options: SpectrumOptions): TrendSpectrum {
@@ -64,4 +66,12 @@ object SpectrumAnalyzer {
return TrendSpectrum(output[1].toInt(), output[2], output[3],
if (error == null) output.drop(4) else emptyList(), error)
}
fun dominantPeak(spectrum: TrendSpectrum, relativeThreshold: Double = 3.0,
absoluteFloor: Double = 1e-6): SpectrumPeak? {
if (spectrum.error != null || spectrum.size <= 0 || spectrum.amplitudes.size < 3) return null
val result = NativeSetProtocol.nativeSpectrumPeak(spectrum.amplitudes.toDoubleArray(),
spectrum.binHz, relativeThreshold, absoluteFloor) ?: return null
return result.takeIf { it.size == 2 }?.let { SpectrumPeak(it[0], it[1]) }
}
}

View File

@@ -7,6 +7,273 @@
#include "set_trends.h"
#include "set_spectrum.h"
#include "balsam_can.h"
#include "periph28335.h"
#include "tms2812.h"
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812Crc16(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
if (input == NULL) return 0;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) return 0;
uint16_t crc = tms2812_crc16((const uint8_t *)data, (size_t)size);
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return (jint)crc;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812BuildUpload(
JNIEnv *env, jobject self, jint controller, jlong word_address,
jlong byte_count)
{
(void)self;
uint8_t output[TMS2812_UPLOAD_REQUEST_SIZE];
if (controller < 0 || controller > 255 || word_address < 0 ||
(uint64_t)word_address > UINT32_MAX || byte_count < 1 ||
(uint64_t)byte_count > UINT32_MAX) return NULL;
size_t written = tms2812_build_upload_request(
(uint8_t)controller, (uint32_t)word_address, (uint32_t)byte_count,
output, sizeof output);
if (written == 0U) return NULL;
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
return result;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812ExpectedUploadSize(
JNIEnv *env, jobject self, jlong byte_count)
{
(void)env; (void)self;
if (byte_count < 1 || (uint64_t)byte_count > UINT32_MAX) return 0;
size_t size = tms2812_expected_upload_response_size((uint32_t)byte_count);
return size <= INT32_MAX ? (jint)size : 0;
}
JNIEXPORT jboolean JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812ValidateUpload(
JNIEnv *env, jobject self, jbyteArray input, jint controller,
jint byte_count)
{
(void)self;
if (input == NULL || controller < 0 || controller > 255 || byte_count < 1)
return JNI_FALSE;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if (data == NULL) return JNI_FALSE;
int status = tms2812_validate_upload_response(
(const uint8_t *)data, (size_t)size, (uint8_t)controller,
(uint32_t)byte_count);
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return status == TMS2812_OK ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812DecodeUpload(
JNIEnv *env, jobject self, jbyteArray input, jint controller,
jint byte_count)
{
(void)self;
if (input == NULL || controller < 0 || controller > 255 || byte_count < 1)
return NULL;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if (data == NULL) return NULL;
uint8_t *output = (uint8_t *)malloc((size_t)byte_count);
if (output == NULL) {
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return NULL;
}
int status = tms2812_decode_upload_response(
(const uint8_t *)data, (size_t)size, (uint8_t)controller,
(uint32_t)byte_count, output, (size_t)byte_count);
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
if (status != TMS2812_OK) { free(output); return NULL; }
jbyteArray result = (*env)->NewByteArray(env, byte_count);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, byte_count, (const jbyte *)output);
free(output);
return result;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335Crc16(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
if (input == NULL) return 0;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) return 0;
uint16_t crc = periph28335_crc16_modbus((const uint8_t *)data, (size_t)size);
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return (jint)crc;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335AppendCrc(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
if (input == NULL) return NULL;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) return NULL;
uint8_t *output = (uint8_t *)malloc((size_t)size + 2U);
if (output == NULL) {
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return NULL;
}
size_t written = periph28335_append_crc(
(const uint8_t *)data, (size_t)size, output, (size_t)size + 2U);
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
if (written == 0U) { free(output); return NULL; }
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
free(output);
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335BuildRead(
JNIEnv *env, jobject self, jint controller, jint start, jint count)
{
(void)self;
uint8_t output[PERIPH28335_REQUEST_SIZE];
if (controller < 0 || controller > 255 || start < 0 || start > 65535 ||
count < 0 || count > 65535) return NULL;
size_t written = periph28335_build_read_registers(
(uint8_t)controller, (uint16_t)start, (uint16_t)count,
output, sizeof output);
if (written == 0U) return NULL;
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335BuildWrite(
JNIEnv *env, jobject self, jint controller, jint address, jint value)
{
(void)self;
uint8_t output[PERIPH28335_REQUEST_SIZE];
if (controller < 0 || controller > 255 || address < 0 || address > 65535 ||
value < 0 || value > 65535) return NULL;
size_t written = periph28335_build_write_register(
(uint8_t)controller, (uint16_t)address, (uint16_t)value,
output, sizeof output);
if (written == 0U) return NULL;
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335BuildCommand(
JNIEnv *env, jobject self, jint controller, jint command_index)
{
(void)self;
uint8_t output[PERIPH28335_REQUEST_SIZE];
if (controller < 0 || controller > 255 || command_index < 0 || command_index > 255) return NULL;
size_t written = periph28335_build_command(
(uint8_t)controller, (uint8_t)command_index, output, sizeof output);
if (written == 0U) return NULL;
jbyteArray result = (*env)->NewByteArray(env, (jsize)written);
if (result != NULL) (*env)->SetByteArrayRegion(
env, result, 0, (jsize)written, (const jbyte *)output);
return result;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ExpectedReadSize(
JNIEnv *env, jobject self, jint count)
{
(void)env; (void)self;
if (count < 0 || count > 65535) return 0;
return (jint)periph28335_expected_read_response_size((uint16_t)count);
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335DecodeRead(
JNIEnv *env, jobject self, jbyteArray input, jint controller, jint count)
{
(void)self;
if (input == NULL || controller < 0 || controller > 255 ||
count < 1 || count > (jint)PERIPH28335_REGISTER_COUNT) return NULL;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) return NULL;
uint16_t words[PERIPH28335_REGISTER_COUNT];
int status = periph28335_decode_read_response(
(const uint8_t *)data, (size_t)size, (uint8_t)controller,
(uint16_t)count, words, PERIPH28335_REGISTER_COUNT);
if (data != NULL) (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
if (status != PERIPH28335_OK) return NULL;
jint values[PERIPH28335_REGISTER_COUNT];
for (jint index = 0; index < count; ++index) values[index] = words[index];
jintArray result = (*env)->NewIntArray(env, count);
if (result != NULL) (*env)->SetIntArrayRegion(env, result, 0, count, values);
return result;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ValidateWrite(
JNIEnv *env, jobject self, jbyteArray response, jbyteArray request)
{
(void)self;
if (response == NULL || request == NULL) return PERIPH28335_ERROR_ARGUMENT;
jsize response_size = (*env)->GetArrayLength(env, response);
jsize request_size = (*env)->GetArrayLength(env, request);
jbyte *response_data = (*env)->GetByteArrayElements(env, response, NULL);
jbyte *request_data = (*env)->GetByteArrayElements(env, request, NULL);
if (response_data == NULL || request_data == NULL) {
if (response_data != NULL) (*env)->ReleaseByteArrayElements(env, response, response_data, JNI_ABORT);
if (request_data != NULL) (*env)->ReleaseByteArrayElements(env, request, request_data, JNI_ABORT);
return PERIPH28335_ERROR_ARGUMENT;
}
int status = periph28335_validate_write_response(
(const uint8_t *)response_data, (size_t)response_size,
(const uint8_t *)request_data, (size_t)request_size);
(*env)->ReleaseByteArrayElements(env, response, response_data, JNI_ABORT);
(*env)->ReleaseByteArrayElements(env, request, request_data, JNI_ABORT);
return status;
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ProjectCount(
JNIEnv *env, jobject self)
{
(void)env; (void)self;
return (jint)periph28335_project_count();
}
JNIEXPORT jstring JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335ProjectName(
JNIEnv *env, jobject self, jint project_index)
{
(void)self;
const char *name = project_index >= 0
? periph28335_project_name((size_t)project_index) : NULL;
return name != NULL ? (*env)->NewStringUTF(env, name) : NULL;
}
JNIEXPORT jstring JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativePeriph28335CommandName(
JNIEnv *env, jobject self, jint project_index, jint command_index)
{
(void)self;
const char *name = project_index >= 0 && command_index >= 0
? periph28335_command_name((size_t)project_index, (size_t)command_index)
: NULL;
return name != NULL ? (*env)->NewStringUTF(env, name) : NULL;
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeBalsamDecode(
@@ -100,6 +367,30 @@ Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendCanValue(
(uint8_t)flags, (const uint8_t *)data, (size_t)size);
}
JNIEXPORT jdoubleArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeSpectrumPeak(
JNIEnv *env, jobject self, jdoubleArray input, jdouble bin_hz,
jdouble relative_threshold, jdouble absolute_floor)
{
(void)self;
jsize count = input == NULL ? 0 : (*env)->GetArrayLength(env, input);
if (count < 3 || count > (jsize)(SET_SPECTRUM_MAX / 2U + 1U)) return NULL;
double *buffer = (double *)malloc(sizeof(double) * (size_t)(count * 2 - 1));
if (buffer == NULL) return NULL;
double *amplitudes = buffer, *scratch = buffer + count, peak[2];
(*env)->GetDoubleArrayRegion(env, input, 0, count, amplitudes);
int status = (*env)->ExceptionCheck(env) ? -1 : set_spectrum_dominant_peak(
amplitudes, (size_t)count, bin_hz, relative_threshold, absolute_floor,
scratch, (size_t)count - 1U, peak, 2U);
jdoubleArray result = NULL;
if (status >= 0) {
result = (*env)->NewDoubleArray(env, status == 1 ? 2 : 0);
if (result != NULL && status == 1) (*env)->SetDoubleArrayRegion(env, result, 0, 2, peak);
}
free(buffer);
return result;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchRequest(
JNIEnv *env, jobject self, jint period, jintArray input)
@@ -122,6 +413,20 @@ Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchRequest(
return result;
}
JNIEXPORT jboolean JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchAck(
JNIEnv *env, jobject self, jbyteArray input, jint period, jint count)
{
(void)self;
if (input == NULL || period < 0 || period > 65535 || count < 0) return JNI_FALSE;
jsize size = (*env)->GetArrayLength(env, input);
if (size != 4) return JNI_FALSE;
jbyte payload[4];
(*env)->GetByteArrayRegion(env, input, 0, size, payload);
return set_trend_watch_ack((const uint8_t *)payload, (size_t)size,
(uint16_t)period, (size_t)count) ? JNI_TRUE : JNI_FALSE;
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchValues(
JNIEnv *env, jobject self, jbyteArray input)
@@ -141,6 +446,28 @@ Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchValues(
return result;
}
JNIEXPORT jlongArray JNICALL
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTrendWatchDecode(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
jsize size = input == NULL ? 0 : (*env)->GetArrayLength(env, input);
if (size < 6 || size > (jsize)(6U + 2U * SET_TREND_WATCH_MAX)) return NULL;
jbyte payload[6U + 2U * SET_TREND_WATCH_MAX];
uint16_t words[SET_TREND_WATCH_MAX];
uint32_t timestamp = 0U;
jlong values[1U + SET_TREND_WATCH_MAX];
(*env)->GetByteArrayRegion(env, input, 0, size, payload);
int count = set_trend_watch_decode((const uint8_t *)payload, (size_t)size,
&timestamp, words, SET_TREND_WATCH_MAX);
if (count < 0) return NULL;
values[0] = (jlong)timestamp;
for (int i = 0; i < count; ++i) values[i + 1] = (jlong)words[i];
jlongArray result = (*env)->NewLongArray(env, count + 1);
if (result != NULL) (*env)->SetLongArrayRegion(env, result, 0, count + 1, values);
return result;
}
typedef struct {
uint8_t *storage;
size_t storage_size;

View File

@@ -0,0 +1,21 @@
package ru.setcorp.setflash.core
import org.junit.Assert.assertEquals
import org.junit.Test
class ProtoCanCatalogTest {
@Test
fun bodyLayoutsMatchSetGuiBuilder() {
assertEquals(0x1234, ProtoCanBody.broadcast(0x123, 0x4))
assertEquals(0x2001, ProtoCanBody.typed(ProtoCanAnalogType.U.code, 1))
assertEquals(0x1234, ProtoCanBody.modbus(0x123, 4))
assertEquals(0xABCD, ProtoCanBody.error(0xAB, 0xCD))
assertEquals(0xFF01, ProtoCanBody.settings(0xFF, 0x01))
}
@Test
fun messageRegistryCoversAllFourBitValues() {
assertEquals((0..0xF).toList(), ProtoCanMessageType.entries.map { it.code })
assertEquals("Settings", ProtoCanId.parse(0x17FE1234).messageTypeName)
}
}

View File

@@ -17,4 +17,22 @@ class BalsamCanProtocolTest {
assertEquals("Показания T° 1", frame.registers.first().displayName)
assertTrue(frame.summary().contains("Трансформатор 1"))
}
@Test
fun encodesControllerWriteForBal2812RegisterSpace() {
val wire = BalsamCanProtocol.encodeWrite(
device = 3,
fromDevice = false,
startAddress = 0x28,
values = listOf(0x1234, 0xFEDC),
)
assertEquals(0x00BA_0002L, wire.canId)
assertEquals(
listOf(0xC0, 0x28, 0x12, 0x34, 0xFE, 0xDC, 0x00, 0x00),
wire.data.map { it.toInt() and 0xFF },
)
val decoded = requireNotNull(BalsamCanProtocol.decode(wire.canId, wire.data))
assertEquals(false, decoded.fromDevice)
assertEquals(listOf(0x1234, 0xFEDC), decoded.registers.map { it.value })
}
}

View File

@@ -0,0 +1,35 @@
package ru.setcorp.setprotocol.periph28335
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
class Periph28335ProtocolTest {
@Test fun readRequestMatchesDelphiByteOrder() {
val request = Periph28335Protocol.buildReadRegisters(16, 24, 64)
assertTrue(request.copyOf(6).contentEquals(byteArrayOf(0x10, 0x03, 0x00, 0x18, 0x00, 0x40)))
assertEquals(
Periph28335Protocol.crc16Modbus(request.copyOf(6)),
(request[6].toInt() and 0xFF) or ((request[7].toInt() and 0xFF) shl 8),
)
}
@Test fun writeAndCommandUseRegister127() {
assertTrue(
Periph28335Protocol.buildWriteRegister(16, 7, 0x1234).copyOf(6)
.contentEquals(byteArrayOf(0x10, 0x06, 0x00, 0x07, 0x12, 0x34)),
)
assertTrue(
Periph28335Protocol.buildCommand(16, 15).copyOf(6)
.contentEquals(byteArrayOf(0x10, 0x06, 0x00, 0x7F, 0x80.toByte(), 0x00)),
)
}
@Test fun responseAndBitsRoundTrip() {
val response = Periph28335Protocol.withCrc(byteArrayOf(0x10, 0x03, 0x04, 0x80.toByte(), 0x05, 0x12, 0x34))
assertEquals(listOf(0x8005, 0x1234), Periph28335Protocol.decodeReadResponse(response, 16, 2))
val bits = Periph28335Protocol.bitsLsbFirst(0x8005)
assertTrue(bits[0] && bits[2] && bits[15])
assertEquals(0x8005, Periph28335Protocol.wordFromBits(bits))
}
}

View File

@@ -36,4 +36,12 @@ class PlotViewportTest {
}
SpectrumOptions().validate()
}
@Test fun absoluteLimitsAndDominantPeakUseNativeCore() {
assertEquals(PlotLimits(0.0, 500.0, -1.0, 1.0),
PlotLimits(0.0, 500.0, -1.0, 1.0).validated())
assertTrue(runCatching { PlotLimits(1.0, 1.0, -1.0, 1.0).validated() }.isFailure)
val spectrum = TrendSpectrum(12, 60.0, 0.0,
listOf(10.0, .01, .02, .8, .03, .4, .02))
assertEquals(15.0, SpectrumAnalyzer.dominantPeak(spectrum)!!.frequencyHz, 0.0)
}
}

View File

@@ -0,0 +1,23 @@
# SETProtocol v2 firmware port for TMS320F2812
`setp_tms2812_boot.c` implements the shared SETP v2 CAN firmware service for
F2812 projects. It owns CAN reassembly, request/response framing, firmware state,
idempotent blocks, CRC32 and SHA-256 verification. It does not include TI or
board headers.
The target application provides callbacks for classic-CAN transmission, Flash
erase/program/read, optional signature authorization, and reset. Receive ISR
code must only copy frames into a queue; call `setp_tms2812_boot_process()` from
task context.
Add these sources to a CCS project:
- `c/set-protocol/src/set_protocol.c`
- `c/set-protocol/src/set_can.c`
- `c/set-protocol/src/set_firmware.c`
- `c/set-protocol/ports/tms320f2812/setp_tms2812_boot.c`
Add `c/set-protocol/include` and this directory to include paths. The port uses
extended 29-bit SETP CAN identifiers and supports one firmware slot. A single
slot has no power-loss rollback; production hardware should provide staging or
A/B storage.

View File

@@ -0,0 +1,407 @@
#include "setp_tms2812_boot.h"
#include <string.h>
#define SHA256_BLOCK_SIZE 64U
typedef struct {
uint32_t state[8];
uint64_t bit_count;
uint8_t block[SHA256_BLOCK_SIZE];
uint16_t block_length;
} sha256_ctx_t;
static uint32_t rotr32(uint32_t value, uint8_t bits)
{
return (value >> bits) | (value << (32U - bits));
}
static void sha256_transform(sha256_ctx_t *ctx, const uint8_t *block)
{
static const uint32_t k[64] = {
0x428A2F98UL, 0x71374491UL, 0xB5C0FBCFUL, 0xE9B5DBA5UL,
0x3956C25BUL, 0x59F111F1UL, 0x923F82A4UL, 0xAB1C5ED5UL,
0xD807AA98UL, 0x12835B01UL, 0x243185BEUL, 0x550C7DC3UL,
0x72BE5D74UL, 0x80DEB1FEUL, 0x9BDC06A7UL, 0xC19BF174UL,
0xE49B69C1UL, 0xEFBE4786UL, 0x0FC19DC6UL, 0x240CA1CCUL,
0x2DE92C6FUL, 0x4A7484AAUL, 0x5CB0A9DCUL, 0x76F988DAUL,
0x983E5152UL, 0xA831C66DUL, 0xB00327C8UL, 0xBF597FC7UL,
0xC6E00BF3UL, 0xD5A79147UL, 0x06CA6351UL, 0x14292967UL,
0x27B70A85UL, 0x2E1B2138UL, 0x4D2C6DFCUL, 0x53380D13UL,
0x650A7354UL, 0x766A0ABBUL, 0x81C2C92EUL, 0x92722C85UL,
0xA2BFE8A1UL, 0xA81A664BUL, 0xC24B8B70UL, 0xC76C51A3UL,
0xD192E819UL, 0xD6990624UL, 0xF40E3585UL, 0x106AA070UL,
0x19A4C116UL, 0x1E376C08UL, 0x2748774CUL, 0x34B0BCB5UL,
0x391C0CB3UL, 0x4ED8AA4AUL, 0x5B9CCA4FUL, 0x682E6FF3UL,
0x748F82EEUL, 0x78A5636FUL, 0x84C87814UL, 0x8CC70208UL,
0x90BEFFFAUL, 0xA4506CEBUL, 0xBEF9A3F7UL, 0xC67178F2UL
};
uint32_t w[64];
uint32_t a, b, c, d, e, f, g, h, s0, s1, ch, maj, temp1, temp2;
uint16_t i;
for (i = 0U; i < 16U; i++) {
uint16_t p = (uint16_t)(i * 4U);
w[i] = ((uint32_t)block[p] << 24U)
| ((uint32_t)block[p + 1U] << 16U)
| ((uint32_t)block[p + 2U] << 8U)
| (uint32_t)block[p + 3U];
}
for (i = 16U; i < 64U; i++) {
s0 = rotr32(w[i - 15U], 7U) ^ rotr32(w[i - 15U], 18U)
^ (w[i - 15U] >> 3U);
s1 = rotr32(w[i - 2U], 17U) ^ rotr32(w[i - 2U], 19U)
^ (w[i - 2U] >> 10U);
w[i] = w[i - 16U] + s0 + w[i - 7U] + s1;
}
a = ctx->state[0]; b = ctx->state[1]; c = ctx->state[2]; d = ctx->state[3];
e = ctx->state[4]; f = ctx->state[5]; g = ctx->state[6]; h = ctx->state[7];
for (i = 0U; i < 64U; i++) {
s1 = rotr32(e, 6U) ^ rotr32(e, 11U) ^ rotr32(e, 25U);
ch = (e & f) ^ ((~e) & g);
temp1 = h + s1 + ch + k[i] + w[i];
s0 = rotr32(a, 2U) ^ rotr32(a, 13U) ^ rotr32(a, 22U);
maj = (a & b) ^ (a & c) ^ (b & c);
temp2 = s0 + maj;
h = g; g = f; f = e; e = d + temp1;
d = c; c = b; b = a; a = temp1 + temp2;
}
ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d;
ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h;
}
static void sha256_init(sha256_ctx_t *ctx)
{
static const uint32_t initial[8] = {
0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
};
(void)memcpy(ctx->state, initial, sizeof(initial));
ctx->bit_count = 0U;
ctx->block_length = 0U;
}
static void sha256_update(sha256_ctx_t *ctx, const uint8_t *data, uint16_t length)
{
uint16_t i;
for (i = 0U; i < length; i++) {
ctx->block[ctx->block_length++] = data[i];
ctx->bit_count += 8U;
if (ctx->block_length == SHA256_BLOCK_SIZE) {
sha256_transform(ctx, ctx->block);
ctx->block_length = 0U;
}
}
}
static void sha256_finish(sha256_ctx_t *ctx, uint8_t digest[SETP_SHA256_SIZE])
{
uint16_t i;
uint64_t bits = ctx->bit_count;
ctx->block[ctx->block_length++] = 0x80U;
if (ctx->block_length > 56U) {
while (ctx->block_length < SHA256_BLOCK_SIZE) ctx->block[ctx->block_length++] = 0U;
sha256_transform(ctx, ctx->block);
ctx->block_length = 0U;
}
while (ctx->block_length < 56U) ctx->block[ctx->block_length++] = 0U;
for (i = 0U; i < 8U; i++) {
ctx->block[63U - i] = (uint8_t)(bits & 0xFFU);
bits >>= 8U;
}
sha256_transform(ctx, ctx->block);
for (i = 0U; i < 8U; i++) {
digest[i * 4U] = (uint8_t)(ctx->state[i] >> 24U);
digest[i * 4U + 1U] = (uint8_t)(ctx->state[i] >> 16U);
digest[i * 4U + 2U] = (uint8_t)(ctx->state[i] >> 8U);
digest[i * 4U + 3U] = (uint8_t)ctx->state[i];
}
}
static void boot_status(const setp_tms2812_boot_t *boot, setp_fw_status_t *status)
{
status->state = boot->state;
status->active_slot = boot->config.active_slot;
status->max_block_size = boot->config.max_block_size;
status->next_offset = boot->next_offset;
status->image_size = boot->manifest.image_size;
status->last_error = boot->last_error;
status->flags = 0U;
}
static bool boot_send_response(setp_tms2812_boot_t *boot,
const setp_frame_t *request,
const setp_can_id_t *request_id,
uint16_t status,
const uint8_t *body, uint16_t body_length)
{
setp_frame_t response;
setp_can_id_t response_id;
size_t packet_length;
if ((uint32_t)body_length + 2U > sizeof(boot->response_payload)) return false;
setp_put_u16(boot->response_payload, status);
if (body_length != 0U) (void)memcpy(&boot->response_payload[2], body, body_length);
response.flags = SETP_FLAG_RESPONSE;
if (status != SETP_STATUS_OK) response.flags |= SETP_FLAG_ERROR;
if ((request->flags & SETP_FLAG_PRIORITY) != 0U) response.flags |= SETP_FLAG_PRIORITY;
response.message_type = request->message_type;
response.source = boot->config.node_id;
response.destination = request->source;
response.sequence = request->sequence;
response.payload_length = (uint16_t)(body_length + 2U);
response.payload = boot->response_payload;
packet_length = setp_frame_encode(&response, boot->response_packet,
sizeof(boot->response_packet));
if (packet_length == 0U) return false;
response_id.destination = request_id->source;
response_id.source = boot->config.node_id;
response_id.priority = (response.flags & SETP_FLAG_PRIORITY) != 0U ? 1U : 0U;
response_id.channel = request_id->channel;
return setp_can_segment(boot->response_packet, (uint16_t)packet_length,
setp_can_id_pack(&response_id), boot->port.send_can,
boot->port_user);
}
static uint16_t boot_verify_image(setp_tms2812_boot_t *boot)
{
uint8_t data[SETP_TMS2812_MAX_BLOCK_SIZE];
uint8_t digest[SETP_SHA256_SIZE];
sha256_ctx_t sha;
uint32_t crc = 0xFFFFFFFFUL;
uint32_t offset = 0U;
uint16_t i;
sha256_init(&sha);
while (offset < boot->manifest.image_size) {
uint32_t remaining = boot->manifest.image_size - offset;
uint16_t length = remaining > sizeof(data) ? (uint16_t)sizeof(data) : (uint16_t)remaining;
if (!boot->port.read_image(boot->port_user, offset, data, length)) {
return SETP_STATUS_INTERNAL;
}
sha256_update(&sha, data, length);
for (i = 0U; i < length; i++) {
uint8_t bit;
crc ^= data[i];
for (bit = 0U; bit < 8U; bit++)
crc = (crc >> 1U) ^ (((crc & 1U) != 0U) ? 0xEDB88320UL : 0U);
}
offset += length;
}
sha256_finish(&sha, digest);
crc ^= 0xFFFFFFFFUL;
if ((crc != boot->manifest.image_crc32)
|| (memcmp(digest, boot->manifest.sha256, SETP_SHA256_SIZE) != 0)) {
return SETP_STATUS_VERIFY_FAILED;
}
return SETP_STATUS_OK;
}
static uint16_t boot_fw_begin(setp_tms2812_boot_t *boot, const setp_frame_t *request)
{
setp_fw_begin_t value;
bool same_manifest;
if (!setp_fw_begin_decode(request->payload, request->payload_length, &value))
return SETP_STATUS_INVALID_LENGTH;
if ((value.image_size == 0U) || (value.image_size > boot->config.max_image_size)
|| (value.slot != boot->config.active_slot)
|| ((value.base_address != 0U)
&& (value.base_address != boot->config.app_base_address))
|| (value.block_size == 0U)
|| (value.block_size > boot->config.max_block_size))
return SETP_STATUS_INVALID_ARGUMENT;
if (((value.flags & SETP_FW_FLAG_SIGNED) != 0U) || boot->config.require_signature) {
if ((boot->port.authorize == NULL)
|| !boot->port.authorize(boot->port_user, &value))
return SETP_STATUS_AUTH_FAILED;
}
same_manifest = boot->state == SETP_FW_RECEIVING
&& boot->manifest.image_size == value.image_size
&& boot->manifest.image_crc32 == value.image_crc32
&& boot->manifest.image_version == value.image_version
&& memcmp(boot->manifest.sha256, value.sha256, SETP_SHA256_SIZE) == 0;
if (same_manifest && ((value.flags & SETP_FW_FLAG_RESUME) != 0U)) return SETP_STATUS_OK;
if (!boot->port.erase_image(boot->port_user, value.image_size))
return SETP_STATUS_INTERNAL;
boot->manifest = value;
boot->manifest.signature = NULL;
boot->manifest.signature_length = 0U;
boot->next_offset = 0U;
boot->state = SETP_FW_RECEIVING;
return SETP_STATUS_OK;
}
static uint16_t boot_fw_data(setp_tms2812_boot_t *boot, const setp_frame_t *request)
{
setp_fw_data_t value;
uint8_t current[SETP_TMS2812_MAX_BLOCK_SIZE];
if (boot->state != SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE;
if (!setp_fw_data_decode(request->payload, request->payload_length, &value))
return SETP_STATUS_CRC;
if ((value.data_length > boot->manifest.block_size)
|| (value.data_length > boot->config.max_block_size)
|| (value.offset > boot->manifest.image_size)
|| ((uint32_t)value.data_length > boot->manifest.image_size - value.offset))
return SETP_STATUS_INVALID_ARGUMENT;
if (value.offset < boot->next_offset) {
if ((value.offset + value.data_length > boot->next_offset)
|| !boot->port.read_image(boot->port_user, value.offset,
current, value.data_length)
|| memcmp(current, value.data, value.data_length) != 0)
return SETP_STATUS_SEQUENCE;
return SETP_STATUS_OK;
}
if (value.offset != boot->next_offset) return SETP_STATUS_SEQUENCE;
if (!boot->port.write_image(boot->port_user, value.offset,
value.data, value.data_length))
return SETP_STATUS_INTERNAL;
boot->next_offset += value.data_length;
return SETP_STATUS_OK;
}
static uint16_t boot_fw_end(setp_tms2812_boot_t *boot, const setp_frame_t *request)
{
setp_fw_end_t value;
uint16_t status;
if (boot->state != SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE;
if (!setp_fw_end_decode(request->payload, request->payload_length, &value))
return SETP_STATUS_INVALID_LENGTH;
if ((boot->next_offset != boot->manifest.image_size)
|| (value.image_size != boot->manifest.image_size)
|| (value.image_crc32 != boot->manifest.image_crc32)
|| (memcmp(value.sha256, boot->manifest.sha256, SETP_SHA256_SIZE) != 0))
return SETP_STATUS_VERIFY_FAILED;
boot->state = SETP_FW_VERIFYING;
status = boot_verify_image(boot);
boot->state = status == SETP_STATUS_OK ? SETP_FW_READY : SETP_FW_FAILED;
return status;
}
static bool boot_dispatch(setp_tms2812_boot_t *boot, const setp_frame_t *request,
const setp_can_id_t *request_id, uint32_t now_ms)
{
uint8_t body[SETP_TMS2812_RESPONSE_PAYLOAD_SIZE - 2U];
uint16_t body_length = 0U;
uint16_t status = SETP_STATUS_OK;
uint8_t reboot = 0U;
setp_fw_status_t fw_status;
if ((request->flags & (SETP_FLAG_RESPONSE | SETP_FLAG_EVENT)) != 0U) return false;
if ((request->source > 0xFFU) || (request->destination != boot->config.node_id)) return false;
switch (request->message_type) {
case SETP_MSG_PING:
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
else { setp_put_u32(body, now_ms); body_length = 4U; }
break;
case SETP_MSG_DEVICE_INFO:
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
else {
setp_device_info_t info;
info.schema_version = SETP_DEVICE_INFO_SCHEMA_VERSION;
info.device_class = boot->config.device_class;
info.hardware_version = boot->config.hardware_version;
info.firmware_version = boot->config.firmware_version;
info.dictionary_version = boot->config.dictionary_version;
info.serial_number = boot->config.serial_number;
info.model_length = boot->config.model_length;
info.model = boot->config.model;
body_length = (uint16_t)setp_device_info_encode(&info, body, sizeof(body));
if (body_length == 0U) status = SETP_STATUS_INTERNAL;
}
break;
case SETP_MSG_CAPABILITIES:
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
else {
setp_capabilities_t caps;
caps.schema_version = SETP_CAPABILITIES_SCHEMA_VERSION;
caps.max_payload = (uint16_t)(SETP_FW_DATA_HEADER_SIZE + boot->config.max_block_size);
caps.interface_mask = SETP_IFACE_MASK(SETP_IFACE_CAN);
caps.feature_flags = SETP_FEATURE_FIRMWARE;
caps.max_read_items = 0U; caps.max_write_items = 0U;
caps.max_subscriptions = 0U; caps.max_publish_items = 0U;
body_length = (uint16_t)setp_capabilities_encode(&caps, body, sizeof(body));
if (body_length == 0U) status = SETP_STATUS_INTERNAL;
}
break;
case SETP_MSG_FW_BEGIN:
status = boot_fw_begin(boot, request);
setp_put_u32(body, boot->next_offset); body_length = 4U;
break;
case SETP_MSG_FW_DATA:
status = boot_fw_data(boot, request);
setp_put_u32(body, boot->next_offset); body_length = 4U;
break;
case SETP_MSG_FW_END:
status = boot_fw_end(boot, request);
setp_put_u32(body, boot->next_offset); body_length = 4U;
break;
case SETP_MSG_FW_ABORT:
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
else setp_tms2812_boot_abort(boot);
setp_put_u32(body, boot->next_offset); body_length = 4U;
break;
case SETP_MSG_FW_STATUS:
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
else {
boot_status(boot, &fw_status);
body_length = (uint16_t)setp_fw_status_encode(&fw_status, body, sizeof(body));
}
break;
case SETP_MSG_FW_ACTIVATE:
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
else if (boot->state != SETP_FW_READY) status = SETP_STATUS_WRONG_STATE;
else { boot->state = SETP_FW_ACTIVE; reboot = 1U; }
setp_put_u32(body, boot->next_offset); body_length = 4U;
break;
default:
status = SETP_STATUS_UNSUPPORTED;
break;
}
boot->last_error = status == SETP_STATUS_OK ? 0U : status;
if (!boot_send_response(boot, request, request_id, status, body, body_length)) return false;
if (reboot != 0U) boot->port.reboot(boot->port_user);
return true;
}
bool setp_tms2812_boot_init(setp_tms2812_boot_t *boot,
const setp_tms2812_boot_config_t *config,
const setp_tms2812_boot_port_t *port,
void *port_user)
{
if ((boot == NULL) || (config == NULL) || (port == NULL)
|| (config->model == NULL) || (config->model_length > SETP_DEVICE_MODEL_MAX)
|| (config->max_image_size == 0U) || (config->max_block_size == 0U)
|| (config->max_block_size > SETP_TMS2812_MAX_BLOCK_SIZE)
|| (port->send_can == NULL) || (port->erase_image == NULL)
|| (port->write_image == NULL) || (port->read_image == NULL)
|| (port->reboot == NULL)) return false;
(void)memset(boot, 0, sizeof(*boot));
boot->config = *config;
boot->port = *port;
boot->port_user = port_user;
boot->state = SETP_FW_IDLE;
setp_can_rx_init(&boot->rx);
return true;
}
void setp_tms2812_boot_abort(setp_tms2812_boot_t *boot)
{
if (boot == NULL) return;
boot->state = SETP_FW_IDLE;
boot->next_offset = 0U;
boot->last_error = 0U;
(void)memset(&boot->manifest, 0, sizeof(boot->manifest));
}
bool setp_tms2812_boot_process(setp_tms2812_boot_t *boot,
const setp_can_frame_t *frame,
uint32_t now_ms)
{
setp_can_packet_t packet;
setp_can_rx_result_t result;
setp_can_id_t can_id;
setp_frame_t request;
if ((boot == NULL) || (frame == NULL)) return false;
result = setp_can_rx_feed(&boot->rx, frame, now_ms, &packet);
if (result != SETP_CAN_RX_COMPLETE) return result == SETP_CAN_RX_NONE;
if (!setp_can_id_unpack(packet.can_id, &can_id)
|| (can_id.destination != boot->config.node_id)
|| !setp_frame_decode_datagram(packet.data, packet.length, &request)) return false;
return boot_dispatch(boot, &request, &can_id, now_ms);
}

View File

@@ -0,0 +1,74 @@
#ifndef SETP_TMS2812_BOOT_H
#define SETP_TMS2812_BOOT_H
#include "set_can.h"
#include "set_firmware.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifndef SETP_TMS2812_MAX_BLOCK_SIZE
#define SETP_TMS2812_MAX_BLOCK_SIZE 64U
#endif
#define SETP_TMS2812_RESPONSE_PAYLOAD_SIZE \
(2U + SETP_DEVICE_INFO_FIXED_SIZE + SETP_DEVICE_MODEL_MAX)
typedef struct {
uint8_t node_id;
uint16_t device_class;
uint32_t hardware_version;
uint32_t firmware_version;
uint32_t dictionary_version;
uint64_t serial_number;
const uint8_t *model;
uint8_t model_length;
uint32_t app_base_address;
uint32_t max_image_size;
uint16_t max_block_size;
uint8_t active_slot;
uint8_t require_signature;
} setp_tms2812_boot_config_t;
typedef struct {
setp_can_send_fn send_can;
bool (*erase_image)(void *user, uint32_t image_size);
bool (*write_image)(void *user, uint32_t offset,
const uint8_t *data, uint16_t length);
bool (*read_image)(void *user, uint32_t offset,
uint8_t *data, uint16_t length);
bool (*authorize)(void *user, const setp_fw_begin_t *manifest);
void (*reboot)(void *user);
} setp_tms2812_boot_port_t;
typedef struct {
setp_tms2812_boot_config_t config;
setp_tms2812_boot_port_t port;
void *port_user;
setp_can_rx_t rx;
setp_fw_begin_t manifest;
uint32_t next_offset;
uint16_t last_error;
uint8_t state;
uint8_t response_payload[SETP_TMS2812_RESPONSE_PAYLOAD_SIZE];
uint8_t response_packet[SETP_FRAME_MAX];
} setp_tms2812_boot_t;
bool setp_tms2812_boot_init(setp_tms2812_boot_t *boot,
const setp_tms2812_boot_config_t *config,
const setp_tms2812_boot_port_t *port,
void *port_user);
/** Process one classic-CAN frame in task context, never from an ISR. */
bool setp_tms2812_boot_process(setp_tms2812_boot_t *boot,
const setp_can_frame_t *frame,
uint32_t now_ms);
void setp_tms2812_boot_abort(setp_tms2812_boot_t *boot);
#ifdef __cplusplus
}
#endif
#endif /* SETP_TMS2812_BOOT_H */

View File

@@ -4,6 +4,8 @@
#include "pcan_crc.h"
#include "balsam_can.h"
#include "periph28335.h"
#include "tms2812.h"
#include "pcan_frame.h"
#include "pcan_id.h"
#include "gui_frame.h"
@@ -88,6 +90,113 @@ size_t pcan_abi_balsam_register_name(uint8_t device, uint16_t address,
return balsam_can_register_name(device, address, output, output_size);
}
uint16_t pcan_abi_periph28335_crc16(const uint8_t *data, size_t size)
{
return periph28335_crc16_modbus(data, size);
}
size_t pcan_abi_periph28335_append_crc(
const uint8_t *payload, size_t payload_size,
uint8_t *output, size_t output_size)
{
return periph28335_append_crc(payload, payload_size, output, output_size);
}
size_t pcan_abi_periph28335_build_read(
uint8_t controller, uint16_t start, uint16_t count,
uint8_t *output, size_t output_size)
{
return periph28335_build_read_registers(
controller, start, count, output, output_size);
}
size_t pcan_abi_periph28335_build_write(
uint8_t controller, uint16_t address, uint16_t value,
uint8_t *output, size_t output_size)
{
return periph28335_build_write_register(
controller, address, value, output, output_size);
}
size_t pcan_abi_periph28335_build_command(
uint8_t controller, uint8_t command_index,
uint8_t *output, size_t output_size)
{
return periph28335_build_command(
controller, command_index, output, output_size);
}
size_t pcan_abi_periph28335_expected_read_size(uint16_t count)
{
return periph28335_expected_read_response_size(count);
}
int pcan_abi_periph28335_decode_read(
const uint8_t *data, size_t size, uint8_t controller, uint16_t count,
uint16_t *output, size_t output_count)
{
return periph28335_decode_read_response(
data, size, controller, count, output, output_count);
}
int pcan_abi_periph28335_validate_write(
const uint8_t *response, size_t response_size,
const uint8_t *request, size_t request_size)
{
return periph28335_validate_write_response(
response, response_size, request, request_size);
}
size_t pcan_abi_periph28335_project_count(void)
{
return periph28335_project_count();
}
const char *pcan_abi_periph28335_project_name(size_t project_index)
{
return periph28335_project_name(project_index);
}
const char *pcan_abi_periph28335_command_name(
size_t project_index, size_t command_index)
{
return periph28335_command_name(project_index, command_index);
}
uint16_t pcan_abi_tms2812_crc16(const uint8_t *data, size_t size)
{
return tms2812_crc16(data, size);
}
size_t pcan_abi_tms2812_build_upload(
uint8_t controller, uint32_t word_address, uint32_t byte_count,
uint8_t *output, size_t output_size)
{
return tms2812_build_upload_request(
controller, word_address, byte_count, output, output_size);
}
size_t pcan_abi_tms2812_expected_upload_size(uint32_t byte_count)
{
return tms2812_expected_upload_response_size(byte_count);
}
int pcan_abi_tms2812_validate_upload(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count)
{
return tms2812_validate_upload_response(
data, size, controller, byte_count);
}
int pcan_abi_tms2812_decode_upload(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count, uint8_t *output, size_t output_size)
{
return tms2812_decode_upload_response(
data, size, controller, byte_count, output, output_size);
}
size_t pcan_abi_frame_encode(uint8_t sequence, uint8_t flags,
uint32_t can_id, const uint8_t *data, uint8_t dlc,
uint8_t *output, size_t output_size)

View File

@@ -0,0 +1,161 @@
#include "periph28335.h"
#include <string.h>
#include "set_crc.h"
typedef struct {
const char *project;
const char *commands[PERIPH28335_COMMAND_COUNT];
} periph28335_project_t;
static const periph28335_project_t projects[] = {
{ "По умолчанию", { "Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Light", "Raw", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "23470", { "Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "-", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "23550", { "Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "Send", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "23550.2", { "Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Send", "Raw", "Beep", "", "", "", "", "Log", "Reset", "Nothing at all" } },
{ "ICE 22220.1-3", { "Test", "Zero", "Save", "Def", "Calibr", "Read", "ExtLamp", "ExtLite", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "ICE 22220.4-5", { "Test", "Def", "Save", "Load", "Raw", "Read", "ExtLamp", "ExtLite", "No log", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "Бальзам 161", { "Test", "Zero", "Save", "Def", "Calibr", "Clbr 400", "Stop", "Start", "Init", "Secret", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "Бальзам 162", { "Test", "Def", "Save", "Load", "Calibr", "Secret", "Stop", "Start", "Init", "Tune", "-", "-", "-", "-", "-", "Reset", "Nothing at all" } },
{ "Бальзам 163", { "Test", "Def", "Save", "Load", "Calibr", "Calcul", "Stop", "Start", "Init", "Tune", "Secret", "-", "-", "-", "-", "Reset", "Nothing at all" } }
};
static void put_be16(uint8_t *output, uint16_t value)
{
output[0] = (uint8_t)(value >> 8);
output[1] = (uint8_t)value;
}
static uint16_t get_be16(const uint8_t *input)
{
return (uint16_t)(((uint16_t)input[0] << 8) | input[1]);
}
uint16_t periph28335_crc16_modbus(const uint8_t *data, size_t size)
{
return set_crc16_modbus(data, size);
}
size_t periph28335_append_crc(const uint8_t *payload, size_t payload_size,
uint8_t *output, size_t output_size)
{
uint16_t crc;
if ((output == NULL) || ((payload == NULL) && (payload_size != 0U)) ||
(payload_size > output_size) || (output_size - payload_size < 2U)) {
return 0U;
}
if (payload_size != 0U) memcpy(output, payload, payload_size);
crc = periph28335_crc16_modbus(payload, payload_size);
output[payload_size] = (uint8_t)crc;
output[payload_size + 1U] = (uint8_t)(crc >> 8);
return payload_size + 2U;
}
size_t periph28335_build_read_registers(
uint8_t controller, uint16_t start, uint16_t count,
uint8_t *output, size_t output_size)
{
uint8_t payload[6];
if ((count == 0U) || (count > PERIPH28335_REGISTER_COUNT) ||
(start >= PERIPH28335_REGISTER_COUNT) ||
((uint32_t)start + count > PERIPH28335_REGISTER_COUNT)) return 0U;
payload[0] = controller;
payload[1] = 3U;
put_be16(&payload[2], start);
put_be16(&payload[4], count);
return periph28335_append_crc(payload, sizeof payload, output, output_size);
}
size_t periph28335_build_write_register(
uint8_t controller, uint16_t address, uint16_t value,
uint8_t *output, size_t output_size)
{
uint8_t payload[6];
if (address >= PERIPH28335_REGISTER_COUNT) return 0U;
payload[0] = controller;
payload[1] = 6U;
put_be16(&payload[2], address);
put_be16(&payload[4], value);
return periph28335_append_crc(payload, sizeof payload, output, output_size);
}
size_t periph28335_build_command(
uint8_t controller, uint8_t command_index,
uint8_t *output, size_t output_size)
{
uint16_t value;
if (command_index >= PERIPH28335_COMMAND_COUNT) return 0U;
value = command_index < 16U ? (uint16_t)(1UL << command_index) : 0U;
return periph28335_build_write_register(
controller, PERIPH28335_REGISTER_COUNT - 1U, value,
output, output_size);
}
size_t periph28335_expected_read_response_size(uint16_t count)
{
return (count >= 1U && count <= PERIPH28335_REGISTER_COUNT)
? (size_t)count * 2U + 5U : 0U;
}
int periph28335_decode_read_response(
const uint8_t *data, size_t size, uint8_t controller, uint16_t count,
uint16_t *output, size_t output_count)
{
size_t expected = periph28335_expected_read_response_size(count);
uint16_t crc;
size_t index;
if ((data == NULL) || (output == NULL)) return PERIPH28335_ERROR_ARGUMENT;
if (expected == 0U) return PERIPH28335_ERROR_RANGE;
if (size != expected) return PERIPH28335_ERROR_LENGTH;
if (output_count < count) return PERIPH28335_ERROR_CAPACITY;
crc = periph28335_crc16_modbus(data, size - 2U);
if ((data[size - 2U] != (uint8_t)crc) ||
(data[size - 1U] != (uint8_t)(crc >> 8))) {
return PERIPH28335_ERROR_CRC;
}
if ((data[0] != controller) || (data[1] != 3U) ||
(data[2] != (uint8_t)(count * 2U))) {
return PERIPH28335_ERROR_HEADER;
}
for (index = 0U; index < count; ++index) {
output[index] = get_be16(&data[3U + index * 2U]);
}
return PERIPH28335_OK;
}
int periph28335_validate_write_response(
const uint8_t *response, size_t response_size,
const uint8_t *request, size_t request_size)
{
uint16_t crc;
if ((response == NULL) || (request == NULL)) return PERIPH28335_ERROR_ARGUMENT;
if ((response_size != PERIPH28335_REQUEST_SIZE) ||
(request_size != PERIPH28335_REQUEST_SIZE)) return PERIPH28335_ERROR_LENGTH;
crc = periph28335_crc16_modbus(response, response_size - 2U);
if ((response[response_size - 2U] != (uint8_t)crc) ||
(response[response_size - 1U] != (uint8_t)(crc >> 8))) {
return PERIPH28335_ERROR_CRC;
}
return memcmp(response, request, PERIPH28335_REQUEST_SIZE) == 0
? PERIPH28335_OK : PERIPH28335_ERROR_HEADER;
}
size_t periph28335_project_count(void)
{
return sizeof projects / sizeof projects[0];
}
const char *periph28335_project_name(size_t project_index)
{
return project_index < periph28335_project_count()
? projects[project_index].project : NULL;
}
const char *periph28335_command_name(size_t project_index,
size_t command_index)
{
return project_index < periph28335_project_count() &&
command_index < PERIPH28335_COMMAND_COUNT
? projects[project_index].commands[command_index] : NULL;
}

View File

@@ -0,0 +1,18 @@
#include "set_crc.h"
uint16_t set_crc16_modbus(const uint8_t *data, size_t size)
{
uint16_t crc = 0xFFFFU;
size_t index;
uint8_t bit;
if ((data == NULL) && (size != 0U)) return 0U;
for (index = 0U; index < size; ++index) {
crc ^= data[index];
for (bit = 0U; bit < 8U; ++bit) {
crc = (crc & 1U) != 0U
? (uint16_t)((crc >> 1) ^ 0xA001U)
: (uint16_t)(crc >> 1);
}
}
return crc;
}

View File

@@ -11,10 +11,10 @@ static int finite_values(const double *v, size_t n) {
uint32_t set_plot_abi_version(void) { return 1U; }
size_t set_plot_eval(uint32_t op, const double *v, size_t n, double *out, size_t cap) {
static const size_t sizes[] = {10, 3, 4, 4, 6, 2, 3, 2};
static const size_t sizes[] = {10, 3, 4, 4, 6, 2, 3, 2, 4};
double span, fraction;
if (op > SET_PLOT_DB_DELTA || !v || !out || n != sizes[op] ||
cap < (op == SET_PLOT_TRANSFORM ? 4U : 1U)) return 0;
if (op > SET_PLOT_LIMITS || !v || !out || n != sizes[op] ||
cap < (op == SET_PLOT_TRANSFORM || op == SET_PLOT_LIMITS ? 4U : 1U)) return 0;
if (op == SET_PLOT_TRANSFORM) {
double w, h, fx, fy;
if (!finite_values(v, 4) || v[2] < 1.0/128 || v[2] > 1 ||
@@ -69,6 +69,10 @@ size_t set_plot_eval(uint32_t op, const double *v, size_t n, double *out, size_t
if (v[0] == 0 || v[1] == 0) return 0;
out[0] = 20 * log10(fabs(v[1] / v[0]));
break;
case SET_PLOT_LIMITS:
if (v[0] >= v[1] || v[2] >= v[3]) return 0;
out[0] = v[0]; out[1] = v[1]; out[2] = v[2]; out[3] = v[3];
return 4;
default: return 0;
}
return isfinite(out[0]) ? 1 : 0;

View File

@@ -4,6 +4,13 @@
#define PI 3.14159265358979323846
static int compare_double(const void *left, const void *right)
{
const double a = *(const double *)left;
const double b = *(const double *)right;
return (a > b) - (a < b);
}
static double window_value(int window, size_t index, size_t n)
{
double phase = 2.0 * PI * (double)index / (double)n;
@@ -124,3 +131,35 @@ int set_spectrum_analyze(const double *times, const double *values, size_t count
free(scratch);
return result;
}
int set_spectrum_dominant_peak(const double *amplitudes, size_t count,
double bin_hz, double relative_threshold, double absolute_floor,
double *scratch, size_t scratch_capacity, double *peak, size_t peak_capacity)
{
size_t i, usable = 0U, peak_bin = 0U;
double peak_amplitude = -1.0, median, threshold;
if (amplitudes == NULL || scratch == NULL || peak == NULL || count < 3U ||
scratch_capacity < count - 1U || peak_capacity < 2U || !isfinite(bin_hz) ||
bin_hz <= 0.0 || !isfinite(relative_threshold) || relative_threshold <= 0.0 ||
!isfinite(absolute_floor) || absolute_floor < 0.0) return -1;
for (i = 1U; i < count; ++i) {
if (isfinite(amplitudes[i]) && amplitudes[i] >= 0.0)
scratch[usable++] = amplitudes[i];
}
if (usable < 2U) return 0;
qsort(scratch, usable, sizeof(double), compare_double);
median = scratch[usable / 2U];
for (i = 1U; i + 1U < count; ++i) {
const double value = amplitudes[i];
if (isfinite(value) && isfinite(amplitudes[i - 1U]) && isfinite(amplitudes[i + 1U]) &&
value >= amplitudes[i - 1U] && value > amplitudes[i + 1U] && value > peak_amplitude) {
peak_bin = i;
peak_amplitude = value;
}
}
threshold = fmax(absolute_floor, median * relative_threshold);
if (peak_bin == 0U || peak_amplitude < threshold) return 0;
peak[0] = (double)peak_bin * bin_hz;
peak[1] = peak_amplitude;
return 1;
}

View File

@@ -6,6 +6,12 @@ static uint16_t get16(const uint8_t *p)
return (uint16_t)((uint16_t)p[0] | ((uint16_t)p[1] << 8));
}
static uint32_t get32(const uint8_t *p)
{
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
}
static void put16(uint8_t *p, uint16_t value)
{
p[0] = (uint8_t)value;
@@ -63,6 +69,12 @@ int set_trend_watch_ack(const uint8_t *payload, size_t size, uint16_t period_ms,
}
int set_trend_watch_values(const uint8_t *payload, size_t size, uint16_t *words, size_t capacity)
{
return set_trend_watch_decode(payload, size, NULL, words, capacity);
}
int set_trend_watch_decode(const uint8_t *payload, size_t size, uint32_t *timestamp_ms,
uint16_t *words, size_t capacity)
{
size_t i, count;
if (payload == NULL || size < 6U) return -1;
@@ -70,5 +82,6 @@ int set_trend_watch_values(const uint8_t *payload, size_t size, uint16_t *words,
if (count > SET_TREND_WATCH_MAX || size != 6U + count * 2U || count > capacity ||
(count > 0U && words == NULL)) return -1;
for (i = 0U; i < count; ++i) words[i] = get16(payload + 6U + i * 2U);
if (timestamp_ms != NULL) *timestamp_ms = get32(payload);
return (int)count;
}

View File

@@ -0,0 +1,78 @@
#include "tms2812.h"
#include <stdint.h>
#include <string.h>
#include "set_crc.h"
static void put_le32(uint8_t *output, uint32_t value)
{
output[0] = (uint8_t)value;
output[1] = (uint8_t)(value >> 8);
output[2] = (uint8_t)(value >> 16);
output[3] = (uint8_t)(value >> 24);
}
uint16_t tms2812_crc16(const uint8_t *data, size_t size)
{
return set_crc16_modbus(data, size);
}
size_t tms2812_build_upload_request(
uint8_t controller, uint32_t word_address, uint32_t byte_count,
uint8_t *output, size_t output_size)
{
uint16_t crc;
if ((output == NULL) || (output_size < TMS2812_UPLOAD_REQUEST_SIZE) ||
(byte_count == 0U)) return 0U;
output[0] = controller;
output[1] = TMS2812_CMD_UPLOAD;
put_le32(&output[2], word_address);
put_le32(&output[6], byte_count);
crc = tms2812_crc16(output, 10U);
output[10] = (uint8_t)crc;
output[11] = (uint8_t)(crc >> 8);
return TMS2812_UPLOAD_REQUEST_SIZE;
}
size_t tms2812_expected_upload_response_size(uint32_t byte_count)
{
if (byte_count == 0U ||
(uint64_t)byte_count + TMS2812_UPLOAD_RESPONSE_OVERHEAD > SIZE_MAX) {
return 0U;
}
return (size_t)byte_count + TMS2812_UPLOAD_RESPONSE_OVERHEAD;
}
int tms2812_validate_upload_response(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count)
{
size_t expected = tms2812_expected_upload_response_size(byte_count);
size_t crc_offset;
uint16_t expected_crc;
uint16_t actual_crc;
if (data == NULL) return TMS2812_ERROR_ARGUMENT;
if (expected == 0U) return TMS2812_ERROR_RANGE;
if (size != expected) return TMS2812_ERROR_LENGTH;
if ((data[0] != controller) || (data[1] != TMS2812_CMD_UPLOAD))
return TMS2812_ERROR_HEADER;
crc_offset = (size_t)byte_count + 2U;
expected_crc = tms2812_crc16(data, crc_offset);
actual_crc = (uint16_t)(data[crc_offset] |
((uint16_t)data[crc_offset + 1U] << 8));
return actual_crc == expected_crc ? TMS2812_OK : TMS2812_ERROR_CRC;
}
int tms2812_decode_upload_response(
const uint8_t *data, size_t size, uint8_t controller,
uint32_t byte_count, uint8_t *output, size_t output_size)
{
int status = tms2812_validate_upload_response(
data, size, controller, byte_count);
if (status != TMS2812_OK) return status;
if ((output == NULL) || (output_size < byte_count))
return TMS2812_ERROR_CAPACITY;
memcpy(output, &data[2], byte_count);
return TMS2812_OK;
}

View File

@@ -23,6 +23,8 @@
{"name":"db_gain","op":7,"input":[1,10],"output":[20]},
{"name":"db_attenuation","op":7,"input":[10,1],"output":[-20]},
{"name":"db_zero_reference","op":7,"input":[0,1],"output":null},
{"name":"absolute_limits","op":8,"input":[0,500,-2,2],"output":[0,500,-2,2]},
{"name":"reversed_x_limits","op":8,"input":[1,1,-2,2],"output":null},
{"name":"zero_range","op":2,"input":[1,1,1,0],"output":null},
{"name":"zero_pixels","op":4,"input":[0,1,0,0,1,0],"output":null},
{"name":"bad_viewport","op":0,"input":[0,0,0,1,2,1,0,0,0.5,0.5],"output":null}

View File

@@ -0,0 +1,32 @@
#include <stdio.h>
#include <string.h>
#include "periph28335.h"
int main(void)
{
uint8_t request[PERIPH28335_REQUEST_SIZE];
static const uint8_t read_prefix[] = { 0x10U, 0x03U, 0x00U, 0x18U, 0x00U, 0x40U };
static const uint8_t response_payload[] = { 0x10U, 0x03U, 0x04U, 0x80U, 0x05U, 0x12U, 0x34U };
uint8_t response[sizeof response_payload + 2U];
uint16_t words[2];
if (periph28335_build_read_registers(16U, 24U, 64U, request, sizeof request)
!= sizeof request || memcmp(request, read_prefix, sizeof read_prefix) != 0) return 1;
if (periph28335_build_write_register(16U, 7U, 0x1234U, request, sizeof request)
!= sizeof request || memcmp(request, "\x10\x06\x00\x07\x12\x34", 6U) != 0) return 2;
if (periph28335_build_command(16U, 15U, request, sizeof request)
!= sizeof request || memcmp(request, "\x10\x06\x00\x7F\x80\x00", 6U) != 0) return 3;
if (periph28335_append_crc(response_payload, sizeof response_payload,
response, sizeof response) != sizeof response) return 4;
if (periph28335_decode_read_response(response, sizeof response, 16U, 2U,
words, 2U) != PERIPH28335_OK || words[0] != 0x8005U || words[1] != 0x1234U) return 5;
response[0] = 17U;
if (periph28335_decode_read_response(response, sizeof response, 16U, 2U,
words, 2U) != PERIPH28335_ERROR_CRC) return 6;
if (periph28335_project_count() != 9U ||
strcmp(periph28335_project_name(2U), "23550") != 0 ||
strcmp(periph28335_command_name(2U, 7U), "Send") != 0) return 7;
puts("PM35/TMS320F28335 protocol tests passed");
return 0;
}

View File

@@ -26,6 +26,13 @@ int main(void) {
assert(output[0] == y[0]);
assert(set_plot_eval(SET_PLOT_DRAG, drag, 6, output, 4) == 1);
assert(output[0] == -4);
{
const double limits[] = {0, 500, -2, 2};
const double reversed[] = {1, 1, -2, 2};
assert(set_plot_eval(SET_PLOT_LIMITS, limits, 4, output, 4) == 4);
assert(output[0] == 0 && output[1] == 500 && output[2] == -2 && output[3] == 2);
assert(set_plot_eval(SET_PLOT_LIMITS, reversed, 4, output, 4) == 0);
}
puts("shared plot: OK");
return 0;
}

View File

@@ -17,6 +17,15 @@ int main(void)
assert(set_spectrum_analyze(times, values, 32, 32, 0, SET_FILTER_LOW_PASS, 0, 16, 1, output, 17, meta) == SET_SPECTRUM_CUTOFF);
times[12] = times[11];
assert(set_spectrum_analyze(times, values, 32, 32, 0, 0, 0, 0, 1, output, 17, meta) == SET_SPECTRUM_TIMING);
{
const double amplitudes[] = {10.0, .01, .02, .8, .03, .4, .02};
const double noise[] = {0, .10, .12, .11, .09, .10};
double scratch[6], peak[2];
assert(set_spectrum_dominant_peak(amplitudes, 7, 5, 3, 1e-6, scratch, 6, peak, 2) == 1);
assert(peak[0] == 15 && peak[1] == .8);
assert(set_spectrum_dominant_peak(noise, 6, 1, 3, 1e-6, scratch, 6, peak, 2) == 0);
assert(set_spectrum_dominant_peak(amplitudes, 7, 0, 3, 1e-6, scratch, 6, peak, 2) == -1);
}
puts("shared spectrum: OK");
return 0;
}

View File

@@ -0,0 +1,35 @@
#include <stdio.h>
#include <string.h>
#include "tms2812.h"
int main(void)
{
uint8_t request[TMS2812_UPLOAD_REQUEST_SIZE];
static const uint8_t prefix[] = {
0x05U, 0x34U, 0x78U, 0x56U, 0x34U, 0x12U,
0x00U, 0x10U, 0x00U, 0x00U
};
uint8_t reply[4U + TMS2812_UPLOAD_RESPONSE_OVERHEAD];
uint8_t decoded[4];
uint16_t crc;
if (tms2812_build_upload_request(5U, 0x12345678UL, 0x1000U,
request, sizeof request) != sizeof request ||
memcmp(request, prefix, sizeof prefix) != 0) return 1;
reply[0] = 5U;
reply[1] = TMS2812_CMD_UPLOAD;
reply[2] = 0x10U; reply[3] = 0x20U; reply[4] = 0x30U; reply[5] = 0x40U;
crc = tms2812_crc16(reply, 6U);
reply[6] = (uint8_t)crc;
reply[7] = (uint8_t)(crc >> 8);
memset(&reply[8], 0, 4U);
if (tms2812_decode_upload_response(reply, sizeof reply, 5U, 4U,
decoded, sizeof decoded) != TMS2812_OK ||
memcmp(decoded, &reply[2], sizeof decoded) != 0) return 2;
reply[6] ^= 1U;
if (tms2812_validate_upload_response(reply, sizeof reply, 5U, 4U)
!= TMS2812_ERROR_CRC) return 3;
puts("PM67/TMS320F2812 upload protocol tests passed");
return 0;
}

View File

@@ -0,0 +1,225 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include "setp_tms2812_boot.h"
#define NODE_ID 13U
#define IMAGE_SIZE 100U
typedef struct {
uint8_t flash[512];
setp_can_rx_t response_rx;
setp_can_packet_t response_packet;
uint32_t now_ms;
unsigned int response_complete;
unsigned int erased;
unsigned int rebooted;
} fake_t;
typedef struct {
setp_tms2812_boot_t *boot;
fake_t *fake;
} request_context_t;
static bool fake_send_response(const setp_can_frame_t *frame, void *user)
{
fake_t *fake = (fake_t *)user;
setp_can_rx_result_t result = setp_can_rx_feed(
&fake->response_rx, frame, fake->now_ms++, &fake->response_packet);
if (result == SETP_CAN_RX_COMPLETE) fake->response_complete++;
return result == SETP_CAN_RX_NONE || result == SETP_CAN_RX_COMPLETE;
}
static bool fake_erase(void *user, uint32_t image_size)
{
fake_t *fake = (fake_t *)user;
if (image_size > sizeof(fake->flash)) return false;
(void)memset(fake->flash, 0xFF, sizeof(fake->flash));
fake->erased++;
return true;
}
static bool fake_write(void *user, uint32_t offset,
const uint8_t *data, uint16_t length)
{
fake_t *fake = (fake_t *)user;
if (offset + length > sizeof(fake->flash)) return false;
(void)memcpy(&fake->flash[offset], data, length);
return true;
}
static bool fake_read(void *user, uint32_t offset,
uint8_t *data, uint16_t length)
{
fake_t *fake = (fake_t *)user;
if (offset + length > sizeof(fake->flash)) return false;
(void)memcpy(data, &fake->flash[offset], length);
return true;
}
static void fake_reboot(void *user)
{
((fake_t *)user)->rebooted++;
}
static bool send_request_frame(const setp_can_frame_t *frame, void *user)
{
request_context_t *context = (request_context_t *)user;
return setp_tms2812_boot_process(context->boot, frame,
context->fake->now_ms++);
}
static setp_frame_t transact(setp_tms2812_boot_t *boot, fake_t *fake,
uint16_t sequence, uint16_t type,
const uint8_t *payload, uint16_t payload_length)
{
uint8_t packet[SETP_FRAME_MAX];
setp_frame_t request;
setp_frame_t response;
setp_can_id_t id;
request_context_t context;
size_t packet_length;
fake->response_complete = 0U;
setp_can_rx_init(&fake->response_rx);
request.flags = SETP_FLAG_ACK_REQUIRED | SETP_FLAG_PRIORITY;
request.message_type = type;
request.source = 0U;
request.destination = NODE_ID;
request.sequence = sequence;
request.payload_length = payload_length;
request.payload = payload;
packet_length = setp_frame_encode(&request, packet, sizeof(packet));
assert(packet_length != 0U);
id.destination = NODE_ID;
id.source = 0U;
id.priority = 1U;
id.channel = 1U;
context.boot = boot;
context.fake = fake;
assert(setp_can_segment(packet, (uint16_t)packet_length,
setp_can_id_pack(&id), send_request_frame, &context));
assert(fake->response_complete == 1U);
assert(setp_frame_decode_datagram(fake->response_packet.data,
fake->response_packet.length, &response));
assert(response.flags & SETP_FLAG_RESPONSE);
assert(response.message_type == type);
assert(response.sequence == sequence);
assert(response.source == NODE_ID);
assert(response.destination == 0U);
return response;
}
static void assert_ok(const setp_frame_t *response)
{
assert(response->payload_length >= 2U);
assert(setp_get_u16(response->payload) == SETP_STATUS_OK);
assert((response->flags & SETP_FLAG_ERROR) == 0U);
}
int main(void)
{
static const uint8_t model[] = "BALZAM-166";
static const uint8_t expected_sha[SETP_SHA256_SIZE] = {
0xBC, 0xE0, 0xAF, 0xF1, 0x9C, 0xF5, 0xAA, 0x6A,
0x74, 0x69, 0xA3, 0x0D, 0x61, 0xD0, 0x4E, 0x43,
0x76, 0xE4, 0xBB, 0xF6, 0x38, 0x10, 0x52, 0xEE,
0x9E, 0x7F, 0x33, 0x92, 0x5C, 0x95, 0x4D, 0x52
};
uint8_t image[IMAGE_SIZE];
uint8_t payload[SETP_MAX_PAYLOAD];
setp_tms2812_boot_t boot;
setp_tms2812_boot_config_t config;
setp_tms2812_boot_port_t port;
setp_fw_begin_t begin;
setp_fw_end_t end;
setp_fw_status_t status;
setp_frame_t response;
size_t length;
uint32_t offset;
uint16_t sequence = 1U;
fake_t fake;
unsigned int i;
(void)memset(&fake, 0, sizeof(fake));
(void)memset(&config, 0, sizeof(config));
(void)memset(&port, 0, sizeof(port));
for (i = 0U; i < IMAGE_SIZE; i++) image[i] = (uint8_t)i;
config.node_id = NODE_ID;
config.device_class = 0x0166U;
config.hardware_version = 1U;
config.firmware_version = 2U;
config.model = model;
config.model_length = (uint8_t)(sizeof(model) - 1U);
config.app_base_address = 0x00100000UL;
config.max_image_size = sizeof(fake.flash);
config.max_block_size = 32U;
config.active_slot = 0U;
port.send_can = fake_send_response;
port.erase_image = fake_erase;
port.write_image = fake_write;
port.read_image = fake_read;
port.reboot = fake_reboot;
assert(setp_tms2812_boot_init(&boot, &config, &port, &fake));
response = transact(&boot, &fake, sequence++, SETP_MSG_PING, NULL, 0U);
assert_ok(&response);
assert(response.payload_length == 6U);
(void)memset(&begin, 0, sizeof(begin));
begin.image_size = IMAGE_SIZE;
begin.image_crc32 = setp_crc32(image, sizeof(image));
begin.image_version = 0x01020304UL;
begin.base_address = config.app_base_address;
begin.slot = 0U;
begin.flags = SETP_FW_FLAG_RESUME | SETP_FW_FLAG_ERASE_SLOT;
begin.block_size = 32U;
(void)memcpy(begin.sha256, expected_sha, sizeof(expected_sha));
length = setp_fw_begin_encode(&begin, payload, sizeof(payload));
assert(length != 0U);
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_BEGIN,
payload, (uint16_t)length);
assert_ok(&response);
assert(fake.erased == 1U);
for (offset = 0U; offset < IMAGE_SIZE;) {
uint16_t chunk = (uint16_t)(IMAGE_SIZE - offset);
if (chunk > begin.block_size) chunk = begin.block_size;
length = setp_fw_data_encode(offset, 0U, &image[offset], chunk,
payload, sizeof(payload));
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_DATA,
payload, (uint16_t)length);
assert_ok(&response);
assert(setp_get_u32(&response.payload[2]) == offset + chunk);
if (offset == 0U) {
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_DATA,
payload, (uint16_t)length);
assert_ok(&response);
assert(setp_get_u32(&response.payload[2]) == chunk);
}
offset += chunk;
}
assert(memcmp(fake.flash, image, sizeof(image)) == 0);
end.image_size = begin.image_size;
end.image_crc32 = begin.image_crc32;
(void)memcpy(end.sha256, expected_sha, sizeof(expected_sha));
length = setp_fw_end_encode(&end, payload, sizeof(payload));
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_END,
payload, (uint16_t)length);
assert_ok(&response);
assert(boot.state == SETP_FW_READY);
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_STATUS, NULL, 0U);
assert_ok(&response);
assert(setp_fw_status_decode(&response.payload[2],
(uint16_t)(response.payload_length - 2U), &status));
assert(status.state == SETP_FW_READY);
assert(status.next_offset == IMAGE_SIZE);
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_ACTIVATE, NULL, 0U);
assert_ok(&response);
assert(fake.rebooted == 1U);
puts("SETProtocol v2 TMS320F2812 firmware port tests passed");
return 0;
}

View File

@@ -34,8 +34,11 @@ int main(void)
assert(!set_trend_watch_ack(expected, 4, 1000, 3));
const uint8_t packet[] = {1, 2, 3, 4, 2, 0, 52, 18, 255, 255};
uint16_t words[64];
uint32_t timestamp = 0;
assert(set_trend_watch_values(packet, sizeof(packet), words, 64) == 2);
assert(words[0] == 0x1234 && words[1] == 0xFFFF);
assert(set_trend_watch_decode(packet, sizeof(packet), &timestamp, words, 64) == 2);
assert(timestamp == 0x04030201UL);
assert(set_trend_watch_values(packet, sizeof(packet) - 1, words, 64) == -1);
assert(set_trend_watch_values(packet, sizeof(packet), words, 1) == -1);
puts("Shared trend tests passed");

View File

@@ -21,7 +21,7 @@ JNI_INCLUDES: list[Path] = []
SOURCES = [
ROOT / "src" / name for name in (
"set_protocol.c", "set_can.c", "set_firmware.c", "set_telemetry.c", "set_plot.c", "set_trends.c", "set_spectrum.c",
"balsam_can.c", "gui_catalog.c", "gui_frame.c", "pcan_abi.c", "pcan_crc.c",
"balsam_can.c", "set_crc.c", "periph28335.c", "tms2812.c", "gui_catalog.c", "gui_frame.c", "pcan_abi.c", "pcan_crc.c",
"pcan_frame.c", "pcan_id.c", "pcan_link.c", "pcan_ring.c",
"pcan_gas.c", "pcan_modbus_server.c",
)
@@ -84,7 +84,10 @@ def main() -> int:
if not (include / "jni.h").is_file():
parser.error("--java-home must contain include/jni.h")
JNI_INCLUDES.extend([include, include / {"Windows": "win32", "Darwin": "darwin"}.get(platform.system(), "linux")])
SOURCES.append(ROOT / "ports" / "android" / "set_plot_jni.c")
SOURCES.extend([
ROOT / "ports" / "android" / "set_plot_jni.c",
ROOT / "ports" / "android" / "setprotocol_jni.c",
])
output = args.output.resolve()
output.parent.mkdir(parents=True, exist_ok=True)
build_dir = output.parent / ".setprotocol-build"