feat(protocan): добавь общий ABI для GUI

This commit is contained in:
2026-08-31 20:45:34 +03:00
parent 648457e587
commit 7910a07f2f
14 changed files with 703 additions and 3 deletions

1
.gitignore vendored
View File

@@ -22,3 +22,4 @@ __pycache__/
*.uvguix.*
Thumbs.db
Desktop.ini
.codex-build/

View File

@@ -30,7 +30,7 @@ templates/
| [`c/eeprom-ft24c256`](c/eeprom-ft24c256) | EEPROM 24Cxx по I²C с нарезкой записи по страницам | `stdint.h` | две I²C-транзакции, задержка |
| [`c/can-sensor`](c/can-sensor) | передача 64-битных ROM датчиков парой CAN-кадров | `stdint.h` | отправка и приём CAN-кадра |
| [`c/ds18b20`](c/ds18b20) | термометры DS18B20 поверх программной 1-Wire | `stdint.h` | Init, DelayUs, Reset, WriteBit, ReadBit — **порты STM32F103, STM32G431 и STM32G474 в комплекте** |
| [`c/protocan-transport`](c/protocan-transport) | транспорт ProtoCAN: кадр, канал, CRC, общее адресное пространство, каталог GUI | `stdint.h` | запись в поток и запрос свободного места — **порт STM32F4 в комплекте** |
| [`c/protocan-transport`](c/protocan-transport) | транспорт ProtoCAN: кадр, канал, CRC, общее адресное пространство, каталог GUI и стабильный host ABI | `stdint.h` | запись в поток и запрос свободного места — **порты STM32F4, Windows/Linux/macOS FFI и Android JNI в комплекте** |
| [`c/protocan-boot`](c/protocan-boot) | адресная прошивка по ProtoCAN: A/B-слоты, сессия, CRC32, verify и rollback-контракт | C99 | CAN TX, erase/write Flash, boot metadata, проверка образа и reboot |
| [`c/rs485-boot`](c/rs485-boot) | прошивка по RS-485 в формате SETGUI v1: потоковый parser, CRC32 и resume | C99 | UART TX/RX, DE, Flash — **порты STM32F103 и STM32G474VET в комплекте** |
| [`c/set-protocol`](c/set-protocol) | единый SET protocol v2: управление, real-time телеметрия и обновление прошивки через UART/CAN/USB/Ethernet | C99 | доставка целого stream/datagram-кадра, часы, backend карты и загрузчика |

View File

