Move PM35 protocol into shared C core
This commit is contained in:
80
RULES.md
Normal file
80
RULES.md
Normal 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).
|
||||||
@@ -18,6 +18,7 @@ set(SETPROTOCOL_V2_SOURCES
|
|||||||
# Совместимые ProtoCAN/SETGUI v1 форматы переходного периода.
|
# Совместимые ProtoCAN/SETGUI v1 форматы переходного периода.
|
||||||
set(SETPROTOCOL_LEGACY_SOURCES
|
set(SETPROTOCOL_LEGACY_SOURCES
|
||||||
src/balsam_can.c
|
src/balsam_can.c
|
||||||
|
src/periph28335.c
|
||||||
src/gui_catalog.c
|
src/gui_catalog.c
|
||||||
src/gui_frame.c
|
src/gui_frame.c
|
||||||
src/pcan_abi.c
|
src/pcan_abi.c
|
||||||
@@ -98,6 +99,9 @@ if(SETP_BUILD_TESTS)
|
|||||||
add_executable(test_balsam_can tests/test_balsam_can.c)
|
add_executable(test_balsam_can tests/test_balsam_can.c)
|
||||||
target_link_libraries(test_balsam_can PRIVATE setprotocol_static)
|
target_link_libraries(test_balsam_can PRIVATE setprotocol_static)
|
||||||
add_test(NAME legacy_balsam_can COMMAND test_balsam_can)
|
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)
|
add_executable(test_trends tests/test_trends.c)
|
||||||
target_link_libraries(test_trends PRIVATE setprotocol_static)
|
target_link_libraries(test_trends PRIVATE setprotocol_static)
|
||||||
add_test(NAME shared_trends COMMAND test_trends)
|
add_test(NAME shared_trends COMMAND test_trends)
|
||||||
|
|||||||
@@ -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(
|
PCAN_ABI_API size_t pcan_abi_balsam_register_name(
|
||||||
uint8_t device, uint16_t address, char *output, size_t output_size);
|
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,
|
PCAN_ABI_API size_t pcan_abi_frame_encode(uint8_t sequence, uint8_t flags,
|
||||||
uint32_t can_id,
|
uint32_t can_id,
|
||||||
const uint8_t *data, uint8_t dlc,
|
const uint8_t *data, uint8_t dlc,
|
||||||
|
|||||||
68
c/set-protocol/include/periph28335.h
Normal file
68
c/set-protocol/include/periph28335.h
Normal 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 */
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
#define SETPROTOCOL_H
|
#define SETPROTOCOL_H
|
||||||
|
|
||||||
#include "balsam_can.h"
|
#include "balsam_can.h"
|
||||||
|
#include "periph28335.h"
|
||||||
|
|
||||||
/* Основной SET protocol v2. */
|
/* Основной SET protocol v2. */
|
||||||
#include "set_protocol.h"
|
#include "set_protocol.h"
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ LOCAL_SRC_FILES := \
|
|||||||
../../src/set_trends.c \
|
../../src/set_trends.c \
|
||||||
../../src/set_spectrum.c \
|
../../src/set_spectrum.c \
|
||||||
../../src/balsam_can.c \
|
../../src/balsam_can.c \
|
||||||
|
../../src/periph28335.c \
|
||||||
../../src/gui_catalog.c \
|
../../src/gui_catalog.c \
|
||||||
../../src/gui_frame.c \
|
../../src/gui_frame.c \
|
||||||
../../src/pcan_abi.c \
|
../../src/pcan_abi.c \
|
||||||
|
|||||||
@@ -4,7 +4,10 @@ package ru.setcorp.setprotocol
|
|||||||
object NativeSetProtocol {
|
object NativeSetProtocol {
|
||||||
val available: Boolean by lazy {
|
val available: Boolean by lazy {
|
||||||
runCatching {
|
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
|
nativeAbiVersion() == 1
|
||||||
}.getOrDefault(false)
|
}.getOrDefault(false)
|
||||||
}
|
}
|
||||||
@@ -32,6 +35,17 @@ object NativeSetProtocol {
|
|||||||
external fun nativeBalsamDecode(canId: Long, input: ByteArray): IntArray?
|
external fun nativeBalsamDecode(canId: Long, input: ByteArray): IntArray?
|
||||||
external fun nativeBalsamDeviceName(device: Int): String
|
external fun nativeBalsamDeviceName(device: Int): String
|
||||||
external fun nativeBalsamRegisterName(device: Int, address: 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(
|
external fun nativeEncodeFrame(
|
||||||
sequence: Int,
|
sequence: Int,
|
||||||
flags: Int,
|
flags: Int,
|
||||||
|
|||||||
@@ -1,37 +1,42 @@
|
|||||||
package ru.setcorp.setprotocol.periph28335
|
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 {
|
object Periph28335Protocol {
|
||||||
const val REGISTER_COUNT = 128
|
const val REGISTER_COUNT = 128
|
||||||
const val DEFAULT_CONTROLLER = 16
|
const val DEFAULT_CONTROLLER = 16
|
||||||
const val DEFAULT_BAUD_RATE = 115_200
|
const val DEFAULT_BAUD_RATE = 115_200
|
||||||
|
|
||||||
val projectCommands: Map<String, List<String>> = linkedMapOf(
|
val projectCommands: Map<String, List<String>> by lazy {
|
||||||
"По умолчанию" to listOf("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Light", "Raw", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
requireNative()
|
||||||
"23470" to listOf("Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "-", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
buildMap {
|
||||||
"23550" to listOf("Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "Send", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
repeat(NativeSetProtocol.nativePeriph28335ProjectCount()) { project ->
|
||||||
"23550.2" to listOf("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Send", "Raw", "Beep", "", "", "", "", "Log", "Reset", "Nothing at all"),
|
val name = requireNotNull(
|
||||||
"ICE 22220.1-3" to listOf("Test", "Zero", "Save", "Def", "Calibr", "Read", "ExtLamp", "ExtLite", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
NativeSetProtocol.nativePeriph28335ProjectName(project),
|
||||||
"ICE 22220.4-5" to listOf("Test", "Def", "Save", "Load", "Raw", "Read", "ExtLamp", "ExtLite", "No log", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
) { "Повреждён каталог проектов ПМ35" }
|
||||||
"Бальзам 161" to listOf("Test", "Zero", "Save", "Def", "Calibr", "Clbr 400", "Stop", "Start", "Init", "Secret", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
put(name, List(17) { command ->
|
||||||
"Бальзам 162" to listOf("Test", "Def", "Save", "Load", "Calibr", "Secret", "Stop", "Start", "Init", "Tune", "-", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
requireNotNull(
|
||||||
"Бальзам 163" to listOf("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Stop", "Start", "Init", "Tune", "Secret", "-", "-", "-", "-", "Reset", "Nothing at all"),
|
NativeSetProtocol.nativePeriph28335CommandName(project, command),
|
||||||
)
|
) { "Повреждён каталог команд ПМ35" }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun crc16Modbus(data: ByteArray, initial: Int = 0xFFFF): Int {
|
fun crc16Modbus(data: ByteArray, initial: Int = 0xFFFF): Int {
|
||||||
var crc = initial and 0xFFFF
|
require(initial == 0xFFFF) {
|
||||||
data.forEach { value ->
|
"Произвольное начальное значение CRC не входит в протокол ПМ35"
|
||||||
crc = crc xor (value.toInt() and 0xFF)
|
|
||||||
repeat(8) {
|
|
||||||
crc = if (crc and 1 != 0) (crc ushr 1) xor 0xA001 else crc ushr 1
|
|
||||||
}
|
}
|
||||||
}
|
requireNative()
|
||||||
return crc and 0xFFFF
|
return NativeSetProtocol.nativePeriph28335Crc16(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun withCrc(payload: ByteArray): ByteArray {
|
fun withCrc(payload: ByteArray): ByteArray {
|
||||||
val crc = crc16Modbus(payload)
|
requireNative()
|
||||||
return payload + byteArrayOf(crc.toByte(), (crc ushr 8).toByte())
|
return requireNotNull(NativeSetProtocol.nativePeriph28335AppendCrc(payload)) {
|
||||||
|
"SETProtocol отклонил данные ПМ35"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildReadRegisters(controller: Int, start: Int, count: Int): ByteArray {
|
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) {
|
require(count in 1..REGISTER_COUNT && start + count <= REGISTER_COUNT) {
|
||||||
"Диапазон регистров должен находиться в 0..127"
|
"Диапазон регистров должен находиться в 0..127"
|
||||||
}
|
}
|
||||||
return withCrc(byteArrayOf(
|
requireNative()
|
||||||
controller.toByte(), 3,
|
return requireNotNull(
|
||||||
(start ushr 8).toByte(), start.toByte(),
|
NativeSetProtocol.nativePeriph28335BuildRead(controller, start, count),
|
||||||
(count ushr 8).toByte(), count.toByte(),
|
) { "SETProtocol отклонил запрос чтения ПМ35" }
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildWriteRegister(controller: Int, address: Int, value: Int): ByteArray {
|
fun buildWriteRegister(controller: Int, address: Int, value: Int): ByteArray {
|
||||||
requireRange("Адрес контроллера", controller, 0xFF)
|
requireRange("Адрес контроллера", controller, 0xFF)
|
||||||
requireRange("Адрес регистра", address, REGISTER_COUNT - 1)
|
requireRange("Адрес регистра", address, REGISTER_COUNT - 1)
|
||||||
requireRange("Значение", value, 0xFFFF)
|
requireRange("Значение", value, 0xFFFF)
|
||||||
return withCrc(byteArrayOf(
|
requireNative()
|
||||||
controller.toByte(), 6,
|
return requireNotNull(
|
||||||
(address ushr 8).toByte(), address.toByte(),
|
NativeSetProtocol.nativePeriph28335BuildWrite(controller, address, value),
|
||||||
(value ushr 8).toByte(), value.toByte(),
|
) { "SETProtocol отклонил запрос записи ПМ35" }
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildCommand(controller: Int, commandIndex: Int): ByteArray {
|
fun buildCommand(controller: Int, commandIndex: Int): ByteArray {
|
||||||
require(commandIndex in 0..16) { "Номер команды должен быть в диапазоне 0..16" }
|
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 {
|
fun expectedReadResponseSize(count: Int): Int {
|
||||||
require(count in 1..REGISTER_COUNT)
|
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> {
|
fun decodeReadResponse(data: ByteArray, controller: Int, count: Int): List<Int> {
|
||||||
val expected = expectedReadResponseSize(count)
|
val expected = expectedReadResponseSize(count)
|
||||||
require(data.size == expected) { "Ожидалось $expected байт, получено ${data.size}" }
|
require(data.size == expected) { "Ожидалось $expected байт, получено ${data.size}" }
|
||||||
validateCrc(data)
|
requireNative()
|
||||||
require(data[0].toInt() and 0xFF == controller) { "Ответ другого контроллера" }
|
return requireNotNull(
|
||||||
require(data[1].toInt() and 0xFF == 3) { "Неверная функция ответа" }
|
NativeSetProtocol.nativePeriph28335DecodeRead(data, controller, count),
|
||||||
require(data[2].toInt() and 0xFF == count * 2) { "Неверная длина данных ответа" }
|
) { "Повреждён ответ ПМ35: заголовок, длина или CRC" }.toList()
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateWriteResponse(data: ByteArray, request: ByteArray): Boolean =
|
fun validateWriteResponse(data: ByteArray, request: ByteArray): Boolean {
|
||||||
data.size == 8 && request.size == 8 && data.contentEquals(request) && runCatching { validateCrc(data) }.isSuccess
|
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> {
|
fun bitsLsbFirst(value: Int): List<Boolean> {
|
||||||
requireRange("Значение", value, 0xFFFF)
|
requireRange("Значение", value, 0xFFFF)
|
||||||
return (0 until 16).map { bit -> value and (1 shl bit) != 0 }
|
return (0 until 16).map { bit -> value and (1 shl bit) != 0 }
|
||||||
@@ -91,7 +97,9 @@ object Periph28335Protocol {
|
|||||||
|
|
||||||
fun wordFromBits(bits: List<Boolean>): Int {
|
fun wordFromBits(bits: List<Boolean>): Int {
|
||||||
require(bits.size == 16) { "Должно быть ровно 16 бит" }
|
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 {
|
fun signedWord(value: Int): Int {
|
||||||
@@ -99,11 +107,8 @@ object Periph28335Protocol {
|
|||||||
return if (value < 0x8000) value else value - 0x10000
|
return if (value < 0x8000) value else value - 0x10000
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun validateCrc(data: ByteArray) {
|
private fun requireNative() {
|
||||||
require(data.size >= 2)
|
check(NativeSetProtocol.available) { "Нативное ядро SETProtocol недоступно" }
|
||||||
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 requireRange(name: String, value: Int, maximum: Int) {
|
private fun requireRange(name: String, value: Int, maximum: Int) {
|
||||||
|
|||||||
@@ -7,6 +7,182 @@
|
|||||||
#include "set_trends.h"
|
#include "set_trends.h"
|
||||||
#include "set_spectrum.h"
|
#include "set_spectrum.h"
|
||||||
#include "balsam_can.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
|
JNIEXPORT jintArray JNICALL
|
||||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeBalsamDecode(
|
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeBalsamDecode(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "pcan_crc.h"
|
#include "pcan_crc.h"
|
||||||
#include "balsam_can.h"
|
#include "balsam_can.h"
|
||||||
|
#include "periph28335.h"
|
||||||
#include "pcan_frame.h"
|
#include "pcan_frame.h"
|
||||||
#include "pcan_id.h"
|
#include "pcan_id.h"
|
||||||
#include "gui_frame.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);
|
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,
|
size_t pcan_abi_frame_encode(uint8_t sequence, uint8_t flags,
|
||||||
uint32_t can_id, const uint8_t *data, uint8_t dlc,
|
uint32_t can_id, const uint8_t *data, uint8_t dlc,
|
||||||
uint8_t *output, size_t output_size)
|
uint8_t *output, size_t output_size)
|
||||||
|
|||||||
171
c/set-protocol/src/periph28335.c
Normal file
171
c/set-protocol/src/periph28335.c
Normal 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;
|
||||||
|
}
|
||||||
32
c/set-protocol/tests/test_periph28335.c
Normal file
32
c/set-protocol/tests/test_periph28335.c
Normal 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;
|
||||||
|
}
|
||||||
@@ -21,7 +21,7 @@ JNI_INCLUDES: list[Path] = []
|
|||||||
SOURCES = [
|
SOURCES = [
|
||||||
ROOT / "src" / name for name in (
|
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",
|
"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_frame.c", "pcan_id.c", "pcan_link.c", "pcan_ring.c",
|
||||||
"pcan_gas.c",
|
"pcan_gas.c",
|
||||||
)
|
)
|
||||||
@@ -84,7 +84,10 @@ def main() -> int:
|
|||||||
if not (include / "jni.h").is_file():
|
if not (include / "jni.h").is_file():
|
||||||
parser.error("--java-home must contain include/jni.h")
|
parser.error("--java-home must contain include/jni.h")
|
||||||
JNI_INCLUDES.extend([include, include / {"Windows": "win32", "Darwin": "darwin"}.get(platform.system(), "linux")])
|
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 = args.output.resolve()
|
||||||
output.parent.mkdir(parents=True, exist_ok=True)
|
output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
build_dir = output.parent / ".setprotocol-build"
|
build_dir = output.parent / ".setprotocol-build"
|
||||||
|
|||||||
43
doc/CROSS_PLATFORM_AUDIT.md
Normal file
43
doc/CROSS_PLATFORM_AUDIT.md
Normal 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-порт.
|
||||||
@@ -139,6 +139,44 @@ class NativeProtocol:
|
|||||||
ctypes.c_uint8, ctypes.c_uint16, ctypes.c_void_p, ctypes.c_size_t,
|
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_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 = [
|
lib.pcan_abi_frame_encode.argtypes = [
|
||||||
ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint32,
|
ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint32,
|
||||||
ctypes.c_void_p, ctypes.c_uint8, ctypes.c_void_p, ctypes.c_size_t,
|
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))
|
device, address, output, len(output))
|
||||||
return output.value.decode("utf-8")
|
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:
|
def encode(self, sequence: int, flags: int, can_id: int, data: bytes) -> bytes:
|
||||||
if len(data) > 8:
|
if len(data) > 8:
|
||||||
raise ValueError("DLC cannot exceed 8 bytes")
|
raise ValueError("DLC cannot exceed 8 bytes")
|
||||||
|
|||||||
@@ -1,66 +1,85 @@
|
|||||||
"""Portable RS command helpers from Set_Terminal_28335.
|
"""Thin Python port of the shared C99 PM35/TMS320F28335 protocol."""
|
||||||
|
|
||||||
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.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
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:
|
def crc16_modbus(data: bytes, crc: int = 0xFFFF) -> int:
|
||||||
for byte in data:
|
if crc != 0xFFFF:
|
||||||
crc ^= byte
|
raise ValueError("Произвольное начальное значение CRC не входит в протокол ПМ35")
|
||||||
for _ in range(8):
|
return _core().periph28335_crc16(bytes(data))
|
||||||
crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
|
|
||||||
return crc & 0xFFFF
|
|
||||||
|
|
||||||
|
|
||||||
def with_crc(payload: bytes) -> bytes:
|
def with_crc(payload: bytes) -> bytes:
|
||||||
crc = crc16_modbus(payload)
|
return _core().periph28335_append_crc(bytes(payload))
|
||||||
return bytes(payload) + crc.to_bytes(2, "little")
|
|
||||||
|
|
||||||
|
|
||||||
def build_read_registers(controller: int, start: int, count: int) -> bytes:
|
def build_read_registers(controller: int, start: int, count: int) -> bytes:
|
||||||
_range("Адрес контроллера", controller, 0xFF)
|
_range("Адрес контроллера", controller, 0xFF)
|
||||||
_range("Начальный регистр", start, 0xFFFF)
|
_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")
|
raise ValueError("Диапазон регистров должен находиться в 0..127")
|
||||||
return with_crc(bytes((controller, 3)) + start.to_bytes(2, "big")
|
return _core().periph28335_build_read(controller, start, count)
|
||||||
+ count.to_bytes(2, "big"))
|
|
||||||
|
|
||||||
|
|
||||||
def build_write_register(controller: int, address: int, value: int) -> bytes:
|
def build_write_register(controller: int, address: int, value: int) -> bytes:
|
||||||
_range("Адрес контроллера", controller, 0xFF)
|
_range("Адрес контроллера", controller, 0xFF)
|
||||||
_range("Адрес регистра", address, 127)
|
_range("Адрес регистра", address, REGISTER_COUNT - 1)
|
||||||
_range("Значение", value, 0xFFFF)
|
_range("Значение", value, 0xFFFF)
|
||||||
return with_crc(bytes((controller, 6)) + address.to_bytes(2, "big")
|
return _core().periph28335_build_write(controller, address, value)
|
||||||
+ value.to_bytes(2, "big"))
|
|
||||||
|
|
||||||
|
|
||||||
def build_command(controller: int, command_index: int) -> bytes:
|
def build_command(controller: int, command_index: int) -> bytes:
|
||||||
|
_range("Адрес контроллера", controller, 0xFF)
|
||||||
if not 0 <= command_index <= 16:
|
if not 0 <= command_index <= 16:
|
||||||
raise ValueError("Номер команды должен быть в диапазоне 0..16")
|
raise ValueError("Номер команды должен быть в диапазоне 0..16")
|
||||||
value = 1 << command_index if command_index < 16 else 0
|
return _core().periph28335_build_command(controller, command_index)
|
||||||
return build_write_register(controller, 127, value)
|
|
||||||
|
|
||||||
|
|
||||||
def expected_read_response_size(count: int) -> int:
|
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)
|
expected = expected_read_response_size(count)
|
||||||
if len(data) != expected:
|
if len(data) != expected:
|
||||||
raise ValueError(f"Ожидалось {expected} байт, получено {len(data)}")
|
raise ValueError(f"Ожидалось {expected} байт, получено {len(data)}")
|
||||||
if crc16_modbus(data[:-2]) != int.from_bytes(data[-2:], "little"):
|
expected_controller = data[0] if controller is None and data else controller
|
||||||
raise ValueError("Ошибка CRC ответа")
|
if expected_controller is None:
|
||||||
# Historical replies have a three-byte header; registers are big-endian.
|
raise ValueError("Пустой ответ ПМ35")
|
||||||
body = data[3:-2]
|
status, values = _core().periph28335_decode_read(
|
||||||
if len(body) != count * 2:
|
bytes(data), expected_controller, count)
|
||||||
raise ValueError("Неверная длина данных ответа")
|
errors = {
|
||||||
return tuple(int.from_bytes(body[offset:offset + 2], "big")
|
-1: "Неверные аргументы ответа",
|
||||||
for offset in range(0, len(body), 2))
|
-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, ...]:
|
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}")
|
raise ValueError(f"{name} вне диапазона 0..{maximum}")
|
||||||
|
|
||||||
|
|
||||||
PROJECT_COMMANDS = {
|
PROJECT_COMMANDS = _core().periph28335_catalog()
|
||||||
"По умолчанию": ("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"),
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user