feat: implement cel_crsf_param_parse
Parse PARAM_ENTRY payload into cel_crsf_param struct. Handles TEXT_SELECT with options string and UINT8/INT8 with min/max/default/ value fields. Respects hidden flag (bit 7 of type byte). Truncates name and options to buffer limits.
This commit is contained in:
+61
-18
@@ -59,22 +59,65 @@ int cel_crsf_param_set_power(cel_serial_port* port, int mw,
|
|||||||
|
|
||||||
int cel_crsf_param_parse(cel_crsf_param* out, uint8_t const* payload,
|
int cel_crsf_param_parse(cel_crsf_param* out, uint8_t const* payload,
|
||||||
size_t len) {
|
size_t len) {
|
||||||
/* TODO: parse PARAM_ENTRY payload into cel_crsf_param.
|
if (out == NULL || payload == NULL) return -1;
|
||||||
* Layout:
|
if (len < 6) return -1;
|
||||||
* [0] dest (uint8)
|
|
||||||
* [1] src (uint8)
|
memset(out, 0, sizeof(*out));
|
||||||
* [2] index (uint8)
|
|
||||||
* [3] chunks_remaining (uint8)
|
size_t offset = 0;
|
||||||
* [4] parent (uint8)
|
(void)payload[offset++]; /* dest */
|
||||||
* [5] type (uint8, bit 7 = hidden flag)
|
(void)payload[offset++]; /* src */
|
||||||
* [6..] name (null-terminated ASCII string)
|
out->index = payload[offset++];
|
||||||
* Then type-specific data after name:
|
(void)payload[offset++]; /* chunks_remaining */
|
||||||
* TEXT_SELECT: options (null-terminated, semicolon-separated),
|
(void)payload[offset++]; /* parent */
|
||||||
* then [value][min][max][default] (1 byte each)
|
uint8_t raw_type = payload[offset++];
|
||||||
* UINT8/INT8: [min][max][default][value] (1 byte each)
|
out->hidden = (raw_type & 0x80) ? 1 : 0;
|
||||||
* Minimum payload length: 6 bytes. */
|
out->type = raw_type & 0x7F;
|
||||||
(void)out;
|
|
||||||
(void)payload;
|
/* Parse name (null-terminated) */
|
||||||
(void)len;
|
size_t name_start = offset;
|
||||||
return -1;
|
while (offset < len && payload[offset] != '\0') offset++;
|
||||||
|
size_t name_len = offset - name_start;
|
||||||
|
if (name_len >= sizeof(out->name)) name_len = sizeof(out->name) - 1;
|
||||||
|
memcpy(out->name, payload + name_start, name_len);
|
||||||
|
out->name[name_len] = '\0';
|
||||||
|
if (offset < len) offset++; /* skip null terminator */
|
||||||
|
|
||||||
|
/* Parse type-specific data */
|
||||||
|
switch (out->type) {
|
||||||
|
case CEL_PARAM_TEXT_SELECT: {
|
||||||
|
/* Options: null-terminated, semicolon-separated string */
|
||||||
|
size_t opts_start = offset;
|
||||||
|
while (offset < len && payload[offset] != '\0') offset++;
|
||||||
|
size_t opts_len = offset - opts_start;
|
||||||
|
if (opts_len >= sizeof(out->options)) opts_len = sizeof(out->options) - 1;
|
||||||
|
memcpy(out->options, payload + opts_start, opts_len);
|
||||||
|
out->options[opts_len] = '\0';
|
||||||
|
if (offset < len) offset++; /* skip null terminator */
|
||||||
|
|
||||||
|
/* [value][min][max][default] */
|
||||||
|
if (offset + 4 <= len) {
|
||||||
|
out->value = payload[offset++];
|
||||||
|
out->min_val = payload[offset++];
|
||||||
|
out->max_val = payload[offset++];
|
||||||
|
out->default_val = payload[offset++];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CEL_PARAM_UINT8:
|
||||||
|
case CEL_PARAM_INT8:
|
||||||
|
/* [min][max][default][value] */
|
||||||
|
if (offset + 4 <= len) {
|
||||||
|
out->min_val = payload[offset++];
|
||||||
|
out->max_val = payload[offset++];
|
||||||
|
out->default_val = payload[offset++];
|
||||||
|
out->value = payload[offset++];
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
/* Other types have no simple value representation */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,6 +55,14 @@ target_compile_features(test_crsf_telemetry PRIVATE c_std_23)
|
|||||||
add_test(NAME test_crsf_telemetry COMMAND test_crsf_telemetry)
|
add_test(NAME test_crsf_telemetry COMMAND test_crsf_telemetry)
|
||||||
list(APPEND TEST_TARGETS test_crsf_telemetry)
|
list(APPEND TEST_TARGETS test_crsf_telemetry)
|
||||||
|
|
||||||
|
# CRSF param tests — pure functions (parse), no mock needed
|
||||||
|
add_executable(test_crsf_param test_crsf_param.c)
|
||||||
|
target_include_directories(test_crsf_param PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
|
target_link_libraries(test_crsf_param PRIVATE celrs_crsf Unity::Unity)
|
||||||
|
target_compile_features(test_crsf_param PRIVATE c_std_23)
|
||||||
|
add_test(NAME test_crsf_param COMMAND test_crsf_param)
|
||||||
|
list(APPEND TEST_TARGETS test_crsf_param)
|
||||||
|
|
||||||
# Serial tests — mocks the platform backend (serial_internal.h)
|
# Serial tests — mocks the platform backend (serial_internal.h)
|
||||||
add_executable(test_serial test_serial.c)
|
add_executable(test_serial test_serial.c)
|
||||||
target_include_directories(test_serial PRIVATE "${CMAKE_SOURCE_DIR}")
|
target_include_directories(test_serial PRIVATE "${CMAKE_SOURCE_DIR}")
|
||||||
|
|||||||
@@ -0,0 +1,176 @@
|
|||||||
|
#include "unity.h"
|
||||||
|
#include "celrs/crsf_param.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
void setUp(void) {}
|
||||||
|
void tearDown(void) {}
|
||||||
|
|
||||||
|
/* cel_crsf_param_parse tests */
|
||||||
|
|
||||||
|
void test_param_parse_null_args(void) {
|
||||||
|
uint8_t payload[16] = {0};
|
||||||
|
TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_parse(NULL, payload, sizeof(payload)));
|
||||||
|
TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_parse(NULL, NULL, 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_param_parse_too_short(void) {
|
||||||
|
cel_crsf_param param;
|
||||||
|
uint8_t payload[5] = {0};
|
||||||
|
TEST_ASSERT_EQUAL_INT(-1, cel_crsf_param_parse(¶m, payload, sizeof(payload)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_param_parse_text_select(void) {
|
||||||
|
cel_crsf_param param;
|
||||||
|
/* dest=0x10, src=0xEE, index=5, chunks=0, parent=0, type=TEXT_SELECT */
|
||||||
|
/* name="TX Power", options="10 mW;25 mW;100 mW;500 mW;1000 mW", value=2, min=0, max=4, default=0 */
|
||||||
|
uint8_t payload[] = {
|
||||||
|
0x10, /* dest */
|
||||||
|
0xEE, /* src */
|
||||||
|
0x05, /* index */
|
||||||
|
0x00, /* chunks_remaining */
|
||||||
|
0x00, /* parent */
|
||||||
|
0x09, /* type = CEL_PARAM_TEXT_SELECT */
|
||||||
|
'T', 'X', ' ', 'P', 'o', 'w', 'e', 'r', '\0', /* name */
|
||||||
|
'1', '0', ' ', 'm', 'W', ';', /* options */
|
||||||
|
'2', '5', ' ', 'm', 'W', ';',
|
||||||
|
'1', '0', '0', ' ', 'm', 'W', ';',
|
||||||
|
'5', '0', '0', ' ', 'm', 'W', ';',
|
||||||
|
'1', '0', '0', '0', ' ', 'm', 'W', '\0', /* end of options */
|
||||||
|
0x02, /* value */
|
||||||
|
0x00, /* min */
|
||||||
|
0x04, /* max */
|
||||||
|
0x00, /* default */
|
||||||
|
};
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload)));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(5, param.index);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_TEXT_SELECT, param.type);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0, param.hidden);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("TX Power", param.name);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("10 mW;25 mW;100 mW;500 mW;1000 mW", param.options);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(2, param.value);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0, param.min_val);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(4, param.max_val);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0, param.default_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_param_parse_hidden_flag(void) {
|
||||||
|
cel_crsf_param param;
|
||||||
|
uint8_t payload[] = {
|
||||||
|
0x10, 0xEE, 0x00, 0x00, 0x00,
|
||||||
|
0x88, /* type with hidden bit set (0x80 | 0x08) */
|
||||||
|
'H', 'i', 'd', 'd', 'e', 'n', '\0',
|
||||||
|
'A', ';', 'B', '\0',
|
||||||
|
0x00, 0x00, 0x01, 0x00,
|
||||||
|
};
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload)));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_FLOAT, param.type);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(1, param.hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_param_parse_uint8_type(void) {
|
||||||
|
cel_crsf_param param;
|
||||||
|
uint8_t payload[] = {
|
||||||
|
0x10, 0xEE, 0x10, 0x00, 0x00,
|
||||||
|
0x00, /* type = CEL_PARAM_UINT8 */
|
||||||
|
'V', 'a', 'l', '\0', /* name */
|
||||||
|
0x00, /* min */
|
||||||
|
0xFF, /* max */
|
||||||
|
0x80, /* default */
|
||||||
|
0x42, /* value */
|
||||||
|
};
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload)));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0x10, param.index);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_UINT8, param.type);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("Val", param.name);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0x42, param.value);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0x00, param.min_val);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0xFF, param.max_val);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0x80, param.default_val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_param_parse_int8_type(void) {
|
||||||
|
cel_crsf_param param;
|
||||||
|
uint8_t payload[] = {
|
||||||
|
0x10, 0xEE, 0x01, 0x00, 0x00,
|
||||||
|
0x01, /* type = CEL_PARAM_INT8 */
|
||||||
|
'S', '\0',
|
||||||
|
0x80, /* min (-128) */
|
||||||
|
0x7F, /* max (127) */
|
||||||
|
0x00, /* default */
|
||||||
|
0x10, /* value */
|
||||||
|
};
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload)));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_INT8, param.type);
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0x10, param.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_param_parse_folder(void) {
|
||||||
|
cel_crsf_param param;
|
||||||
|
uint8_t payload[] = {
|
||||||
|
0x10, 0xEE, 0xFF, 0x00, 0x00,
|
||||||
|
0x0B, /* type = CEL_PARAM_FOLDER */
|
||||||
|
'F', 'o', 'l', 'd', 'e', 'r', '\0',
|
||||||
|
};
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload)));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(CEL_PARAM_FOLDER, param.type);
|
||||||
|
TEST_ASSERT_EQUAL_STRING("Folder", param.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_param_parse_name_truncation(void) {
|
||||||
|
cel_crsf_param param;
|
||||||
|
/* Name longer than 63 chars (should be truncated) */
|
||||||
|
uint8_t payload[128] = {0};
|
||||||
|
payload[0] = 0x10; /* dest */
|
||||||
|
payload[1] = 0xEE; /* src */
|
||||||
|
payload[2] = 0x00; /* index */
|
||||||
|
payload[3] = 0x00; /* chunks */
|
||||||
|
payload[4] = 0x00; /* parent */
|
||||||
|
payload[5] = 0x0B; /* type = FOLDER */
|
||||||
|
/* Fill name with 70 'A' chars */
|
||||||
|
for (int i = 0; i < 70; i++) payload[6 + i] = 'A';
|
||||||
|
payload[6 + 70] = '\0';
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload)));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(63, strlen(param.name)); /* truncated to 63 */
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_param_parse_options_truncation(void) {
|
||||||
|
cel_crsf_param param;
|
||||||
|
/* Options longer than 255 chars (should be truncated) */
|
||||||
|
uint8_t payload[400] = {0};
|
||||||
|
payload[0] = 0x10; /* dest */
|
||||||
|
payload[1] = 0xEE; /* src */
|
||||||
|
payload[2] = 0x00; /* index */
|
||||||
|
payload[3] = 0x00; /* chunks */
|
||||||
|
payload[4] = 0x00; /* parent */
|
||||||
|
payload[5] = 0x09; /* type = TEXT_SELECT */
|
||||||
|
strcpy((char*)(payload + 6), "Name");
|
||||||
|
payload[6 + 4] = '\0';
|
||||||
|
/* Fill options with 300 'O' chars */
|
||||||
|
size_t opts_start = 6 + 5; /* after name */
|
||||||
|
for (int i = 0; i < 299; i++) payload[opts_start + i] = 'O';
|
||||||
|
payload[opts_start + 299] = '\0';
|
||||||
|
/* Add type-specific data after options */
|
||||||
|
size_t after_opts = opts_start + 300;
|
||||||
|
payload[after_opts] = 0x00; /* value */
|
||||||
|
payload[after_opts + 1] = 0x00; /* min */
|
||||||
|
payload[after_opts + 2] = 0x00; /* max */
|
||||||
|
payload[after_opts + 3] = 0x00; /* default */
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_INT(0, cel_crsf_param_parse(¶m, payload, sizeof(payload)));
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(255, strlen(param.options)); /* truncated to 255 */
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void) {
|
||||||
|
UNITY_BEGIN();
|
||||||
|
RUN_TEST(test_param_parse_null_args);
|
||||||
|
RUN_TEST(test_param_parse_too_short);
|
||||||
|
RUN_TEST(test_param_parse_text_select);
|
||||||
|
RUN_TEST(test_param_parse_hidden_flag);
|
||||||
|
RUN_TEST(test_param_parse_uint8_type);
|
||||||
|
RUN_TEST(test_param_parse_int8_type);
|
||||||
|
RUN_TEST(test_param_parse_folder);
|
||||||
|
RUN_TEST(test_param_parse_name_truncation);
|
||||||
|
RUN_TEST(test_param_parse_options_truncation);
|
||||||
|
return UNITY_END();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user