@@ -5,7 +5,8 @@ set(CMAKE_C_STANDARD 99)
set(CMAKE_C_STANDARD_REQUIRED ON)
# Ядро библиотеки: платформенных зависимостей нет, собирается под что угодно.
add_library(protocan_transport STATIC
set(PCAN_CORE_SOURCES
src/pcan_abi.c
src/pcan_crc.c
src/pcan_frame.c
src/pcan_id.c
@@ -14,14 +15,36 @@ add_library(protocan_transport STATIC
src/pcan_gas.c
)
add_library(protocan_transport STATIC ${PCAN_CORE_SOURCES})
target_include_directories(protocan_transport PUBLIC include)
option(PCAN_BUILD_SHARED "Build stable host ABI shared library" ON)
if(PCAN_BUILD_SHARED)
add_library(setcore SHARED ${PCAN_CORE_SOURCES})
target_include_directories(setcore PUBLIC include)
target_compile_definitions(setcore PRIVATE PCAN_ABI_BUILD_DLL)
set_target_properties(setcore PROPERTIES
C_VISIBILITY_PRESET hidden
VISIBILITY_INLINES_HIDDEN YES
OUTPUT_NAME setcore
)
endif()
if(MSVC)
target_compile_options(protocan_transport PRIVATE /W4)
else()
target_compile_options(protocan_transport PRIVATE -Wall -Wextra -Wpedantic)
endif()
if(TARGET setcore)
if(MSVC)
target_compile_options(setcore PRIVATE /W4)
else()
target_compile_options(setcore PRIVATE -Wall -Wextra -Wpedantic)
endif()
endif()
# Порт ports/stm32f4 сюда не входит: ему нужен CMSIS-заголовок stm32f4xx.h,
# он подключается напрямую в проект прошивки.
@@ -32,4 +55,7 @@ if(PCAN_BUILD_TESTS)
add_executable(test_transport tests/test_transport.c)
target_link_libraries(test_transport PRIVATE protocan_transport)
add_test(NAME transport COMMAND test_transport)
add_executable(test_abi tests/test_abi.c)
target_link_libraries(test_abi PRIVATE protocan_transport)
add_test(NAME abi COMMAND test_abi)
endif()

View File

@@ -27,7 +27,9 @@
| `pcan_ring` | кольцевой буфер, отдаёт непрерывный участок для DMA |
| `pcan_id` | упаковка и разбор 29-битного идентификатора ProtoCAN |
| `pcan_gas` | общее адресное пространство: карта регионов, чтение/запись, мост к кадрам |
| `pcan_abi` | стабильный C ABI для Python, JNI, Swift и других FFI |
| `ports/stm32f4` | готовый порт USART + DMA (пакетная передача, кольцевой приём) |
| `ports/android` | JNI и Kotlin-фасад; собирает `libsetcore.so` из этого же C99-кода |
## Кадр
@@ -93,6 +95,15 @@ size_t my_space(void *ctx);
cmake -B build && cmake --build build && ctest --test-dir build
```
Для desktop CMake дополнительно собирает shared library `setcore`
(`setcore.dll`, `libsetcore.so` или `libsetcore.dylib`). Публичная граница
для приложений описана в `include/pcan_abi.h`; внутренние структуры ядра через
FFI не экспортируются. Python-обёртка находится в `python/protocan/native.py`.
Android-проект подключает `ports/android/Android.mk` и добавляет
`ports/android/kotlin` в `sourceSets`. JNI-код занимается только преобразованием
типов и временем жизни parser context, а правила кадра и CAN ID остаются в C.
Либо напрямую:
```bash

View File

@@ -0,0 +1,74 @@
/**
* @file pcan_abi.h
* @brief Stable C ABI for desktop, Android and other foreign runtimes.
*
* The firmware-facing API remains allocation free. This header exposes
* scalar arguments and explicitly sized buffers so ctypes/JNI/Swift can use
* the same C99 implementation without reproducing the wire protocol.
*/
#ifndef PCAN_ABI_H
#define PCAN_ABI_H
#include <stddef.h>
#include <stdint.h>
#if defined(_WIN32) && defined(PCAN_ABI_BUILD_DLL)
# define PCAN_ABI_API __declspec(dllexport)
#elif defined(_WIN32) && defined(PCAN_ABI_USE_DLL)
# define PCAN_ABI_API __declspec(dllimport)
#elif defined(__GNUC__) && (__GNUC__ >= 4)
# define PCAN_ABI_API __attribute__((visibility("default")))
#else
# define PCAN_ABI_API
#endif
#ifdef __cplusplus
extern "C" {
#endif
#define PCAN_ABI_VERSION 1U
typedef struct {
uint32_t can_id;
uint8_t sequence;
uint8_t flags;
uint8_t dlc;
uint8_t data[8];
} pcan_abi_frame_t;
PCAN_ABI_API uint32_t pcan_abi_version(void);
PCAN_ABI_API uint32_t pcan_abi_id_pack(uint8_t priority, uint8_t route,
uint8_t device_type, uint8_t device_id,
uint8_t message_type, uint16_t body);
PCAN_ABI_API void pcan_abi_id_unpack(uint32_t raw, uint8_t *priority,
uint8_t *route, uint8_t *device_type,
uint8_t *device_id, uint8_t *message_type,
uint16_t *body);
PCAN_ABI_API uint16_t pcan_abi_crc16(const uint8_t *data, size_t size);
PCAN_ABI_API size_t pcan_abi_frame_encode(uint8_t sequence, uint8_t flags,
uint32_t can_id,
const uint8_t *data, uint8_t dlc,
uint8_t *output,
size_t output_size);
/* The caller owns parser_storage; no allocation is performed by the core. */
PCAN_ABI_API size_t pcan_abi_parser_size(void);
PCAN_ABI_API int pcan_abi_parser_init(void *parser_storage,
size_t storage_size);
PCAN_ABI_API int pcan_abi_parser_push(void *parser_storage, uint8_t byte,
pcan_abi_frame_t *output);
PCAN_ABI_API int pcan_abi_parser_stats(const void *parser_storage,
uint32_t *frames,
uint32_t *crc_errors,
uint32_t *bad_length,
uint32_t *stray_bytes);
#ifdef __cplusplus
}
#endif
#endif /* PCAN_ABI_H */

View File

@@ -17,6 +17,7 @@
PCAN_VERSION_PATCH)
#include "pcan_config.h"
#include "pcan_abi.h"
#include "pcan_crc.h"
#include "pcan_frame.h"
#include "pcan_gas.h"

View File

@@ -0,0 +1,17 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := setcore
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../include
LOCAL_SRC_FILES := \
../../src/pcan_abi.c \
../../src/pcan_crc.c \
../../src/pcan_frame.c \
../../src/pcan_id.c \
../../src/pcan_link.c \
../../src/pcan_ring.c \
../../src/pcan_gas.c \
setcore_jni.c
LOCAL_CFLAGS := -std=c99 -Wall -Wextra -Wpedantic -fvisibility=hidden
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)

View File

@@ -0,0 +1,10 @@
# Android port
`Android.mk` builds `libsetcore.so` from the same C99 sources used by MCU and
desktop builds. `setcore_jni.c` contains only JNI marshalling; protocol rules
remain in the core. Add the Kotlin directory as an Android source directory
and include this `Android.mk` from the application NDK build.
The JNI streaming parser returns fixed 15-byte records
`SEQ | FLAGS | CAN_ID_LE | DLC | DATA[8]`. Dynamic allocation is confined to
the Android adapter; the portable core remains allocation free.

View File

@@ -0,0 +1,32 @@
package ru.setcorp.setcore
/** Thin Kotlin facade over the shared C99 ProtoCAN core. */
object NativeProtoCan {
val available: Boolean by lazy {
runCatching {
System.loadLibrary("setcore")
nativeAbiVersion() == 1
}.getOrDefault(false)
}
external fun nativeAbiVersion(): Int
external fun nativePackId(
priority: Int,
route: Int,
deviceType: Int,
deviceId: Int,
messageType: Int,
body: Int,
): Long
external fun nativeCrc16(input: ByteArray): Int
external fun nativeEncodeFrame(
sequence: Int,
flags: Int,
canId: Long,
input: ByteArray,
): ByteArray?
external fun nativeCreateParser(): Long
external fun nativeDestroyParser(handle: Long)
external fun nativeFeedParser(handle: Long, input: ByteArray): ByteArray?
external fun nativeParserStats(handle: Long): IntArray?
}

View File

