# tft_app: UX test - dummy parameter
This commit is contained in:
parent
8c72be8708
commit
00c2d43cbd
5 changed files with 137 additions and 91 deletions
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
START([Новая настройка]) --> Q{Имеет смысл ТОЛЬКО<br/>для одного протокола?<br/>(напр. адрес станции)}
|
START([Новая настройка]) --> Q{Имеет смысл ТОЛЬКО<br/>для одного протокола?<br/>}
|
||||||
Q -->|да| PROTO["Протокольная<br/>→ proto_slice + sul_settings_desc_t<br/>см. §5"]
|
Q -->|да| PROTO["Протокольная<br/>→ proto_slice + sul_settings_desc_t<br/>см. §5"]
|
||||||
Q -->|нет| USER["Пользовательская/device<br/>→ своё поле в settings_t<br/>см. §4"]
|
Q -->|нет| USER["Пользовательская/device<br/>→ своё поле в settings_t<br/>см. §4"]
|
||||||
```
|
```
|
||||||
|
|
@ -168,7 +168,7 @@ log_set_enabled(g_menu.settings->device.log_enabled != 0U);
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. Пример целиком — тумблер логов (эталон пользовательской BOOL-настройки)
|
## 7. Пример — тумблер логов (эталон пользовательской BOOL-настройки)
|
||||||
|
|
||||||
| Шаг | Файл | Что именно |
|
| Шаг | Файл | Что именно |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,14 @@
|
||||||
/* Метки значений (ярус B/устройство). */
|
/* Метки значений (ярус B/устройство). */
|
||||||
static const char *const K_BOOL_LABELS[] = { "Выкл", "Вкл" };
|
static const char *const K_BOOL_LABELS[] = { "Выкл", "Вкл" };
|
||||||
|
|
||||||
|
static const char *const K_DUMMY_LABELS[] = { "Гойда!", "Зрада!" };
|
||||||
/* Верхняя граница на число протоколов в реестре — только размер буфера меток
|
/* Верхняя граница на число протоколов в реестре — только размер буфера меток
|
||||||
* (menu_tree_refresh_protocol_section), не ограничение самого реестра.
|
* (menu_tree_refresh_protocol_section), не ограничение самого реестра.
|
||||||
* Сейчас 2 (НКУ-CAN, демо), с запасом под Фазу 8 (+ УИМ/SD7/УЭЛ/УКЛ — 6). */
|
* Сейчас 2 (НКУ-CAN, демо), с запасом под Фазу 8 (+ УИМ/SD7/УЭЛ/УКЛ — 6). */
|
||||||
#define MENU_TREE_MAX_PROTOCOLS 8U
|
#define MENU_TREE_MAX_PROTOCOLS 8U
|
||||||
static const char *s_proto_labels[MENU_TREE_MAX_PROTOCOLS] = { "НКУ-CAN" }; /* фолбэк до refresh() */
|
static const char *g_s_proto_labels[MENU_TREE_MAX_PROTOCOLS] = {
|
||||||
|
"НКУ-CAN"
|
||||||
|
}; /* фолбэк до refresh() */
|
||||||
|
|
||||||
/* Индексы пунктов дерева. */
|
/* Индексы пунктов дерева. */
|
||||||
enum
|
enum
|
||||||
|
|
@ -22,7 +25,9 @@ enum
|
||||||
T_PROTO_PARAM, /* единственный параметр АКТИВНОГО протокола (§8) — популируется
|
T_PROTO_PARAM, /* единственный параметр АКТИВНОГО протокола (§8) — популируется
|
||||||
* из sul_settings_desc_t, см. menu_tree_refresh_protocol_section() */
|
* из sul_settings_desc_t, см. menu_tree_refresh_protocol_section() */
|
||||||
T_LOG,
|
T_LOG,
|
||||||
|
T_DUMMY,
|
||||||
T_EXIT,
|
T_EXIT,
|
||||||
|
|
||||||
T_COUNT,
|
T_COUNT,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -35,7 +40,7 @@ enum
|
||||||
* протокола, не только к его параметрам. Значения ниже — safe-фолбэк на
|
* протокола, не только к его параметрам. Значения ниже — safe-фолбэк на
|
||||||
* случай, если refresh() почему-то не вызван (совпадает с тем, что было
|
* случай, если refresh() почему-то не вызван (совпадает с тем, что было
|
||||||
* до Фазы 3.3, когда протокол был всего один). */
|
* до Фазы 3.3, когда протокол был всего один). */
|
||||||
static menu_item_desc_t s_tree[T_COUNT] = {
|
static menu_item_desc_t g_s_tree[T_COUNT] = {
|
||||||
[T_ROOT] = { .label = "Настройки",
|
[T_ROOT] = { .label = "Настройки",
|
||||||
.type = MENU_SUBMENU,
|
.type = MENU_SUBMENU,
|
||||||
.parent = MENU_ROOT_INDEX,
|
.parent = MENU_ROOT_INDEX,
|
||||||
|
|
@ -47,7 +52,7 @@ static menu_item_desc_t s_tree[T_COUNT] = {
|
||||||
.min = 0U,
|
.min = 0U,
|
||||||
.max = 0U,
|
.max = 0U,
|
||||||
.parent = MENU_ROOT_INDEX,
|
.parent = MENU_ROOT_INDEX,
|
||||||
.options = s_proto_labels },
|
.options = g_s_proto_labels },
|
||||||
[T_PROTO_PARAM] = { .label = "Адрес",
|
[T_PROTO_PARAM] = { .label = "Адрес",
|
||||||
.type = MENU_BYTE,
|
.type = MENU_BYTE,
|
||||||
.value_offset = offsetof(settings_t, user.proto_slice[0]),
|
.value_offset = offsetof(settings_t, user.proto_slice[0]),
|
||||||
|
|
@ -61,6 +66,13 @@ static menu_item_desc_t s_tree[T_COUNT] = {
|
||||||
.max = 1U,
|
.max = 1U,
|
||||||
.parent = MENU_ROOT_INDEX,
|
.parent = MENU_ROOT_INDEX,
|
||||||
.options = K_BOOL_LABELS },
|
.options = K_BOOL_LABELS },
|
||||||
|
[T_DUMMY] = { .label = "Общий?",
|
||||||
|
.type = MENU_BOOL,
|
||||||
|
.value_offset = offsetof(settings_t, user.dummy_option),
|
||||||
|
.min = 0U,
|
||||||
|
.max = 1U,
|
||||||
|
.parent = MENU_ROOT_INDEX,
|
||||||
|
.options = K_DUMMY_LABELS },
|
||||||
[T_EXIT] = { .label = "Выход", .type = MENU_BACK, .parent = MENU_ROOT_INDEX },
|
[T_EXIT] = { .label = "Выход", .type = MENU_BACK, .parent = MENU_ROOT_INDEX },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -86,9 +98,9 @@ void menu_tree_refresh_protocol_section(settings_t *p_settings_rw)
|
||||||
for (uint8_t i = 0U; i < VISIBLE; i++)
|
for (uint8_t i = 0U; i < VISIBLE; i++)
|
||||||
{
|
{
|
||||||
const sul_driver_t *p_drv = sul_registry_find(i);
|
const sul_driver_t *p_drv = sul_registry_find(i);
|
||||||
s_proto_labels[i] = (p_drv != NULL) ? p_drv->p_name : "?";
|
g_s_proto_labels[i] = (p_drv != NULL) ? p_drv->p_name : "?";
|
||||||
}
|
}
|
||||||
s_tree[T_PROTO].max = (uint8_t) (VISIBLE - 1U);
|
g_s_tree[T_PROTO].max = (uint8_t) (VISIBLE - 1U);
|
||||||
|
|
||||||
/* Единственный параметр активного протокола (§8). Сейчас у каждого
|
/* Единственный параметр активного протокола (§8). Сейчас у каждого
|
||||||
* зарегистрированного протокола ровно один (НКУ-CAN: адрес; демо:
|
* зарегистрированного протокола ровно один (НКУ-CAN: адрес; демо:
|
||||||
|
|
@ -101,19 +113,19 @@ void menu_tree_refresh_protocol_section(settings_t *p_settings_rw)
|
||||||
{
|
{
|
||||||
const sul_settings_entry_t *p_entry = &p_settings->p_entries[0];
|
const sul_settings_entry_t *p_entry = &p_settings->p_entries[0];
|
||||||
|
|
||||||
s_tree[T_PROTO_PARAM].label = p_entry->p_label;
|
g_s_tree[T_PROTO_PARAM].label = p_entry->p_label;
|
||||||
s_tree[T_PROTO_PARAM].type = menu_type_from_sul(p_entry->type);
|
g_s_tree[T_PROTO_PARAM].type = menu_type_from_sul(p_entry->type);
|
||||||
s_tree[T_PROTO_PARAM].value_offset =
|
g_s_tree[T_PROTO_PARAM].value_offset =
|
||||||
(uint16_t) (offsetof(settings_t, user.proto_slice) + p_entry->slice_offset);
|
(uint16_t) (offsetof(settings_t, user.proto_slice) + p_entry->slice_offset);
|
||||||
s_tree[T_PROTO_PARAM].min = p_entry->min;
|
g_s_tree[T_PROTO_PARAM].min = p_entry->min;
|
||||||
s_tree[T_PROTO_PARAM].max = p_entry->max;
|
g_s_tree[T_PROTO_PARAM].max = p_entry->max;
|
||||||
s_tree[T_PROTO_PARAM].options = p_entry->p_options;
|
g_s_tree[T_PROTO_PARAM].options = p_entry->p_options;
|
||||||
|
|
||||||
/* Клампим ТЕКУЩЕЕ значение под новый диапазон — proto_slice[0] мог
|
/* Клампим ТЕКУЩЕЕ значение под новый диапазон — proto_slice[0] мог
|
||||||
* остаться от другого протокола с более широким диапазоном (напр.
|
* остаться от другого протокола с более широким диапазоном (напр.
|
||||||
* адрес НКУ-CAN 0..15 -> скорость демо 0..2); без этого рендер читал
|
* адрес НКУ-CAN 0..15 -> скорость демо 0..2); без этого рендер читал
|
||||||
* бы options[value] за пределами массива меток нового протокола. */
|
* бы options[value] за пределами массива меток нового протокола. */
|
||||||
uint8_t *p_val = (uint8_t *) p_settings_rw + s_tree[T_PROTO_PARAM].value_offset;
|
uint8_t *p_val = (uint8_t *) p_settings_rw + g_s_tree[T_PROTO_PARAM].value_offset;
|
||||||
if (*p_val > p_entry->max)
|
if (*p_val > p_entry->max)
|
||||||
{
|
{
|
||||||
*p_val = p_entry->max;
|
*p_val = p_entry->max;
|
||||||
|
|
@ -123,18 +135,18 @@ void menu_tree_refresh_protocol_section(settings_t *p_settings_rw)
|
||||||
{
|
{
|
||||||
/* Протокол без параметров — инертный дефолт (не встречается пока
|
/* Протокол без параметров — инертный дефолт (не встречается пока
|
||||||
* ни у одного зарегистрированного протокола). */
|
* ни у одного зарегистрированного протокола). */
|
||||||
s_tree[T_PROTO_PARAM].label = "—";
|
g_s_tree[T_PROTO_PARAM].label = "—";
|
||||||
s_tree[T_PROTO_PARAM].type = MENU_BYTE;
|
g_s_tree[T_PROTO_PARAM].type = MENU_BYTE;
|
||||||
s_tree[T_PROTO_PARAM].value_offset = offsetof(settings_t, user.proto_slice[0]);
|
g_s_tree[T_PROTO_PARAM].value_offset = offsetof(settings_t, user.proto_slice[0]);
|
||||||
s_tree[T_PROTO_PARAM].min = 0U;
|
g_s_tree[T_PROTO_PARAM].min = 0U;
|
||||||
s_tree[T_PROTO_PARAM].max = 0U;
|
g_s_tree[T_PROTO_PARAM].max = 0U;
|
||||||
s_tree[T_PROTO_PARAM].options = NULL;
|
g_s_tree[T_PROTO_PARAM].options = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const menu_item_desc_t *menu_tree_items(void)
|
const menu_item_desc_t *menu_tree_items(void)
|
||||||
{
|
{
|
||||||
return s_tree;
|
return g_s_tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t menu_tree_count(void)
|
uint8_t menu_tree_count(void)
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ typedef struct
|
||||||
uint8_t music_volume_idx;
|
uint8_t music_volume_idx;
|
||||||
uint8_t year_production; /**< 0 = скрыть, иначе 2000+N (25 = 2025) */
|
uint8_t year_production; /**< 0 = скрыть, иначе 2000+N (25 = 2025) */
|
||||||
char serial[SETTINGS_SERIAL_LEN]; /**< ASCII, '\0'-терминирован */
|
char serial[SETTINGS_SERIAL_LEN]; /**< ASCII, '\0'-терминирован */
|
||||||
|
uint8_t dummy_option;
|
||||||
/* Ярус B — протокольные: активный протокол трактует slice через
|
/* Ярус B — протокольные: активный протокол трактует slice через
|
||||||
sul_settings_desc_t (§8). НКУ-CAN: proto_slice[0] = адрес 0..15. */
|
sul_settings_desc_t (§8). НКУ-CAN: proto_slice[0] = адрес 0..15. */
|
||||||
uint8_t proto_slice[SETTINGS_PROTO_SLICE_LEN];
|
uint8_t proto_slice[SETTINGS_PROTO_SLICE_LEN];
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ static const settings_t K_DEFAULTS = {
|
||||||
.music_volume_idx = 1U,
|
.music_volume_idx = 1U,
|
||||||
.year_production = 0U, /* скрыть */
|
.year_production = 0U, /* скрыть */
|
||||||
.serial = "",
|
.serial = "",
|
||||||
|
.dummy_option = 0U, // Моя опция
|
||||||
.proto_slice = {0}, /* НКУ: адрес 0 */
|
.proto_slice = {0}, /* НКУ: адрес 0 */
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -70,7 +71,8 @@ bool settings_deserialize(const settings_page_t *p_page, settings_t *p_out)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const uint32_t CRC = settings_crc32((const uint8_t *) p_page, sizeof(*p_page) - sizeof(uint32_t));
|
const uint32_t CRC =
|
||||||
|
settings_crc32((const uint8_t *) p_page, sizeof(*p_page) - sizeof(uint32_t));
|
||||||
if (CRC != p_page->crc32)
|
if (CRC != p_page->crc32)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,12 @@
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
void setUp(void) {}
|
void setUp(void)
|
||||||
void tearDown(void) {}
|
{
|
||||||
|
}
|
||||||
|
void tearDown(void)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/* T_PROTO — первый ребёнок корня (гарантия menu.h: items[MENU_ROOT_INDEX] —
|
/* T_PROTO — первый ребёнок корня (гарантия menu.h: items[MENU_ROOT_INDEX] —
|
||||||
* корневой SUBMENU). T_PROTO_PARAM — следующий по порядку в дереве
|
* корневой SUBMENU). T_PROTO_PARAM — следующий по порядку в дереве
|
||||||
|
|
@ -111,6 +115,33 @@ static void test_protocol_param_edits_correct_settings_field(void)
|
||||||
TEST_ASSERT_EQUAL_UINT8(1U, s.user.proto_slice[0]);
|
TEST_ASSERT_EQUAL_UINT8(1U, s.user.proto_slice[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Конец-в-конец: правка пункта, построенного из дескриптора, действительно
|
||||||
|
* попадает в то самое поле settings_t, которое назвал дескриптор протокола —
|
||||||
|
* не только структура данных совпадает, но и реальный edit через menu.c. */
|
||||||
|
static void test_dummy_param_edits_correct_settings_field(void)
|
||||||
|
{
|
||||||
|
settings_t s;
|
||||||
|
memset(&s, 0, sizeof(s));
|
||||||
|
|
||||||
|
sul_registry_set_active(SUL_PROTOCOL_NKU_CAN);
|
||||||
|
menu_tree_refresh_protocol_section(&s);
|
||||||
|
|
||||||
|
menu_ctx_t ctx;
|
||||||
|
menu_init(&ctx, menu_tree_items(), menu_tree_count(), &s);
|
||||||
|
menu_open(&ctx);
|
||||||
|
menu_next(&ctx); /* T_PROTO -> T_PROTO_PARAM */
|
||||||
|
menu_next(&ctx); /* T_PROTO_PARAM -> T_LOG */
|
||||||
|
menu_next(&ctx); /* T_LOG -> T_DUMMY */
|
||||||
|
|
||||||
|
menu_action(&ctx); /* инкремент значения 0 -> 1 */
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(1U, s.user.dummy_option);
|
||||||
|
|
||||||
|
menu_action(&ctx); /* инкремент значения 1 -> 0 */
|
||||||
|
|
||||||
|
TEST_ASSERT_EQUAL_UINT8(0U, s.user.dummy_option);
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
UNITY_BEGIN();
|
UNITY_BEGIN();
|
||||||
|
|
@ -119,6 +150,7 @@ int main(void)
|
||||||
RUN_TEST(test_protocol_section_switches_to_demo_descriptor);
|
RUN_TEST(test_protocol_section_switches_to_demo_descriptor);
|
||||||
RUN_TEST(test_stale_value_clamped_on_protocol_switch);
|
RUN_TEST(test_stale_value_clamped_on_protocol_switch);
|
||||||
RUN_TEST(test_protocol_param_edits_correct_settings_field);
|
RUN_TEST(test_protocol_param_edits_correct_settings_field);
|
||||||
|
RUN_TEST(test_dummy_param_edits_correct_settings_field);
|
||||||
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue