From ed927e16b1ea78fcb5c2d4a1e6d064d4f69c9cab Mon Sep 17 00:00:00 2001 From: Adolfo Reyna Date: Sun, 28 Dec 2025 21:59:59 -0500 Subject: [PATCH] feature to send image from tcp connection --- command_processor.cpp | 1 + command_processor.h | 1 + epaper_manager.cpp | 42 +++++++++++++++++++- epaper_manager.h | 8 ++++ generate_image.py | 90 ++++++++++++++++++++++++++++++++++++++++++ hello_usb.cpp | 59 +++++++++++++++++++++++++++ tcp_debug.cpp | 11 +++++- tcp_debug.h | 3 ++ test_image.bin | Bin 0 -> 15000 bytes 9 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 generate_image.py create mode 100644 test_image.bin diff --git a/command_processor.cpp b/command_processor.cpp index 590f9c1..8a10b95 100644 --- a/command_processor.cpp +++ b/command_processor.cpp @@ -12,6 +12,7 @@ CommandAction parse_command(const char* input) { if (strcmp(input, "/scan") == 0) return CMD_SCAN; if (strncmp(input, "/connect ", 9) == 0 || strcmp(input, "/connect") == 0) return CMD_CONNECT; if (strcmp(input, "/status") == 0) return CMD_STATUS; + if (strcmp(input, "/image") == 0) return CMD_IMAGE; return CMD_UNKNOWN; } diff --git a/command_processor.h b/command_processor.h index 34ea461..281723e 100644 --- a/command_processor.h +++ b/command_processor.h @@ -8,6 +8,7 @@ enum CommandAction { CMD_SCAN, CMD_CONNECT, CMD_STATUS, + CMD_IMAGE, CMD_UNKNOWN }; diff --git a/epaper_manager.cpp b/epaper_manager.cpp index 091a66d..d5ce74b 100644 --- a/epaper_manager.cpp +++ b/epaper_manager.cpp @@ -45,6 +45,7 @@ typedef struct { UWORD ystart; UWORD xend; UWORD yend; + UBYTE *raw_image; // Pointer to raw image data (if not NULL, display this instead of text) } DisplayMessage; static void init_epaper_display() { @@ -141,7 +142,11 @@ static void core1_display_init() { // printf("[Core 1] Updating display with %d entries\n", msg->entries.count); // Update the e-Paper display - if (g_epd_image != NULL) { + if (msg->raw_image != NULL) { + printf("[Core 1] Displaying raw image...\n"); + EPD_4IN2_V2_Display(msg->raw_image); + free(msg->raw_image); // Free the image buffer allocated by Core 0 + } else if (g_epd_image != NULL) { // For partial refresh, only clear the text area UWORD y_start = msg->ystart; UWORD y_end = msg->yend; @@ -219,6 +224,7 @@ void epaper_send_update(const char *entry, bool finish_line) { // Copy entry list to message msg->entries = g_entry_list; + msg->raw_image = NULL; // Not an image update // We want to show the current line being edited, so we treat count as count + 1 for display purposes msg->entries.count = g_entry_list.count + 1; @@ -246,7 +252,11 @@ void epaper_send_update(const char *entry, bool finish_line) { if (queue_try_add(&g_display_queue, &msg)) { // printf("[Core 0] Display update sent to core 1\n"); } else { - printf("[Core 0] Queue full, skipping display update\n"); + static int full_count = 0; + if (full_count < 5) { + printf("[Core 0] Queue full, skipping display update\n"); + full_count++; + } free(msg); } @@ -267,3 +277,31 @@ void epaper_force_refresh() { g_force_full_refresh = true; epaper_send_update("", false); } + +void epaper_display_full_image(const unsigned char* image_data, unsigned int len) { + if (!g_display_ready) return; + + // Allocate message structure + DisplayMessage *msg = (DisplayMessage *)malloc(sizeof(DisplayMessage)); + if (msg == NULL) { + printf("[Core 0] Failed to allocate display message for image!\n"); + return; + } + + // We are passing ownership of the image buffer to the message/Core 1 + // The caller should have allocated image_data with malloc + msg->raw_image = (UBYTE*)image_data; + msg->use_partial = false; // Full refresh for images + + // Send message pointer to core 1 via Queue + if (queue_try_add(&g_display_queue, &msg)) { + printf("[Core 0] Image update sent to core 1\n"); + } else { + printf("[Core 0] Queue full, skipping image update\n"); + free(msg); + // Note: We don't free image_data here because the caller might expect it to be consumed or not. + // Ideally, if we fail to send, we should free it if we took ownership. + // Let's assume we take ownership. + free((void*)image_data); + } +} diff --git a/epaper_manager.h b/epaper_manager.h index 48132b6..0fdf26c 100644 --- a/epaper_manager.h +++ b/epaper_manager.h @@ -17,6 +17,14 @@ void epaper_start_background_thread(); */ void epaper_send_update(const char *entry, bool finish_line); +/** + * Displays a full-screen raw image. + * @param image_data Pointer to the raw image data (1 bit per pixel). + * The buffer must be allocated with malloc and will be freed by the display core. + * @param len Length of the image data in bytes. + */ +void epaper_display_full_image(const unsigned char* image_data, unsigned int len); + /** * Clears all text entries and refreshes the display to white. */ diff --git a/generate_image.py b/generate_image.py new file mode 100644 index 0000000..22c2d05 --- /dev/null +++ b/generate_image.py @@ -0,0 +1,90 @@ +import sys +import socket +import time +from PIL import Image, ImageDraw, ImageFont + +if len(sys.argv) < 2: + print("Usage: python3 generate_image.py [TEXT]") + sys.exit(1) + +IP_ADDRESS = sys.argv[1] +PORT = 4242 +TEXT_TO_DRAW = sys.argv[2] if len(sys.argv) > 2 else "Hello from Python!" + +WIDTH = 400 +HEIGHT = 300 + +# Create a new image with white background (1 = White, 0 = Black) +image = Image.new('1', (WIDTH, HEIGHT), 1) +draw = ImageDraw.Draw(image) + +print("Generating pattern...") + +# Draw Borders +draw.rectangle([(0, 0), (WIDTH-1, HEIGHT-1)], outline=0, width=3) + +# Draw Diagonal Cross +draw.line([(0, 0), (WIDTH-1, HEIGHT-1)], fill=0, width=2) +draw.line([(0, HEIGHT-1), (WIDTH-1, 0)], fill=0, width=2) + +# Draw a filled box in the center +center_x = WIDTH // 2 +center_y = HEIGHT // 2 +box_size = 50 +draw.rectangle([(center_x - box_size, center_y - box_size), + (center_x + box_size, center_y + box_size)], fill=0) + +# Draw Text +try: + # Try to load a nice font + # On macOS, Arial is usually available. + font = ImageFont.truetype("/Library/Fonts/Arial.ttf", 36) +except IOError: + try: + # Try Linux path + font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 36) + except IOError: + print("Warning: Could not load custom font, using default.") + font = ImageFont.load_default() + +# Calculate text size +try: + # Pillow >= 9.2.0 + left, top, right, bottom = draw.textbbox((0, 0), TEXT_TO_DRAW, font=font) + text_width = right - left + text_height = bottom - top +except AttributeError: + # Older Pillow + text_width, text_height = draw.textsize(TEXT_TO_DRAW, font=font) + +text_x = (WIDTH - text_width) // 2 +text_y = 40 # Top area + +# Draw white background for text so it's readable +padding = 10 +draw.rectangle([(text_x - padding, text_y - padding), + (text_x + text_width + padding, text_y + text_height + padding)], + fill=1, outline=0) + +# Draw text +draw.text((text_x, text_y), TEXT_TO_DRAW, font=font, fill=0) + +# Convert to bytes +# PIL tobytes for mode '1' packs 8 pixels per byte, MSB first. +buffer = image.tobytes() + +print(f"Connecting to {IP_ADDRESS}:{PORT}...") + +try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((IP_ADDRESS, PORT)) + print("Connected. Sending command...") + s.sendall(b"/image\n") + time.sleep(0.5) # Small delay to ensure command is processed + print(f"Sending {len(buffer)} bytes of image data...") + s.sendall(buffer) + time.sleep(1) + print("Done.") +except Exception as e: + print(f"Error: {e}") + diff --git a/hello_usb.cpp b/hello_usb.cpp index d7b0b6b..e032dea 100644 --- a/hello_usb.cpp +++ b/hello_usb.cpp @@ -36,8 +36,41 @@ DisplayManager* g_display_manager = nullptr; static char g_cmd_buffer[MAX_INPUT_LEN]; static int g_cmd_index = 0; +// Image reception state +static bool g_receiving_image = false; +static uint8_t *g_image_buffer = nullptr; +static uint32_t g_image_bytes_received = 0; +static const uint32_t IMAGE_SIZE = 15000; // 400x300 / 8 + +void on_tcp_connection_change(bool connected) { + if (!connected) { + if (g_receiving_image) { + printf("TCP disconnected, resetting image reception\n"); + g_receiving_image = false; + if (g_image_buffer) { + free(g_image_buffer); + g_image_buffer = nullptr; + } + } + // Also reset command buffer to avoid partial commands + g_cmd_index = 0; + g_cmd_buffer[0] = '\0'; + } +} + static bool execute_command(CommandAction action, const char* input) { switch (action) { + case CMD_IMAGE: + printf("Command: /image - Waiting for %d bytes...\n", IMAGE_SIZE); + if (g_image_buffer) free(g_image_buffer); + g_image_buffer = (uint8_t*)malloc(IMAGE_SIZE); + if (!g_image_buffer) { + printf("Failed to allocate image buffer!\n"); + return true; + } + g_receiving_image = true; + g_image_bytes_received = 0; + return true; case CMD_REFRESH: printf("Command: /refresh\n"); epaper_force_refresh(); @@ -82,6 +115,7 @@ static bool execute_command(CommandAction action, const char* input) { } else { epaper_send_update("Connected!", true); tcp_debug_init(); + tcp_set_connection_callback(on_tcp_connection_change); } } else { if (g_display_manager) g_display_manager->refresh("Connection Failed", nullptr); @@ -175,6 +209,23 @@ void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t cons } static void process_input_char(char c) { + if (g_receiving_image) { + if (g_image_buffer) { + g_image_buffer[g_image_bytes_received++] = (uint8_t)c; + if (g_image_bytes_received >= IMAGE_SIZE) { + printf("\nImage received (%d bytes). Displaying...\n", g_image_bytes_received); + epaper_display_full_image(g_image_buffer, IMAGE_SIZE); + g_receiving_image = false; + g_image_buffer = nullptr; // Ownership transferred + } + } else { + g_receiving_image = false; // Error state + } + return; + } + + if (c == 0) return; // Ignore null characters in text mode + if (c == '\b' || c == 127) { if (g_cmd_index > 0) { printf("\b \b"); @@ -190,12 +241,20 @@ static void process_input_char(char c) { } else if (c == '\r' || c == '\n') { printf("\n"); + // Debug: Print buffer content + printf("Processing line: '%s' (len=%d)\n", g_cmd_buffer, g_cmd_index); + printf("Hex: "); + for(int i=0; i 0) { epaper_send_update(g_cmd_buffer, true); } CommandAction action = parse_command(g_cmd_buffer); + printf("Parsed Action: %d\n", action); // Debug action + if (execute_command(action, g_cmd_buffer)) { // Command handled if (g_display_manager) { diff --git a/tcp_debug.cpp b/tcp_debug.cpp index f6aec9c..30bb0d2 100644 --- a/tcp_debug.cpp +++ b/tcp_debug.cpp @@ -8,7 +8,7 @@ #define TCP_PORT 4242 #define DEBUG_BUF_SIZE 2048 -#define TCP_IN_BUF_SIZE 128 +#define TCP_IN_BUF_SIZE 20000 // Increased to hold full image + commands // Circular buffer for debug output static char debug_buf[DEBUG_BUF_SIZE]; @@ -97,11 +97,18 @@ static err_t tcp_server_poll(void *arg, struct tcp_pcb *tpcb) { return ERR_OK; } +static tcp_connection_callback_t g_conn_callback = NULL; + +void tcp_set_connection_callback(tcp_connection_callback_t callback) { + g_conn_callback = callback; +} + static err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { if (!p) { tcp_close(tpcb); client_pcb = NULL; printf("TCP debug client disconnected\n"); + if (g_conn_callback) g_conn_callback(false); return ERR_OK; } @@ -133,6 +140,7 @@ static err_t tcp_server_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { // Already have a client, reject or close old? // Let's close the old one and accept the new one (last one wins) tcp_abort(client_pcb); + if (g_conn_callback) g_conn_callback(false); } client_pcb = newpcb; @@ -144,6 +152,7 @@ static err_t tcp_server_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { // However, we can also trigger writes from the main loop if we wanted, but polling is safer for threading. printf("TCP debug client connected\n"); + if (g_conn_callback) g_conn_callback(true); return ERR_OK; } diff --git a/tcp_debug.h b/tcp_debug.h index 2a5746b..cfc336b 100644 --- a/tcp_debug.h +++ b/tcp_debug.h @@ -10,4 +10,7 @@ */ bool tcp_debug_init(void); +typedef void (*tcp_connection_callback_t)(bool connected); +void tcp_set_connection_callback(tcp_connection_callback_t callback); + #endif diff --git a/test_image.bin b/test_image.bin new file mode 100644 index 0000000000000000000000000000000000000000..d527580d6c417105584f16be215fa940c0aef692 GIT binary patch literal 15000 zcmeI&L5|cg5QX8n0Y~5nELgB$AV=W{EZD;MVu)M-(HB6<1&{`5&@`fZVv;zHt6r7u z@`er0fT>+#Mv9)I392LACwYw_(3KeQxQzZ6qTaaoHdtn*qlVV%~Z32R)7CamRl=UcF@ek&TV zE}>|^I)|bG>lBIxtT7Y~Sc}tYWye~mLNP5DremQBg&Aw13WXVKp$df=Ym9{%%f-Tq zwNiyuSzp7IYA=jfE7e{Yu~w?RFk+3VFk-n>*swOLeN{HBjVcxQ5dksu;}e?pXJhk<#AI+o{B2gPL+xpR-_`uw0~;m6sbs2!HQI*s9;4ZQdF?UxY{^Z z)yZyEsAy4~tb>XcC9FiniYb!IbE0BJ0V`3l zqJWjCSW&%tI{WnQDLECF)A!nEJlTeip8j~ zP$!E~VWDAhDp5?7K3sDuQ7BlPN)!qfrxJyNHS&e>xl$*~l?#alRAN4DaFJKq*97|ULciHJn#akl;VjONTn1rFOW(p z{4M=?+)6#U71aR?|s!EUvU*Kioci*6MqmUEwy zJ|$!v*315W5Rm?U^!I~|Lw`R2qrV^h{eY3^|9%jV{(k&_@5k@$k@sJolVJ##%KPz) z7B4KtS6aNZ6rXAF#!`Hw#alyh<Y3md2wGaPP`ENS#nh)FU01bEF3Sy zPN^ua_&=`!9z+xuET&o^_Nx#}e8vg>Nb!{;QH)q1mM9z+h=qmN+7sWR!9r~9dE6<* zLQr}9eijyj$|DvQg39Cfv#=0UVp|ysg$l8tP^b_K3WW->pirm~3krn_v7k_>5Q`To z6=3l~r2;Hos8oQ(3zZ76c%f1O7B5sPz+y#)ies@NL&dRJk)h&Ptnikj7Z#;(Pq*q; zb%8!5b)$Gkw~7+=bGND+#XGuHl&GJ(Roy7w(XFCH{oJkUM)8hr6(#EDZdEsmcl;A8 Jx(RB#{tM8PKCb`( literal 0 HcmV?d00001