@@ -0,0 +1,191 @@
#include <jni.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "pcan_abi.h"
typedef struct {
uint8_t *storage;
size_t storage_size;
int last_sequence;
uint32_t sequence_lost;
} android_parser_t;
JNIEXPORT jint JNICALL
Java_ru_setcorp_setcore_NativeProtoCan_nativeAbiVersion(JNIEnv *env, jobject self)
{
(void)env;
(void)self;
return (jint)pcan_abi_version();
}
JNIEXPORT jlong JNICALL
Java_ru_setcorp_setcore_NativeProtoCan_nativePackId(
JNIEnv *env, jobject self, jint priority, jint route, jint device_type,
jint device_id, jint message_type, jint body)
{
(void)env;
(void)self;
return (jlong)pcan_abi_id_pack((uint8_t)priority, (uint8_t)route,
(uint8_t)device_type, (uint8_t)device_id,
(uint8_t)message_type, (uint16_t)body);
}
JNIEXPORT jint JNICALL
Java_ru_setcorp_setcore_NativeProtoCan_nativeCrc16(
JNIEnv *env, jobject self, jbyteArray input)
{
(void)self;
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if (data == NULL) {
return 0;
}
uint16_t crc = pcan_abi_crc16((const uint8_t *)data, (size_t)size);
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
return (jint)crc;
}
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setcore_NativeProtoCan_nativeEncodeFrame(
JNIEnv *env, jobject self, jint sequence, jint flags, jlong can_id,
jbyteArray input)
{
(void)self;
jsize size = (*env)->GetArrayLength(env, input);
if (size > 8) {
return NULL;
}
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) {
return NULL;
}
uint8_t output[19];
size_t written = pcan_abi_frame_encode(
(uint8_t)sequence, (uint8_t)flags, (uint32_t)can_id,
(const uint8_t *)data, (uint8_t)size, output, sizeof output);
if (data != NULL) {
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
}
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 jlong JNICALL
Java_ru_setcorp_setcore_NativeProtoCan_nativeCreateParser(JNIEnv *env, jobject self)
{
(void)env;
(void)self;
android_parser_t *parser = (android_parser_t *)calloc(1U, sizeof *parser);
if (parser == NULL) {
return 0;
}
parser->storage_size = pcan_abi_parser_size();
parser->storage = (uint8_t *)malloc(parser->storage_size);
parser->last_sequence = -1;
if ((parser->storage == NULL) ||
!pcan_abi_parser_init(parser->storage, parser->storage_size)) {
free(parser->storage);
free(parser);
return 0;
}
return (jlong)(intptr_t)parser;
}
JNIEXPORT void JNICALL
Java_ru_setcorp_setcore_NativeProtoCan_nativeDestroyParser(
JNIEnv *env, jobject self, jlong handle)
{
(void)env;
(void)self;
android_parser_t *parser = (android_parser_t *)(intptr_t)handle;
if (parser != NULL) {
free(parser->storage);
free(parser);
}
}
/* Each returned record is 15 bytes: seq, flags, id LE, dlc, data[8]. */
JNIEXPORT jbyteArray JNICALL
Java_ru_setcorp_setcore_NativeProtoCan_nativeFeedParser(
JNIEnv *env, jobject self, jlong handle, jbyteArray input)
{
(void)self;
android_parser_t *parser = (android_parser_t *)(intptr_t)handle;
if (parser == NULL) {
return NULL;
}
jsize size = (*env)->GetArrayLength(env, input);
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
if ((data == NULL) && (size != 0)) {
return NULL;
}
size_t capacity = ((size_t)size / 11U + 2U) * 15U;
uint8_t *records = (uint8_t *)malloc(capacity);
if (records == NULL) {
if (data != NULL) {
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
}
return NULL;
}
size_t used = 0U;
pcan_abi_frame_t frame;
for (jsize i = 0; i < size; ++i) {
if (pcan_abi_parser_push(parser->storage, (uint8_t)data[i], &frame) > 0) {
uint8_t *record = &records[used];
record[0] = frame.sequence;
record[1] = frame.flags;
record[2] = (uint8_t)frame.can_id;
record[3] = (uint8_t)(frame.can_id >> 8);
record[4] = (uint8_t)(frame.can_id >> 16);
record[5] = (uint8_t)(frame.can_id >> 24);
record[6] = frame.dlc;
memset(&record[7], 0, 8U);
memcpy(&record[7], frame.data, frame.dlc);
used += 15U;
if (parser->last_sequence >= 0) {
parser->sequence_lost += (uint8_t)(
frame.sequence - (uint8_t)parser->last_sequence - 1U);
}
parser->last_sequence = frame.sequence;
}
}
if (data != NULL) {
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
}
jbyteArray result = (*env)->NewByteArray(env, (jsize)used);
if (result != NULL && used != 0U) {
(*env)->SetByteArrayRegion(env, result, 0, (jsize)used,
(const jbyte *)records);
}
free(records);
return result;
}
JNIEXPORT jintArray JNICALL
Java_ru_setcorp_setcore_NativeProtoCan_nativeParserStats(
JNIEnv *env, jobject self, jlong handle)
{
(void)self;
android_parser_t *parser = (android_parser_t *)(intptr_t)handle;
if (parser == NULL) {
return NULL;
}
uint32_t frames = 0U, crc = 0U, bad_len = 0U, stray = 0U;
pcan_abi_parser_stats(parser->storage, &frames, &crc, &bad_len, &stray);
jint values[5] = { (jint)frames, (jint)crc, (jint)bad_len,
(jint)stray, (jint)parser->sequence_lost };
jintArray result = (*env)->NewIntArray(env, 5);
if (result != NULL) {
(*env)->SetIntArrayRegion(env, result, 0, 5, values);
}
return result;
}

