avatar

松果工作室

欢迎光临

  • 首页
  • ESP
  • LVGL
  • CubeMX
  • freeRTOS
  • 快速笔记
  • 个人收藏
  • 我的服务
  • 考察日志
Home [adb] 读取屏幕内容与点击,用于测试
文章

[adb] 读取屏幕内容与点击,用于测试

Posted 2 days ago Updated 2 days ago
By YCP
16~21 min read

adb 可以实现点击屏幕与抓取屏幕的 xml 结构(包括元素坐标),因此可以利用这个特性写一些 APP 测试脚本

本程序实现了adb 命令的发送,xml 数据的解析,串口的发送。以达到预期效果

#include <libxml/parser.h>
#include <libxml/tree.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#include <termios.h>

int fd = 0;

void serial_send(uint8_t *data, int len) {
    fd = open("/dev/cu.wchusbserial140", O_RDWR | O_NOCTTY);
    if (fd < 0) {
        printf("open serial port failed");
        return;
    }else{
        printf("open serial port success");
    }
    struct termios tty;
    tcgetattr(fd, &tty);
    cfsetospeed(&tty, B115200);
    cfsetispeed(&tty, B115200);
    tty.c_cflag |= (CLOCAL | CREAD);
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;
    tty.c_cflag &= ~PARENB;
    tty.c_cflag &= ~CSTOPB;
    tcsetattr(fd, TCSANOW, &tty);
    printf("set serial port success");
    write(fd, data, len);
    printf("send success\n");
    close(fd);
}


typedef enum {
    SUCCESS = 0,
    FAIL = -1,
} status_t;

// 执行命令
int run_cmd(const char *cmd) {
    return system(cmd);
}

// 解析 bounds
status_t parse_bounds(const char *bounds, int *x, int *y) {
    int x1, y1, x2, y2;
    if (sscanf(bounds, "[%d,%d][%d,%d]", &x1, &y1, &x2, &y2) == 4) {
        *x = (x1 + x2) / 2;
        *y = (y1 + y2) / 2;
        return SUCCESS;
    }
    return FAIL;
}

// 判断是否匹配(支持“包含匹配”)
status_t is_match(xmlNode *cur, const char *txt) {
    xmlChar *text = xmlGetProp(cur, (const xmlChar *) "text");
    xmlChar *desc = xmlGetProp(cur, (const xmlChar *) "content-desc");

    status_t ret = FAIL;

    if ((text && strstr((char *) text, txt)) ||
        (desc && strstr((char *) desc, txt))) {
        ret = SUCCESS;
    }

    if (text) xmlFree(text);
    if (desc) xmlFree(desc);

    return ret;
}

// 解析坐标
status_t get_xy(xmlNode *cur, int *x, int *y) {
    xmlChar *bounds = xmlGetProp(cur, (const xmlChar *) "bounds");
    if (!bounds) return FAIL;

    status_t ret = parse_bounds((char *) bounds, x, y);

    xmlFree(bounds);
    return ret;
}

// 递归查找(找到第一个就返回)
status_t find_txt(xmlNode *node, const char *txt, int *x, int *y) {
    for (xmlNode *cur = node; cur; cur = cur->next) {

        if (cur->type == XML_ELEMENT_NODE) {

            if (is_match(cur, txt) == SUCCESS) {
                if (get_xy(cur, x, y) == SUCCESS) {
                    return SUCCESS;
                }
            }
        }

        if (find_txt(cur->children, txt, x, y) == SUCCESS) {
            return SUCCESS;
        }
    }
    return FAIL;
}

// dump + pull
status_t dump_ui() {
    if (run_cmd("adb shell uiautomator dump /sdcard/ui.xml") != 0) return FAIL;
    if (run_cmd("adb pull /sdcard/ui.xml ./ui.xml") != 0) return FAIL;
    return SUCCESS;
}

void tap(int x, int y) {
    char cmd[128];
    sprintf(cmd, "adb shell input tap %d %d", x, y);
    run_cmd(cmd);

}

void tap_back() {
    tap(102, 217);
}

typedef enum {
    NULL_MENU = 0,
    HOME,
    DEVICE,
    MY,
    SETTING,
    LANGUAGE,
} menu_t;

static menu_t current_menu = NULL_MENU;

uint8_t back = 0;

void business_task(xmlNode *root) {
    int x = 0, y = 0;
    switch (current_menu) {
        case NULL_MENU:
            if (back == 0) {
                if (find_txt(root, "家庭", &x, &y) == SUCCESS) {
                    tap(x, y);
                    current_menu = HOME;
                    printf("tap home\n");
                    break;
                }
            }
            break;
        case HOME:
            if (back == 0) {
                if (find_txt(root, "设备", &x, &y) == SUCCESS) {
                    tap(x, y);
                    current_menu = DEVICE;
                    printf("tap device\n");
                    break;
                }
            } else {
                back = 0;
            }
            break;
        case DEVICE:
            if (back == 0) {
                if (find_txt(root, "我的", &x, &y) == SUCCESS) {
                    tap(x, y);
                    current_menu = MY;
                    printf("tap my\n");
                    break;
                }
            } else {
                if (find_txt(root, "家庭", &x, &y) == SUCCESS) {
                    tap(x, y);
                    current_menu = HOME;
                    printf("tap home\n");
                    break;
                }
            }
            break;
        case MY:
            if (back == 0) {
                if (find_txt(root, "设置", &x, &y) == SUCCESS) {
                    tap(x, y);
                    current_menu = SETTING;
                    printf("tap setting\n");
                    break;
                }
            } else {
                if (find_txt(root, "设备", &x, &y) == SUCCESS) {
                    tap(x, y);
                    current_menu = DEVICE;
                    printf("tap device\n");
                    break;
                }
            }
            break;
        case SETTING:
            if (back == 0) {
                if (find_txt(root, "语言", &x, &y) == SUCCESS) {
                    tap(x, y);
                    current_menu = LANGUAGE;
                    printf("tap language\n");
                    break;
                }
            } else {
                tap_back();
                current_menu = MY;
                printf("tap back\n");
            }
            break;
        case LANGUAGE:
            if (back == 0) {
                tap_back();
                back = 1;
                current_menu = SETTING;
                printf("tap back\n");
            }
            break;
        default:
            break;
    }
    serial_send((uint8_t *) "lock", 4);
}


int main() {
    while (1) {
        // 1. 拉取当前界面的 XML 文件
        if (dump_ui() == SUCCESS) {
            printf("dump success %ld\n", time(NULL));
        } else {
            printf("dump failed %ld\n", time(NULL));
            sleep(1);
            continue;
        }

        // 2. 解析 XML 文件
        xmlDoc *doc = xmlReadFile("ui.xml", NULL, 0);
        if (doc == NULL) {
            printf("xml read failed %ld\n", time(NULL));
            sleep(1);
            continue;
        } else {
            printf("xml read success %ld\n", time(NULL));
        }

        xmlNode *root = xmlDocGetRootElement(doc);
        business_task(root);
        xmlFreeDoc(doc);
    }
}

windows读取串口

#include <windows.h>
#include <stdio.h>

int main() {
    HANDLE hSerial;

    // 打开串口
    hSerial = CreateFile("COM1",
                         GENERIC_WRITE,
                         0,
                         NULL,
                         OPEN_EXISTING,
                         0,
                         NULL);

    if (hSerial == INVALID_HANDLE_VALUE) {
        printf("无法打开串口\n");
        return -1;
    }

    // 配置串口参数(必须)
    DCB dcb = {0};
    dcb.DCBlength = sizeof(dcb);

    GetCommState(hSerial, &dcb);

    dcb.BaudRate = CBR_115200;
    dcb.ByteSize = 8;
    dcb.StopBits = ONESTOPBIT;
    dcb.Parity   = NOPARITY;

    SetCommState(hSerial, &dcb);

    // 要发送的数据
    unsigned char data[3] = {0xFF, 0xFF, 0xFF};

    DWORD bytesWritten;
    WriteFile(hSerial, data, 3, &bytesWritten, NULL);

    printf("发送了 %ld 字节\n", bytesWritten);

    CloseHandle(hSerial);
    return 0;
}
License:  CC BY 4.0
Share

Further Reading

OLDER

NEWER

[CubeMX] SPI

Recently Updated

  • [adb] 读取屏幕内容与点击,用于测试
  • [CubeMX] SPI
  • [CubeMX] 串口 DMA
  • [CubeMX] 基础工程配置
  • (LVGL)接口预览 样式

Trending Tags

LVGL WCH Linux Elec ThatProject freeRTOS STM ESP Flutter Others

Contents

©2026 松果工作室. Some rights reserved.

Using the Halo theme Chirpy