You are using staging server - a separate instance of the ESP Component Registry that allows you to try distribution tools and processes without affecting the real registry.

espressif2022/esp-gsp-src

0.1.1

Latest
uploaded 16 hours ago
Espressif Graphics Scene Processor, an ahead-of-time compiled UI framework for ESP-IDF.

Readme (zh)

# ESP-GSP

[English](README.md) | 中文

ESP-GSP(Espressif Graphics Scene Processor)是面向 ESP-IDF 的预编译 UI
框架。它在构建阶段编译 JSON 场景、字体和图片,将资源嵌入固件,并为具名 UI
元素生成类型明确的 C 接口。

```text
JSON 场景 -> ESP-IDF 构建 -> 生成 C API -> ESP-GSP 运行时 -> 显示屏
```

ESP-GSP 适合界面结构在构建时确定,而数值、文本、可见性、媒体和导航需要在
运行时变化的嵌入式产品。

## 适用场景

ESP-GSP 适合控制面板、家电界面、仪表盘、仪器屏幕、智能家居中控和设备
启动器等在固件构建时已经确定布局的产品。当项目希望在构建期发现页面错误、
通过生成 C API 操作 UI、控制运行时资源上限,并让多种显示接口使用同一套
应用模型时,ESP-GSP 尤其合适。

如果应用必须在运行时任意创建 UI 树、需要多点触控,或者依赖从右到左布局
和复杂文字塑形,应评估其他框架。

## 实现机制

ESP-GSP 将不变的页面结构与运行时状态分开处理。场景、样式、字体、本地图片
和组件结构在主机上编译;设备端只保留交互、动画、动态内容和渲染实际需要的
状态与服务。

```text
构建阶段
  JSON 场景 + 字体 + 图片
              |
              v
          gspc 编译器
              |
              +--> 内嵌 GSP Bundle
              +--> 生成 bundle_gsp.h

运行阶段
  输入 / 生成 setter / 媒体生产者
              |
              v
          异步状态更新
              |
              v
        脏区域命令重放
              |
              v
      显示呈现层 -> esp_lcd 面板
```

`gsp_add_bundle()` 将编译器接入 ESP-IDF 构建系统,跟踪场景和引用资源,把
编译后的场景与资源打包成一个内嵌 Bundle,并生成应用头文件。运行时负责
校验 Bundle、处理输入与状态变化、只重放受影响的绘制命令,再将帧缓冲和
面板传输策略交给 `esp_display_present`。

设备端不存在需要长期维护的 UI 对象树。具名控件和属性会解析到编译后的描述
信息及有界状态槽,因此普通更新保持轻量,同时仍可支持动态文字、媒体、列表、
模板、导航和动画。

职责边界如下:

| 部分 | 职责 |
|---|---|
| BSP | 初始化面板、帧缓冲、触摸、物理旋转和字节序 |
| 场景 | 描述布局、外观、交互和资源 |
| 构建 | 通过 `gsp_add_bundle()` 编译并内嵌场景 |
| 应用 | 管理产品状态、处理事件并更新具名控件 |
| ESP-GSP | 校验 Bundle、路由输入、更新状态、渲染脏区并提交画面 |

## 特性概览

| 类别 | 已有能力 |
|---|---|
| 基础控件 | button、label、image、progress、slider、toggle、checkbox、radio、arc、spinner、chart |
| 结构与复用 | container、layer、行/列布局、styles、themes、用户自定义组合组件 |
| 数据与导航 | list、wheel、PageFlow、StackView、Drawer、TabView、Dropdown、Table、Keyboard、Msgbox |
| 图形 | 矩形、圆角矩形、圆形、椭圆、线条、渐变、指针、模拟时钟 |
| 运行时属性 | 数值、选中状态、文字、可见性、颜色、有界几何、选择项、图片资源 |
| 媒体 | PNG、JPEG、QOI、RLE16、GIF/EAF 动画、运行时编码图片、Canvas 帧 |
| 交互 | 单点触摸、点击、长按、数值拖动、滚动、甩动与回弹、场景滑动 |
| 动效 | 属性动画、组件定位、分页与抽屉运动、场景切换 |
| 渲染 | RGB565、RGB888、脏区域、裁剪、Alpha、软件回退和目标硬件加速 |
| 显示路径 | 通过 ESP-LCD 呈现策略支持 RGB、MIPI-DSI、SPI、QSPI |
| 工具 | 生成 C API、主机预览、编译器测试、参考渲染器、硬件 benchmark |

