Skip to content

Commit f24edd0

Browse files
committed
Initial work on repository client
1 parent 71eaa8e commit f24edd0

6 files changed

Lines changed: 260 additions & 19 deletions

File tree

main/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ idf_component_register(
3838
"sdcard.c"
3939
"app_metadata_parser.c"
4040
"python.c"
41+
"menu/repository_client.c"
42+
"http_download.c"
4143
PRIV_REQUIRES
4244
esp_lcd
4345
fatfs

main/main.c

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
#include "esp_lcd_types.h"
2020
#include "esp_log.h"
2121
#include "esp_vfs_fat.h"
22-
#include "freertos/idf_additions.h"
2322
#include "gui_footer.h"
2423
#include "gui_menu.h"
2524
#include "gui_style.h"
@@ -525,7 +524,6 @@ void app_main(void) {
525524
#if CONFIG_IDF_TARGET_ESP32P4
526525
// Only integrate Python into the launcher on ESP32-P4 targets
527526
#if 0
528-
// Enabling Python currently causes crashes
529527
python_initialize();
530528
#endif
531529
#endif

main/menu/home.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "icons.h"
2525
#include "menu/nametag.h"
2626
#include "menu/textedit.h"
27+
#include "repository_client.h"
2728
#include "sdcard.h"
2829
#include "terminal.h"
2930
#include "unistd.h"
@@ -53,6 +54,9 @@ static void execute_action(pax_buf_t* fb, menu_home_action_t action, gui_theme_t
5354
case ACTION_RFTEST:
5455
menu_rftest(fb, theme);
5556
break;
57+
case ACTION_REPOSITORY:
58+
menu_repository_client(fb, theme);
59+
break;
5660
default:
5761
break;
5862
}
@@ -121,7 +125,7 @@ void menu_home(pax_buf_t* buffer, gui_theme_t* theme) {
121125
if (access("/sd/nametag.png", F_OK) == 0) {
122126
menu_insert_item_icon(&menu, "Nametag", NULL, (void*)ACTION_NAMETAG, -1, get_icon(ICON_TAG));
123127
}
124-
// menu_insert_item_icon(&menu, "Repository", NULL, (void*)ACTION_REPOSITORY, -1, get_icon(ICON_REPOSITORY));
128+
menu_insert_item_icon(&menu, "Repository", NULL, (void*)ACTION_REPOSITORY, -1, get_icon(ICON_REPOSITORY));
125129
menu_insert_item_icon(&menu, "Settings", NULL, (void*)ACTION_SETTINGS, -1, get_icon(ICON_SETTINGS));
126130
if (access("/int/rftest_local.bin", F_OK) == 0) {
127131
menu_insert_item_icon(&menu, "RF test", NULL, (void*)ACTION_RFTEST, -1, get_icon(ICON_DEV));

main/menu/repository_client.c

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
#include "repository_client.h"
2+
#include <stdio.h>
3+
#include <string.h>
4+
#include "bsp/input.h"
5+
#include "cJSON.h"
6+
#include "common/display.h"
7+
#include "esp_log.h"
8+
#include "gui_menu.h"
9+
#include "gui_style.h"
10+
#include "http_download.h"
11+
#include "icons.h"
12+
#include "menu/message_dialog.h"
13+
#include "pax_codecs.h"
14+
#include "pax_types.h"
15+
#include "wifi_connection.h"
16+
17+
#define MAX_PROJECTS 32
18+
19+
extern bool wifi_stack_get_initialized(void);
20+
21+
static const char* TAG = "Repository client";
22+
23+
static pax_buf_t icons[MAX_PROJECTS] = {0};
24+
25+
#if defined(CONFIG_BSP_TARGET_KAMI)
26+
#define ICON_WIDTH 32
27+
#define ICON_HEIGHT 32
28+
#define ICON_BUFFER_SIZE (ICON_WIDTH * ICON_HEIGHT * 4) // 32x32 pixels, 2 bits per pixel
29+
#define ICON_COLOR_FORMAT PAX_BUF_2_PAL
30+
#else
31+
#define ICON_WIDTH 32
32+
#define ICON_HEIGHT 32
33+
#define ICON_BUFFER_SIZE (ICON_WIDTH * ICON_HEIGHT * 4) // 32x32 pixels, 4 bytes per pixel (ARGB8888)
34+
#define ICON_COLOR_FORMAT PAX_BUF_32_8888ARGB
35+
#endif
36+
37+
// Repository
38+
39+
static char* data_projects = NULL;
40+
static size_t size_projects = 0;
41+
static cJSON* json_projects = NULL;
42+
43+
static char* data_information = NULL;
44+
static size_t size_information = 0;
45+
static cJSON* json_information = NULL;
46+
47+
static bool load_information(void) {
48+
if (data_information == NULL) {
49+
char url[128];
50+
sprintf(url, "https://apps.tanmatsu.cloud/v1/information");
51+
bool success = download_ram(url, (uint8_t**)&data_information, &size_information);
52+
if (!success) return false;
53+
}
54+
if (data_information == NULL) return false;
55+
json_information = cJSON_ParseWithLength(data_information, size_information);
56+
if (json_information == NULL) return false;
57+
return true;
58+
}
59+
60+
static void free_information(void) {
61+
if (json_information != NULL) {
62+
cJSON_Delete(json_information);
63+
json_information = NULL;
64+
}
65+
if (data_information != NULL) {
66+
free(data_information);
67+
data_information = NULL;
68+
size_information = 0;
69+
}
70+
}
71+
72+
static bool load_projects(void) {
73+
if (data_projects == NULL) {
74+
char url[128];
75+
sprintf(url, "https://apps.tanmatsu.cloud/v1/projects?amount=%u", MAX_PROJECTS);
76+
bool success = download_ram(url, (uint8_t**)&data_projects, &size_projects);
77+
if (!success) return false;
78+
}
79+
if (data_projects == NULL) return false;
80+
json_projects = cJSON_ParseWithLength(data_projects, size_projects);
81+
if (json_projects == NULL) return false;
82+
return true;
83+
}
84+
85+
static void free_projects(void) {
86+
if (json_projects != NULL) {
87+
cJSON_Delete(json_projects);
88+
json_projects = NULL;
89+
}
90+
if (data_projects != NULL) {
91+
free(data_projects);
92+
data_projects = NULL;
93+
size_projects = 0;
94+
}
95+
}
96+
97+
static void populate_project_list(menu_t* menu) {
98+
cJSON* entry_obj;
99+
int i = 0;
100+
cJSON_ArrayForEach(entry_obj, json_projects) {
101+
cJSON* slug_obj = cJSON_GetObjectItem(entry_obj, "slug");
102+
cJSON* project_obj = cJSON_GetObjectItem(entry_obj, "project");
103+
if (project_obj == NULL) {
104+
ESP_LOGE(TAG, "Project object is NULL for entry %d", i);
105+
continue;
106+
}
107+
cJSON* name_obj = cJSON_GetObjectItem(project_obj, "name");
108+
if (name_obj == NULL) {
109+
ESP_LOGE(TAG, "Name object is NULL for entry %d", i);
110+
continue;
111+
}
112+
printf("Project [%s]: %s\r\n", slug_obj->valuestring, name_obj->valuestring);
113+
menu_insert_item_icon(menu, name_obj->valuestring, NULL, (void*)i, -1, get_icon(ICON_DOWNLOADING));
114+
115+
void* buffer = heap_caps_calloc(1, ICON_BUFFER_SIZE, MALLOC_CAP_SPIRAM);
116+
if (buffer == NULL) {
117+
ESP_LOGE(TAG, "Failed to allocate memory for icon for entry %u", i);
118+
continue;
119+
}
120+
pax_buf_init(&icons[i], buffer, ICON_WIDTH, ICON_HEIGHT, ICON_COLOR_FORMAT);
121+
#if defined(CONFIG_BSP_TARGET_KAMI)
122+
icons[i].palette = palette;
123+
icons[i].palette_size = sizeof(palette) / sizeof(pax_col_t);
124+
#endif
125+
/*if (!pax_insert_png_buf(&icons[i], icon_buf, icon_size, 0, 0, 0)) {
126+
ESP_LOGE(TAG, "Failed to decode icon for entry %u", i);
127+
}*/
128+
129+
i++;
130+
}
131+
}
132+
133+
static void render(pax_buf_t* buffer, gui_theme_t* theme, menu_t* menu, bool partial, bool icons) {
134+
int header_height = theme->header.height + (theme->header.vertical_margin * 2);
135+
int footer_height = theme->footer.height + (theme->footer.vertical_margin * 2);
136+
137+
pax_vec2_t position = {
138+
.x0 = theme->menu.horizontal_margin + theme->menu.horizontal_padding,
139+
.y0 = header_height + theme->menu.vertical_margin + theme->menu.vertical_padding,
140+
.x1 = pax_buf_get_width(buffer) - theme->menu.horizontal_margin - theme->menu.horizontal_padding,
141+
.y1 = pax_buf_get_height(buffer) - footer_height - theme->menu.vertical_margin - theme->menu.vertical_padding,
142+
};
143+
144+
if (!partial || icons) {
145+
render_base_screen_statusbar(buffer, theme, !partial, !partial || icons, !partial,
146+
((gui_header_field_t[]){{get_icon(ICON_REPOSITORY), "Repository"}}), 1,
147+
((gui_header_field_t[]){{get_icon(ICON_ESC), "/"}, {get_icon(ICON_F1), "Back"}}),
148+
2, ((gui_header_field_t[]){{NULL, "↑ / ↓ Navigate ⏎ Select"}}), 1);
149+
}
150+
menu_render(buffer, menu, position, theme, partial);
151+
display_blit_buffer(buffer);
152+
}
153+
154+
void menu_repository_client(pax_buf_t* buffer, gui_theme_t* theme) {
155+
if (!wifi_stack_get_initialized()) {
156+
ESP_LOGE(TAG, "WiFi stack not initialized");
157+
return;
158+
}
159+
160+
if (!wifi_connection_is_connected()) {
161+
if (wifi_connect_try_all() != ESP_OK) {
162+
ESP_LOGE(TAG, "Not connected to WiFi");
163+
return;
164+
}
165+
}
166+
167+
bool success = load_information();
168+
if (!success) {
169+
ESP_LOGE(TAG, "Failed to load repository information");
170+
return;
171+
}
172+
173+
success = load_projects();
174+
if (!success) {
175+
ESP_LOGE(TAG, "Failed to load projects");
176+
return;
177+
}
178+
179+
QueueHandle_t input_event_queue = NULL;
180+
ESP_ERROR_CHECK(bsp_input_get_queue(&input_event_queue));
181+
182+
menu_t menu = {0};
183+
menu_initialize(&menu);
184+
populate_project_list(&menu);
185+
186+
render(buffer, theme, &menu, false, true);
187+
while (1) {
188+
bsp_input_event_t event;
189+
if (xQueueReceive(input_event_queue, &event, pdMS_TO_TICKS(1000)) == pdTRUE) {
190+
switch (event.type) {
191+
case INPUT_EVENT_TYPE_NAVIGATION: {
192+
if (event.args_navigation.state) {
193+
switch (event.args_navigation.key) {
194+
case BSP_INPUT_NAVIGATION_KEY_ESC:
195+
case BSP_INPUT_NAVIGATION_KEY_F1:
196+
case BSP_INPUT_NAVIGATION_KEY_GAMEPAD_B:
197+
menu_free(&menu);
198+
return;
199+
case BSP_INPUT_NAVIGATION_KEY_UP:
200+
menu_navigate_previous(&menu);
201+
render(buffer, theme, &menu, true, false);
202+
break;
203+
case BSP_INPUT_NAVIGATION_KEY_DOWN:
204+
menu_navigate_next(&menu);
205+
render(buffer, theme, &menu, true, false);
206+
break;
207+
case BSP_INPUT_NAVIGATION_KEY_RETURN:
208+
case BSP_INPUT_NAVIGATION_KEY_GAMEPAD_A:
209+
case BSP_INPUT_NAVIGATION_KEY_JOYSTICK_PRESS: {
210+
void* arg = menu_get_callback_args(&menu, menu_get_position(&menu));
211+
// execute_action(buffer, (menu_home_action_t)arg, theme);
212+
render(buffer, theme, &menu, false, true);
213+
break;
214+
}
215+
default:
216+
break;
217+
}
218+
}
219+
break;
220+
}
221+
default:
222+
break;
223+
}
224+
} else {
225+
render(buffer, theme, &menu, true, true);
226+
}
227+
}
228+
229+
free_projects();
230+
free_information();
231+
}

main/menu/repository_client.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#pragma once
2+
3+
#include "gui_style.h"
4+
#include "pax_types.h"
5+
6+
void menu_repository_client(pax_buf_t* buffer, gui_theme_t* theme);

main/menu/rftest.c

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,17 @@ static void execute_action(pax_buf_t* fb, menu_home_action_t action, gui_theme_t
4848
}
4949
}
5050

51-
static void render(pax_buf_t* buffer, gui_theme_t* theme, menu_t* menu, pax_vec2_t position, bool partial, bool icons) {
51+
static void render(pax_buf_t* buffer, gui_theme_t* theme, menu_t* menu, bool partial, bool icons) {
52+
int header_height = theme->header.height + (theme->header.vertical_margin * 2);
53+
int footer_height = theme->footer.height + (theme->footer.vertical_margin * 2);
54+
55+
pax_vec2_t position = {
56+
.x0 = theme->menu.horizontal_margin + theme->menu.horizontal_padding,
57+
.y0 = header_height + theme->menu.vertical_margin + theme->menu.vertical_padding,
58+
.x1 = pax_buf_get_width(buffer) - theme->menu.horizontal_margin - theme->menu.horizontal_padding,
59+
.y1 = pax_buf_get_height(buffer) - footer_height - theme->menu.vertical_margin - theme->menu.vertical_padding,
60+
};
61+
5262
if (!partial || icons) {
5363
render_base_screen_statusbar(buffer, theme, !partial, !partial || icons, !partial,
5464
((gui_header_field_t[]){{get_icon(ICON_SETTINGS), "Radio test"}}), 1,
@@ -74,17 +84,7 @@ void menu_rftest(pax_buf_t* buffer, gui_theme_t* theme) {
7484
menu_insert_item_icon(&menu, "Terminal for RF test local firmware", NULL, (void*)ACTION_TERMINAL, -1,
7585
get_icon(ICON_DEVICE_INFO));
7686

77-
int header_height = theme->header.height + (theme->header.vertical_margin * 2);
78-
int footer_height = theme->footer.height + (theme->footer.vertical_margin * 2);
79-
80-
pax_vec2_t position = {
81-
.x0 = theme->menu.horizontal_margin + theme->menu.horizontal_padding,
82-
.y0 = header_height + theme->menu.vertical_margin + theme->menu.vertical_padding,
83-
.x1 = pax_buf_get_width(buffer) - theme->menu.horizontal_margin - theme->menu.horizontal_padding,
84-
.y1 = pax_buf_get_height(buffer) - footer_height - theme->menu.vertical_margin - theme->menu.vertical_padding,
85-
};
86-
87-
render(buffer, theme, &menu, position, false, true);
87+
render(buffer, theme, &menu, false, true);
8888
while (1) {
8989
bsp_input_event_t event;
9090
if (xQueueReceive(input_event_queue, &event, pdMS_TO_TICKS(1000)) == pdTRUE) {
@@ -99,18 +99,18 @@ void menu_rftest(pax_buf_t* buffer, gui_theme_t* theme) {
9999
return;
100100
case BSP_INPUT_NAVIGATION_KEY_UP:
101101
menu_navigate_previous(&menu);
102-
render(buffer, theme, &menu, position, true, false);
102+
render(buffer, theme, &menu, true, false);
103103
break;
104104
case BSP_INPUT_NAVIGATION_KEY_DOWN:
105105
menu_navigate_next(&menu);
106-
render(buffer, theme, &menu, position, true, false);
106+
render(buffer, theme, &menu, true, false);
107107
break;
108108
case BSP_INPUT_NAVIGATION_KEY_RETURN:
109109
case BSP_INPUT_NAVIGATION_KEY_GAMEPAD_A:
110110
case BSP_INPUT_NAVIGATION_KEY_JOYSTICK_PRESS: {
111111
void* arg = menu_get_callback_args(&menu, menu_get_position(&menu));
112112
execute_action(buffer, (menu_home_action_t)arg, theme);
113-
render(buffer, theme, &menu, position, false, true);
113+
render(buffer, theme, &menu, false, true);
114114
break;
115115
}
116116
default:
@@ -123,7 +123,7 @@ void menu_rftest(pax_buf_t* buffer, gui_theme_t* theme) {
123123
break;
124124
}
125125
} else {
126-
render(buffer, theme, &menu, position, true, true);
126+
render(buffer, theme, &menu, true, true);
127127
}
128128
}
129129
}

0 commit comments

Comments
 (0)