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 (strcmp(input, "/scan") == 0) return CMD_SCAN;
|
||||||
if (strncmp(input, "/connect ", 9) == 0 || strcmp(input, "/connect") == 0) return CMD_CONNECT;
|
if (strncmp(input, "/connect ", 9) == 0 || strcmp(input, "/connect") == 0) return CMD_CONNECT;
|
||||||
if (strcmp(input, "/status") == 0) return CMD_STATUS;
|
if (strcmp(input, "/status") == 0) return CMD_STATUS;
|
||||||
|
if (strcmp(input, "/image") == 0) return CMD_IMAGE;
|
||||||
|
|
||||||
return CMD_UNKNOWN;
|
return CMD_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ enum CommandAction {
|
|||||||
CMD_SCAN,
|
CMD_SCAN,
|
||||||
CMD_CONNECT,
|
CMD_CONNECT,
|
||||||
CMD_STATUS,
|
CMD_STATUS,
|
||||||
|
CMD_IMAGE,
|
||||||
CMD_UNKNOWN
|
CMD_UNKNOWN
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ typedef struct {
|
|||||||
UWORD ystart;
|
UWORD ystart;
|
||||||
UWORD xend;
|
UWORD xend;
|
||||||
UWORD yend;
|
UWORD yend;
|
||||||
|
UBYTE *raw_image; // Pointer to raw image data (if not NULL, display this instead of text)
|
||||||
} DisplayMessage;
|
} DisplayMessage;
|
||||||
|
|
||||||
static void init_epaper_display() {
|
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);
|
// printf("[Core 1] Updating display with %d entries\n", msg->entries.count);
|
||||||
|
|
||||||
// Update the e-Paper display
|
// 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
|
// For partial refresh, only clear the text area
|
||||||
UWORD y_start = msg->ystart;
|
UWORD y_start = msg->ystart;
|
||||||
UWORD y_end = msg->yend;
|
UWORD y_end = msg->yend;
|
||||||
@@ -219,6 +224,7 @@ void epaper_send_update(const char *entry, bool finish_line) {
|
|||||||
|
|
||||||
// Copy entry list to message
|
// Copy entry list to message
|
||||||
msg->entries = g_entry_list;
|
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
|
// 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;
|
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)) {
|
if (queue_try_add(&g_display_queue, &msg)) {
|
||||||
// printf("[Core 0] Display update sent to core 1\n");
|
// printf("[Core 0] Display update sent to core 1\n");
|
||||||
} else {
|
} 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);
|
free(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -267,3 +277,31 @@ void epaper_force_refresh() {
|
|||||||
g_force_full_refresh = true;
|
g_force_full_refresh = true;
|
||||||
epaper_send_update("", false);
|
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);
|
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.
|
* 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 char g_cmd_buffer[MAX_INPUT_LEN];
|
||||||
static int g_cmd_index = 0;
|
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) {
|
static bool execute_command(CommandAction action, const char* input) {
|
||||||
switch (action) {
|
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:
|
case CMD_REFRESH:
|
||||||
printf("Command: /refresh\n");
|
printf("Command: /refresh\n");
|
||||||
epaper_force_refresh();
|
epaper_force_refresh();
|
||||||
@@ -82,6 +115,7 @@ static bool execute_command(CommandAction action, const char* input) {
|
|||||||
} else {
|
} else {
|
||||||
epaper_send_update("Connected!", true);
|
epaper_send_update("Connected!", true);
|
||||||
tcp_debug_init();
|
tcp_debug_init();
|
||||||
|
tcp_set_connection_callback(on_tcp_connection_change);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (g_display_manager) g_display_manager->refresh("Connection Failed", nullptr);
|
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) {
|
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 (c == '\b' || c == 127) {
|
||||||
if (g_cmd_index > 0) {
|
if (g_cmd_index > 0) {
|
||||||
printf("\b \b");
|
printf("\b \b");
|
||||||
@@ -190,12 +241,20 @@ static void process_input_char(char c) {
|
|||||||
} else if (c == '\r' || c == '\n') {
|
} else if (c == '\r' || c == '\n') {
|
||||||
printf("\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
|
// Always commit the input line to e-Paper so commands are visible
|
||||||
if (g_cmd_index > 0) {
|
if (g_cmd_index > 0) {
|
||||||
epaper_send_update(g_cmd_buffer, true);
|
epaper_send_update(g_cmd_buffer, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandAction action = parse_command(g_cmd_buffer);
|
CommandAction action = parse_command(g_cmd_buffer);
|
||||||
|
printf("Parsed Action: %d\n", action); // Debug action
|
||||||
|
|
||||||
if (execute_command(action, g_cmd_buffer)) {
|
if (execute_command(action, g_cmd_buffer)) {
|
||||||
// Command handled
|
// Command handled
|
||||||
if (g_display_manager) {
|
if (g_display_manager) {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
#define TCP_PORT 4242
|
#define TCP_PORT 4242
|
||||||
#define DEBUG_BUF_SIZE 2048
|
#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
|
// Circular buffer for debug output
|
||||||
static char debug_buf[DEBUG_BUF_SIZE];
|
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;
|
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) {
|
static err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) {
|
||||||
if (!p) {
|
if (!p) {
|
||||||
tcp_close(tpcb);
|
tcp_close(tpcb);
|
||||||
client_pcb = NULL;
|
client_pcb = NULL;
|
||||||
printf("TCP debug client disconnected\n");
|
printf("TCP debug client disconnected\n");
|
||||||
|
if (g_conn_callback) g_conn_callback(false);
|
||||||
return ERR_OK;
|
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?
|
// Already have a client, reject or close old?
|
||||||
// Let's close the old one and accept the new one (last one wins)
|
// Let's close the old one and accept the new one (last one wins)
|
||||||
tcp_abort(client_pcb);
|
tcp_abort(client_pcb);
|
||||||
|
if (g_conn_callback) g_conn_callback(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
client_pcb = newpcb;
|
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.
|
// 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");
|
printf("TCP debug client connected\n");
|
||||||
|
if (g_conn_callback) g_conn_callback(true);
|
||||||
return ERR_OK;
|
return ERR_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,4 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
bool tcp_debug_init(void);
|
bool tcp_debug_init(void);
|
||||||
|
|
||||||
|
typedef void (*tcp_connection_callback_t)(bool connected);
|
||||||
|
void tcp_set_connection_callback(tcp_connection_callback_t callback);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
BIN
test_image.bin
Normal file
BIN
test_image.bin
Normal file
Binary file not shown.
Reference in New Issue
Block a user