完整的场景字段和控件列表见[场景编写参考](docs/authoring.md)。

## 环境要求

- ESP-IDF 6.0 或更高版本。
- 当前 ESP-IDF 环境中的 Python 3.10 或更高版本。
- 在该 Python 环境中安装 Pillow。
- 使用专家级 `PROFILE` Bundle 选项时安装 PyYAML。
- 应用或 BSP 已完成 `esp_lcd` 面板初始化。

## 安装

在 ESP-IDF 工程中添加首个公开版本:

```sh
idf.py add-dependency "espressif/esp-gsp^0.1.0"
```

也可以写入应用组件的 `idf_component.yml`:

```yaml
dependencies:
  espressif/esp-gsp: "^0.1.0"
```

使用源码仓库时,可通过组件管理器的 `override_path` 或
`EXTRA_COMPONENT_DIRS` 引入。

## 运行示例

`examples/hello_world` 是最小的完整集成。使用仓库内 ESP32-P4 MIPI-DSI
配置时:

```sh
cd examples/hello_world
idf.py -D 'SDKCONFIG_DEFAULTS=sdkconfig.defaults;sdkconfig.defaults.esp32p4' \
  set-target esp32p4 build
idf.py flash monitor
```

该示例会初始化屏幕、在构建时编译场景、使用可选触摸启动 UI,并从应用代码
更新具名控件。其他 `sdkconfig.defaults.<target>` 文件展示相同流程;引脚和面板
时序必须按照实际硬件修改。

## 快速开始

### 1. 编写场景

创建 `ui/main.json`:

```json
{
  "screen": "main",
  "w": 320,
  "h": 240,
  "screen_bg": "#101820",
  "font": "assets/DejaVuSans.ttf",
  "objects": [
    {
      "type": "label",
      "parent": -1,
      "x": 40,
      "y": 48,
      "w": 240,
      "h": 32,
      "text": "Volume",
      "fg_color": "#FFFFFF"
    },
    {
      "type": "slider",
      "parent": -1,
      "name": "volume",
      "x": 40,
      "y": 96,
      "w": 240,
      "h": 32,
      "value": 30,
      "fg_color": "#4CC9F0"
    },
    {
      "type": "button",
      "parent": -1,
      "x": 100,
      "y": 164,
      "w": 120,
      "h": 44,
      "text": "Save",
      "callback": "save"
    }
  ]
}
```

资源路径相对于场景文件。需要由应用更新的控件使用 `name`,需要向应用上报
动作的控件使用 `callback`。

### 2. 注册 Bundle

在应用组件的 `CMakeLists.txt` 中添加:

```cmake
idf_component_register(SRCS "app_main.c"
                       PRIV_REQUIRES esp-gsp)

gsp_add_bundle(${COMPONENT_LIB}
    SCENES "../ui/main.json"
    PIXEL_FORMAT rgb565)
```

构建会嵌入 Bundle 并生成 `bundle_gsp.h`。场景或资源变化后会自动重新编译。

### 3. 启动并更新 UI

```c
#include "esp_gsp_esp_lcd.h"
#include "bundle_gsp.h"

static void on_ui_event(esp_gsp_handle_t ui,
                        const esp_gsp_event_t *event,
                        void *user_ctx)
{
    if (gsp_main_event_is_save(event)) {
        /* 通知应用任务。 */
    }
}

void app_main(void)
{
    esp_display_present_target_config_t display;
    ESP_ERROR_CHECK(board_display_init(&display));

    esp_gsp_config_t app = gsp_bundle_config();
    esp_gsp_esp_lcd_config_t lcd = ESP_GSP_ESP_LCD_CONFIG_INIT();
    lcd.display = display;
    esp_lcd_touch_handle_t touch = NULL;
    (void)board_touch_init(&touch); /* 触摸可选。 */
    lcd.touch = touch;

    esp_gsp_handle_t ui;
    ESP_ERROR_CHECK(esp_gsp_esp_lcd_start(&app, &lcd, &ui));
    ESP_ERROR_CHECK(esp_gsp_on_event(ui, on_ui_event, NULL));
    ESP_ERROR_CHECK(gsp_main_volume_set_value(ui, 60));
}
```

