feat(setprotocol): define system payload schemas
This commit is contained in:
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user