Move PM35 protocol into shared C core

This commit is contained in:
2026-09-04 21:06:40 +03:00
parent 2316a5a26a
commit 6a82b309cc
16 changed files with 919 additions and 95 deletions

80
RULES.md Normal file
View File

@@ -0,0 +1,80 @@
# Правила общего кроссплатформенного кода
Этот репозиторий — единственный источник общих алгоритмов для `GUI_Android`,
`SETGUI`, прошивок и будущих GUI. Копирование одной реализации между Kotlin,
Python, C# или другим языком запрещено.
## 1. Граница C-ядра и порта
В `c/set-protocol` на C99 обязательно размещаются:
- форматы кадров и идентификаторов, CRC/checksum, endian-преобразования;
- построение команд, разбор и проверка ответов;
- автоматы обмена, сегментация, повтор, таймаутные состояния без системных часов;
- общие вычисления, таблицы и каталоги, влияющие на поведение протокола;
- проверка образов прошивки и других бинарных форматов.
Порт на языке GUI содержит только:
- вызовы C через стабильный ABI (`ctypes`, JNI, P/Invoke, Swift FFI и т. п.);
- преобразование C-структур в модели языка без повторения алгоритма;
- работу с USB, COM, Bluetooth, SocketCAN и API операционной системы;
- жизненный цикл, потоки, разрешения, хранение настроек и UI;
- локализованный текст и чисто визуальные преобразования.
Порт не вычисляет CRC, не собирает wire-пакет и не разбирает его поля заново.
Если для функции C-ядро недоступно, приложение сообщает об ошибке сборки или
загрузки. Алгоритмический fallback на языке GUI запрещён: он снова создаёт две
версии протокола.
## 2. Разделение контроллеров
Профили контроллеров нельзя сливать по совпадению названия транспорта:
- **ПМ67 / TMS320F2812** — основной контроллер, собственные RS и CAN;
- **ПМ35 / TMS320F28335 periph** — отдельный контроллер и отдельный CAN для
настроечного терминала, а также собственный прямой RS232/485-протокол.
Выбор профиля выполняется в GUI, но выбранный профиль вызывает свой отдельный
модуль C-ядра. Наличие одной CAN-линии не даёт права удалить или подменить
другую.
## 3. Порядок изменения протокола
1. Добавить или изменить публичный заголовок и реализацию в
`c/set-protocol/include` и `c/set-protocol/src`.
2. Зафиксировать эталонные байты и ошибочные случаи в C-тесте.
3. При необходимости расширить `pcan_abi.h`, сохраняя бинарную совместимость.
4. Добавить тонкие порты в `ports/<platform>` и `python/`; в них не должно быть
второго кодека.
5. Одними и теми же векторами проверить C, Python и Android/JVM.
6. Собрать SETGUI и Android с одним commit submodule `templates`.
Изменение только в одном GUI считается незавершённым. Сначала меняется
`templates`, затем оба потребителя обновляют ссылку submodule на проверенный
commit.
## 4. Требования к C-ядру
- C99, без зависимости от GUI и конкретной ОС.
- Буферы и их размеры передаются явно; владение памятью остаётся у вызывающего.
- Для MCU основная логика не требует heap, исключений или файловой системы.
- Endian и размеры целых задаются через `stdint.h`, структуры wire-формата не
передаются через ABI без явного стабильного представления.
- Экспорт shared library идёт через `PCAN_ABI_API`; существующие символы не
меняют смысл и сигнатуру.
- Ошибки возвращаются детерминированным кодом и тестируются наряду с успехом.
## 5. Проверка на ревью
Изменение нельзя принимать, если ответ «да» хотя бы на один вопрос:
- появился одинаковый CRC/parser/builder в двух языках;
- UI знает byte offset, endian или служебный байт wire-протокола;
- Python и Kotlin содержат одинаковую таблицу команд, влияющую на обмен;
- добавлен тихий fallback, поведение которого отличается от C;
- обновлён один GUI без обновления и теста `templates`;
- ПМ67 и ПМ35 сведены к одному соединению или одному состоянию контроллера.
Текущее состояние и очередь переноса перечислены в
[`doc/CROSS_PLATFORM_AUDIT.md`](doc/CROSS_PLATFORM_AUDIT.md).

View File

@@ -18,6 +18,7 @@ set(SETPROTOCOL_V2_SOURCES
# Совместимые ProtoCAN/SETGUI v1 форматы переходного периода.
set(SETPROTOCOL_LEGACY_SOURCES
src/balsam_can.c
src/periph28335.c
src/gui_catalog.c
src/gui_frame.c
src/pcan_abi.c
@@ -98,6 +99,9 @@ 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_trends tests/test_trends.c)
target_link_libraries(test_trends PRIVATE setprotocol_static)
add_test(NAME shared_trends COMMAND test_trends)

View File

@@ -73,6 +73,34 @@ 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);
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

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

View File

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

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)
}
@@ -32,6 +35,17 @@ 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 nativeEncodeFrame(
sequence: Int,
flags: Int,

View File

@@ -1,37 +1,42 @@
package ru.setcorp.setprotocol.periph28335
/** Shared wire protocol migrated from Set_Terminal_28335/DTrans.pas and UNiiefa.pas. */
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>> = linkedMapOf(
"По умолчанию" to listOf("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Light", "Raw", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
"23470" to listOf("Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "-", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
"23550" to listOf("Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "Send", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
"23550.2" to listOf("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Send", "Raw", "Beep", "", "", "", "", "Log", "Reset", "Nothing at all"),
"ICE 22220.1-3" to listOf("Test", "Zero", "Save", "Def", "Calibr", "Read", "ExtLamp", "ExtLite", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
"ICE 22220.4-5" to listOf("Test", "Def", "Save", "Load", "Raw", "Read", "ExtLamp", "ExtLite", "No log", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
"Бальзам 161" to listOf("Test", "Zero", "Save", "Def", "Calibr", "Clbr 400", "Stop", "Start", "Init", "Secret", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
"Бальзам 162" to listOf("Test", "Def", "Save", "Load", "Calibr", "Secret", "Stop", "Start", "Init", "Tune", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
"Бальзам 163" to listOf("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Stop", "Start", "Init", "Tune", "Secret", "-", "-", "-", "-", "Reset", "Nothing at all"),
)
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 {
var crc = initial and 0xFFFF
data.forEach { value ->
crc = crc xor (value.toInt() and 0xFF)
repeat(8) {
crc = if (crc and 1 != 0) (crc ushr 1) xor 0xA001 else crc ushr 1
require(initial == 0xFFFF) {
"Произвольное начальное значение CRC не входит в протокол ПМ35"
}
}
return crc and 0xFFFF
requireNative()
return NativeSetProtocol.nativePeriph28335Crc16(data)
}
fun withCrc(payload: ByteArray): ByteArray {
val crc = crc16Modbus(payload)
return payload + byteArrayOf(crc.toByte(), (crc ushr 8).toByte())
requireNative()
return requireNotNull(NativeSetProtocol.nativePeriph28335AppendCrc(payload)) {
"SETProtocol отклонил данные ПМ35"
}
}
fun buildReadRegisters(controller: Int, start: Int, count: Int): ByteArray {
@@ -40,50 +45,51 @@ object Periph28335Protocol {
require(count in 1..REGISTER_COUNT && start + count <= REGISTER_COUNT) {
"Диапазон регистров должен находиться в 0..127"
}
return withCrc(byteArrayOf(
controller.toByte(), 3,
(start ushr 8).toByte(), start.toByte(),
(count ushr 8).toByte(), count.toByte(),
))
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)
return withCrc(byteArrayOf(
controller.toByte(), 6,
(address ushr 8).toByte(), address.toByte(),
(value ushr 8).toByte(), value.toByte(),
))
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335BuildWrite(controller, address, value),
) { "SETProtocol отклонил запрос записи ПМ35" }
}
fun buildCommand(controller: Int, commandIndex: Int): ByteArray {
require(commandIndex in 0..16) { "Номер команды должен быть в диапазоне 0..16" }
return buildWriteRegister(controller, 127, if (commandIndex < 16) 1 shl commandIndex else 0)
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335BuildCommand(controller, commandIndex),
) { "SETProtocol отклонил команду ПМ35" }
}
fun expectedReadResponseSize(count: Int): Int {
require(count in 1..REGISTER_COUNT)
return count * 2 + 5
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}" }
validateCrc(data)
require(data[0].toInt() and 0xFF == controller) { "Ответ другого контроллера" }
require(data[1].toInt() and 0xFF == 3) { "Неверная функция ответа" }
require(data[2].toInt() and 0xFF == count * 2) { "Неверная длина данных ответа" }
return (0 until count).map { index ->
val offset = 3 + index * 2
((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF)
}
requireNative()
return requireNotNull(
NativeSetProtocol.nativePeriph28335DecodeRead(data, controller, count),
) { "Повреждён ответ ПМ35: заголовок, длина или CRC" }.toList()
}
fun validateWriteResponse(data: ByteArray, request: ByteArray): Boolean =
data.size == 8 && request.size == 8 && data.contentEquals(request) && runCatching { validateCrc(data) }.isSuccess
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 }
@@ -91,7 +97,9 @@ object Periph28335Protocol {
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 }
return bits.foldIndexed(0) { bit, value, checked ->
if (checked) value or (1 shl bit) else value
}
}
fun signedWord(value: Int): Int {
@@ -99,11 +107,8 @@ object Periph28335Protocol {
return if (value < 0x8000) value else value - 0x10000
}
private fun validateCrc(data: ByteArray) {
require(data.size >= 2)
val expected = crc16Modbus(data.copyOf(data.size - 2))
val actual = (data[data.lastIndex - 1].toInt() and 0xFF) or ((data.last().toInt() and 0xFF) shl 8)
require(actual == expected) { "Ошибка CRC ответа" }
private fun requireNative() {
check(NativeSetProtocol.available) { "Нативное ядро SETProtocol недоступно" }
}
private fun requireRange(name: String, value: Int, maximum: Int) {

View File

@@ -7,6 +7,182 @@
#include "set_trends.h"
#include "set_spectrum.h"
#include "balsam_can.h"
#include "periph28335.h"
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(

View File

@@ -4,6 +4,7 @@
#include "pcan_crc.h"
#include "balsam_can.h"
#include "periph28335.h"
#include "pcan_frame.h"
#include "pcan_id.h"
#include "gui_frame.h"
@@ -88,6 +89,79 @@ 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);
}
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,171 @@
#include "periph28335.h"
#include <string.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)
{
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;
}
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,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

@@ -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", "periph28335.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",
)
@@ -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"

View File

@@ -0,0 +1,43 @@
# Аудит общего кода GUI_Android и SETGUI
Дата проверки: 2026-09-04.
## Уже является общим C-ядром
| Область | Каноническая реализация |
|---|---|
| SETProtocol v2, CAN segmentation, firmware flow, telemetry | `c/set-protocol/src/set_*.c` |
| ProtoCAN ID, кадр, parser, link, CRC-CCITT | `c/set-protocol/src/pcan_*.c` |
| GUI frame и каталог | `c/set-protocol/src/gui_*.c` |
| CAN Бальзам | `c/set-protocol/src/balsam_can.c` |
| График, тренды и спектр | `c/set-protocol/src/set_plot.c`, `set_trends.c`, `set_spectrum.c` |
| ПМ35 / TMS320F28335: MODBUS-подобные запросы, CRC16/Modbus, ответы и каталог команд | `c/set-protocol/src/periph28335.c` |
Python вызывает это ядро через `python/protocan/native.py`, Android — через
`ports/android/setprotocol_jni.c`. Файлы на Python и Kotlin являются портами и
не должны содержать wire-алгоритм.
## Найденное общее, которое ещё нужно перенести
| Приоритет | Android | SETGUI | Что вынести в C |
|---|---|---|---|
| 1 | `core/Tms2812Protocol.kt` | `core/tms_flash.py`, `core/tms_terminal.py` | весь RS-протокол ПМ67/2812, CRC, команды и validators |
| 2 | `core/Ds18b20Protocol.kt` | `core/ds18b20.py` | кодеки списков/данных/EEPROM, CRC8 и значения датчиков |
| 3 | `core/CanFirmwareProtocol.kt` | `core/can_firmware.py` | автомат CAN boot, статусы, окна блоков |
| 4 | `core/SlCanProtocol.kt` | `adapters/slcan_adapter.py` и CAN transport | ASCII codec/parser SLCAN; доступ к COM остаётся в портах |
| 5 | `core/FirmwareImage.kt` | `core/firmware.py` | Intel HEX parser, CRC32/SHA и правила диапазонов |
Это очередь миграции, а не разрешение поддерживать две реализации. При первом
изменении любой строки из таблицы сначала создаётся соответствующий модуль C и
общие тестовые векторы.
## Что должно остаться платформенным
- Compose и PySide widgets, навигация и внешний вид;
- Android USB host, разрешения, foreground lifecycle;
- Windows COM/MOXA/драйверы и выбор последовательного порта;
- хранилище настроек, диалоги файлов и уведомления;
- привязка моделей C к Kotlin/Python и локализованные сообщения UI.
Таким образом, язык GUI можно менять без повторного написания протокола: новый
клиент реализует только FFI и свой транспортный/UI-порт.

View File

@@ -139,6 +139,44 @@ class NativeProtocol:
ctypes.c_uint8, ctypes.c_uint16, ctypes.c_void_p, ctypes.c_size_t,
]
lib.pcan_abi_balsam_register_name.restype = ctypes.c_size_t
lib.pcan_abi_periph28335_crc16.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
lib.pcan_abi_periph28335_crc16.restype = ctypes.c_uint16
lib.pcan_abi_periph28335_append_crc.argtypes = [
ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p, ctypes.c_size_t,
]
lib.pcan_abi_periph28335_append_crc.restype = ctypes.c_size_t
lib.pcan_abi_periph28335_build_read.argtypes = [
ctypes.c_uint8, ctypes.c_uint16, ctypes.c_uint16,
ctypes.c_void_p, ctypes.c_size_t,
]
lib.pcan_abi_periph28335_build_read.restype = ctypes.c_size_t
lib.pcan_abi_periph28335_build_write.argtypes = [
ctypes.c_uint8, ctypes.c_uint16, ctypes.c_uint16,
ctypes.c_void_p, ctypes.c_size_t,
]
lib.pcan_abi_periph28335_build_write.restype = ctypes.c_size_t
lib.pcan_abi_periph28335_build_command.argtypes = [
ctypes.c_uint8, ctypes.c_uint8, ctypes.c_void_p, ctypes.c_size_t,
]
lib.pcan_abi_periph28335_build_command.restype = ctypes.c_size_t
lib.pcan_abi_periph28335_expected_read_size.argtypes = [ctypes.c_uint16]
lib.pcan_abi_periph28335_expected_read_size.restype = ctypes.c_size_t
lib.pcan_abi_periph28335_decode_read.argtypes = [
ctypes.c_void_p, ctypes.c_size_t, ctypes.c_uint8, ctypes.c_uint16,
ctypes.POINTER(ctypes.c_uint16), ctypes.c_size_t,
]
lib.pcan_abi_periph28335_decode_read.restype = ctypes.c_int
lib.pcan_abi_periph28335_validate_write.argtypes = [
ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p, ctypes.c_size_t,
]
lib.pcan_abi_periph28335_validate_write.restype = ctypes.c_int
lib.pcan_abi_periph28335_project_count.restype = ctypes.c_size_t
lib.pcan_abi_periph28335_project_name.argtypes = [ctypes.c_size_t]
lib.pcan_abi_periph28335_project_name.restype = ctypes.c_char_p
lib.pcan_abi_periph28335_command_name.argtypes = [
ctypes.c_size_t, ctypes.c_size_t,
]
lib.pcan_abi_periph28335_command_name.restype = ctypes.c_char_p
lib.pcan_abi_frame_encode.argtypes = [
ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint32,
ctypes.c_void_p, ctypes.c_uint8, ctypes.c_void_p, ctypes.c_size_t,
@@ -220,6 +258,83 @@ class NativeProtocol:
device, address, output, len(output))
return output.value.decode("utf-8")
@staticmethod
def _bytes_buffer(data: bytes):
return (ctypes.c_uint8 * len(data)).from_buffer_copy(data) if data else None
def periph28335_crc16(self, data: bytes) -> int:
return int(self.lib.pcan_abi_periph28335_crc16(
self._bytes_buffer(data), len(data)))
def periph28335_append_crc(self, payload: bytes) -> bytes:
source = self._bytes_buffer(payload)
output = (ctypes.c_uint8 * (len(payload) + 2))()
size = int(self.lib.pcan_abi_periph28335_append_crc(
source, len(payload), output, len(output)))
if size == 0:
raise ValueError("SETProtocol rejected PM35 payload")
return bytes(output[:size])
def periph28335_build_read(self, controller: int, start: int,
count: int) -> bytes:
output = (ctypes.c_uint8 * 8)()
size = int(self.lib.pcan_abi_periph28335_build_read(
controller, start, count, output, len(output)))
if size == 0:
raise ValueError("SETProtocol rejected PM35 read request")
return bytes(output[:size])
def periph28335_build_write(self, controller: int, address: int,
value: int) -> bytes:
output = (ctypes.c_uint8 * 8)()
size = int(self.lib.pcan_abi_periph28335_build_write(
controller, address, value, output, len(output)))
if size == 0:
raise ValueError("SETProtocol rejected PM35 write request")
return bytes(output[:size])
def periph28335_build_command(self, controller: int,
command_index: int) -> bytes:
output = (ctypes.c_uint8 * 8)()
size = int(self.lib.pcan_abi_periph28335_build_command(
controller, command_index, output, len(output)))
if size == 0:
raise ValueError("SETProtocol rejected PM35 command")
return bytes(output[:size])
def periph28335_expected_read_size(self, count: int) -> int:
return int(self.lib.pcan_abi_periph28335_expected_read_size(count))
def periph28335_decode_read(self, data: bytes, controller: int,
count: int) -> tuple[int, tuple[int, ...]]:
source = self._bytes_buffer(data)
output = (ctypes.c_uint16 * count)()
status = int(self.lib.pcan_abi_periph28335_decode_read(
source, len(data), controller, count, output, count))
return status, tuple(int(value) for value in output) if status == 0 else ()
def periph28335_validate_write(self, response: bytes,
request: bytes) -> int:
return int(self.lib.pcan_abi_periph28335_validate_write(
self._bytes_buffer(response), len(response),
self._bytes_buffer(request), len(request)))
def periph28335_catalog(self) -> dict[str, tuple[str, ...]]:
result: dict[str, tuple[str, ...]] = {}
for project in range(int(self.lib.pcan_abi_periph28335_project_count())):
raw_name = self.lib.pcan_abi_periph28335_project_name(project)
if not raw_name:
raise NativeProtocolUnavailable("invalid PM35 project catalog")
commands = []
for command in range(17):
raw_command = self.lib.pcan_abi_periph28335_command_name(
project, command)
if raw_command is None:
raise NativeProtocolUnavailable("invalid PM35 command catalog")
commands.append(raw_command.decode("utf-8"))
result[raw_name.decode("utf-8")] = tuple(commands)
return result
def encode(self, sequence: int, flags: int, can_id: int, data: bytes) -> bytes:
if len(data) > 8:
raise ValueError("DLC cannot exceed 8 bytes")

View File

@@ -1,66 +1,85 @@
"""Portable RS command helpers from Set_Terminal_28335.
The byte order and CRC match ``DTrans.pas``/``UNiiefa.pas``. This module is
deliberately UI- and serial-port-independent so desktop and Android clients
can share the same request builders.
"""
"""Thin Python port of the shared C99 PM35/TMS320F28335 protocol."""
from __future__ import annotations
from .native import get_native_protocol
REGISTER_COUNT = 128
DEFAULT_CONTROLLER = 16
DEFAULT_BAUD_RATE = 115_200
def _core():
return get_native_protocol()
def crc16_modbus(data: bytes, crc: int = 0xFFFF) -> int:
for byte in data:
crc ^= byte
for _ in range(8):
crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
return crc & 0xFFFF
if crc != 0xFFFF:
raise ValueError("Произвольное начальное значение CRC не входит в протокол ПМ35")
return _core().periph28335_crc16(bytes(data))
def with_crc(payload: bytes) -> bytes:
crc = crc16_modbus(payload)
return bytes(payload) + crc.to_bytes(2, "little")
return _core().periph28335_append_crc(bytes(payload))
def build_read_registers(controller: int, start: int, count: int) -> bytes:
_range("Адрес контроллера", controller, 0xFF)
_range("Начальный регистр", start, 0xFFFF)
if not 1 <= count <= 128 or start + count > 128:
if not 1 <= count <= REGISTER_COUNT or start + count > REGISTER_COUNT:
raise ValueError("Диапазон регистров должен находиться в 0..127")
return with_crc(bytes((controller, 3)) + start.to_bytes(2, "big")
+ count.to_bytes(2, "big"))
return _core().periph28335_build_read(controller, start, count)
def build_write_register(controller: int, address: int, value: int) -> bytes:
_range("Адрес контроллера", controller, 0xFF)
_range("Адрес регистра", address, 127)
_range("Адрес регистра", address, REGISTER_COUNT - 1)
_range("Значение", value, 0xFFFF)
return with_crc(bytes((controller, 6)) + address.to_bytes(2, "big")
+ value.to_bytes(2, "big"))
return _core().periph28335_build_write(controller, address, value)
def build_command(controller: int, command_index: int) -> bytes:
_range("Адрес контроллера", controller, 0xFF)
if not 0 <= command_index <= 16:
raise ValueError("Номер команды должен быть в диапазоне 0..16")
value = 1 << command_index if command_index < 16 else 0
return build_write_register(controller, 127, value)
return _core().periph28335_build_command(controller, command_index)
def expected_read_response_size(count: int) -> int:
return count * 2 + 5
if not 1 <= count <= REGISTER_COUNT:
raise ValueError("Число регистров должно быть в диапазоне 1..128")
size = _core().periph28335_expected_read_size(count)
if size == 0:
raise ValueError("SETProtocol отклонил размер ответа ПМ35")
return size
def decode_read_response(data: bytes, count: int) -> tuple[int, ...]:
def decode_read_response(data: bytes, count: int,
controller: int | None = None) -> tuple[int, ...]:
expected = expected_read_response_size(count)
if len(data) != expected:
raise ValueError(f"Ожидалось {expected} байт, получено {len(data)}")
if crc16_modbus(data[:-2]) != int.from_bytes(data[-2:], "little"):
raise ValueError("Ошибка CRC ответа")
# Historical replies have a three-byte header; registers are big-endian.
body = data[3:-2]
if len(body) != count * 2:
raise ValueError("Неверная длина данных ответа")
return tuple(int.from_bytes(body[offset:offset + 2], "big")
for offset in range(0, len(body), 2))
expected_controller = data[0] if controller is None and data else controller
if expected_controller is None:
raise ValueError("Пустой ответ ПМ35")
status, values = _core().periph28335_decode_read(
bytes(data), expected_controller, count)
errors = {
-1: "Неверные аргументы ответа",
-2: "Диапазон регистров вне 0..127",
-3: "Неверная длина данных ответа",
-4: "Ошибка CRC ответа",
-5: "Неверный заголовок ответа",
-6: "Недостаточный буфер ответа",
}
if status != 0:
raise ValueError(errors.get(status, f"Ошибка ответа ПМ35: {status}"))
return values
def validate_write_response(data: bytes, request: bytes) -> bool:
return _core().periph28335_validate_write(bytes(data), bytes(request)) == 0
def bits_lsb_first(value: int) -> tuple[bool, ...]:
@@ -85,14 +104,4 @@ def _range(name: str, value: int, maximum: int) -> None:
raise ValueError(f"{name} вне диапазона 0..{maximum}")
PROJECT_COMMANDS = {
"По умолчанию": ("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"),
}
PROJECT_COMMANDS = _core().periph28335_catalog()