feature to send image from tcp connection

This commit is contained in:
Adolfo Reyna
2025-12-28 21:59:59 -05:00
parent 03e826653b
commit ed927e16b1
9 changed files with 212 additions and 3 deletions

View File

@@ -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;
}

View File

@@ -8,6 +8,7 @@ enum CommandAction {
CMD_SCAN,
CMD_CONNECT,
CMD_STATUS,
CMD_IMAGE,
CMD_UNKNOWN
};

View File

@@ -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 {
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);
}
}

View File

@@ -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.
*/

90
generate_image.py Normal file
View File

@@ -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 <IP_ADDRESS> [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}")

View File

@@ -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<g_cmd_index; i++) printf("%02X ", (unsigned char)g_cmd_buffer[i]);
printf("\n");
// Always commit the input line to e-Paper so commands are visible
if (g_cmd_index > 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) {

View File

@@ -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;
}

View File

@@ -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

BIN
test_image.bin Normal file

Binary file not shown.