`board_display_init()` 和 `board_touch_init()` 代表 BSP 接口。面板初始化示例见
[`examples/common/hw_init`](examples/common/hw_init)。

生成接口的命名形式为:

```text
gsp_<scene>_<control>_<operation>()
```

生成头文件是当前 Bundle API 的准确信息源。`esp_gsp.h` 中的通用接口覆盖动态
图片、列表、Canvas、导航和数据驱动集成;诊断接口位于 `esp_gsp_debug.h`,集成
辅助接口位于 `esp_gsp_advanced.h`。

## 生成 API

应用包含 `<symbol>_gsp.h` 即可,默认 symbol 为 `bundle`。该头文件提供
Bundle 配置、场景 ID、事件辅助函数、模板描述符,以及具名控件的类型化接口。

生成函数遵循以下命名方式:

```text
gsp_<scene>_<control>_<operation>()
```

对于 `screen: "main"` 和 `name: "volume"`:

```c
int32_t volume;

ESP_ERROR_CHECK(gsp_main_volume_set_value(ui, 60));
ESP_ERROR_CHECK(gsp_main_volume_get_value(ui, &volume));
ESP_ERROR_CHECK(gsp_main_volume_animate_value_to(
    ui, 80, 250, ESP_GSP_EASE_OUT));
```

实际函数取决于控件类型及其动态属性。具名 label 可以生成 `set_text()`,image
可以生成 `set_image()`,toggle 可以生成 checked 状态接口,clock 可以生成
`set_time()`。应通过编辑器补全查看 `bundle_gsp.h`,它是当前场景 API 的准确
依据。

Callback 同样会生成判断函数:

```c
if (gsp_main_event_is_save(event)) {
    /* 执行对应的产品操作。 */
}
```

普通应用应优先使用生成接口。数据驱动集成可以使用通用 API。默认情况下,
原始 bind、action、object、property 和 template ID 不会暴露;确实需要时,
可以在包含生成头文件前定义 `GSP_BUNDLE_ENABLE_RAW_IDS`。

## 常见功能用法

### 静态图片与字体

资源路径相对于场景文件:

```json
{
  "type": "image",
  "parent": -1,
  "x": 24,
  "y": 24,
  "w": 64,
  "h": 64,
  "image": "assets/status.png",
  "codec": "auto"
}
```

使用场景级 `font` 和 `default_font_size` 设置默认字体,也可以在单个文字对象
上覆盖。引用的资源会自动重新构建并内嵌。如果运行时文字可能包含构建阶段
无法预知的字形,可添加动态字体:

```cmake
gsp_add_bundle(${COMPONENT_LIB}
    SCENES "../ui/chat.json"
    PIXEL_FORMAT rgb565
    DYNAMIC_FONT "../assets/NotoSansSC-Regular.otf")
```

### 运行时图片、Canvas 与相册

对于偶尔更新的运行时编码图片,使用具名 image 及其生成的 `set_image()`。
默认接口会复制编码数据,同时也提供借用和所有权转移版本。

摄像头预览、视频帧或实时曲线等持续产生的像素数据使用 Canvas。Canvas
缓冲区会一直保持借用状态,直到释放回调执行。

对于复用行的相册,在模板 image 上设置 `"dynamic_image": true`。普通模板
实例使用生成 setter;List 行绑定器使用 `esp_gsp_row_set_image()` 和生成的
资源槽。`config.dynamic_image_slots` 应按“同时活跃的图片目标数”配置,即
可见行加预留行,而不是按相册总图片数配置。

COPY、BORROW、TAKE、回调上下文和停止阶段的所有权规则见
[应用生命周期](docs/application-lifecycle.md)。

### 列表与运行时数据