View File

@@ -0,0 +1,136 @@
#include "pcan_abi.h"
#include <string.h>
#include "pcan_crc.h"
#include "pcan_frame.h"
#include "pcan_id.h"
uint32_t pcan_abi_version(void)
{
return PCAN_ABI_VERSION;
}
uint32_t pcan_abi_id_pack(uint8_t priority, uint8_t route,
uint8_t device_type, uint8_t device_id,
uint8_t message_type, uint16_t body)
{
pcan_id_t id;
id.msg_body = body;
id.msg_type = message_type;
id.device_id = device_id;
id.device_type = device_type;
id.route = route;
id.priority = priority;
return pcan_id_pack(&id);
}
void pcan_abi_id_unpack(uint32_t raw, uint8_t *priority, uint8_t *route,
uint8_t *device_type, uint8_t *device_id,
uint8_t *message_type, uint16_t *body)
{
pcan_id_t id;
pcan_id_unpack(raw, &id);
if (priority != NULL) {
*priority = id.priority;
}
if (route != NULL) {
*route = id.route;
}
if (device_type != NULL) {
*device_type = id.device_type;
}
if (device_id != NULL) {
*device_id = id.device_id;
}
if (message_type != NULL) {
*message_type = id.msg_type;
}
if (body != NULL) {
*body = id.msg_body;
}
}
uint16_t pcan_abi_crc16(const uint8_t *data, size_t size)
{
if ((data == NULL) && (size != 0U)) {
return 0U;
}
return pcan_crc16(data, size);
}
size_t pcan_abi_frame_encode(uint8_t sequence, uint8_t flags,
uint32_t can_id, const uint8_t *data, uint8_t dlc,
uint8_t *output, size_t output_size)
{
pcan_frame_t frame;
if ((output == NULL) || (dlc > PCAN_DATA_MAX) ||
((data == NULL) && (dlc != 0U))) {
return 0U;
}
memset(&frame, 0, sizeof frame);
frame.seq = sequence;
frame.flags = flags;
frame.id = can_id;
frame.dlc = dlc;
if (dlc != 0U) {
memcpy(frame.data, data, dlc);
}
return pcan_frame_encode(&frame, output, output_size);
}
size_t pcan_abi_parser_size(void)
{
return sizeof(pcan_parser_t);
}
int pcan_abi_parser_init(void *parser_storage, size_t storage_size)
{
if ((parser_storage == NULL) || (storage_size < sizeof(pcan_parser_t))) {
return 0;
}
pcan_parser_init((pcan_parser_t *)parser_storage);
return 1;
}
int pcan_abi_parser_push(void *parser_storage, uint8_t byte,
pcan_abi_frame_t *output)
{
pcan_frame_t frame;
if ((parser_storage == NULL) || (output == NULL)) {
return -1;
}
if (!pcan_parser_push((pcan_parser_t *)parser_storage, byte, &frame)) {
return 0;
}
output->can_id = frame.id;
output->sequence = frame.seq;
output->flags = frame.flags;
output->dlc = frame.dlc;
memset(output->data, 0, sizeof output->data);
memcpy(output->data, frame.data, frame.dlc);
return 1;
}
int pcan_abi_parser_stats(const void *parser_storage, uint32_t *frames,
uint32_t *crc_errors, uint32_t *bad_length,
uint32_t *stray_bytes)
{
const pcan_parser_t *parser = (const pcan_parser_t *)parser_storage;
if (parser == NULL) {
return 0;
}
if (frames != NULL) {
*frames = parser->stats.frames;
}
if (crc_errors != NULL) {
*crc_errors = parser->stats.crc_errors;
}
if (bad_length != NULL) {
*bad_length = parser->stats.bad_len;
}
if (stray_bytes != NULL) {
*stray_bytes = parser->stats.stray_bytes;
}
return 1;
}

