mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-11-02 10:51:57 +00:00
feat(l2): screenshots over USB (RGB565); USB tools moved to the Solo site
- CMD_GET_SCREENSHOT on colour screens: display type 2 (RGB565 LE), 32-bit payload size; ui-lvgl captureFrame() shared with the SD BMP - ENABLE_SCREENSHOT in the L2 build; tools/screenshot.py decodes type 2 - docs and READMEs point to solo.marekzegarek.com/#pc-tools - USB popup: "Keep card" instead of "Charge only" (only the card is lent; charging and USB serial work either way) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -111,6 +111,7 @@
|
||||
#define RESP_CODE_DEFAULT_FLOOD_SCOPE 28
|
||||
#ifdef ENABLE_SCREENSHOT
|
||||
#define RESP_CODE_SCREENSHOT 29 // Response with screenshot data
|
||||
#define SCREENSHOT_TYPE_RGB565 2 // display_type of a colour screen's frame
|
||||
#endif
|
||||
|
||||
#define MAX_CHANNEL_DATA_LENGTH (MAX_FRAME_SIZE - 9)
|
||||
@@ -3139,6 +3140,17 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
void MyMesh::handleScreenshotRequest() {
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern UITask ui_task; // main.cpp -- the Listener may be the UI Core, not UITask
|
||||
#ifdef UI_SCREENSHOT_RGB565
|
||||
// A colour UI renders a frame for it (RGB565, display type 2).
|
||||
int w, h;
|
||||
uint16_t* px = ui_task.captureFrame(w, h);
|
||||
if (!px) {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
return;
|
||||
}
|
||||
sendScreenshotResponse(SCREENSHOT_TYPE_RGB565, 0, (uint16_t)w, (uint16_t)h, (const uint8_t*)px, (uint32_t)w * h * 2);
|
||||
free(px);
|
||||
#else
|
||||
if (!ui_task.hasDisplay()) {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
return;
|
||||
@@ -3154,19 +3166,24 @@ void MyMesh::handleScreenshotRequest() {
|
||||
uint16_t bufferSize = display->getBufferSize();
|
||||
|
||||
if (buffer && bufferSize > 0) {
|
||||
sendScreenshotResponse(display, buffer, bufferSize);
|
||||
sendScreenshotResponse(display->getDisplayType(), display->screenshotRotation(),
|
||||
(uint16_t)display->screenshotWidth(), (uint16_t)display->screenshotHeight(),
|
||||
buffer, bufferSize);
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
#endif
|
||||
}
|
||||
|
||||
void MyMesh::sendScreenshotResponse(DisplayDriver* display, const uint8_t* buffer, uint16_t bufferSize) {
|
||||
void MyMesh::sendScreenshotResponse(uint8_t displayType, uint8_t rotation, uint16_t width, uint16_t height,
|
||||
const uint8_t* buffer, uint32_t bufferSize) {
|
||||
// Frame format (11-byte header, little-endian multi-byte fields):
|
||||
// [0] resp_code = RESP_CODE_SCREENSHOT
|
||||
// [1] display_type (0=OLED page-based, 1=e-ink row-major MSB-first 1=white)
|
||||
// [1] display_type (0=OLED page-based, 1=e-ink row-major MSB-first 1=white,
|
||||
// 2=RGB565 little-endian, row-major -- colour screens)
|
||||
// [2] rotation (0-3, GxEPD2/Adafruit_GFX value; only meaningful for e-ink)
|
||||
// [3..4] width (uint16 LE — GxEPD2-reported visible width)
|
||||
// [5..6] height (uint16 LE — GxEPD2-reported visible height)
|
||||
@@ -3174,23 +3191,22 @@ void MyMesh::sendScreenshotResponse(DisplayDriver* display, const uint8_t* buffe
|
||||
// [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 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;
|
||||
const uint32_t MAX_DATA_PER_FRAME = MAX_FRAME_SIZE - HEADER_SIZE;
|
||||
const uint16_t totalChunks = (uint16_t)((bufferSize + MAX_DATA_PER_FRAME - 1) / MAX_DATA_PER_FRAME);
|
||||
|
||||
for (uint16_t chunkIdx = 0; chunkIdx < totalChunks; chunkIdx++) {
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_CODE_SCREENSHOT;
|
||||
out_frame[i++] = display->getDisplayType();
|
||||
out_frame[i++] = display->screenshotRotation();
|
||||
out_frame[i++] = displayType;
|
||||
out_frame[i++] = rotation;
|
||||
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);
|
||||
memcpy(&out_frame[i], buffer + chunkIdx * MAX_DATA_PER_FRAME, chunkSize);
|
||||
const uint32_t offset = (uint32_t)chunkIdx * MAX_DATA_PER_FRAME;
|
||||
const uint32_t chunkSize = min(MAX_DATA_PER_FRAME, bufferSize - offset);
|
||||
memcpy(&out_frame[i], buffer + offset, chunkSize);
|
||||
i += chunkSize;
|
||||
|
||||
_serial->writeFrame(out_frame, i);
|
||||
|
||||
Reference in New Issue
Block a user