Добавить порт SETProtocol v2 для TMS320F2812
This commit is contained in:
@@ -34,8 +34,9 @@ templates/
|
||||
| [`c/eeprom-ft24c256`](c/eeprom-ft24c256) | EEPROM 24Cxx по I²C с нарезкой записи по страницам | `stdint.h` | две I²C-транзакции, задержка |
|
||||
| [`c/can-sensor`](c/can-sensor) | однокадровые SETCAN SETTINGS для 64-битных ROM | ядро: `stdint.h`; порт F1: CMSIS | callbacks либо готовый bxCAN STM32F1 |
|
||||
| [`c/ds18b20`](c/ds18b20) | термометры DS18B20 поверх программной 1-Wire | `stdint.h` | Init, DelayUs, Reset, WriteBit, ReadBit — **порты STM32F103, STM32G431 и STM32G474 в комплекте** |
|
||||
| [`c/set-protocol`](c/set-protocol) | единое ядро SETProtocol: SET v2, совместимые ProtoCAN/GUI v1, GAS, телеметрия, firmware flow и стабильный host ABI | C99 | COM/SLCAN/SocketCAN/USB/Ethernet или callbacks — **Windows, Android и STM32F4-порты в комплекте** |
|
||||
| [`c/set-protocol`](c/set-protocol) | единое ядро SETProtocol: SET v2, совместимые ProtoCAN/GUI v1, GAS, телеметрия, firmware flow и стабильный host ABI | C99 | COM/SLCAN/SocketCAN/USB/Ethernet или callbacks — **Windows, Android, STM32F4 и TMS320F2812-порты в комплекте** |
|
||||
| [`c/set-protocol/ports/stm32-bxcan`](c/set-protocol/ports/stm32-bxcan) | порт прикладного ProtoCAN для STM32, бывший SETCAN; сохранён API `PROTOCAN_*` | STM32 HAL CAN/RTC/TIM + общее ядро `pcan_id` | classic bxCAN; настройки платы предоставляет прошивка |
|
||||
| [`c/set-protocol/ports/tms320f2812`](c/set-protocol/ports/tms320f2812) | SETProtocol v2 firmware service по segmented classic CAN | `set_protocol`, `set_can`, `set_firmware` | CAN TX, Flash erase/write/read, optional signature policy и reboot |
|
||||
| [`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/rtc-service`](c/rtc-service) | RTC с резервированным backup-томом | `stdint.h` | доступ к RTC и backup-памяти — **порт K1921VK028 в комплекте** |
|
||||
|
||||
@@ -107,6 +107,13 @@ if(SETP_BUILD_TESTS)
|
||||
add_executable(test_tms2812 tests/test_tms2812.c)
|
||||
target_link_libraries(test_tms2812 PRIVATE setprotocol_static)
|
||||
add_test(NAME shared_tms2812 COMMAND test_tms2812)
|
||||
add_executable(test_tms2812_boot
|
||||
tests/test_tms2812_boot.c
|
||||
ports/tms320f2812/setp_tms2812_boot.c
|
||||
)
|
||||
target_include_directories(test_tms2812_boot PRIVATE ports/tms320f2812)
|
||||
target_link_libraries(test_tms2812_boot PRIVATE setprotocol_static)
|
||||
add_test(NAME setp_tms2812_boot COMMAND test_tms2812_boot)
|
||||
add_executable(test_trends tests/test_trends.c)
|
||||
target_link_libraries(test_trends PRIVATE setprotocol_static)
|
||||
add_test(NAME shared_trends COMMAND test_trends)
|
||||
|
||||
@@ -43,6 +43,11 @@ multicast запрещены WRITE, FW_BEGIN и FW_ACTIVATE.
|
||||
В проект устройства добавляются `ports/stm32-bxcan/protocan.c` и `src/pcan_id.c`,
|
||||
а в include paths — `ports/stm32-bxcan` и `include`.
|
||||
|
||||
Для TMS320F2812 доступен SETProtocol v2 firmware service
|
||||
[`ports/tms320f2812`](ports/tms320f2812/README.md). Он использует общую
|
||||
сегментацию `set_can`, собирается без TI headers и получает eCAN, Flash и reset
|
||||
через callbacks проекта платы.
|
||||
|
||||
Не вызывайте прикладной router из ISR. ISR принимает CAN-кадры в очередь,
|
||||
сегментация собирается в главном цикле, и только полный SETP-кадр передаётся
|
||||
parser/router. Сборка должна иметь timeout, контроль номера сегмента и одного
|
||||
|
||||
23
c/set-protocol/ports/tms320f2812/README.md
Normal file
23
c/set-protocol/ports/tms320f2812/README.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# SETProtocol v2 firmware port for TMS320F2812
|
||||
|
||||
`setp_tms2812_boot.c` implements the shared SETP v2 CAN firmware service for
|
||||
F2812 projects. It owns CAN reassembly, request/response framing, firmware state,
|
||||
idempotent blocks, CRC32 and SHA-256 verification. It does not include TI or
|
||||
board headers.
|
||||
|
||||
The target application provides callbacks for classic-CAN transmission, Flash
|
||||
erase/program/read, optional signature authorization, and reset. Receive ISR
|
||||
code must only copy frames into a queue; call `setp_tms2812_boot_process()` from
|
||||
task context.
|
||||
|
||||
Add these sources to a CCS project:
|
||||
|
||||
- `c/set-protocol/src/set_protocol.c`
|
||||
- `c/set-protocol/src/set_can.c`
|
||||
- `c/set-protocol/src/set_firmware.c`
|
||||
- `c/set-protocol/ports/tms320f2812/setp_tms2812_boot.c`
|
||||
|
||||
Add `c/set-protocol/include` and this directory to include paths. The port uses
|
||||
extended 29-bit SETP CAN identifiers and supports one firmware slot. A single
|
||||
slot has no power-loss rollback; production hardware should provide staging or
|
||||
A/B storage.
|
||||
407
c/set-protocol/ports/tms320f2812/setp_tms2812_boot.c
Normal file
407
c/set-protocol/ports/tms320f2812/setp_tms2812_boot.c
Normal file
@@ -0,0 +1,407 @@
|
||||
#include "setp_tms2812_boot.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#define SHA256_BLOCK_SIZE 64U
|
||||
|
||||
typedef struct {
|
||||
uint32_t state[8];
|
||||
uint64_t bit_count;
|
||||
uint8_t block[SHA256_BLOCK_SIZE];
|
||||
uint16_t block_length;
|
||||
} sha256_ctx_t;
|
||||
|
||||
static uint32_t rotr32(uint32_t value, uint8_t bits)
|
||||
{
|
||||
return (value >> bits) | (value << (32U - bits));
|
||||
}
|
||||
|
||||
static void sha256_transform(sha256_ctx_t *ctx, const uint8_t *block)
|
||||
{
|
||||
static const uint32_t k[64] = {
|
||||
0x428A2F98UL, 0x71374491UL, 0xB5C0FBCFUL, 0xE9B5DBA5UL,
|
||||
0x3956C25BUL, 0x59F111F1UL, 0x923F82A4UL, 0xAB1C5ED5UL,
|
||||
0xD807AA98UL, 0x12835B01UL, 0x243185BEUL, 0x550C7DC3UL,
|
||||
0x72BE5D74UL, 0x80DEB1FEUL, 0x9BDC06A7UL, 0xC19BF174UL,
|
||||
0xE49B69C1UL, 0xEFBE4786UL, 0x0FC19DC6UL, 0x240CA1CCUL,
|
||||
0x2DE92C6FUL, 0x4A7484AAUL, 0x5CB0A9DCUL, 0x76F988DAUL,
|
||||
0x983E5152UL, 0xA831C66DUL, 0xB00327C8UL, 0xBF597FC7UL,
|
||||
0xC6E00BF3UL, 0xD5A79147UL, 0x06CA6351UL, 0x14292967UL,
|
||||
0x27B70A85UL, 0x2E1B2138UL, 0x4D2C6DFCUL, 0x53380D13UL,
|
||||
0x650A7354UL, 0x766A0ABBUL, 0x81C2C92EUL, 0x92722C85UL,
|
||||
0xA2BFE8A1UL, 0xA81A664BUL, 0xC24B8B70UL, 0xC76C51A3UL,
|
||||
0xD192E819UL, 0xD6990624UL, 0xF40E3585UL, 0x106AA070UL,
|
||||
0x19A4C116UL, 0x1E376C08UL, 0x2748774CUL, 0x34B0BCB5UL,
|
||||
0x391C0CB3UL, 0x4ED8AA4AUL, 0x5B9CCA4FUL, 0x682E6FF3UL,
|
||||
0x748F82EEUL, 0x78A5636FUL, 0x84C87814UL, 0x8CC70208UL,
|
||||
0x90BEFFFAUL, 0xA4506CEBUL, 0xBEF9A3F7UL, 0xC67178F2UL
|
||||
};
|
||||
uint32_t w[64];
|
||||
uint32_t a, b, c, d, e, f, g, h, s0, s1, ch, maj, temp1, temp2;
|
||||
uint16_t i;
|
||||
for (i = 0U; i < 16U; i++) {
|
||||
uint16_t p = (uint16_t)(i * 4U);
|
||||
w[i] = ((uint32_t)block[p] << 24U)
|
||||
| ((uint32_t)block[p + 1U] << 16U)
|
||||
| ((uint32_t)block[p + 2U] << 8U)
|
||||
| (uint32_t)block[p + 3U];
|
||||
}
|
||||
for (i = 16U; i < 64U; i++) {
|
||||
s0 = rotr32(w[i - 15U], 7U) ^ rotr32(w[i - 15U], 18U)
|
||||
^ (w[i - 15U] >> 3U);
|
||||
s1 = rotr32(w[i - 2U], 17U) ^ rotr32(w[i - 2U], 19U)
|
||||
^ (w[i - 2U] >> 10U);
|
||||
w[i] = w[i - 16U] + s0 + w[i - 7U] + s1;
|
||||
}
|
||||
a = ctx->state[0]; b = ctx->state[1]; c = ctx->state[2]; d = ctx->state[3];
|
||||
e = ctx->state[4]; f = ctx->state[5]; g = ctx->state[6]; h = ctx->state[7];
|
||||
for (i = 0U; i < 64U; i++) {
|
||||
s1 = rotr32(e, 6U) ^ rotr32(e, 11U) ^ rotr32(e, 25U);
|
||||
ch = (e & f) ^ ((~e) & g);
|
||||
temp1 = h + s1 + ch + k[i] + w[i];
|
||||
s0 = rotr32(a, 2U) ^ rotr32(a, 13U) ^ rotr32(a, 22U);
|
||||
maj = (a & b) ^ (a & c) ^ (b & c);
|
||||
temp2 = s0 + maj;
|
||||
h = g; g = f; f = e; e = d + temp1;
|
||||
d = c; c = b; b = a; a = temp1 + temp2;
|
||||
}
|
||||
ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d;
|
||||
ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h;
|
||||
}
|
||||
|
||||
static void sha256_init(sha256_ctx_t *ctx)
|
||||
{
|
||||
static const uint32_t initial[8] = {
|
||||
0x6A09E667UL, 0xBB67AE85UL, 0x3C6EF372UL, 0xA54FF53AUL,
|
||||
0x510E527FUL, 0x9B05688CUL, 0x1F83D9ABUL, 0x5BE0CD19UL
|
||||
};
|
||||
(void)memcpy(ctx->state, initial, sizeof(initial));
|
||||
ctx->bit_count = 0U;
|
||||
ctx->block_length = 0U;
|
||||
}
|
||||
|
||||
static void sha256_update(sha256_ctx_t *ctx, const uint8_t *data, uint16_t length)
|
||||
{
|
||||
uint16_t i;
|
||||
for (i = 0U; i < length; i++) {
|
||||
ctx->block[ctx->block_length++] = data[i];
|
||||
ctx->bit_count += 8U;
|
||||
if (ctx->block_length == SHA256_BLOCK_SIZE) {
|
||||
sha256_transform(ctx, ctx->block);
|
||||
ctx->block_length = 0U;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void sha256_finish(sha256_ctx_t *ctx, uint8_t digest[SETP_SHA256_SIZE])
|
||||
{
|
||||
uint16_t i;
|
||||
uint64_t bits = ctx->bit_count;
|
||||
ctx->block[ctx->block_length++] = 0x80U;
|
||||
if (ctx->block_length > 56U) {
|
||||
while (ctx->block_length < SHA256_BLOCK_SIZE) ctx->block[ctx->block_length++] = 0U;
|
||||
sha256_transform(ctx, ctx->block);
|
||||
ctx->block_length = 0U;
|
||||
}
|
||||
while (ctx->block_length < 56U) ctx->block[ctx->block_length++] = 0U;
|
||||
for (i = 0U; i < 8U; i++) {
|
||||
ctx->block[63U - i] = (uint8_t)(bits & 0xFFU);
|
||||
bits >>= 8U;
|
||||
}
|
||||
sha256_transform(ctx, ctx->block);
|
||||
for (i = 0U; i < 8U; i++) {
|
||||
digest[i * 4U] = (uint8_t)(ctx->state[i] >> 24U);
|
||||
digest[i * 4U + 1U] = (uint8_t)(ctx->state[i] >> 16U);
|
||||
digest[i * 4U + 2U] = (uint8_t)(ctx->state[i] >> 8U);
|
||||
digest[i * 4U + 3U] = (uint8_t)ctx->state[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void boot_status(const setp_tms2812_boot_t *boot, setp_fw_status_t *status)
|
||||
{
|
||||
status->state = boot->state;
|
||||
status->active_slot = boot->config.active_slot;
|
||||
status->max_block_size = boot->config.max_block_size;
|
||||
status->next_offset = boot->next_offset;
|
||||
status->image_size = boot->manifest.image_size;
|
||||
status->last_error = boot->last_error;
|
||||
status->flags = 0U;
|
||||
}
|
||||
|
||||
static bool boot_send_response(setp_tms2812_boot_t *boot,
|
||||
const setp_frame_t *request,
|
||||
const setp_can_id_t *request_id,
|
||||
uint16_t status,
|
||||
const uint8_t *body, uint16_t body_length)
|
||||
{
|
||||
setp_frame_t response;
|
||||
setp_can_id_t response_id;
|
||||
size_t packet_length;
|
||||
if ((uint32_t)body_length + 2U > sizeof(boot->response_payload)) return false;
|
||||
setp_put_u16(boot->response_payload, status);
|
||||
if (body_length != 0U) (void)memcpy(&boot->response_payload[2], body, body_length);
|
||||
response.flags = SETP_FLAG_RESPONSE;
|
||||
if (status != SETP_STATUS_OK) response.flags |= SETP_FLAG_ERROR;
|
||||
if ((request->flags & SETP_FLAG_PRIORITY) != 0U) response.flags |= SETP_FLAG_PRIORITY;
|
||||
response.message_type = request->message_type;
|
||||
response.source = boot->config.node_id;
|
||||
response.destination = request->source;
|
||||
response.sequence = request->sequence;
|
||||
response.payload_length = (uint16_t)(body_length + 2U);
|
||||
response.payload = boot->response_payload;
|
||||
packet_length = setp_frame_encode(&response, boot->response_packet,
|
||||
sizeof(boot->response_packet));
|
||||
if (packet_length == 0U) return false;
|
||||
response_id.destination = request_id->source;
|
||||
response_id.source = boot->config.node_id;
|
||||
response_id.priority = (response.flags & SETP_FLAG_PRIORITY) != 0U ? 1U : 0U;
|
||||
response_id.channel = request_id->channel;
|
||||
return setp_can_segment(boot->response_packet, (uint16_t)packet_length,
|
||||
setp_can_id_pack(&response_id), boot->port.send_can,
|
||||
boot->port_user);
|
||||
}
|
||||
|
||||
static uint16_t boot_verify_image(setp_tms2812_boot_t *boot)
|
||||
{
|
||||
uint8_t data[SETP_TMS2812_MAX_BLOCK_SIZE];
|
||||
uint8_t digest[SETP_SHA256_SIZE];
|
||||
sha256_ctx_t sha;
|
||||
uint32_t crc = 0xFFFFFFFFUL;
|
||||
uint32_t offset = 0U;
|
||||
uint16_t i;
|
||||
sha256_init(&sha);
|
||||
while (offset < boot->manifest.image_size) {
|
||||
uint32_t remaining = boot->manifest.image_size - offset;
|
||||
uint16_t length = remaining > sizeof(data) ? (uint16_t)sizeof(data) : (uint16_t)remaining;
|
||||
if (!boot->port.read_image(boot->port_user, offset, data, length)) {
|
||||
return SETP_STATUS_INTERNAL;
|
||||
}
|
||||
sha256_update(&sha, data, length);
|
||||
for (i = 0U; i < length; i++) {
|
||||
uint8_t bit;
|
||||
crc ^= data[i];
|
||||
for (bit = 0U; bit < 8U; bit++)
|
||||
crc = (crc >> 1U) ^ (((crc & 1U) != 0U) ? 0xEDB88320UL : 0U);
|
||||
}
|
||||
offset += length;
|
||||
}
|
||||
sha256_finish(&sha, digest);
|
||||
crc ^= 0xFFFFFFFFUL;
|
||||
if ((crc != boot->manifest.image_crc32)
|
||||
|| (memcmp(digest, boot->manifest.sha256, SETP_SHA256_SIZE) != 0)) {
|
||||
return SETP_STATUS_VERIFY_FAILED;
|
||||
}
|
||||
return SETP_STATUS_OK;
|
||||
}
|
||||
|
||||
static uint16_t boot_fw_begin(setp_tms2812_boot_t *boot, const setp_frame_t *request)
|
||||
{
|
||||
setp_fw_begin_t value;
|
||||
bool same_manifest;
|
||||
if (!setp_fw_begin_decode(request->payload, request->payload_length, &value))
|
||||
return SETP_STATUS_INVALID_LENGTH;
|
||||
if ((value.image_size == 0U) || (value.image_size > boot->config.max_image_size)
|
||||
|| (value.slot != boot->config.active_slot)
|
||||
|| ((value.base_address != 0U)
|
||||
&& (value.base_address != boot->config.app_base_address))
|
||||
|| (value.block_size == 0U)
|
||||
|| (value.block_size > boot->config.max_block_size))
|
||||
return SETP_STATUS_INVALID_ARGUMENT;
|
||||
if (((value.flags & SETP_FW_FLAG_SIGNED) != 0U) || boot->config.require_signature) {
|
||||
if ((boot->port.authorize == NULL)
|
||||
|| !boot->port.authorize(boot->port_user, &value))
|
||||
return SETP_STATUS_AUTH_FAILED;
|
||||
}
|
||||
same_manifest = boot->state == SETP_FW_RECEIVING
|
||||
&& boot->manifest.image_size == value.image_size
|
||||
&& boot->manifest.image_crc32 == value.image_crc32
|
||||
&& boot->manifest.image_version == value.image_version
|
||||
&& memcmp(boot->manifest.sha256, value.sha256, SETP_SHA256_SIZE) == 0;
|
||||
if (same_manifest && ((value.flags & SETP_FW_FLAG_RESUME) != 0U)) return SETP_STATUS_OK;
|
||||
if (!boot->port.erase_image(boot->port_user, value.image_size))
|
||||
return SETP_STATUS_INTERNAL;
|
||||
boot->manifest = value;
|
||||
boot->manifest.signature = NULL;
|
||||
boot->manifest.signature_length = 0U;
|
||||
boot->next_offset = 0U;
|
||||
boot->state = SETP_FW_RECEIVING;
|
||||
return SETP_STATUS_OK;
|
||||
}
|
||||
|
||||
static uint16_t boot_fw_data(setp_tms2812_boot_t *boot, const setp_frame_t *request)
|
||||
{
|
||||
setp_fw_data_t value;
|
||||
uint8_t current[SETP_TMS2812_MAX_BLOCK_SIZE];
|
||||
if (boot->state != SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE;
|
||||
if (!setp_fw_data_decode(request->payload, request->payload_length, &value))
|
||||
return SETP_STATUS_CRC;
|
||||
if ((value.data_length > boot->manifest.block_size)
|
||||
|| (value.data_length > boot->config.max_block_size)
|
||||
|| (value.offset > boot->manifest.image_size)
|
||||
|| ((uint32_t)value.data_length > boot->manifest.image_size - value.offset))
|
||||
return SETP_STATUS_INVALID_ARGUMENT;
|
||||
if (value.offset < boot->next_offset) {
|
||||
if ((value.offset + value.data_length > boot->next_offset)
|
||||
|| !boot->port.read_image(boot->port_user, value.offset,
|
||||
current, value.data_length)
|
||||
|| memcmp(current, value.data, value.data_length) != 0)
|
||||
return SETP_STATUS_SEQUENCE;
|
||||
return SETP_STATUS_OK;
|
||||
}
|
||||
if (value.offset != boot->next_offset) return SETP_STATUS_SEQUENCE;
|
||||
if (!boot->port.write_image(boot->port_user, value.offset,
|
||||
value.data, value.data_length))
|
||||
return SETP_STATUS_INTERNAL;
|
||||
boot->next_offset += value.data_length;
|
||||
return SETP_STATUS_OK;
|
||||
}
|
||||
|
||||
static uint16_t boot_fw_end(setp_tms2812_boot_t *boot, const setp_frame_t *request)
|
||||
{
|
||||
setp_fw_end_t value;
|
||||
uint16_t status;
|
||||
if (boot->state != SETP_FW_RECEIVING) return SETP_STATUS_WRONG_STATE;
|
||||
if (!setp_fw_end_decode(request->payload, request->payload_length, &value))
|
||||
return SETP_STATUS_INVALID_LENGTH;
|
||||
if ((boot->next_offset != boot->manifest.image_size)
|
||||
|| (value.image_size != boot->manifest.image_size)
|
||||
|| (value.image_crc32 != boot->manifest.image_crc32)
|
||||
|| (memcmp(value.sha256, boot->manifest.sha256, SETP_SHA256_SIZE) != 0))
|
||||
return SETP_STATUS_VERIFY_FAILED;
|
||||
boot->state = SETP_FW_VERIFYING;
|
||||
status = boot_verify_image(boot);
|
||||
boot->state = status == SETP_STATUS_OK ? SETP_FW_READY : SETP_FW_FAILED;
|
||||
return status;
|
||||
}
|
||||
|
||||
static bool boot_dispatch(setp_tms2812_boot_t *boot, const setp_frame_t *request,
|
||||
const setp_can_id_t *request_id, uint32_t now_ms)
|
||||
{
|
||||
uint8_t body[SETP_TMS2812_RESPONSE_PAYLOAD_SIZE - 2U];
|
||||
uint16_t body_length = 0U;
|
||||
uint16_t status = SETP_STATUS_OK;
|
||||
uint8_t reboot = 0U;
|
||||
setp_fw_status_t fw_status;
|
||||
if ((request->flags & (SETP_FLAG_RESPONSE | SETP_FLAG_EVENT)) != 0U) return false;
|
||||
if ((request->source > 0xFFU) || (request->destination != boot->config.node_id)) return false;
|
||||
switch (request->message_type) {
|
||||
case SETP_MSG_PING:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else { setp_put_u32(body, now_ms); body_length = 4U; }
|
||||
break;
|
||||
case SETP_MSG_DEVICE_INFO:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else {
|
||||
setp_device_info_t info;
|
||||
info.schema_version = SETP_DEVICE_INFO_SCHEMA_VERSION;
|
||||
info.device_class = boot->config.device_class;
|
||||
info.hardware_version = boot->config.hardware_version;
|
||||
info.firmware_version = boot->config.firmware_version;
|
||||
info.dictionary_version = boot->config.dictionary_version;
|
||||
info.serial_number = boot->config.serial_number;
|
||||
info.model_length = boot->config.model_length;
|
||||
info.model = boot->config.model;
|
||||
body_length = (uint16_t)setp_device_info_encode(&info, body, sizeof(body));
|
||||
if (body_length == 0U) status = SETP_STATUS_INTERNAL;
|
||||
}
|
||||
break;
|
||||
case SETP_MSG_CAPABILITIES:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else {
|
||||
setp_capabilities_t caps;
|
||||
caps.schema_version = SETP_CAPABILITIES_SCHEMA_VERSION;
|
||||
caps.max_payload = (uint16_t)(SETP_FW_DATA_HEADER_SIZE + boot->config.max_block_size);
|
||||
caps.interface_mask = SETP_IFACE_MASK(SETP_IFACE_CAN);
|
||||
caps.feature_flags = SETP_FEATURE_FIRMWARE;
|
||||
caps.max_read_items = 0U; caps.max_write_items = 0U;
|
||||
caps.max_subscriptions = 0U; caps.max_publish_items = 0U;
|
||||
body_length = (uint16_t)setp_capabilities_encode(&caps, body, sizeof(body));
|
||||
if (body_length == 0U) status = SETP_STATUS_INTERNAL;
|
||||
}
|
||||
break;
|
||||
case SETP_MSG_FW_BEGIN:
|
||||
status = boot_fw_begin(boot, request);
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
case SETP_MSG_FW_DATA:
|
||||
status = boot_fw_data(boot, request);
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
case SETP_MSG_FW_END:
|
||||
status = boot_fw_end(boot, request);
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
case SETP_MSG_FW_ABORT:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else setp_tms2812_boot_abort(boot);
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
case SETP_MSG_FW_STATUS:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else {
|
||||
boot_status(boot, &fw_status);
|
||||
body_length = (uint16_t)setp_fw_status_encode(&fw_status, body, sizeof(body));
|
||||
}
|
||||
break;
|
||||
case SETP_MSG_FW_ACTIVATE:
|
||||
if (request->payload_length != 0U) status = SETP_STATUS_INVALID_LENGTH;
|
||||
else if (boot->state != SETP_FW_READY) status = SETP_STATUS_WRONG_STATE;
|
||||
else { boot->state = SETP_FW_ACTIVE; reboot = 1U; }
|
||||
setp_put_u32(body, boot->next_offset); body_length = 4U;
|
||||
break;
|
||||
default:
|
||||
status = SETP_STATUS_UNSUPPORTED;
|
||||
break;
|
||||
}
|
||||
boot->last_error = status == SETP_STATUS_OK ? 0U : status;
|
||||
if (!boot_send_response(boot, request, request_id, status, body, body_length)) return false;
|
||||
if (reboot != 0U) boot->port.reboot(boot->port_user);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setp_tms2812_boot_init(setp_tms2812_boot_t *boot,
|
||||
const setp_tms2812_boot_config_t *config,
|
||||
const setp_tms2812_boot_port_t *port,
|
||||
void *port_user)
|
||||
{
|
||||
if ((boot == NULL) || (config == NULL) || (port == NULL)
|
||||
|| (config->model == NULL) || (config->model_length > SETP_DEVICE_MODEL_MAX)
|
||||
|| (config->max_image_size == 0U) || (config->max_block_size == 0U)
|
||||
|| (config->max_block_size > SETP_TMS2812_MAX_BLOCK_SIZE)
|
||||
|| (port->send_can == NULL) || (port->erase_image == NULL)
|
||||
|| (port->write_image == NULL) || (port->read_image == NULL)
|
||||
|| (port->reboot == NULL)) return false;
|
||||
(void)memset(boot, 0, sizeof(*boot));
|
||||
boot->config = *config;
|
||||
boot->port = *port;
|
||||
boot->port_user = port_user;
|
||||
boot->state = SETP_FW_IDLE;
|
||||
setp_can_rx_init(&boot->rx);
|
||||
return true;
|
||||
}
|
||||
|
||||
void setp_tms2812_boot_abort(setp_tms2812_boot_t *boot)
|
||||
{
|
||||
if (boot == NULL) return;
|
||||
boot->state = SETP_FW_IDLE;
|
||||
boot->next_offset = 0U;
|
||||
boot->last_error = 0U;
|
||||
(void)memset(&boot->manifest, 0, sizeof(boot->manifest));
|
||||
}
|
||||
|
||||
bool setp_tms2812_boot_process(setp_tms2812_boot_t *boot,
|
||||
const setp_can_frame_t *frame,
|
||||
uint32_t now_ms)
|
||||
{
|
||||
setp_can_packet_t packet;
|
||||
setp_can_rx_result_t result;
|
||||
setp_can_id_t can_id;
|
||||
setp_frame_t request;
|
||||
if ((boot == NULL) || (frame == NULL)) return false;
|
||||
result = setp_can_rx_feed(&boot->rx, frame, now_ms, &packet);
|
||||
if (result != SETP_CAN_RX_COMPLETE) return result == SETP_CAN_RX_NONE;
|
||||
if (!setp_can_id_unpack(packet.can_id, &can_id)
|
||||
|| (can_id.destination != boot->config.node_id)
|
||||
|| !setp_frame_decode_datagram(packet.data, packet.length, &request)) return false;
|
||||
return boot_dispatch(boot, &request, &can_id, now_ms);
|
||||
}
|
||||
74
c/set-protocol/ports/tms320f2812/setp_tms2812_boot.h
Normal file
74
c/set-protocol/ports/tms320f2812/setp_tms2812_boot.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#ifndef SETP_TMS2812_BOOT_H
|
||||
#define SETP_TMS2812_BOOT_H
|
||||
|
||||
#include "set_can.h"
|
||||
#include "set_firmware.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef SETP_TMS2812_MAX_BLOCK_SIZE
|
||||
#define SETP_TMS2812_MAX_BLOCK_SIZE 64U
|
||||
#endif
|
||||
|
||||
#define SETP_TMS2812_RESPONSE_PAYLOAD_SIZE \
|
||||
(2U + SETP_DEVICE_INFO_FIXED_SIZE + SETP_DEVICE_MODEL_MAX)
|
||||
|
||||
typedef struct {
|
||||
uint8_t node_id;
|
||||
uint16_t device_class;
|
||||
uint32_t hardware_version;
|
||||
uint32_t firmware_version;
|
||||
uint32_t dictionary_version;
|
||||
uint64_t serial_number;
|
||||
const uint8_t *model;
|
||||
uint8_t model_length;
|
||||
uint32_t app_base_address;
|
||||
uint32_t max_image_size;
|
||||
uint16_t max_block_size;
|
||||
uint8_t active_slot;
|
||||
uint8_t require_signature;
|
||||
} setp_tms2812_boot_config_t;
|
||||
|
||||
typedef struct {
|
||||
setp_can_send_fn send_can;
|
||||
bool (*erase_image)(void *user, uint32_t image_size);
|
||||
bool (*write_image)(void *user, uint32_t offset,
|
||||
const uint8_t *data, uint16_t length);
|
||||
bool (*read_image)(void *user, uint32_t offset,
|
||||
uint8_t *data, uint16_t length);
|
||||
bool (*authorize)(void *user, const setp_fw_begin_t *manifest);
|
||||
void (*reboot)(void *user);
|
||||
} setp_tms2812_boot_port_t;
|
||||
|
||||
typedef struct {
|
||||
setp_tms2812_boot_config_t config;
|
||||
setp_tms2812_boot_port_t port;
|
||||
void *port_user;
|
||||
setp_can_rx_t rx;
|
||||
setp_fw_begin_t manifest;
|
||||
uint32_t next_offset;
|
||||
uint16_t last_error;
|
||||
uint8_t state;
|
||||
uint8_t response_payload[SETP_TMS2812_RESPONSE_PAYLOAD_SIZE];
|
||||
uint8_t response_packet[SETP_FRAME_MAX];
|
||||
} setp_tms2812_boot_t;
|
||||
|
||||
bool setp_tms2812_boot_init(setp_tms2812_boot_t *boot,
|
||||
const setp_tms2812_boot_config_t *config,
|
||||
const setp_tms2812_boot_port_t *port,
|
||||
void *port_user);
|
||||
|
||||
/** Process one classic-CAN frame in task context, never from an ISR. */
|
||||
bool setp_tms2812_boot_process(setp_tms2812_boot_t *boot,
|
||||
const setp_can_frame_t *frame,
|
||||
uint32_t now_ms);
|
||||
|
||||
void setp_tms2812_boot_abort(setp_tms2812_boot_t *boot);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* SETP_TMS2812_BOOT_H */
|
||||
225
c/set-protocol/tests/test_tms2812_boot.c
Normal file
225
c/set-protocol/tests/test_tms2812_boot.c
Normal file
@@ -0,0 +1,225 @@
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "setp_tms2812_boot.h"
|
||||
|
||||
#define NODE_ID 13U
|
||||
#define IMAGE_SIZE 100U
|
||||
|
||||
typedef struct {
|
||||
uint8_t flash[512];
|
||||
setp_can_rx_t response_rx;
|
||||
setp_can_packet_t response_packet;
|
||||
uint32_t now_ms;
|
||||
unsigned int response_complete;
|
||||
unsigned int erased;
|
||||
unsigned int rebooted;
|
||||
} fake_t;
|
||||
|
||||
typedef struct {
|
||||
setp_tms2812_boot_t *boot;
|
||||
fake_t *fake;
|
||||
} request_context_t;
|
||||
|
||||
static bool fake_send_response(const setp_can_frame_t *frame, void *user)
|
||||
{
|
||||
fake_t *fake = (fake_t *)user;
|
||||
setp_can_rx_result_t result = setp_can_rx_feed(
|
||||
&fake->response_rx, frame, fake->now_ms++, &fake->response_packet);
|
||||
if (result == SETP_CAN_RX_COMPLETE) fake->response_complete++;
|
||||
return result == SETP_CAN_RX_NONE || result == SETP_CAN_RX_COMPLETE;
|
||||
}
|
||||
|
||||
static bool fake_erase(void *user, uint32_t image_size)
|
||||
{
|
||||
fake_t *fake = (fake_t *)user;
|
||||
if (image_size > sizeof(fake->flash)) return false;
|
||||
(void)memset(fake->flash, 0xFF, sizeof(fake->flash));
|
||||
fake->erased++;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fake_write(void *user, uint32_t offset,
|
||||
const uint8_t *data, uint16_t length)
|
||||
{
|
||||
fake_t *fake = (fake_t *)user;
|
||||
if (offset + length > sizeof(fake->flash)) return false;
|
||||
(void)memcpy(&fake->flash[offset], data, length);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool fake_read(void *user, uint32_t offset,
|
||||
uint8_t *data, uint16_t length)
|
||||
{
|
||||
fake_t *fake = (fake_t *)user;
|
||||
if (offset + length > sizeof(fake->flash)) return false;
|
||||
(void)memcpy(data, &fake->flash[offset], length);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fake_reboot(void *user)
|
||||
{
|
||||
((fake_t *)user)->rebooted++;
|
||||
}
|
||||
|
||||
static bool send_request_frame(const setp_can_frame_t *frame, void *user)
|
||||
{
|
||||
request_context_t *context = (request_context_t *)user;
|
||||
return setp_tms2812_boot_process(context->boot, frame,
|
||||
context->fake->now_ms++);
|
||||
}
|
||||
|
||||
static setp_frame_t transact(setp_tms2812_boot_t *boot, fake_t *fake,
|
||||
uint16_t sequence, uint16_t type,
|
||||
const uint8_t *payload, uint16_t payload_length)
|
||||
{
|
||||
uint8_t packet[SETP_FRAME_MAX];
|
||||
setp_frame_t request;
|
||||
setp_frame_t response;
|
||||
setp_can_id_t id;
|
||||
request_context_t context;
|
||||
size_t packet_length;
|
||||
fake->response_complete = 0U;
|
||||
setp_can_rx_init(&fake->response_rx);
|
||||
request.flags = SETP_FLAG_ACK_REQUIRED | SETP_FLAG_PRIORITY;
|
||||
request.message_type = type;
|
||||
request.source = 0U;
|
||||
request.destination = NODE_ID;
|
||||
request.sequence = sequence;
|
||||
request.payload_length = payload_length;
|
||||
request.payload = payload;
|
||||
packet_length = setp_frame_encode(&request, packet, sizeof(packet));
|
||||
assert(packet_length != 0U);
|
||||
id.destination = NODE_ID;
|
||||
id.source = 0U;
|
||||
id.priority = 1U;
|
||||
id.channel = 1U;
|
||||
context.boot = boot;
|
||||
context.fake = fake;
|
||||
assert(setp_can_segment(packet, (uint16_t)packet_length,
|
||||
setp_can_id_pack(&id), send_request_frame, &context));
|
||||
assert(fake->response_complete == 1U);
|
||||
assert(setp_frame_decode_datagram(fake->response_packet.data,
|
||||
fake->response_packet.length, &response));
|
||||
assert(response.flags & SETP_FLAG_RESPONSE);
|
||||
assert(response.message_type == type);
|
||||
assert(response.sequence == sequence);
|
||||
assert(response.source == NODE_ID);
|
||||
assert(response.destination == 0U);
|
||||
return response;
|
||||
}
|
||||
|
||||
static void assert_ok(const setp_frame_t *response)
|
||||
{
|
||||
assert(response->payload_length >= 2U);
|
||||
assert(setp_get_u16(response->payload) == SETP_STATUS_OK);
|
||||
assert((response->flags & SETP_FLAG_ERROR) == 0U);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
static const uint8_t model[] = "BALZAM-166";
|
||||
static const uint8_t expected_sha[SETP_SHA256_SIZE] = {
|
||||
0xBC, 0xE0, 0xAF, 0xF1, 0x9C, 0xF5, 0xAA, 0x6A,
|
||||
0x74, 0x69, 0xA3, 0x0D, 0x61, 0xD0, 0x4E, 0x43,
|
||||
0x76, 0xE4, 0xBB, 0xF6, 0x38, 0x10, 0x52, 0xEE,
|
||||
0x9E, 0x7F, 0x33, 0x92, 0x5C, 0x95, 0x4D, 0x52
|
||||
};
|
||||
uint8_t image[IMAGE_SIZE];
|
||||
uint8_t payload[SETP_MAX_PAYLOAD];
|
||||
setp_tms2812_boot_t boot;
|
||||
setp_tms2812_boot_config_t config;
|
||||
setp_tms2812_boot_port_t port;
|
||||
setp_fw_begin_t begin;
|
||||
setp_fw_end_t end;
|
||||
setp_fw_status_t status;
|
||||
setp_frame_t response;
|
||||
size_t length;
|
||||
uint32_t offset;
|
||||
uint16_t sequence = 1U;
|
||||
fake_t fake;
|
||||
unsigned int i;
|
||||
|
||||
(void)memset(&fake, 0, sizeof(fake));
|
||||
(void)memset(&config, 0, sizeof(config));
|
||||
(void)memset(&port, 0, sizeof(port));
|
||||
for (i = 0U; i < IMAGE_SIZE; i++) image[i] = (uint8_t)i;
|
||||
config.node_id = NODE_ID;
|
||||
config.device_class = 0x0166U;
|
||||
config.hardware_version = 1U;
|
||||
config.firmware_version = 2U;
|
||||
config.model = model;
|
||||
config.model_length = (uint8_t)(sizeof(model) - 1U);
|
||||
config.app_base_address = 0x00100000UL;
|
||||
config.max_image_size = sizeof(fake.flash);
|
||||
config.max_block_size = 32U;
|
||||
config.active_slot = 0U;
|
||||
port.send_can = fake_send_response;
|
||||
port.erase_image = fake_erase;
|
||||
port.write_image = fake_write;
|
||||
port.read_image = fake_read;
|
||||
port.reboot = fake_reboot;
|
||||
assert(setp_tms2812_boot_init(&boot, &config, &port, &fake));
|
||||
|
||||
response = transact(&boot, &fake, sequence++, SETP_MSG_PING, NULL, 0U);
|
||||
assert_ok(&response);
|
||||
assert(response.payload_length == 6U);
|
||||
|
||||
(void)memset(&begin, 0, sizeof(begin));
|
||||
begin.image_size = IMAGE_SIZE;
|
||||
begin.image_crc32 = setp_crc32(image, sizeof(image));
|
||||
begin.image_version = 0x01020304UL;
|
||||
begin.base_address = config.app_base_address;
|
||||
begin.slot = 0U;
|
||||
begin.flags = SETP_FW_FLAG_RESUME | SETP_FW_FLAG_ERASE_SLOT;
|
||||
begin.block_size = 32U;
|
||||
(void)memcpy(begin.sha256, expected_sha, sizeof(expected_sha));
|
||||
length = setp_fw_begin_encode(&begin, payload, sizeof(payload));
|
||||
assert(length != 0U);
|
||||
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_BEGIN,
|
||||
payload, (uint16_t)length);
|
||||
assert_ok(&response);
|
||||
assert(fake.erased == 1U);
|
||||
|
||||
for (offset = 0U; offset < IMAGE_SIZE;) {
|
||||
uint16_t chunk = (uint16_t)(IMAGE_SIZE - offset);
|
||||
if (chunk > begin.block_size) chunk = begin.block_size;
|
||||
length = setp_fw_data_encode(offset, 0U, &image[offset], chunk,
|
||||
payload, sizeof(payload));
|
||||
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_DATA,
|
||||
payload, (uint16_t)length);
|
||||
assert_ok(&response);
|
||||
assert(setp_get_u32(&response.payload[2]) == offset + chunk);
|
||||
if (offset == 0U) {
|
||||
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_DATA,
|
||||
payload, (uint16_t)length);
|
||||
assert_ok(&response);
|
||||
assert(setp_get_u32(&response.payload[2]) == chunk);
|
||||
}
|
||||
offset += chunk;
|
||||
}
|
||||
assert(memcmp(fake.flash, image, sizeof(image)) == 0);
|
||||
|
||||
end.image_size = begin.image_size;
|
||||
end.image_crc32 = begin.image_crc32;
|
||||
(void)memcpy(end.sha256, expected_sha, sizeof(expected_sha));
|
||||
length = setp_fw_end_encode(&end, payload, sizeof(payload));
|
||||
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_END,
|
||||
payload, (uint16_t)length);
|
||||
assert_ok(&response);
|
||||
assert(boot.state == SETP_FW_READY);
|
||||
|
||||
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_STATUS, NULL, 0U);
|
||||
assert_ok(&response);
|
||||
assert(setp_fw_status_decode(&response.payload[2],
|
||||
(uint16_t)(response.payload_length - 2U), &status));
|
||||
assert(status.state == SETP_FW_READY);
|
||||
assert(status.next_offset == IMAGE_SIZE);
|
||||
|
||||
response = transact(&boot, &fake, sequence++, SETP_MSG_FW_ACTIVATE, NULL, 0U);
|
||||
assert_ok(&response);
|
||||
assert(fake.rebooted == 1U);
|
||||
puts("SETProtocol v2 TMS320F2812 firmware port tests passed");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
"""Cross-platform Python facade for the canonical SETProtocol core."""
|
||||
|
||||
from .core import * # noqa: F401,F403
|
||||
from .can import CanAddress, CanFrame, CanReassembler, segment
|
||||
|
||||
173
python/setprotocol/can.py
Normal file
173
python/setprotocol/can.py
Normal file
@@ -0,0 +1,173 @@
|
||||
"""Classic-CAN segmentation for canonical SETProtocol v2 frames."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from .core import (
|
||||
CRC_SIZE,
|
||||
FRAME_MAX,
|
||||
HEADER_SIZE,
|
||||
SOF,
|
||||
FrameFlag,
|
||||
SetProtocolError,
|
||||
)
|
||||
|
||||
CAN_ID_MASK = 0x1FFFFFFF
|
||||
CAN_ID_PREFIX = 0x12
|
||||
CAN_ID_PREFIX_MASK = 0x1F000000
|
||||
PCI_FIRST = 0x10
|
||||
PCI_CONSECUTIVE = 0x20
|
||||
PCI_FLOW_CONTROL = 0x30
|
||||
PCI_TYPE_MASK = 0xF0
|
||||
PCI_VALUE_MASK = 0x0F
|
||||
FIRST_DATA_SIZE = 5
|
||||
CONSECUTIVE_DATA_SIZE = 7
|
||||
REASSEMBLY_TIMEOUT_MS = 500
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanAddress:
|
||||
destination: int
|
||||
source: int
|
||||
priority: int = 0
|
||||
channel: int = 0
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if not 0 <= self.destination <= 0xFF:
|
||||
raise SetProtocolError("CAN destination вне диапазона u8")
|
||||
if not 0 <= self.source <= 0xFF:
|
||||
raise SetProtocolError("CAN source вне диапазона u8")
|
||||
if self.priority not in (0, 1):
|
||||
raise SetProtocolError("CAN priority должен быть 0 или 1")
|
||||
if not 0 <= self.channel <= 0x7F:
|
||||
raise SetProtocolError("CAN channel вне диапазона 0..127")
|
||||
|
||||
def pack(self) -> int:
|
||||
return (
|
||||
(CAN_ID_PREFIX << 24)
|
||||
| (self.destination << 16)
|
||||
| (self.source << 8)
|
||||
| (self.priority << 7)
|
||||
| self.channel
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def unpack(cls, can_id: int) -> "CanAddress":
|
||||
if not 0 <= can_id <= CAN_ID_MASK:
|
||||
raise SetProtocolError("CAN ID вне 29-битного диапазона")
|
||||
if can_id & CAN_ID_PREFIX_MASK != CAN_ID_PREFIX << 24:
|
||||
raise SetProtocolError("CAN ID не принадлежит SETProtocol v2")
|
||||
return cls(
|
||||
destination=(can_id >> 16) & 0xFF,
|
||||
source=(can_id >> 8) & 0xFF,
|
||||
priority=(can_id >> 7) & 1,
|
||||
channel=can_id & 0x7F,
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CanFrame:
|
||||
can_id: int
|
||||
data: bytes
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
CanAddress.unpack(self.can_id)
|
||||
if not 1 <= len(self.data) <= 8:
|
||||
raise SetProtocolError("classic CAN data должен содержать 1..8 байт")
|
||||
|
||||
|
||||
def segment(packet: bytes, address: CanAddress) -> list[CanFrame]:
|
||||
"""Split one complete encoded SETP frame into classic-CAN frames."""
|
||||
if not HEADER_SIZE + CRC_SIZE <= len(packet) <= FRAME_MAX:
|
||||
raise SetProtocolError("неверная длина SETP-пакета для CAN")
|
||||
can_id = address.pack()
|
||||
result = [
|
||||
CanFrame(
|
||||
can_id,
|
||||
bytes((PCI_FIRST,))
|
||||
+ len(packet).to_bytes(2, "little")
|
||||
+ packet[:FIRST_DATA_SIZE],
|
||||
)
|
||||
]
|
||||
sequence = 1
|
||||
for offset in range(FIRST_DATA_SIZE, len(packet), CONSECUTIVE_DATA_SIZE):
|
||||
chunk = packet[offset : offset + CONSECUTIVE_DATA_SIZE]
|
||||
result.append(CanFrame(can_id, bytes((PCI_CONSECUTIVE | sequence,)) + chunk))
|
||||
sequence = (sequence + 1) & PCI_VALUE_MASK
|
||||
return result
|
||||
|
||||
|
||||
class CanReassembler:
|
||||
"""Reassemble one SETP packet from one active source/channel."""
|
||||
|
||||
def __init__(self, timeout_ms: int = REASSEMBLY_TIMEOUT_MS) -> None:
|
||||
if timeout_ms <= 0:
|
||||
raise ValueError("timeout_ms должен быть положительным")
|
||||
self.timeout_ms = timeout_ms
|
||||
self.reset()
|
||||
|
||||
def reset(self) -> None:
|
||||
self._buffer = bytearray()
|
||||
self._expected_length = 0
|
||||
self._can_id: int | None = None
|
||||
self._deadline_ms = 0
|
||||
self._next_sequence = 1
|
||||
|
||||
def feed(self, frame: CanFrame, now_ms: int) -> bytes | None:
|
||||
if self._can_id is not None and now_ms >= self._deadline_ms:
|
||||
self.reset()
|
||||
raise SetProtocolError("таймаут сборки SETProtocol CAN")
|
||||
pci = frame.data[0] & PCI_TYPE_MASK
|
||||
if pci == PCI_FIRST:
|
||||
if len(frame.data) != 8:
|
||||
raise SetProtocolError("первый CAN-сегмент должен иметь DLC 8")
|
||||
total = int.from_bytes(frame.data[1:3], "little")
|
||||
if not HEADER_SIZE + CRC_SIZE <= total <= FRAME_MAX:
|
||||
raise SetProtocolError("неверная полная длина SETP CAN")
|
||||
self._buffer = bytearray(frame.data[3:])
|
||||
self._expected_length = total
|
||||
self._can_id = frame.can_id
|
||||
self._deadline_ms = now_ms + self.timeout_ms
|
||||
self._next_sequence = 1
|
||||
return None
|
||||
if pci == PCI_CONSECUTIVE:
|
||||
sequence = frame.data[0] & PCI_VALUE_MASK
|
||||
if (
|
||||
self._can_id is None
|
||||
or frame.can_id != self._can_id
|
||||
or sequence != self._next_sequence
|
||||
or len(frame.data) < 2
|
||||
):
|
||||
self.reset()
|
||||
raise SetProtocolError("нарушена последовательность SETP CAN")
|
||||
remaining = self._expected_length - len(self._buffer)
|
||||
chunk = frame.data[1:]
|
||||
if len(chunk) > remaining:
|
||||
self.reset()
|
||||
raise SetProtocolError("CAN-сегмент длиннее остатка SETP-пакета")
|
||||
self._buffer.extend(chunk)
|
||||
self._next_sequence = (self._next_sequence + 1) & PCI_VALUE_MASK
|
||||
self._deadline_ms = now_ms + self.timeout_ms
|
||||
if len(self._buffer) != self._expected_length:
|
||||
return None
|
||||
packet = bytes(self._buffer)
|
||||
can_id = self._can_id
|
||||
self.reset()
|
||||
assert can_id is not None
|
||||
address = CanAddress.unpack(can_id)
|
||||
if packet[:2] != SOF:
|
||||
raise SetProtocolError("SETP CAN packet не содержит SOF")
|
||||
source = int.from_bytes(packet[6:8], "little")
|
||||
destination = int.from_bytes(packet[8:10], "little")
|
||||
priority = int(bool(packet[3] & int(FrameFlag.PRIORITY)))
|
||||
if (
|
||||
source != address.source
|
||||
or destination != address.destination
|
||||
or priority != address.priority
|
||||
):
|
||||
raise SetProtocolError("SETP header не совпадает с CAN ID")
|
||||
return packet
|
||||
if pci == PCI_FLOW_CONTROL:
|
||||
return None
|
||||
raise SetProtocolError("неизвестный тип SETP CAN-сегмента")
|
||||
42
python/tests/test_setprotocol_can.py
Normal file
42
python/tests/test_setprotocol_can.py
Normal file
@@ -0,0 +1,42 @@
|
||||
from setprotocol import Frame, FrameFlag, MessageType, build_frame, decode_datagram
|
||||
from setprotocol.can import CanAddress, CanFrame, CanReassembler, segment
|
||||
|
||||
|
||||
def test_can_id_roundtrip():
|
||||
value = CanAddress(destination=13, source=0, priority=1, channel=7)
|
||||
assert CanAddress.unpack(value.pack()) == value
|
||||
|
||||
|
||||
def test_segmented_setp_frame_roundtrip():
|
||||
packet = build_frame(
|
||||
Frame(
|
||||
message_type=MessageType.FW_DATA,
|
||||
sequence=0x1234,
|
||||
payload=bytes(range(76)),
|
||||
flags=FrameFlag.ACK_REQUIRED | FrameFlag.PRIORITY,
|
||||
source=0,
|
||||
destination=13,
|
||||
)
|
||||
)
|
||||
frames = segment(packet, CanAddress(13, 0, priority=1, channel=1))
|
||||
assert len(frames) == 14
|
||||
reassembler = CanReassembler()
|
||||
restored = None
|
||||
for now_ms, frame in enumerate(frames):
|
||||
restored = reassembler.feed(frame, now_ms)
|
||||
assert restored == packet
|
||||
assert decode_datagram(restored).payload == bytes(range(76))
|
||||
|
||||
|
||||
def test_reassembler_rejects_wrong_sequence():
|
||||
packet = build_frame(Frame(MessageType.PING, 1, source=0, destination=13))
|
||||
frames = segment(packet, CanAddress(13, 0))
|
||||
reassembler = CanReassembler()
|
||||
assert reassembler.feed(frames[0], 0) is None
|
||||
damaged = CanFrame(frames[1].can_id, bytes((0x22,)) + frames[1].data[1:])
|
||||
try:
|
||||
reassembler.feed(damaged, 1)
|
||||
except ValueError as error:
|
||||
assert "последовательность" in str(error)
|
||||
else:
|
||||
raise AssertionError("wrong sequence must fail")
|
||||
Reference in New Issue
Block a user