List 和 Wheel 只保留有界数量的可见行实例。应用提供总数据量和行绑定器,
为每个可见项发布文字、数值、颜色或图片。滚动时行会被复用,因此不能在
绑定器返回后继续保存 row handle。Row token 会阻止较晚完成的异步图片解码
结果写入已经复用给其他数据项的行。

为场景中的 List 设置稳定名称,例如 `contacts`,然后通过生成的组件 key
绑定应用数据:

```c
static const char *s_contacts[] = {"Ada", "Linus", "Margaret"};

static gsp_err_t bind_contact(esp_gsp_handle_t ui, esp_gsp_row_t row,
                              uint32_t item, void *user_ctx)
{
    (void)user_ctx;
    return esp_gsp_row_text(ui, row, s_contacts[item]);
}

esp_gsp_list_t contacts = esp_gsp_list_bind_component(
    ui, GSP_OBJ_KEY_CONTACTS, bind_contact, NULL);
if (contacts == ESP_GSP_LIST_NONE) {
    /* The configured list quota is exhausted. */
} else {
    ESP_ERROR_CHECK(esp_gsp_list_set_total(
        ui, contacts, sizeof(s_contacts) / sizeof(s_contacts[0])));
}
```

如果相册行模板包含 `dynamic_image`,使用 `esp_gsp_row_set_image()` 和生成的
`GSP_TEMPLATE_<TEMPLATE>_<IMAGE>_RESOURCE_SLOT` 常量提交编码图片。替换列表的
后端数据后,调用 `esp_gsp_list_refresh()` 重新绑定当前可见行。

### 多页面与导航

将相关场景注册到一个 Bundle:

```cmake
gsp_add_bundle(${COMPONENT_LIB}
    SCENES "../ui/home.json"
           "../ui/settings.json"
           "../ui/about.json"
    PIXEL_FORMAT rgb565)
```

可以使用生成的场景 ID、场景 action 或 `esp_gsp_goto_scene()` 导航。PageFlow
和 TabView 在一个场景内切换页面;StackView 提供 push/pop;Drawer 提供边缘
面板。控件拖动、列表滚动、viewport 手势、点击和场景滑动共用一套输入仲裁,
交互子控件会优先于其容器。

选择方式和手势优先级见[导航与 Viewport](docs/viewport-transform.md)。

### 图形、图表与时钟

`shape` 可绘制矩形、圆角矩形、圆形、椭圆和线条。它会编译为绘制命令,
不会在运行时创建对象。`chart` 用于编译期点数据,`needle` 适合仪表和指南针,
`clock` 提供标准模拟表盘及原子更新时间接口。

```json
{
  "type": "shape",
  "shape": "ellipse",
  "parent": -1,
  "x": 24,
  "y": 24,
  "w": 120,
  "h": 64,
  "bg_color": "#2864B0",
  "border_color": "#D8E8FF",
  "border_width": 2
}
```

## 运行时约定

- ESP-IDF 上的 setter 为异步提交;返回成功表示更新已被接受。
- 仅在测试或截图等确定性边界使用 `esp_gsp_flush()`。
- 事件、定时器、列表绑定和释放回调运行在框架任务中,不能阻塞。
- 从应用任务调用 `esp_gsp_stop()`,不要从回调中停止 UI。
- 场景尺寸、Bundle 像素格式和显示配置必须一致。
- 应用缓冲区保持原生 RGB565 或 BGR888 布局;字节交换和物理旋转由 BSP
  显示目标负责。

同步与图片/Canvas 所有权见[应用生命周期](docs/application-lifecycle.md),缓冲与
面板策略见[显示呈现](docs/present_strategy.md)。

## 显示配置

场景尺寸、Bundle 像素格式和初始化后的显示路径必须一致。BSP 应提供准确的
`esp_display_present_target_config_t`,通常保持
`ESP_DISPLAY_PRESENT_MODE_AUTO`。

| 显示路径 | 常规策略 |
|---|---|
| RGB / MIPI-DSI | 直接双缓冲或局部三缓冲 |
| 带 TE 的 SPI / QSPI | TE 同步脏区域传输 |
| 不带 TE 的 SPI / QSPI | 自由运行的脏区域或有界条带传输 |