View File

@@ -0,0 +1,43 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pcan_abi.h"
int main(void)
{
uint32_t raw = pcan_abi_id_pack(1U, 0U, 7U, 14U, 3U, 0x1234U);
if (raw != 0x17E31234UL) {
return 1;
}
uint8_t data[] = { 0xAAU, 0xBBU };
uint8_t encoded[32];
size_t encoded_size = pcan_abi_frame_encode(
1U, 0x05U, raw, data, 2U, encoded, sizeof encoded);
if (encoded_size == 0U) {
return 2;
}
size_t parser_size = pcan_abi_parser_size();
void *parser = malloc(parser_size);
if ((parser == NULL) || !pcan_abi_parser_init(parser, parser_size)) {
free(parser);
return 3;
}
pcan_abi_frame_t frame;
memset(&frame, 0, sizeof frame);
int found = 0;
for (size_t i = 0U; i < encoded_size; ++i) {
found += pcan_abi_parser_push(parser, encoded[i], &frame) > 0;
}
free(parser);
if ((found != 1) || (frame.can_id != raw) || (frame.dlc != 2U) ||
(frame.data[0] != 0xAAU) || (frame.data[1] != 0xBBU)) {
return 4;
}
puts("ABI tests passed");
return 0;
}

View File

@@ -1 +1,5 @@
"""Переносимые модули протоколов ProtoCAN и SETGUI: только stdlib."""
"""Переносимые модули ProtoCAN и тонкая обёртка общего C99-ядра."""
from .native import NativeCore, NativeCoreUnavailable, NativeFrame, NativeParser
__all__ = ["NativeCore", "NativeCoreUnavailable", "NativeFrame", "NativeParser"]

154
python/protocan/native.py Normal file
View File

