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

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;
}