应用侧像素使用小端 RGB565,或按 B、G、R 紧密排列的 RGB888。运行时图片和
Canvas 数据应保持该原生布局。SPI/QSPI 面板如果在线路上需要大端 RGB565,
应在 BSP 显示 target 中设置 `swap_bytes`,不要预先交换应用缓冲区。物理旋转
和帧缓冲配置同样由 BSP 负责。

只有操作、芯片、像素格式和内存位置都满足条件时才会使用硬件加速;不满足
条件时由 CPU 路径保持相同行为。

## Bundle 选项

```cmake
gsp_add_bundle(<component-target>
    SCENES <scene0.json> [scene1.json ...]
    [PIXEL_FORMAT rgb565|rgb888]
    [IMAGE_CACHE_BYTES <bytes>]
    [DYNAMIC_FONT <font.ttf>]
    [SYMBOL <c_identifier>]
    [PROFILE <expert-profile.yaml>])
```

多数应用只需要 `SCENES` 和 `PIXEL_FORMAT`。目标加速能力和默认显示策略会根据
ESP-IDF 与 BSP 能力自动选择。`PROFILE` 会加载专家级 YAML 覆盖配置,因此要求
当前 ESP-IDF Python 环境安装 PyYAML。运行时配额和缓存默认值见
[配置参考](docs/configuration.md)。

## 示例与文档

- [Hello world](https://github.com/espressif/esp-gsp/tree/master/examples/hello_world):最小 ESP-IDF 集成。
- [体验展示](https://github.com/espressif/esp-gsp/tree/master/examples/showcase):面向 800x480 和 1024x600 屏幕的用户交互产品示例。
- [硬件基准](https://github.com/espressif/esp-gsp/tree/master/examples/benchmark):完整功能和目标路径验证。
- [文档索引](docs/README.md):用户指南和技术参考。
- [主机预览](https://github.com/espressif/esp-gsp/blob/master/tools/sim/README.md):从源码仓库运行模拟器。

## 限制

- 场景结构在构建时固定;运行时变化通过属性、模板、列表、动态资源或 Canvas
  实现。
- 仅支持单点触摸。
- 一个 Bundle 使用一种逻辑分辨率和一种输出像素格式。
- 不支持从右到左布局和复杂文字塑形。
- Cross-fade 需要两个场景快照;内存不足时会回退为直接切换。
- 硬件加速取决于芯片、像素格式、内存位置和具体操作;软件路径保证行为一致。

## 常见问题

### 提示 `gsp_add_bundle requires Python 3.10+ and Pillow`

激活需要使用的 ESP-IDF 环境,并在该 Python 环境中安装 Pillow:

```sh
. "$IDF_PATH/export.sh"
python -m pip install Pillow
```

### 使用 `PROFILE` 时提示需要 PyYAML

在同一个已激活的 ESP-IDF Python 环境中安装 PyYAML:

```sh
python -m pip install "PyYAML>=6,<7"
```

### 场景与屏幕不匹配

同时检查场景 `w` 和 `h`、面板分辨率与方向,以及
`gsp_add_bundle(PIXEL_FORMAT ...)`。

### 找不到预期的生成接口

为控件设置稳定的 `name`,确认该属性对该控件是动态属性,重新构建后查看
`bundle_gsp.h`。需要事件判断接口时添加 `callback`。

### Setter 返回成功但画面尚未变化

Setter 是异步接口。只有测试、截图或其他确定性同步边界才需要使用
`esp_gsp_flush()`。

### 触摸没有响应

确认 `lcd.touch` 已赋值、触摸坐标与面板方向一致,并检查目标是否被隐藏或被
后绘制对象覆盖。

## 许可证

Apache-2.0,见 [LICENSE](LICENSE)。

Links

Supports all targets

To add this component to your project, run:

idf.py add-dependency "espressif2022/esp-gsp-src^0.1.1"

download archive

Stats

  • Archive size
    Archive size ~ 4.57 MB
  • Downloaded in total
    Downloaded in total 0 times
  • Downloaded this version
    This version: 0 times

Badge

espressif2022/esp-gsp-src version: 0.1.1
|