feature to send image from tcp connection
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ enum CommandAction {
|
||||
CMD_SCAN,
|
||||
CMD_CONNECT,
|
||||
CMD_STATUS,
|
||||
CMD_IMAGE,
|
||||
CMD_UNKNOWN
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
90
generate_image.py
Normal 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}")
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
BIN
test_image.bin
Normal file
Binary file not shown.
Reference in New Issue
Block a user