mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-07-26 14:58:12 +00:00
fix(screenshot): widen header to uint16, sanity-check buffer, fix ERR path
Protocol header grows from 7 to 11 bytes: display_type, rotation, then uint16 LE width / height / chunk_idx / total_chunks. Removes silent truncation on panels >255 px (e.g. future 4.2" 400x300 e-ink). Decoder now: - checks RESP_CODE_ERR before length so error frames don't surface as "Frame too short"; logs the error code byte - validates received buffer size against expected (OLED: w*h/8; e-ink: ceil(min(w,h)/8) * max(w,h)) so truncated transfers fail loudly - initializes disp_rotation in outer scope and detects missed chunk 0 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -2098,23 +2098,30 @@ void MyMesh::handleScreenshotRequest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::sendScreenshotResponse(DisplayDriver* display, const uint8_t* buffer, uint16_t bufferSize) {
|
void MyMesh::sendScreenshotResponse(DisplayDriver* display, const uint8_t* buffer, uint16_t bufferSize) {
|
||||||
// Frame format: RESP_CODE_SCREENSHOT, width, height, chunk_idx, total_chunks,
|
// Frame format (11-byte header, little-endian multi-byte fields):
|
||||||
// display_type, rotation, [data]
|
// [0] resp_code = RESP_CODE_SCREENSHOT
|
||||||
// display_type: 0=OLED (page-based), 1=e-ink (row-major, MSB-first, 1=white/0=black)
|
// [1] display_type (0=OLED page-based, 1=e-ink row-major MSB-first 1=white)
|
||||||
// rotation: 0-3, Adafruit_GFX/GxEPD2 rotation value (only meaningful for e-ink)
|
// [2] rotation (0-3, GxEPD2/Adafruit_GFX value; only meaningful for e-ink)
|
||||||
const int HEADER_SIZE = 7;
|
// [3..4] width (uint16 LE — GxEPD2-reported visible width)
|
||||||
|
// [5..6] height (uint16 LE — GxEPD2-reported visible height)
|
||||||
|
// [7..8] chunk_idx (uint16 LE)
|
||||||
|
// [9..10] total_chunks (uint16 LE)
|
||||||
|
// [11..] chunk data
|
||||||
|
const int HEADER_SIZE = 11;
|
||||||
const int MAX_DATA_PER_FRAME = MAX_FRAME_SIZE - HEADER_SIZE;
|
const int MAX_DATA_PER_FRAME = MAX_FRAME_SIZE - HEADER_SIZE;
|
||||||
int totalChunks = (bufferSize + MAX_DATA_PER_FRAME - 1) / MAX_DATA_PER_FRAME;
|
const uint16_t width = (uint16_t)display->screenshotWidth();
|
||||||
|
const uint16_t height = (uint16_t)display->screenshotHeight();
|
||||||
|
const uint16_t totalChunks = (bufferSize + MAX_DATA_PER_FRAME - 1) / MAX_DATA_PER_FRAME;
|
||||||
|
|
||||||
for (int chunkIdx = 0; chunkIdx < totalChunks; chunkIdx++) {
|
for (uint16_t chunkIdx = 0; chunkIdx < totalChunks; chunkIdx++) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
out_frame[i++] = RESP_CODE_SCREENSHOT;
|
out_frame[i++] = RESP_CODE_SCREENSHOT;
|
||||||
out_frame[i++] = (uint8_t)display->screenshotWidth();
|
|
||||||
out_frame[i++] = (uint8_t)display->screenshotHeight();
|
|
||||||
out_frame[i++] = (uint8_t)chunkIdx;
|
|
||||||
out_frame[i++] = (uint8_t)totalChunks;
|
|
||||||
out_frame[i++] = display->getDisplayType();
|
out_frame[i++] = display->getDisplayType();
|
||||||
out_frame[i++] = display->screenshotRotation();
|
out_frame[i++] = display->screenshotRotation();
|
||||||
|
out_frame[i++] = width & 0xFF; out_frame[i++] = width >> 8;
|
||||||
|
out_frame[i++] = height & 0xFF; out_frame[i++] = height >> 8;
|
||||||
|
out_frame[i++] = chunkIdx & 0xFF; out_frame[i++] = chunkIdx >> 8;
|
||||||
|
out_frame[i++] = totalChunks & 0xFF; out_frame[i++] = totalChunks >> 8;
|
||||||
|
|
||||||
int chunkSize = min(MAX_DATA_PER_FRAME, (int)bufferSize - chunkIdx * MAX_DATA_PER_FRAME);
|
int chunkSize = min(MAX_DATA_PER_FRAME, (int)bufferSize - chunkIdx * MAX_DATA_PER_FRAME);
|
||||||
memcpy(&out_frame[i], buffer + chunkIdx * MAX_DATA_PER_FRAME, chunkSize);
|
memcpy(&out_frame[i], buffer + chunkIdx * MAX_DATA_PER_FRAME, chunkSize);
|
||||||
|
|||||||
@@ -105,17 +105,35 @@ def send_command(ser, command, data=b""):
|
|||||||
return frame_len + 3
|
return frame_len + 3
|
||||||
|
|
||||||
|
|
||||||
def receive_screenshot(ser, timeout=5):
|
HEADER_SIZE = 11 # see firmware sendScreenshotResponse() for layout
|
||||||
"""Receive screenshot data (may be split across multiple frames).
|
|
||||||
|
|
||||||
Frame format: RESP_CODE_SCREENSHOT, width, height, chunk_idx,
|
|
||||||
total_chunks, display_type, [chunk_data...]
|
def _u16le(buf, off):
|
||||||
"""
|
return buf[off] | (buf[off + 1] << 8)
|
||||||
|
|
||||||
|
|
||||||
|
def _expected_buffer_size(width, height, display_type):
|
||||||
|
"""Bytes the firmware should send for given dims+type."""
|
||||||
|
if display_type == DISPLAY_TYPE_EINK:
|
||||||
|
# GxEPD2 buffer is in physical panel coordinates: row-major,
|
||||||
|
# stride = ceil(WIDTH_VISIBLE / 8) bytes, HEIGHT rows.
|
||||||
|
# WIDTH_VISIBLE = shorter visible dim regardless of rotation.
|
||||||
|
visible_short = min(width, height)
|
||||||
|
phys_stride = (visible_short + 7) // 8
|
||||||
|
phys_height = max(width, height)
|
||||||
|
return phys_stride * phys_height
|
||||||
|
# OLED: packed bits, no padding
|
||||||
|
return (width * height) // 8
|
||||||
|
|
||||||
|
|
||||||
|
def receive_screenshot(ser, timeout=5):
|
||||||
|
"""Receive a multi-chunk screenshot response. Returns (buf, w, h, type, rot) or None."""
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
buffer_data = bytearray()
|
buffer_data = bytearray()
|
||||||
width = None
|
width = None
|
||||||
height = None
|
height = None
|
||||||
display_type = None
|
display_type = None
|
||||||
|
disp_rotation = 0
|
||||||
total_chunks = None
|
total_chunks = None
|
||||||
received_chunks = 0
|
received_chunks = 0
|
||||||
|
|
||||||
@@ -123,21 +141,30 @@ def receive_screenshot(ser, timeout=5):
|
|||||||
frame = read_frame(ser)
|
frame = read_frame(ser)
|
||||||
if frame is None:
|
if frame is None:
|
||||||
continue
|
continue
|
||||||
|
if len(frame) < 1:
|
||||||
if len(frame) < 7:
|
continue
|
||||||
print(f"Error: Frame too short: {len(frame)} bytes")
|
|
||||||
return None
|
|
||||||
|
|
||||||
resp_code = frame[0]
|
resp_code = frame[0]
|
||||||
|
|
||||||
if resp_code == RESP_CODE_SCREENSHOT:
|
if resp_code == RESP_CODE_ERR:
|
||||||
w = frame[1]
|
err = frame[1] if len(frame) > 1 else 0xFF
|
||||||
h = frame[2]
|
print(f"Error: Device returned error code 0x{err:02x}")
|
||||||
chunk_idx = frame[3]
|
return None
|
||||||
num_chunks = frame[4]
|
|
||||||
disp_type = frame[5]
|
if resp_code != RESP_CODE_SCREENSHOT:
|
||||||
rotation = frame[6]
|
continue
|
||||||
chunk_data = frame[7:]
|
|
||||||
|
if len(frame) < HEADER_SIZE:
|
||||||
|
print(f"Error: Screenshot frame too short: {len(frame)} bytes (need >= {HEADER_SIZE})")
|
||||||
|
return None
|
||||||
|
|
||||||
|
disp_type = frame[1]
|
||||||
|
rotation = frame[2]
|
||||||
|
w = _u16le(frame, 3)
|
||||||
|
h = _u16le(frame, 5)
|
||||||
|
chunk_idx = _u16le(frame, 7)
|
||||||
|
num_chunks = _u16le(frame, 9)
|
||||||
|
chunk_data = frame[HEADER_SIZE:]
|
||||||
|
|
||||||
if chunk_idx == 0:
|
if chunk_idx == 0:
|
||||||
width = w
|
width = w
|
||||||
@@ -148,11 +175,13 @@ def receive_screenshot(ser, timeout=5):
|
|||||||
buffer_data = bytearray()
|
buffer_data = bytearray()
|
||||||
received_chunks = 0
|
received_chunks = 0
|
||||||
|
|
||||||
if width is None or width != w or height != h:
|
if width is None:
|
||||||
|
print(f"Error: Missed chunk 0 (first chunk seen: {chunk_idx})")
|
||||||
|
return None
|
||||||
|
if width != w or height != h:
|
||||||
print(f"Error: Inconsistent dimensions in chunk {chunk_idx}")
|
print(f"Error: Inconsistent dimensions in chunk {chunk_idx}")
|
||||||
return None
|
return None
|
||||||
|
if total_chunks != num_chunks:
|
||||||
if total_chunks is None or total_chunks != num_chunks:
|
|
||||||
print(f"Error: Inconsistent chunk count in chunk {chunk_idx}")
|
print(f"Error: Inconsistent chunk count in chunk {chunk_idx}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -160,14 +189,11 @@ def receive_screenshot(ser, timeout=5):
|
|||||||
received_chunks += 1
|
received_chunks += 1
|
||||||
|
|
||||||
if received_chunks >= total_chunks:
|
if received_chunks >= total_chunks:
|
||||||
return bytes(buffer_data), width, height, display_type, disp_rotation
|
expected = _expected_buffer_size(width, height, display_type)
|
||||||
|
if len(buffer_data) != expected:
|
||||||
elif resp_code == RESP_CODE_ERR:
|
print(f"Error: Expected {expected} bytes, got {len(buffer_data)}")
|
||||||
print("Error: Device returned error")
|
|
||||||
return None
|
return None
|
||||||
|
return bytes(buffer_data), width, height, display_type, disp_rotation
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"Error: Timeout waiting for screenshot (received {received_chunks}/{total_chunks or '?'} chunks)"
|
f"Error: Timeout waiting for screenshot (received {received_chunks}/{total_chunks or '?'} chunks)"
|
||||||
|
|||||||
Reference in New Issue
Block a user