feat(setprotocol): define system payload schemas

This commit is contained in:
2026-09-01 11:47:08 +03:00
parent 19becd7b8c
commit 9b636a9f40
5 changed files with 279 additions and 3 deletions

View File

@@ -93,7 +93,59 @@ broadcast; на broadcast-запрос отвечать нельзя, если
Неизвестный тип не является ошибкой кадрирования. Устройство отвечает Неизвестный тип не является ошибкой кадрирования. Устройство отвечает
`UNSUPPORTED`, если запрос был адресован ему и требовал ответа. `UNSUPPORTED`, если запрос был адресован ему и требовал ответа.
## 5. Телеметрия реального времени ## 5. Системные payload schema v1
Все описанные ниже поля входят в body ответа **после** обязательного
`status u16`. Строки передаются в UTF-8 без завершающего нуля. Версия схемы
отделена от версии кадра, чтобы поля можно было расширять независимо.
### DEVICE_INFO
```text
schema_version u16 = 1
device_class u16
hardware_version u32
firmware_version u32
dictionary_version u32
serial_number u64
model_length u8 (0..63)
model u8[model_length], UTF-8
```
Числовые версии являются значениями изделия; рекомендуемая упаковка
`major.minor.patch.build` — по одному байту от старшего к младшему.
`device_class` назначается в реестре изделий SET, а `dictionary_version`
меняется при несовместимом изменении адресов или типов общей карты.
### CAPABILITIES
```text
schema_version u16 = 1
max_payload u16 (1..512 для базового профиля)
interface_mask u32 (бит с номером setp_iface_t)
feature_flags u32
max_read_items u16
max_write_items u16
max_subscriptions u16
max_publish_items u16
```
Стабильные `feature_flags`: `READ=bit0`, `WRITE=bit1`, `CATALOG=bit2`,
`SUBSCRIBE=bit3`, `LOG_READ=bit4`, `FIRMWARE=bit5`, `DIAGNOSTICS=bit6`.
Нулевой лимит означает, что соответствующая функция не поддерживается.
Эталонные body без status:
```text
DEVICE_INFO (SET-01):
01 00 34 12 02 00 01 00 04 00 03 00 44 33 22 11
EF CD AB 89 67 45 23 01 06 53 45 54 2D 30 31
CAPABILITIES (RS232 + CAN, READ + CATALOG + SUBSCRIBE):
01 00 00 02 0A 00 00 00 0D 00 00 00 40 00 20 00 04 00 18 00
```
## 6. Телеметрия реального времени
### SUBSCRIBE ### SUBSCRIBE
@@ -131,7 +183,7 @@ repeat item_count times:
Телеметрия имеет меньший приоритет, чем ответы и прошивка. При переполнении Телеметрия имеет меньший приоритет, чем ответы и прошивка. При переполнении
очереди разрешено отбросить старый `PUBLISH`, но нельзя частично передать кадр. очереди разрешено отбросить старый `PUBLISH`, но нельзя частично передать кадр.
## 6. Прошивка ## 7. Прошивка
Типы: Типы:
@@ -175,7 +227,7 @@ offset u32 | data_length u16 | flags u16 | data_crc32 u32 | data[]
CRC32 защищает линию, SHA-256 — целостность образа, подпись — происхождение. CRC32 защищает линию, SHA-256 — целостность образа, подпись — происхождение.
Один CRC не является защитой от подмены прошивки. Один CRC не является защитой от подмены прошивки.
## 7. Привязки к физическим интерфейсам ## 8. Привязки к физическим интерфейсам
### RS-232, RS-485, USB CDC ### RS-232, RS-485, USB CDC

View File

@@ -23,6 +23,12 @@ extern "C" {
#define SETP_NODE_LOCAL 0x0000U #define SETP_NODE_LOCAL 0x0000U
#define SETP_NODE_BROADCAST 0xFFFFU #define SETP_NODE_BROADCAST 0xFFFFU
#define SETP_DEVICE_INFO_SCHEMA_VERSION 1U
#define SETP_DEVICE_INFO_FIXED_SIZE 25U
#define SETP_DEVICE_MODEL_MAX 63U
#define SETP_CAPABILITIES_SCHEMA_VERSION 1U
#define SETP_CAPABILITIES_SIZE 20U
/* Frame flags. Reserved bits must be transmitted as zero. */ /* Frame flags. Reserved bits must be transmitted as zero. */
#define SETP_FLAG_RESPONSE 0x01U #define SETP_FLAG_RESPONSE 0x01U
#define SETP_FLAG_EVENT 0x02U #define SETP_FLAG_EVENT 0x02U
@@ -86,6 +92,40 @@ typedef enum {
SETP_STATUS_WRONG_STATE = 15 SETP_STATUS_WRONG_STATE = 15
} setp_status_t; } setp_status_t;
/* CAPABILITIES feature_flags. */
#define SETP_FEATURE_READ (1UL << 0)
#define SETP_FEATURE_WRITE (1UL << 1)
#define SETP_FEATURE_CATALOG (1UL << 2)
#define SETP_FEATURE_SUBSCRIBE (1UL << 3)
#define SETP_FEATURE_LOG_READ (1UL << 4)
#define SETP_FEATURE_FIRMWARE (1UL << 5)
#define SETP_FEATURE_DIAGNOSTICS (1UL << 6)
/* CAPABILITIES interface_mask; bit index equals setp_iface_t. */
#define SETP_IFACE_MASK(iface) (1UL << (uint32_t)(iface))
typedef struct {
uint16_t schema_version;
uint16_t device_class;
uint32_t hardware_version;
uint32_t firmware_version;
uint32_t dictionary_version;
uint64_t serial_number;
uint8_t model_length;
const uint8_t *model;
} setp_device_info_t;
typedef struct {
uint16_t schema_version;
uint16_t max_payload;
uint32_t interface_mask;
uint32_t feature_flags;
uint16_t max_read_items;
uint16_t max_write_items;
uint16_t max_subscriptions;
uint16_t max_publish_items;
} setp_capabilities_t;
typedef struct { typedef struct {
uint8_t flags; uint8_t flags;
uint16_t message_type; uint16_t message_type;
@@ -116,8 +156,10 @@ typedef void (*setp_frame_fn)(const setp_frame_t *frame, void *user);
uint16_t setp_get_u16(const uint8_t *data); uint16_t setp_get_u16(const uint8_t *data);
uint32_t setp_get_u32(const uint8_t *data); uint32_t setp_get_u32(const uint8_t *data);
uint64_t setp_get_u64(const uint8_t *data);
void setp_put_u16(uint8_t *data, uint16_t value); void setp_put_u16(uint8_t *data, uint16_t value);
void setp_put_u32(uint8_t *data, uint32_t value); void setp_put_u32(uint8_t *data, uint32_t value);
void setp_put_u64(uint8_t *data, uint64_t value);
uint32_t setp_crc32(const uint8_t *data, size_t length); uint32_t setp_crc32(const uint8_t *data, size_t length);
@@ -141,6 +183,18 @@ size_t setp_status_encode(uint16_t status, uint8_t *out, size_t capacity);
bool setp_status_decode(const setp_frame_t *frame, uint16_t *status, bool setp_status_decode(const setp_frame_t *frame, uint16_t *status,
const uint8_t **body, uint16_t *body_length); const uint8_t **body, uint16_t *body_length);
/** Encode/decode DEVICE_INFO response body (without the status prefix). */
size_t setp_device_info_encode(const setp_device_info_t *info,
uint8_t *out, size_t capacity);
bool setp_device_info_decode(const uint8_t *data, uint16_t length,
setp_device_info_t *out);
/** Encode/decode CAPABILITIES response body (without the status prefix). */
size_t setp_capabilities_encode(const setp_capabilities_t *capabilities,
uint8_t *out, size_t capacity);
bool setp_capabilities_decode(const uint8_t *data, uint16_t length,
setp_capabilities_t *out);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -15,6 +15,12 @@ uint32_t setp_get_u32(const uint8_t *data)
| ((uint32_t)data[3] << 24); | ((uint32_t)data[3] << 24);
} }
uint64_t setp_get_u64(const uint8_t *data)
{
return (uint64_t)setp_get_u32(data)
| ((uint64_t)setp_get_u32(&data[4]) << 32);
}
void setp_put_u16(uint8_t *data, uint16_t value) void setp_put_u16(uint8_t *data, uint16_t value)
{ {
data[0] = (uint8_t)(value & 0xFFU); data[0] = (uint8_t)(value & 0xFFU);
@@ -29,6 +35,12 @@ void setp_put_u32(uint8_t *data, uint32_t value)
data[3] = (uint8_t)(value >> 24); data[3] = (uint8_t)(value >> 24);
} }
void setp_put_u64(uint8_t *data, uint64_t value)
{
setp_put_u32(data, (uint32_t)(value & 0xFFFFFFFFULL));
setp_put_u32(&data[4], (uint32_t)(value >> 32));
}
uint32_t setp_crc32(const uint8_t *data, size_t length) uint32_t setp_crc32(const uint8_t *data, size_t length)
{ {
uint32_t crc = 0xFFFFFFFFUL; uint32_t crc = 0xFFFFFFFFUL;
@@ -278,3 +290,96 @@ bool setp_status_decode(const setp_frame_t *frame, uint16_t *status,
} }
return true; return true;
} }
size_t setp_device_info_encode(const setp_device_info_t *info,
uint8_t *out, size_t capacity)
{
size_t total;
if ((info == NULL) || (out == NULL)
|| (info->schema_version != SETP_DEVICE_INFO_SCHEMA_VERSION)
|| (info->model_length > SETP_DEVICE_MODEL_MAX)
|| ((info->model_length != 0U) && (info->model == NULL))) {
return 0U;
}
total = (size_t)SETP_DEVICE_INFO_FIXED_SIZE + info->model_length;
if (capacity < total) {
return 0U;
}
setp_put_u16(&out[0], info->schema_version);
setp_put_u16(&out[2], info->device_class);
setp_put_u32(&out[4], info->hardware_version);
setp_put_u32(&out[8], info->firmware_version);
setp_put_u32(&out[12], info->dictionary_version);
setp_put_u64(&out[16], info->serial_number);
out[24] = info->model_length;
if (info->model_length != 0U) {
(void)memcpy(&out[SETP_DEVICE_INFO_FIXED_SIZE],
info->model, info->model_length);
}
return total;
}
bool setp_device_info_decode(const uint8_t *data, uint16_t length,
setp_device_info_t *out)
{
uint8_t model_length;
if ((data == NULL) || (out == NULL) || (length < SETP_DEVICE_INFO_FIXED_SIZE)) {
return false;
}
model_length = data[24];
if ((setp_get_u16(&data[0]) != SETP_DEVICE_INFO_SCHEMA_VERSION)
|| (model_length > SETP_DEVICE_MODEL_MAX)
|| (length != (uint16_t)(SETP_DEVICE_INFO_FIXED_SIZE + model_length))) {
return false;
}
out->schema_version = setp_get_u16(&data[0]);
out->device_class = setp_get_u16(&data[2]);
out->hardware_version = setp_get_u32(&data[4]);
out->firmware_version = setp_get_u32(&data[8]);
out->dictionary_version = setp_get_u32(&data[12]);
out->serial_number = setp_get_u64(&data[16]);
out->model_length = model_length;
out->model = &data[SETP_DEVICE_INFO_FIXED_SIZE];
return true;
}
size_t setp_capabilities_encode(const setp_capabilities_t *capabilities,
uint8_t *out, size_t capacity)
{
if ((capabilities == NULL) || (out == NULL)
|| (capacity < SETP_CAPABILITIES_SIZE)
|| (capabilities->schema_version != SETP_CAPABILITIES_SCHEMA_VERSION)
|| (capabilities->max_payload == 0U)
|| (capabilities->max_payload > SETP_MAX_PAYLOAD)) {
return 0U;
}
setp_put_u16(&out[0], capabilities->schema_version);
setp_put_u16(&out[2], capabilities->max_payload);
setp_put_u32(&out[4], capabilities->interface_mask);
setp_put_u32(&out[8], capabilities->feature_flags);
setp_put_u16(&out[12], capabilities->max_read_items);
setp_put_u16(&out[14], capabilities->max_write_items);
setp_put_u16(&out[16], capabilities->max_subscriptions);
setp_put_u16(&out[18], capabilities->max_publish_items);
return SETP_CAPABILITIES_SIZE;
}
bool setp_capabilities_decode(const uint8_t *data, uint16_t length,
setp_capabilities_t *out)
{
if ((data == NULL) || (out == NULL) || (length != SETP_CAPABILITIES_SIZE)
|| (setp_get_u16(&data[0]) != SETP_CAPABILITIES_SCHEMA_VERSION)
|| (setp_get_u16(&data[2]) == 0U)
|| (setp_get_u16(&data[2]) > SETP_MAX_PAYLOAD)) {
return false;
}
out->schema_version = setp_get_u16(&data[0]);
out->max_payload = setp_get_u16(&data[2]);
out->interface_mask = setp_get_u32(&data[4]);
out->feature_flags = setp_get_u32(&data[8]);
out->max_read_items = setp_get_u16(&data[12]);
out->max_write_items = setp_get_u16(&data[14]);
out->max_subscriptions = setp_get_u16(&data[16]);
out->max_publish_items = setp_get_u16(&data[18]);
return true;
}

View File

@@ -125,6 +125,66 @@ static void test_status(void)
CHECK(body[0] == 0xAAU && body[1] == 0x55U); CHECK(body[0] == 0xAAU && body[1] == 0x55U);
} }
static void test_system_payloads(void)
{
static const uint8_t expected_info[] = {
0x01U, 0x00U, 0x34U, 0x12U, 0x02U, 0x00U, 0x01U, 0x00U,
0x04U, 0x00U, 0x03U, 0x00U, 0x44U, 0x33U, 0x22U, 0x11U,
0xEFU, 0xCDU, 0xABU, 0x89U, 0x67U, 0x45U, 0x23U, 0x01U,
0x06U, 0x53U, 0x45U, 0x54U, 0x2DU, 0x30U, 0x31U
};
static const uint8_t expected_capabilities[] = {
0x01U, 0x00U, 0x00U, 0x02U, 0x0AU, 0x00U, 0x00U, 0x00U,
0x0DU, 0x00U, 0x00U, 0x00U, 0x40U, 0x00U, 0x20U, 0x00U,
0x04U, 0x00U, 0x18U, 0x00U
};
static const uint8_t model[] = "SET-01";
setp_device_info_t info;
setp_device_info_t decoded_info;
setp_capabilities_t capabilities;
setp_capabilities_t decoded_capabilities;
uint8_t payload[SETP_MAX_PAYLOAD];
size_t length;
(void)memset(&info, 0, sizeof(info));
info.schema_version = SETP_DEVICE_INFO_SCHEMA_VERSION;
info.device_class = 0x1234U;
info.hardware_version = 0x00010002UL;
info.firmware_version = 0x00030004UL;
info.dictionary_version = 0x11223344UL;
info.serial_number = 0x0123456789ABCDEFULL;
info.model_length = 6U;
info.model = model;
length = setp_device_info_encode(&info, payload, sizeof(payload));
CHECK(length == sizeof(expected_info));
CHECK(memcmp(payload, expected_info, sizeof(expected_info)) == 0);
CHECK(setp_device_info_decode(payload, (uint16_t)length, &decoded_info));
CHECK(decoded_info.serial_number == info.serial_number);
CHECK(decoded_info.model_length == info.model_length);
CHECK(memcmp(decoded_info.model, model, sizeof(model) - 1U) == 0);
payload[24] = SETP_DEVICE_MODEL_MAX + 1U;
CHECK(!setp_device_info_decode(payload, (uint16_t)length, &decoded_info));
(void)memset(&capabilities, 0, sizeof(capabilities));
capabilities.schema_version = SETP_CAPABILITIES_SCHEMA_VERSION;
capabilities.max_payload = SETP_MAX_PAYLOAD;
capabilities.interface_mask = SETP_IFACE_MASK(SETP_IFACE_RS232)
| SETP_IFACE_MASK(SETP_IFACE_CAN);
capabilities.feature_flags = SETP_FEATURE_READ | SETP_FEATURE_CATALOG
| SETP_FEATURE_SUBSCRIBE;
capabilities.max_read_items = 64U;
capabilities.max_write_items = 32U;
capabilities.max_subscriptions = 4U;
capabilities.max_publish_items = 24U;
length = setp_capabilities_encode(&capabilities, payload, sizeof(payload));
CHECK(length == sizeof(expected_capabilities));
CHECK(memcmp(payload, expected_capabilities, sizeof(expected_capabilities)) == 0);
CHECK(setp_capabilities_decode(payload, (uint16_t)length,
&decoded_capabilities));
CHECK(decoded_capabilities.interface_mask == capabilities.interface_mask);
CHECK(decoded_capabilities.feature_flags == capabilities.feature_flags);
}
typedef struct { typedef struct {
setp_can_frame_t frames[96]; setp_can_frame_t frames[96];
uint16_t count; uint16_t count;
@@ -294,6 +354,7 @@ int main(void)
test_crc_and_reference_frame(); test_crc_and_reference_frame();
test_parser(); test_parser();
test_status(); test_status();
test_system_payloads();
test_can_binding(); test_can_binding();
test_firmware(); test_firmware();
test_telemetry(); test_telemetry();

File diff suppressed because one or more lines are too long