@@ -0,0 +1,154 @@
"""ctypes port for the shared C99 ProtoCAN core.
The protocol implementation lives in ``c/protocan-transport``. This module
only converts Python values to the stable ``pcan_abi.h`` interface.
"""
from __future__ import annotations
import ctypes
import ctypes.util
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Iterable
class NativeCoreUnavailable(RuntimeError):
"""Raised when the SETCore shared library cannot be loaded."""
class _AbiFrame(ctypes.Structure):
_fields_ = [
("can_id", ctypes.c_uint32),
("sequence", ctypes.c_uint8),
("flags", ctypes.c_uint8),
("dlc", ctypes.c_uint8),
("data", ctypes.c_uint8 * 8),
]
@dataclass(frozen=True)
class NativeFrame:
sequence: int
flags: int
can_id: int
data: bytes
def _library_candidates() -> Iterable[Path | str]:
explicit = os.environ.get("SETCORE_LIBRARY")
if explicit:
yield Path(explicit)
here = Path(__file__).resolve()
names = ("setcore.dll", "libsetcore.so", "libsetcore.dylib")
for parent in (here.parent, *here.parents[:5]):
for name in names:
yield parent / "native" / name
yield parent / name
discovered = ctypes.util.find_library("setcore")
if discovered:
yield discovered
def _load_library() -> ctypes.CDLL:
errors: list[str] = []
for candidate in _library_candidates():
try:
return ctypes.CDLL(str(candidate))
except OSError as exc:
errors.append(f"{candidate}: {exc}")
raise NativeCoreUnavailable(
"SETCore library not found. Build c/protocan-transport with CMake or "
"set SETCORE_LIBRARY. Tried: " + "; ".join(errors)
)
class NativeCore:
"""Thin owner of the C ABI and its function signatures."""
def __init__(self, library: ctypes.CDLL | None = None) -> None:
self.lib = library or _load_library()
self._bind()
version = int(self.lib.pcan_abi_version())
if version != 1:
raise NativeCoreUnavailable(f"unsupported SETCore ABI {version}")
def _bind(self) -> None:
lib = self.lib
lib.pcan_abi_version.restype = ctypes.c_uint32
lib.pcan_abi_id_pack.argtypes = [
ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint8,
ctypes.c_uint8, ctypes.c_uint8, ctypes.c_uint16,
]
lib.pcan_abi_id_pack.restype = ctypes.c_uint32
lib.pcan_abi_crc16.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
lib.pcan_abi_crc16.restype = ctypes.c_uint16
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,
]
lib.pcan_abi_frame_encode.restype = ctypes.c_size_t
lib.pcan_abi_parser_size.restype = ctypes.c_size_t
lib.pcan_abi_parser_init.argtypes = [ctypes.c_void_p, ctypes.c_size_t]
lib.pcan_abi_parser_init.restype = ctypes.c_int
lib.pcan_abi_parser_push.argtypes = [
ctypes.c_void_p, ctypes.c_uint8, ctypes.POINTER(_AbiFrame),
]
lib.pcan_abi_parser_push.restype = ctypes.c_int
def id_pack(self, priority: int, route: int, device_type: int,
device_id: int, message_type: int, body: int) -> int:
return int(self.lib.pcan_abi_id_pack(
priority, route, device_type, device_id, message_type, body))
def crc16(self, data: bytes) -> int:
source = (ctypes.c_uint8 * len(data)).from_buffer_copy(data) if data else None
return int(self.lib.pcan_abi_crc16(source, len(data)))
def encode(self, sequence: int, flags: int, can_id: int, data: bytes) -> bytes:
if len(data) > 8:
raise ValueError("DLC cannot exceed 8 bytes")
source = (ctypes.c_uint8 * len(data)).from_buffer_copy(data) if data else None
output = (ctypes.c_uint8 * 19)()
size = int(self.lib.pcan_abi_frame_encode(
sequence, flags, can_id, source, len(data), output, len(output)))
if size == 0:
raise ValueError("SETCore rejected the CAN frame")
return bytes(output[:size])
def parser(self) -> "NativeParser":
return NativeParser(self)
class NativeParser:
def __init__(self, core: NativeCore) -> None:
self._core = core
size = int(core.lib.pcan_abi_parser_size())
self._storage = ctypes.create_string_buffer(size)
if not core.lib.pcan_abi_parser_init(self._storage, size):
raise NativeCoreUnavailable("SETCore parser initialization failed")
def feed(self, chunk: bytes) -> list[NativeFrame]:
frames: list[NativeFrame] = []
raw = _AbiFrame()
for byte in chunk:
result = self._core.lib.pcan_abi_parser_push(
self._storage, byte, ctypes.byref(raw))
if result < 0:
raise NativeCoreUnavailable("SETCore parser rejected its context")
if result > 0:
frames.append(NativeFrame(
int(raw.sequence), int(raw.flags), int(raw.can_id),
bytes(raw.data[:raw.dlc])))
return frames
_default_core: NativeCore | None = None
def get_native_core() -> NativeCore:
global _default_core
if _default_core is None:
_default_core = NativeCore()
return _default_core