mirror of
https://github.com/markqvist/RNode_Firmware.git
synced 2026-04-27 14:30:33 +00:00
Partial rendering, remote debug protocol, frame metrics
Switched from full-frame to partial rendering — only dirty rectangles are rendered and flushed. Static watch face flushes in ~750us vs ~18ms (24x faster). PSRAM usage drops from 824KB to 197KB for draw buffers. Remote debug protocol over serial (prefix "RWS" + command byte): S — Screenshot (wakes display, forces full redraw, captures) T — Touch injection (x, y, duration) N — Navigate to tile by column/row M — Frame metrics (JSON: flush/render times, memory) I — Invalidate (force full redraw) Python tool (scripts/screenshot.py) supports all commands: screenshot, metrics, touch, swipe, navigate, invalidate Screenshot now works correctly with partial rendering by keeping the capture flag active across all flush strips and forcing a full invalidation before capture. Frame timing instrumentation added to flush callback and render loop for performance profiling.
This commit is contained in:
parent
7366a671b4
commit
2e9f703121
3 changed files with 329 additions and 93 deletions
206
Gui.h
206
Gui.h
|
|
@ -55,9 +55,10 @@ static const lv_font_t &font_mid = _f28::montserrat_bold_28;
|
|||
static lv_display_t *gui_display = NULL;
|
||||
static lv_indev_t *gui_indev = NULL;
|
||||
|
||||
// Full-frame buffer: renders entire screen at once (eliminates tearing during scroll)
|
||||
// 410*502*2 = 411,640 bytes per buffer — fits in PSRAM
|
||||
#define GUI_BUF_LINES GUI_H
|
||||
// Draw buffer height — partial rendering only redraws dirty areas.
|
||||
// 120 lines covers the tallest glyph (96px) with margin.
|
||||
// Two buffers: 410*120*2 = 98,400 bytes each in PSRAM.
|
||||
#define GUI_BUF_LINES 120
|
||||
static uint8_t *gui_buf1 = NULL;
|
||||
static uint8_t *gui_buf2 = NULL;
|
||||
|
||||
|
|
@ -111,11 +112,27 @@ static uint32_t gui_last_data_update = 0;
|
|||
static uint8_t gui_last_tile_col = 1;
|
||||
static uint8_t gui_last_tile_row = 1;
|
||||
|
||||
// Frame timing metrics
|
||||
static uint32_t gui_frame_count = 0;
|
||||
static uint32_t gui_flush_us_total = 0;
|
||||
static uint32_t gui_flush_us_last = 0;
|
||||
static uint32_t gui_render_us_last = 0;
|
||||
static uint32_t gui_render_start = 0;
|
||||
|
||||
// Remote touch injection
|
||||
static int16_t gui_inject_x = -1;
|
||||
static int16_t gui_inject_y = -1;
|
||||
static bool gui_inject_pressed = false;
|
||||
static uint32_t gui_inject_until = 0; // millis() deadline for injected touch
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LVGL display flush callback
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shadow framebuffer for screenshots (RGB565 swapped / big-endian — same as display)
|
||||
uint16_t *gui_screenshot_buf = NULL;
|
||||
|
||||
// Forward declaration — defined in Display.h after Gui.h is included
|
||||
void display_unblank();
|
||||
static volatile bool gui_screenshot_pending = false; // set true to capture next frame
|
||||
|
||||
static void gui_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_map) {
|
||||
|
|
@ -125,16 +142,20 @@ static void gui_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_
|
|||
uint16_t h = area->y2 - area->y1 + 1;
|
||||
uint16_t *pixels = (uint16_t *)px_map;
|
||||
|
||||
// Copy to shadow framebuffer only when screenshot requested
|
||||
// Copy to shadow framebuffer when screenshot capture is active
|
||||
// Flag stays true across all partial flushes — cleared by screenshot command
|
||||
if (gui_screenshot_buf && gui_screenshot_pending) {
|
||||
for (uint16_t row = 0; row < h; row++) {
|
||||
memcpy(&gui_screenshot_buf[(y1 + row) * GUI_W + x1],
|
||||
&pixels[row * w], w * sizeof(uint16_t));
|
||||
}
|
||||
gui_screenshot_pending = false;
|
||||
}
|
||||
|
||||
uint32_t t0 = micros();
|
||||
co5300_push_pixels(x1, y1, w, h, pixels);
|
||||
gui_flush_us_last = micros() - t0;
|
||||
gui_flush_us_total += gui_flush_us_last;
|
||||
gui_frame_count++;
|
||||
lv_display_flush_ready(disp);
|
||||
}
|
||||
|
||||
|
|
@ -142,6 +163,17 @@ static void gui_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *px_
|
|||
// LVGL touch input read callback
|
||||
// ---------------------------------------------------------------------------
|
||||
static void gui_touch_read_cb(lv_indev_t *indev, lv_indev_data_t *data) {
|
||||
// Check for injected remote touch first
|
||||
if (gui_inject_pressed && millis() < gui_inject_until) {
|
||||
data->point.x = gui_inject_x;
|
||||
data->point.y = gui_inject_y;
|
||||
data->state = LV_INDEV_STATE_PRESSED;
|
||||
last_unblank_event = millis();
|
||||
return;
|
||||
}
|
||||
gui_inject_pressed = false;
|
||||
|
||||
// Real touch hardware
|
||||
int16_t tx, ty;
|
||||
if (gui_touch_fn && gui_touch_fn(&tx, &ty)) {
|
||||
data->point.x = tx;
|
||||
|
|
@ -542,7 +574,7 @@ bool gui_init() {
|
|||
if (!gui_buf1) return false;
|
||||
}
|
||||
lv_display_set_buffers(gui_display, gui_buf1, gui_buf2, buf_size,
|
||||
LV_DISPLAY_RENDER_MODE_FULL);
|
||||
LV_DISPLAY_RENDER_MODE_PARTIAL);
|
||||
|
||||
// Shadow framebuffer for screenshots (410*502*2 = 411,640 bytes)
|
||||
gui_screenshot_buf = (uint16_t *)heap_caps_malloc(GUI_W * GUI_H * sizeof(uint16_t),
|
||||
|
|
@ -605,45 +637,148 @@ bool gui_init() {
|
|||
// Call gui_screenshot() to write /screenshot.raw to SPIFFS (if mounted),
|
||||
// or read gui_screenshot_buf directly via debugger.
|
||||
// ---------------------------------------------------------------------------
|
||||
// Serial screenshot protocol:
|
||||
// Trigger: host sends 4 bytes [0x52, 0x57, 0x53, 0x53] ("RWSS" = R-Watch Screen Shot)
|
||||
// Response: "RWSS" + uint16_t(width) + uint16_t(height) + raw RGB565 LE pixels
|
||||
#define GUI_SS_MAGIC_0 0x52
|
||||
#define GUI_SS_MAGIC_1 0x57
|
||||
#define GUI_SS_MAGIC_2 0x53
|
||||
#define GUI_SS_MAGIC_3 0x53
|
||||
static uint8_t gui_ss_state = 0;
|
||||
// ---------------------------------------------------------------------------
|
||||
// Remote debug protocol over serial
|
||||
// ---------------------------------------------------------------------------
|
||||
// Trigger: 3-byte prefix [0x52, 0x57, 0x53] ("RWS") + command byte + optional payload
|
||||
//
|
||||
// Commands:
|
||||
// 'S' (0x53) — Screenshot: captures next frame, responds RWSS + u16 w + u16 h + pixels
|
||||
// 'T' (0x54) — Touch inject: reads 5 bytes (u16 x, u16 y, u8 duration_100ms)
|
||||
// 'N' (0x4E) — Navigate: reads 2 bytes (u8 col, u8 row) — jump to tile
|
||||
// 'M' (0x4D) — Metrics: responds RWSM + JSON stats
|
||||
// 'I' (0x49) — Invalidate: force full screen redraw
|
||||
|
||||
void gui_check_screenshot_trigger(uint8_t byte_in) {
|
||||
const uint8_t magic[] = {GUI_SS_MAGIC_0, GUI_SS_MAGIC_1, GUI_SS_MAGIC_2, GUI_SS_MAGIC_3};
|
||||
if (byte_in == magic[gui_ss_state]) {
|
||||
gui_ss_state++;
|
||||
if (gui_ss_state == 4) {
|
||||
gui_ss_state = 0;
|
||||
#define GUI_CMD_PREFIX_LEN 3
|
||||
static const uint8_t gui_cmd_prefix[] = {0x52, 0x57, 0x53}; // "RWS"
|
||||
static uint8_t gui_cmd_state = 0;
|
||||
static uint8_t gui_cmd_id = 0;
|
||||
static uint8_t gui_cmd_payload[8];
|
||||
static uint8_t gui_cmd_payload_pos = 0;
|
||||
static uint8_t gui_cmd_payload_len = 0;
|
||||
|
||||
static void gui_cmd_execute();
|
||||
|
||||
void gui_process_serial_byte(uint8_t b) {
|
||||
// Match prefix
|
||||
if (gui_cmd_state < GUI_CMD_PREFIX_LEN) {
|
||||
if (b == gui_cmd_prefix[gui_cmd_state]) {
|
||||
gui_cmd_state++;
|
||||
} else {
|
||||
gui_cmd_state = (b == gui_cmd_prefix[0]) ? 1 : 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefix matched — next byte is command
|
||||
if (gui_cmd_state == GUI_CMD_PREFIX_LEN) {
|
||||
gui_cmd_id = b;
|
||||
gui_cmd_payload_pos = 0;
|
||||
switch (b) {
|
||||
case 'T': gui_cmd_payload_len = 5; break; // x(2) + y(2) + duration(1)
|
||||
case 'N': gui_cmd_payload_len = 2; break; // col(1) + row(1)
|
||||
default: gui_cmd_payload_len = 0; break; // S, M, I — no payload
|
||||
}
|
||||
gui_cmd_state++;
|
||||
if (gui_cmd_payload_len == 0) {
|
||||
gui_cmd_execute();
|
||||
gui_cmd_state = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Collecting payload
|
||||
if (gui_cmd_payload_pos < gui_cmd_payload_len) {
|
||||
gui_cmd_payload[gui_cmd_payload_pos++] = b;
|
||||
if (gui_cmd_payload_pos >= gui_cmd_payload_len) {
|
||||
gui_cmd_execute();
|
||||
gui_cmd_state = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gui_cmd_execute() {
|
||||
const uint8_t hdr[] = {'R', 'W', 'S', gui_cmd_id};
|
||||
|
||||
switch (gui_cmd_id) {
|
||||
case 'S': { // Screenshot
|
||||
if (gui_screenshot_buf) {
|
||||
// Request capture of next rendered frame
|
||||
// Unblank and force full redraw so screenshot captures entire screen
|
||||
if (display_blanked) display_unblank();
|
||||
lv_obj_invalidate(lv_screen_active());
|
||||
gui_screenshot_pending = true;
|
||||
// Wait for the next flush to capture (max 100ms)
|
||||
uint32_t t0 = millis();
|
||||
while (gui_screenshot_pending && millis() - t0 < 100) { delay(1); }
|
||||
|
||||
Serial.write(magic, 4);
|
||||
// Force full-screen render into screenshot buffer
|
||||
lv_tick_inc(1);
|
||||
lv_timer_handler();
|
||||
gui_screenshot_pending = false;
|
||||
Serial.write(hdr, 4);
|
||||
uint16_t w = GUI_W, h = GUI_H;
|
||||
Serial.write((uint8_t *)&w, 2);
|
||||
Serial.write((uint8_t *)&h, 2);
|
||||
Serial.write((uint8_t *)gui_screenshot_buf, GUI_W * GUI_H * 2);
|
||||
Serial.flush();
|
||||
} else {
|
||||
// Buffer not allocated — send error marker
|
||||
Serial.write(magic, 4);
|
||||
uint16_t w = 0, h = 0;
|
||||
Serial.write((uint8_t *)&w, 2);
|
||||
Serial.write((uint8_t *)&h, 2);
|
||||
Serial.write(hdr, 4);
|
||||
uint16_t z = 0;
|
||||
Serial.write((uint8_t *)&z, 2);
|
||||
Serial.write((uint8_t *)&z, 2);
|
||||
Serial.flush();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'T': { // Touch inject
|
||||
gui_inject_x = gui_cmd_payload[0] | (gui_cmd_payload[1] << 8);
|
||||
gui_inject_y = gui_cmd_payload[2] | (gui_cmd_payload[3] << 8);
|
||||
uint32_t dur = gui_cmd_payload[4] * 100; // duration in 100ms units
|
||||
if (dur == 0) dur = 200;
|
||||
gui_inject_pressed = true;
|
||||
gui_inject_until = millis() + dur;
|
||||
// Unblank display on injected touch
|
||||
if (display_blanked) display_unblank();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'N': { // Navigate to tile
|
||||
uint8_t col = gui_cmd_payload[0];
|
||||
uint8_t row = gui_cmd_payload[1];
|
||||
if (gui_tileview) {
|
||||
// Find tile at position
|
||||
lv_obj_t *target = NULL;
|
||||
if (col == 1 && row == 1) target = gui_tile_watch;
|
||||
else if (col == 1 && row == 0) target = gui_tile_radio;
|
||||
else if (col == 0 && row == 1) target = gui_tile_gps;
|
||||
else if (col == 2 && row == 1) target = gui_tile_msg;
|
||||
else if (col == 1 && row == 2) target = gui_tile_set;
|
||||
if (target) {
|
||||
lv_tileview_set_tile(gui_tileview, target, LV_ANIM_ON);
|
||||
}
|
||||
}
|
||||
if (display_blanked) display_unblank();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'M': { // Metrics
|
||||
Serial.write(hdr, 4);
|
||||
char buf[192];
|
||||
uint32_t avg_flush = gui_frame_count > 0 ? gui_flush_us_total / gui_frame_count : 0;
|
||||
snprintf(buf, sizeof(buf),
|
||||
"{\"frames\":%lu,\"flush_last_us\":%lu,\"flush_avg_us\":%lu,"
|
||||
"\"render_last_us\":%lu,\"heap_free\":%lu,\"psram_free\":%lu}\n",
|
||||
gui_frame_count, gui_flush_us_last, avg_flush,
|
||||
gui_render_us_last,
|
||||
(uint32_t)esp_get_free_heap_size(),
|
||||
(uint32_t)heap_caps_get_free_size(MALLOC_CAP_SPIRAM));
|
||||
Serial.write((uint8_t *)buf, strlen(buf));
|
||||
Serial.flush();
|
||||
break;
|
||||
}
|
||||
|
||||
case 'I': { // Invalidate — force full redraw
|
||||
if (gui_screen) lv_obj_invalidate(gui_screen);
|
||||
if (display_blanked) display_unblank();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
gui_ss_state = (byte_in == magic[0]) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -736,9 +871,10 @@ void gui_update() {
|
|||
last_tick = now;
|
||||
|
||||
gui_update_data();
|
||||
|
||||
gui_render_start = micros();
|
||||
lv_timer_handler();
|
||||
// After timer_handler, a new flush may have been queued via gui_flush_cb.
|
||||
// DMA runs in background until next gui_update() call.
|
||||
gui_render_us_last = micros() - gui_render_start;
|
||||
}
|
||||
|
||||
#endif // BOARD_MODEL == BOARD_TWATCH_ULT
|
||||
|
|
|
|||
|
|
@ -2334,7 +2334,7 @@ void buffer_serial() {
|
|||
c++;
|
||||
uint8_t sb = Serial.read();
|
||||
#if BOARD_MODEL == BOARD_TWATCH_ULT && HAS_DISPLAY
|
||||
gui_check_screenshot_trigger(sb);
|
||||
gui_process_serial_byte(sb);
|
||||
#endif
|
||||
if (!fifo_isfull(&channelFIFO[CHANNEL_USB])) { fifo_push(&channelFIFO[CHANNEL_USB], sb); }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,21 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
R-Watch Screenshot — capture display via USB serial
|
||||
R-Watch Remote Debug Tool
|
||||
|
||||
Sends trigger bytes, firmware dumps shadow framebuffer as raw RGB565.
|
||||
Handles KISS protocol data interleaved in the stream.
|
||||
Commands:
|
||||
screenshot [-o file.png] Capture display screenshot
|
||||
metrics Show frame timing and memory stats
|
||||
touch <x> <y> [duration_ms] Inject touch at coordinates
|
||||
swipe <direction> Swipe up/down/left/right
|
||||
navigate <screen> Jump to: watch, radio, gps, messages, settings
|
||||
invalidate Force full screen redraw
|
||||
|
||||
Usage:
|
||||
./scripts/screenshot.py # default port + output
|
||||
./scripts/screenshot.py -p /dev/ttyACM4 # specify port
|
||||
./scripts/screenshot.py -o /tmp/watch.png # specify output
|
||||
./scripts/screenshot.py screenshot
|
||||
./scripts/screenshot.py metrics
|
||||
./scripts/screenshot.py touch 200 250
|
||||
./scripts/screenshot.py swipe down
|
||||
./scripts/screenshot.py navigate radio
|
||||
"""
|
||||
|
||||
import argparse
|
||||
|
|
@ -18,90 +25,183 @@ import time
|
|||
|
||||
WIDTH = 410
|
||||
HEIGHT = 502
|
||||
PIXEL_BYTES = WIDTH * HEIGHT * 2 # 411,640 bytes
|
||||
MAGIC = b"RWSS"
|
||||
HEADER_SIZE = 8 # RWSS + uint16 width + uint16 height
|
||||
PREFIX = b"RWS"
|
||||
DEFAULT_PORT = "/dev/ttyACM4"
|
||||
|
||||
TILES = {
|
||||
"watch": (1, 1),
|
||||
"radio": (1, 0),
|
||||
"gps": (0, 1),
|
||||
"messages": (2, 1),
|
||||
"settings": (1, 2),
|
||||
}
|
||||
|
||||
SWIPES = {
|
||||
"down": [(205, 400), (205, 100)], # swipe up on screen → show tile below
|
||||
"up": [(205, 100), (205, 400)],
|
||||
"left": [(350, 250), (60, 250)],
|
||||
"right": [(60, 250), (350, 250)],
|
||||
}
|
||||
|
||||
|
||||
def capture(port, output_path):
|
||||
def get_serial(port):
|
||||
try:
|
||||
import serial
|
||||
except ImportError:
|
||||
sys.exit("pip install pyserial")
|
||||
s = serial.Serial(port, 115200, timeout=2)
|
||||
time.sleep(0.1)
|
||||
s.reset_input_buffer()
|
||||
return s
|
||||
|
||||
|
||||
def send_cmd(s, cmd_byte, payload=b""):
|
||||
s.write(PREFIX + bytes([cmd_byte]) + payload)
|
||||
s.flush()
|
||||
|
||||
|
||||
def cmd_screenshot(s, output):
|
||||
try:
|
||||
from PIL import Image
|
||||
except ImportError:
|
||||
sys.exit("pip install Pillow")
|
||||
|
||||
s = serial.Serial(port, 115200, timeout=1)
|
||||
send_cmd(s, ord('S'))
|
||||
|
||||
# Drain any pending data
|
||||
time.sleep(0.2)
|
||||
s.reset_input_buffer()
|
||||
|
||||
# Send trigger
|
||||
s.write(MAGIC)
|
||||
s.flush()
|
||||
|
||||
# Scan stream for the magic response header
|
||||
# The firmware may send KISS frames before/after the screenshot data
|
||||
# Scan for response header
|
||||
buf = b""
|
||||
deadline = time.time() + 10
|
||||
magic_idx = -1
|
||||
|
||||
while time.time() < deadline:
|
||||
chunk = s.read(max(1, s.in_waiting))
|
||||
if not chunk:
|
||||
continue
|
||||
buf += chunk
|
||||
|
||||
magic_idx = buf.find(MAGIC)
|
||||
if magic_idx >= 0 and len(buf) >= magic_idx + HEADER_SIZE:
|
||||
chunk = s.read(max(1, s.in_waiting or 1))
|
||||
if chunk:
|
||||
buf += chunk
|
||||
magic = PREFIX + b"S"
|
||||
idx = buf.find(magic)
|
||||
if idx >= 0 and len(buf) >= idx + 8:
|
||||
break
|
||||
else:
|
||||
sys.exit(f"Timeout ({len(buf)} bytes, no header)")
|
||||
|
||||
if magic_idx < 0:
|
||||
s.close()
|
||||
sys.exit(f"Magic not found in {len(buf)} bytes of response")
|
||||
|
||||
# Parse header
|
||||
hdr = buf[magic_idx:magic_idx + HEADER_SIZE]
|
||||
hdr = buf[idx:idx + 8]
|
||||
w, h = struct.unpack("<HH", hdr[4:8])
|
||||
expected = w * h * 2
|
||||
if w == 0:
|
||||
sys.exit("Screenshot buffer not allocated")
|
||||
|
||||
# Collect pixel data (may already have some in buf)
|
||||
data = buf[magic_idx + HEADER_SIZE:]
|
||||
expected = w * h * 2
|
||||
data = buf[idx + 8:]
|
||||
while len(data) < expected and time.time() < deadline:
|
||||
chunk = s.read(min(expected - len(data), 32768))
|
||||
if chunk:
|
||||
data += chunk
|
||||
|
||||
s.close()
|
||||
|
||||
received = len(data)
|
||||
if received < expected:
|
||||
print(f"Warning: got {received}/{expected} bytes ({received*100//expected}%)")
|
||||
|
||||
# Convert RGB565 BE (swapped) to PNG
|
||||
img = Image.new("RGB", (w, h))
|
||||
pixels = img.load()
|
||||
npx = min(w * h, received // 2)
|
||||
for i in range(npx):
|
||||
for i in range(min(w * h, len(data) // 2)):
|
||||
pixel = struct.unpack_from(">H", data, i * 2)[0]
|
||||
r = ((pixel >> 11) & 0x1F) * 255 // 31
|
||||
g = ((pixel >> 5) & 0x3F) * 255 // 63
|
||||
b = (pixel & 0x1F) * 255 // 31
|
||||
pixels[i % w, i // w] = (r, g, b)
|
||||
|
||||
img.save(output_path)
|
||||
print(f"Saved: {output_path} ({w}x{h}, {npx} pixels, {received} bytes)")
|
||||
img.save(output)
|
||||
print(f"Saved: {output} ({w}x{h}, {len(data)} bytes)")
|
||||
|
||||
|
||||
def cmd_metrics(s):
|
||||
send_cmd(s, ord('M'))
|
||||
buf = b""
|
||||
deadline = time.time() + 3
|
||||
while time.time() < deadline:
|
||||
chunk = s.read(max(1, s.in_waiting or 1))
|
||||
if chunk:
|
||||
buf += chunk
|
||||
magic = PREFIX + b"M"
|
||||
idx = buf.find(magic)
|
||||
if idx >= 0:
|
||||
# Find the JSON after the header
|
||||
json_start = idx + 4
|
||||
nl = buf.find(b"\n", json_start)
|
||||
if nl >= 0:
|
||||
print(buf[json_start:nl].decode())
|
||||
return
|
||||
print(f"Timeout ({len(buf)} bytes)")
|
||||
|
||||
|
||||
def cmd_touch(s, x, y, duration_ms=200):
|
||||
dur = max(1, duration_ms // 100)
|
||||
payload = struct.pack("<HHB", x, y, dur)
|
||||
send_cmd(s, ord('T'), payload)
|
||||
print(f"Touch injected: ({x}, {y}) for {dur*100}ms")
|
||||
|
||||
|
||||
def cmd_swipe(s, direction):
|
||||
if direction not in SWIPES:
|
||||
sys.exit(f"Unknown direction: {direction}. Use: {', '.join(SWIPES)}")
|
||||
points = SWIPES[direction]
|
||||
# Press at start, hold briefly, then move to end
|
||||
cmd_touch(s, points[0][0], points[0][1], 400)
|
||||
time.sleep(0.15)
|
||||
cmd_touch(s, points[1][0], points[1][1], 300)
|
||||
print(f"Swipe {direction}")
|
||||
|
||||
|
||||
def cmd_navigate(s, screen):
|
||||
if screen not in TILES:
|
||||
sys.exit(f"Unknown screen: {screen}. Use: {', '.join(TILES)}")
|
||||
col, row = TILES[screen]
|
||||
send_cmd(s, ord('N'), bytes([col, row]))
|
||||
print(f"Navigate → {screen} ({col},{row})")
|
||||
|
||||
|
||||
def cmd_invalidate(s):
|
||||
send_cmd(s, ord('I'))
|
||||
print("Invalidated — full redraw requested")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="R-Watch screenshot")
|
||||
parser.add_argument("-p", "--port", default="/dev/ttyACM4")
|
||||
parser.add_argument("-o", "--output", default="/tmp/watch_screenshot.png")
|
||||
parser = argparse.ArgumentParser(description="R-Watch remote debug")
|
||||
parser.add_argument("-p", "--port", default=DEFAULT_PORT)
|
||||
sub = parser.add_subparsers(dest="command")
|
||||
|
||||
ss = sub.add_parser("screenshot", aliases=["ss"])
|
||||
ss.add_argument("-o", "--output", default="/tmp/watch_screenshot.png")
|
||||
|
||||
sub.add_parser("metrics", aliases=["m"])
|
||||
|
||||
t = sub.add_parser("touch", aliases=["t"])
|
||||
t.add_argument("x", type=int)
|
||||
t.add_argument("y", type=int)
|
||||
t.add_argument("duration", type=int, nargs="?", default=200)
|
||||
|
||||
sw = sub.add_parser("swipe", aliases=["sw"])
|
||||
sw.add_argument("direction", choices=["up", "down", "left", "right"])
|
||||
|
||||
n = sub.add_parser("navigate", aliases=["nav", "n"])
|
||||
n.add_argument("screen", choices=list(TILES.keys()))
|
||||
|
||||
sub.add_parser("invalidate", aliases=["inv"])
|
||||
|
||||
args = parser.parse_args()
|
||||
capture(args.port, args.output)
|
||||
if not args.command:
|
||||
parser.print_help()
|
||||
return
|
||||
|
||||
s = get_serial(args.port)
|
||||
try:
|
||||
if args.command in ("screenshot", "ss"):
|
||||
cmd_screenshot(s, args.output)
|
||||
elif args.command in ("metrics", "m"):
|
||||
cmd_metrics(s)
|
||||
elif args.command in ("touch", "t"):
|
||||
cmd_touch(s, args.x, args.y, args.duration)
|
||||
elif args.command in ("swipe", "sw"):
|
||||
cmd_swipe(s, args.direction)
|
||||
elif args.command in ("navigate", "nav", "n"):
|
||||
cmd_navigate(s, args.screen)
|
||||
elif args.command in ("invalidate", "inv"):
|
||||
cmd_invalidate(s)
|
||||
finally:
|
||||
s.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue