# Tutorial de ADB en Python: controlar un emulador Android

> Tutorial de ADB en Python para Macro Automation Studio: toca, desliza, escribe, pulsa teclas y haz capturas en BlueStacks mientras la app gestiona adb por ti.

Source: https://automationmacro.com/es/docs/guides/control-an-emulator-from-python (Guías, updated 2026-09-05)

La mayoría de los tutoriales de ADB en Python acaban en un envoltorio de `subprocess` alrededor de `adb shell input tap`. Este tutorial de ADB en Python para Macro Automation Studio (MAS) toma otro camino: la app es la dueña de adb, y tu script habla con la app a través del paquete `mas`. Esta guía va desde un proyecto nuevo hasta un script que abre una app, busca un término, hace scroll y guarda una captura. Está pensada para desarrolladores de Python que quieren controlar BlueStacks, LDPlayer, MuMu Player o MEmu sin escribir llamadas a adb.

## Antes de empezar

- MAS está instalado y has iniciado sesión con la prueba o un plan. Consulta [Instalar MAS](/docs/install).
- Un emulador está en marcha con ADB activado y añadido como dispositivo en **Grupos de dispositivos**. Consulta [Dispositivos](/docs/devices).
- Sabes algo de Python. No instalas nada más: MAS incluye su propio entorno de Python 3.13 con `mas` dentro.

## Cómo encajan las piezas

MAS inicia tu script como `python -u -m src.app` con la carpeta del proyecto como directorio de trabajo. Le pasa los datos de conexión en variables de entorno (`MAS_RPC_PORT`, `MAS_RPC_HOST`, `MAS_DEVICE_ID`, `MAS_SESSION_TOKEN`). Cada llamada `mas.*` se convierte en una petición JSON-RPC 2.0 a la app. La app ejecuta el comando adb, la búsqueda de plantilla o el OCR, y devuelve el resultado. El dispositivo está vinculado antes de que se ejecute tu primera línea, así que no hay llamada `connect()` ni número de serie que gestionar. Consulta [Conceptos](/docs/concepts).

## Crear el proyecto

1. Abre **Macros** y haz clic en **Crear nuevo proyecto**.
2. Elige **Basado en código**, pon **Dispositivo objetivo** en móvil, nombra el proyecto y haz clic en **Crear proyecto**.
3. Abre el proyecto. El Code Editor muestra `src/app.py`.
4. Elige el emulador en el selector de dispositivo y haz clic en **Ejecutar** (<kbd>F5</kbd>) cada vez que quieras probar un fragmento. **Detener** es <kbd>Mayús</kbd>+<kbd>F5</kbd>.

## Tamaño de pantalla y coordenadas

Cada coordenada que pasas al SDK es un desplazamiento en píxeles desde la esquina superior izquierda de la pantalla del dispositivo, en la resolución propia del dispositivo. El tamaño de la ventana del emulador en tu monitor no importa.

```python
import mas

size = mas.get_screen_size()
print(f"Screen: {size.width}x{size.height}")

center = (size.width // 2, size.height // 2)
mas.click(*center)
```

`get_screen_size()` devuelve un `ScreenSize` con `width` y `height`. `get_device_info()` devuelve lo mismo más el `name`, el `type` y el indicador `connected` del dispositivo. Cuando un script debe sobrevivir a un cambio de resolución, calcula las posiciones como fracciones del tamaño, como hace el ejemplo del final. Las imágenes de plantilla y las regiones OCR no lo sobreviven; consulta la [guía de reconocimiento de imagen](/docs/guides/image-recognition-macros).

## Hacer una captura de pantalla con Python y adb

```python
import base64
import mas

shot = mas.take_screenshot()
print(shot.width, shot.height, shot.timestamp)

with open("screen.png", "wb") as f:
    f.write(base64.b64decode(shot.base64))
```

`take_screenshot()` devuelve un `Screenshot`: `base64` contiene un PNG, `width` y `height` son su tamaño en píxeles y `timestamp` es una cadena ISO 8601. El archivo de arriba acaba en la carpeta del proyecto porque ese es el directorio de trabajo. Pasa el mismo objeto a varias llamadas `find_object` con `screenshot=shot` y todas buscan en un solo fotograma en lugar de capturar de nuevo.

