#include "McpScreen.h" #include #include #include #include #include #include #include #include static const char* TAG = "McpScreen"; static void set_label_text_locked(lv_obj_t* label, const char* text) { if (label != NULL && lv_obj_is_valid(label)) { lv_label_set_text(label, text); } } void mcp_ui_set_server_status(McpScreenState* state, const char* status) { if (state == NULL || !state->visible) { return; } if (tt_lvgl_lock(pdMS_TO_TICKS(1000))) { if (state->visible) { set_label_text_locked(state->server_label, status); } tt_lvgl_unlock(); } } void mcp_ui_set_last_tool(McpScreenState* state, const char* tool) { if (state == NULL || !state->visible) { return; } if (tt_lvgl_lock(pdMS_TO_TICKS(1000))) { if (state->visible && state->tool_label != NULL && lv_obj_is_valid(state->tool_label)) { lv_label_set_text_fmt(state->tool_label, "Last: %s", tool); } tt_lvgl_unlock(); } } bool mcp_ui_clear(McpScreenState* state, int color) { if (state == NULL || !state->visible || state->framebuffer == NULL) { return false; } bool success = false; if (tt_lvgl_lock(pdMS_TO_TICKS(1500))) { if (state->visible && state->draw_area != NULL && lv_obj_is_valid(state->draw_area)) { lv_obj_clean(state->draw_area); uint16_t fill = color == 1 ? 0x0000 : 0xFFFF; size_t pixel_count = (size_t)state->draw_width * state->draw_height; for (size_t i = 0; i < pixel_count; ++i) { state->framebuffer[i] = fill; } lv_obj_invalidate(state->draw_area); state->draw_color = color; state->override_active = true; success = true; } tt_lvgl_unlock(); } return success; } bool mcp_ui_draw_text( McpScreenState* state, const char* text, int x, int y, int size ) { if (state == NULL || text == NULL || !state->visible) { return false; } bool success = false; if (tt_lvgl_lock(pdMS_TO_TICKS(1500))) { if (state->visible && state->draw_area != NULL && lv_obj_is_valid(state->draw_area)) { int max_x = state->draw_width > 0 ? state->draw_width - 1 : 0; int max_y = state->draw_height > 0 ? state->draw_height - 1 : 0; if (x < 0) x = 0; if (y < 0) y = 0; if (x > max_x) x = max_x; if (y > max_y) y = max_y; lv_obj_t* label = lv_label_create(state->draw_area); lv_label_set_text(label, text); lv_label_set_long_mode(label, LV_LABEL_LONG_WRAP); lv_obj_set_width(label, LV_MAX(1, state->draw_width - x)); lv_obj_set_pos(label, x, y); lv_obj_set_style_text_color( label, state->draw_color == 0 ? lv_color_black() : lv_color_white(), LV_PART_MAIN ); #if LV_FONT_MONTSERRAT_24 if (size >= 2) { lv_obj_set_style_text_font(label, &lv_font_montserrat_24, LV_PART_MAIN); } #else (void)size; #endif state->override_active = true; success = true; } tt_lvgl_unlock(); } return success; } static void* create_data(void) { McpScreenState* state = calloc(1, sizeof(McpScreenState)); if (state != NULL) { state->http_socket = -1; state->client_socket = -1; state->discovery_socket = -1; } return state; } static void destroy_data(void* data) { McpScreenState* state = data; if (state != NULL && state->framebuffer != NULL) { heap_caps_free(state->framebuffer); } free(state); } static void on_create(AppHandle app, void* data) { McpScreenState* state = data; state->app = app; } static void on_destroy(AppHandle app, void* data) { (void)app; mcp_services_stop((McpScreenState*)data); } static void on_show(AppHandle app, void* data, lv_obj_t* parent) { McpScreenState* state = data; ESP_LOGI(TAG, "onShow: starting foreground UI and MCP service"); state->visible = true; lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE); lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN); lv_obj_set_style_pad_all(parent, 0, LV_PART_MAIN); lv_obj_set_style_pad_row(parent, 0, LV_PART_MAIN); lv_obj_t* toolbar = tt_lvgl_toolbar_create_for_app(parent, app); lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0); state->server_label = lv_label_create(toolbar); lv_label_set_text(state->server_label, "MCP starting"); lv_obj_set_style_text_color( state->server_label, lv_palette_main(LV_PALETTE_ORANGE), LV_PART_MAIN ); state->tool_label = lv_label_create(toolbar); lv_label_set_text(state->tool_label, "Last: none"); if (!mcp_services_start(state)) { ESP_LOGE(TAG, "Failed to start foreground MCP service"); lv_label_set_text(state->server_label, "MCP start failed"); lv_obj_set_style_text_color( state->server_label, lv_palette_main(LV_PALETTE_RED), LV_PART_MAIN ); } state->draw_area = lv_canvas_create(parent); lv_obj_set_width(state->draw_area, LV_PCT(100)); lv_obj_set_flex_grow(state->draw_area, 1); lv_obj_set_style_radius(state->draw_area, 0, LV_PART_MAIN); lv_obj_set_style_border_width(state->draw_area, 0, LV_PART_MAIN); lv_obj_set_style_pad_all(state->draw_area, 0, LV_PART_MAIN); lv_obj_remove_flag(state->draw_area, LV_OBJ_FLAG_SCROLLABLE); lv_display_t* display = lv_obj_get_display(parent); state->display_width = lv_display_get_horizontal_resolution(display); state->display_height = lv_display_get_vertical_resolution(display); lv_obj_update_layout(parent); state->draw_width = lv_obj_get_content_width(state->draw_area); state->draw_height = lv_obj_get_content_height(state->draw_area); size_t required_size = (size_t)state->draw_width * state->draw_height * sizeof(uint16_t); if (state->framebuffer == NULL || state->framebuffer_size != required_size) { if (state->framebuffer != NULL) { heap_caps_free(state->framebuffer); state->framebuffer = NULL; } state->framebuffer = heap_caps_malloc(required_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); if (state->framebuffer == NULL) { state->framebuffer = heap_caps_malloc(required_size, MALLOC_CAP_8BIT); } state->framebuffer_size = state->framebuffer == NULL ? 0 : required_size; } if (state->framebuffer == NULL) { ESP_LOGE(TAG, "Failed to allocate %u-byte framebuffer", (unsigned)required_size); lv_obj_t* error = lv_label_create(state->draw_area); lv_label_set_text(error, "Framebuffer allocation failed"); lv_obj_center(error); return; } lv_canvas_set_buffer( state->draw_area, state->framebuffer, state->draw_width, state->draw_height, LV_COLOR_FORMAT_RGB565 ); if (!state->override_active) { for (size_t i = 0; i < (size_t)state->draw_width * state->draw_height; ++i) { state->framebuffer[i] = 0x10C3; } state->draw_color = 1; lv_obj_t* title = lv_label_create(state->draw_area); lv_label_set_text(title, "MCP Screen"); lv_obj_set_style_text_color(title, lv_color_white(), LV_PART_MAIN); #if LV_FONT_MONTSERRAT_24 lv_obj_set_style_text_font(title, &lv_font_montserrat_24, LV_PART_MAIN); #endif lv_obj_align(title, LV_ALIGN_CENTER, 0, -55); lv_obj_t* dimensions = lv_label_create(state->draw_area); lv_label_set_text_fmt( dimensions, "%ux%u display\nHTTP :80\nDiscovery: bridge subnet scan", state->display_width, state->display_height ); lv_obj_set_style_text_align(dimensions, LV_TEXT_ALIGN_CENTER, LV_PART_MAIN); lv_obj_set_style_text_color(dimensions, lv_palette_lighten(LV_PALETTE_BLUE, 3), LV_PART_MAIN); lv_obj_align(dimensions, LV_ALIGN_CENTER, 0, 0); lv_obj_t* wifi = lv_label_create(state->draw_area); lv_label_set_text_fmt( wifi, "Wi-Fi: %s", tt_wifi_radio_state_to_string(tt_wifi_get_radio_state()) ); lv_obj_set_style_text_color(wifi, lv_color_white(), LV_PART_MAIN); lv_obj_align(wifi, LV_ALIGN_CENTER, 0, 48); } } static void on_hide(AppHandle app, void* data) { (void)app; McpScreenState* state = data; ESP_LOGI(TAG, "onHide: stopping foreground MCP service"); state->visible = false; mcp_services_hide(state); state->draw_area = NULL; state->server_label = NULL; state->tool_label = NULL; } int main(int argc, char* argv[]) { (void)argc; (void)argv; tt_app_register((AppRegistration) { .createData = create_data, .destroyData = destroy_data, .onCreate = on_create, .onDestroy = on_destroy, .onShow = on_show, .onHide = on_hide }); return 0; }