## Tocar

```python
mas.click(270, 800)                 # tap, then wait 1000 ms
mas.click(270, 800, delay_ms=300)   # shorter pause for tight loops
```

`click(x, y, delay_ms=1000)` toca una vez y espera `delay_ms` después para que la app reaccione. Un toque no tiene duración de mantenimiento; para una pulsación larga usa `key_press` con `duration_ms`.

## Deslizar

```python
mas.swipe((270, 750), (270, 300), duration_ms=400)    # scroll a list down: drag from lower to upper
mas.swipe((100, 500), (400, 500), duration_ms=1000)   # drag and drop: slow and deliberate
```

`swipe(from_coords, to_coords, duration_ms=1000)` recibe dos tuplas `(x, y)`. Una duración corta es un gesto rápido; una larga es un arrastre. Los gestos de pellizco son llamadas aparte: `zoom_in()` y `zoom_out()` usan por defecto el centro de la pantalla con `percent=50`.

## Escribir

```python
mas.click(270, 120, delay_ms=500)          # focus the field first
mas.input_text("hello world")
mas.input_text("new value", clear=True)    # replace what is there
```

`input_text(text, delay_ms=0, clear=False)` escribe en el campo con el foco, así que toca el campo primero. `clear=True` mueve el cursor al final y borra los caracteres existentes antes de escribir.

## Pulsar teclas

```python
from mas import KeyCode

mas.key_press(KeyCode.BACK)
mas.key_press(KeyCode.HOME)
mas.key_press(KeyCode.ENTER)
mas.key_press(KeyCode.DELETE, repeat=10)          # ten presses in one command
mas.key_press(KeyCode.POWER, duration_ms=3000)    # long press
```

`key_press(key_code, modifiers=None, duration_ms=100, repeat=1)` envía un evento de tecla de Android. Un `duration_ms` de 500 o más se convierte en pulsación larga; el número exacto de milisegundos por encima de eso no se respeta. `repeat` va de 1 a 100 y es mucho más rápido que un bucle de Python. `KeyCode` también tiene las teclas de la cruceta, el volumen, los medios y los números.

## Abrir y comprobar apps

```python
mas.open_app("com.android.settings", timeout_ms=5000)
print(mas.get_current_app())                        # package name in front
print(mas.is_app_focused("com.android.settings"))   # True or False

if mas.get_app_state("com.android.chrome") == mas.NOT_RUNNING:
    mas.open_app("com.android.chrome")

mas.close_app("com.android.settings")
```

`open_app(package_name, timeout_ms=2000)` lanza por nombre de paquete y espera `timeout_ms`. Para conocer un nombre de paquete, abre la app a mano e imprime `mas.get_current_app()`. `get_app_state` devuelve `NOT_INSTALLED`, `NOT_RUNNING`, `RUNNING_IN_BACKGROUND_SUSPENDED`, `RUNNING_IN_BACKGROUND` o `RUNNING_IN_FOREGROUND`. `close_app` fuerza la detención del paquete.

## Un script completo

El script abre los Ajustes de Android, busca un término, hace scroll por los resultados, guarda una captura y comprueba con OCR que el término está en pantalla. El cuadro de búsqueda es una plantilla que recortas en Asset Lab; sustituye el ID por el tuyo. El deslizamiento usa fracciones del tamaño de pantalla para que funcione a cualquier resolución.

```python
import base64
import sys
import time

import mas
from mas import KeyCode

PACKAGE = "com.android.settings"
TERM = "Display"

images = mas.images({"search_box": 301})


def main():
    size = mas.get_screen_size()
    mas.log(f"Screen {size.width}x{size.height}")

    mas.open_app(PACKAGE, timeout_ms=5000)
    if not mas.is_app_focused(PACKAGE):
        mas.log(f"{PACKAGE} did not come to the front", level="error")
        sys.exit(2)

    box = mas.find_object_retry(images.search_box, total_tries=3, time_sleep=2.0)
    if box is None:
        mas.log("Search box not found; crop it in Asset Lab", level="error")
        sys.exit(2)
    mas.click(box.x, box.y, delay_ms=800)
    mas.input_text(TERM, clear=True)
    mas.key_press(KeyCode.ENTER)
    time.sleep(2)

    x = size.width // 2
    mas.swipe((x, int(size.height * 0.75)), (x, int(size.height * 0.35)), duration_ms=600)
    time.sleep(1)

    shot = mas.take_screenshot()
    with open("search_results.png", "wb") as f:
        f.write(base64.b64decode(shot.base64))
    mas.log(f"Saved search_results.png ({shot.width}x{shot.height})")

    text = mas.read_text(screenshot=shot, psm=11)
    mas.log(f"Term on screen: {TERM.lower() in text.text.lower()}")

    mas.key_press(KeyCode.HOME)


if __name__ == "__main__":
    main()
```

`read_text` con `psm=11` lee texto disperso por todo el fotograma, lo que va bien para una lista de resultados. La [guía de OCR](/docs/guides/ocr-text-reading) cubre las regiones y los modos para leer un solo contador. Si prefieres describir la tarea en vez de escribirla, [MAS Agent](/agent) escribe este tipo de script por ti.

## Dónde encaja adb en este tutorial de Python

Nunca llamas a adb, pero es quien hace el trabajo por debajo.

- **Binario.** El instalador incluye adb. En Windows está en `C:\ProgramData\MacroAutomationStudio\3rdparty`, con una copia integrada como alternativa. En un Mac está dentro del paquete de la app, con el adb de Homebrew como alternativa. Nada entra en tu PATH. La página [Instalación](/docs/install) tiene los detalles.
- **Conexión.** Los emuladores exponen adb en un puerto TCP local. Cuando haces clic en **Iniciar** o **Ejecutar**, MAS ejecuta `adb connect 127.0.0.1:<port>` con el puerto de la tarjeta del dispositivo y mantiene esa sesión durante la ejecución.
- **Entrada.** `click` se convierte en `adb shell input tap`, `swipe` en `adb shell input touchscreen swipe`, `input_text` en `adb shell input text` y `key_press` en `adb shell input keyevent`. Los gestos de pellizco se escriben directamente en el dispositivo de entrada táctil del emulador.
- **Pantalla.** `take_screenshot` y cada `find_object` capturan con `adb exec-out screencap -p`, la forma segura para binarios que no estropea el PNG en Windows. `get_screen_size` lee `wm size`; `get_current_app` lee el gestor de ventanas.
- **Apps.** `open_app` lanza el intent del launcher mediante `monkey` y recurre a `am start` si falla; `close_app` ejecuta `am force-stop`.
- **Servidores.** MAS ejecuta su propio servidor adb. Si otra versión de adb en tu PATH inicia el suyo, los dos se sustituyen entre sí y tu dispositivo cae a Conectando. La página [Solución de problemas de ADB](/docs/adb-troubleshooting) lista las soluciones, puerto por puerto.

Como la app hace todo esto, un script escrito en BlueStacks se ejecuta sin cambios en LDPlayer, en un dispositivo en la nube o en tu propio móvil a través de un puerto local.

## Qué puede salir mal

### Could not discover RPC port

El script se inició desde una terminal en lugar de desde MAS, así que faltan las variables de entorno. Ejecútalo con **Ejecutar** en el Code Editor o desde una tarjeta de dispositivo.

### Los toques caen en el sitio equivocado

Estás usando píxeles de la ventana en lugar de píxeles del dispositivo, o la resolución del emulador cambió. Imprime `mas.get_screen_size()` y compáralo con las coordenadas que pasas. Las coordenadas de Asset Lab ya son píxeles del dispositivo.

### input_text no escribió nada

Ningún campo tenía el foco. Toca el campo con `click` y dale `delay_ms=500` antes de escribir. Algunas apps abren un teclado que tapa el campo; `key_press(KeyCode.BACK)` lo cierra después de escribir.

### Dispositivo no encontrado u offline

ADB está apagado en el emulador, el puerto de la tarjeta del dispositivo es incorrecto o otro servidor adb tomó el control. Sigue [Solución de problemas de ADB](/docs/adb-troubleshooting).

Si la app que controlas es un juego, trátala con el mismo cuidado que cualquier otra cuenta automatizada. Ninguna herramienta de automatización está libre de riesgos al 100 %, así que automatiza con responsabilidad y bajo tu propio criterio.
