esp-adf Mock Services 示例精讲:基于 esp_service_t 的 ASYNC/SYNC/QUEUE 三种服务模式与状态机实战

发布时间:2026/10/14 3:20:04
esp-adf Mock Services 示例精讲:基于 esp_service_t 的 ASYNC/SYNC/QUEUE 三种服务模式与状态机实战
音视频音频处理嵌入式物联网语音【免费下载链接】esp-adfEspressif Advanced Development Framework for Multimedia Applications项目地址https://gitcode.com/gh_mirrors/es/esp-adf点击查看免费下载导读esp_service是 ESP-IDFesp-adf中一套三层服务基础设施其核心是作为所有服务基类的esp_service_t而 Mock Services 示例 是这个框架最小、最聚焦的入门演示它在板子启动时自包含地跑完一组冒烟测试smoke test逐一演示六个 mock 服务以及esp_service_t生命周期 API创建、启动、暂停/恢复、停止、销毁的三种典型用法——player_service的 ASYNC 模式、led_service的 SYNC 模式、cloud_service的 QUEUE 模式并通过 event hub 订阅服务的STATE_CHANGED状态变化事件。读完本文你将掌握esp_service_t的状态机与虚函数表vtable子类化机制、三种服务模式各自的任务/队列/阻塞行为差异以及如何在无 Wi-Fi、无传输层的环境下快速构建并验证自己的服务。示例定位只管“服务”不碰“框架”Mock Services 示例刻意保持极简它只展示components/目录下六个 mock 服务本身的使用方式而框架层测试service manager、MCP 服务器、HTTP / SSE / WS / UART 传输、PC 端模拟全部被收敛到components/esp_service/test_apps/中。也就是说这个示例是“服务用法”的窗口不是“框架功能”的演示场。它的特点可以概括为演示如何对每种esp_service_t风格执行完整生命周期create、start、exercise、pause/resume、stop、destroy演示如何通过 event hub 订阅服务的STATE_CHANGED事件全部逻辑在开机时作为自包含冒烟测试运行不需要 Wi-Fi、不涉及任何传输层。六个 Mock Service 一览示例在 components/esp_service/examples/mock_services/components/ 下提供了六个模拟服务覆盖网络、音频、系统、显示、输入五大类别组件类别模拟能力wifi_service网络模拟 Wi-Fi 扫描/连接并产生域事件domain eventsplayer_service音频模拟音频播放带异步生命周期async lifecycleota_service系统模拟 OTA 检查/升级带进度事件led_service显示模拟 LED 开/关/闪烁并响应其他服务的事件cloud_service网络通过 task queue 模拟云端连接与数据发送button_service输入模拟按键按压事件值得注意的设计每个面向网络的服务组件同时交付两份代码——服务实现xxx_service.c和 MCP 工具处理器xxx_service_mcp.c。例如 player_service 组件 下同时存在player_service.c、player_service_mcp.c与player_service_mcp.jsonJSON Schema 工具描述。但 Mock Services 示例故意不链接 MCP 处理器它们由test_apps的 runner 消费——这再次印证了示例“只演示服务本身”的边界。esp_service_t基类与状态机三种模式的共同地基在展开三个冒烟测试之前需要先理解它们共同继承的基类。esp_service_t定义于 esp_service.h其核心机制有三点状态机UNINITIALIZED → INITIALIZED → RUNNING ⇄ PAUSED外加内部态STOPPING与错误态ERROR。所有生命周期 APIesp_service_start/stop/pause/resume都在调用者任务上下文中同步执行对应的 vtable 操作vtable 子类化派生服务把esp_service_t作为第一个成员嵌入并填充esp_service_ops_t函数指针on_init/on_deinit/on_start/on_stop/on_pause/on_resume/on_lowpower_enter/on_lowpower_exit/event_to_name全部可选NULL 即空操作Event Hub 集成每个服务在esp_service_init()时自动创建一个以自己的name为域的adf_event_hub_t通过esp_service_publish_event()/esp_service_event_subscribe()做发布-订阅。底层实现esp_service.c清晰地展示了状态迁移的同步语义以esp_service_start()为例它先校验状态必须是INITIALIZED否则返回ESP_ERR_INVALID_STATE随后handle_start_sync()调用ops-on_start()若回调失败则记录last_err并把状态置为ERROR成功则置为RUNNING。esp_service_t::state是原子变量_Atomic esp_service_state_t state任何任务无需加锁即可读取。派生服务结构体的标准写法如下来自 esp_service.h 的注释约定typedef struct { esp_service_t base; // 必须作为第一个成员 // ... 派生字段 ... } my_service_t;三种服务模式实战冒烟测试逐段拆解示例的入口 main.c 极其精简app_main()只做一件事——调用smoke_test_run()并打印结果横幅失败数为 0 时输出ALL SMOKE TESTS PASSED。真正的逻辑全部在 smoke_test.c 中它依次执行三个测试函数并输出一个 ASYNC vs SYNC 对比表。ASYNC 模式player_servicetest_player_async()演示了任务支撑、API 非阻塞的服务模式。关键片段player_service_cfg_t cfg {.uri http://example.com/music.mp3, .volume 80}; ret player_service_create(cfg, player); subscribe_state_events((esp_service_t *)player); esp_service_start((esp_service_t *)player); esp_service_get_state((esp_service_t *)player, state); // 立即为 RUNNING player_service_play(player); esp_service_pause((esp_service_t *)player); // 异步不阻塞调用者 vTaskDelay(pdMS_TO_TICKS(100)); // 稍后 state PAUSED esp_service_resume((esp_service_t *)player); vTaskDelay(pdMS_TO_TICKS(100)); // 稍后 state RUNNING esp_service_stop((esp_service_t *)player); vTaskDelay(pdMS_TO_TICKS(100)); player_service_destroy(player);从 player_service.c 的实现可以看到 ASYNC 模式的典型特征player_on_start()中若配置了sim_actions 0会通过xTaskCreate()创建名为player_sim的模拟任务该任务每轮随机执行音量调整、播放、暂停/恢复、进度上报等动作并通过esp_service_publish_event()发出PLAYER_EVT_STARTED / PAUSED / PROGRESS / VOLUME_CHANGED / ERROR等域事件。player_on_stop()则置sim_running false并通过二进制信号量stop_sem最多等待STOP_WAIT_MS2000 ms让模拟任务退出。player_service_cfg_t的配置项为字段含义备注uri媒体 URI模拟播放目标传入空指针时默认 volume 50volume初始音量0–100超过 100 会被钳制为 100sim_actions随机 play/pause/音量动作次数0 不启动模拟任务SYNC 模式led_servicetest_led_sync()演示无任务、API 阻塞的服务模式。其状态变化“立即可见”无需延时轮询led_service_cfg_t cfg {.gpio_num 5, .blink_period 500}; ret led_service_create(cfg, led); esp_service_get_state((esp_service_t *)led, state); // 初始即 INITIALIZED esp_service_start((esp_service_t *)led); // 同步调用返回时已 RUNNING led_service_on(led); led_service_set_brightness(led, 75); led_service_blink(led, 200); esp_service_pause((esp_service_t *)led); // 返回后立即 PAUSED esp_service_resume((esp_service_t *)led); // 返回后立即 RUNNING esp_service_lowpower_enter((esp_service_t *)led); // 低功耗钩子不改变状态 esp_service_lowpower_exit((esp_service_t *)led); esp_service_stop((esp_service_t *)led); // 返回后回到 INITIALIZED led_service_destroy(led);led_service.c 中没有任何xTaskCreate——led_on_start/on_stop/on_pause/on_resume都是纯同步回调直接更新内部状态并调用publish_pattern_changed()发布LED_EVT_PATTERN_CHANGED事件。led_on_pause()会把闪烁中的 LED 固化为常亮LED_STATE_BLINK → LED_STATE_ONled_on_lowpower_enter()则直接熄灭 LED 以节能。这正是 SYNC 模式的定位简单、即时、无后台任务的操作如 GPIO 翻转、亮度设置。led_service_cfg_t配置项gpio_numLED 引脚缺省 2与blink_period闪烁周期毫秒缺省 500。QUEUE 模式cloud_servicetest_cloud_queue()演示任务 命令队列、事件驱动的模式——这是三类中最“重”的一种cloud_service_cfg_t cfg CLOUD_SERVICE_CFG_DEFAULT(); cfg.endpoint mqtts://cloud.example.com:8883; cfg.device_id esp32-test-001; ret cloud_service_create(cfg, cloud); esp_service_start((esp_service_t *)cloud); // 启动队列任务 cloud_service_connect(cloud); // 非阻塞仅入队 vTaskDelay(pdMS_TO_TICKS(500)); cloud_service_get_status(cloud, cloud_st); // 期望 CONNECTED const char *test_data {\temp\:25.5,\humidity\:60}; ret cloud_service_send_data(cloud, test_data, strlen(test_data)); // 同步阻塞至处理完成 cloud_service_reconnect(cloud); vTaskDelay(pdMS_TO_TICKS(500)); cloud_service_disconnect(cloud); vTaskDelay(pdMS_TO_TICKS(300)); // 期望 IDLE esp_service_stop((esp_service_t *)cloud); // 经队列发 DESTROY 命令 cloud_service_destroy(cloud);从 cloud_service.h 和 cloud_service.c 可以看到完整的命令协议内部队列消息cloud_task_msg_t由cloud_cmd_t枚举驱动包括CLOUD_CMD_CONNECT / CONNECTED / DISCONNECT / SEND_DATA / RECV_DATA / RECONNECT / DESTROY。关键实现细节cloud_on_start()按配置创建名为cloud_svc的任务SMP 内核用xTaskCreatePinnedToCore支持指定核cloud_service_connect()/disconnect()/reconnect()都是非阻塞的——仅xQueueSend一条命令后立即返回结果通过CLOUD_EVT_CONNECTED / DISCONNECTED / ERROR事件通知cloud_service_send_data()是同步的——入队前先xEventGroupClearBits(CLOUD_EVT_BIT_SENT)入队后xEventGroupWaitBits阻塞等待任务处理完成超时上限为CLOUD_SYNC_TIMEOUT_MS2000 ms超时返回ESP_FAILcloud_on_stop()向队首发送CLOUD_CMD_DESTROYxQueueSendToFront并等待CLOUD_EVT_BIT_STOPPED确认任务退出。cloud_service_cfg_t及其默认值CLOUD_SERVICE_CFG_DEFAULT()字段含义默认值endpoint云服务器端点 URLNULLdevice_id设备标识NULLtask_stack服务任务栈大小字节4096task_prio服务任务优先级5task_core任务绑核-1 任意0/1 指定核-1queue_size命令队列深度8ASYNC vs SYNC 对比冒烟测试最后调用compare_async_sync()在日志中直接打印两种模式的对照表这也是理解三种模式差异的最直观入口维度ASYNCplayer_serviceSYNCled_servicetask.stack_size 0如 40960是否创建任务是否是否创建队列是否API 行为非阻塞阻塞状态变更发生地任务上下文调用者上下文适用场景长操作、事件驱动简单、即时操作QUEUE 模式cloud_service可以理解为“任务 命令队列 事件组同步”的完整形态既有 ASYNC 的后台任务与事件发布又通过事件组为特定 API如send_data提供同步确认语义。订阅STATE_CHANGED事件event hub 的用法三个冒烟测试在创建服务后都会调用subscribe_state_events()这是示例演示的另一个核心技能——通过 event hub 订阅服务状态变化static void on_state_changed(const adf_event_t *event, void *ctx) { if (event-event_id ! ESP_SERVICE_EVENT_STATE_CHANGED || event-payload NULL || event-payload_len sizeof(esp_service_state_changed_payload_t)) { return; } const esp_service_state_changed_payload_t *p event-payload; const char *old_str ?, *new_str ?; esp_service_state_to_str(p-old_state, old_str); esp_service_state_to_str(p-new_state, new_str); ESP_LOGI(TAG, [%s] State: %s - %s, event-domain, old_str, new_str); } static void subscribe_state_events(esp_service_t *service) { adf_event_subscribe_info_t info ADF_EVENT_SUBSCRIBE_INFO_DEFAULT(); info.event_id ESP_SERVICE_EVENT_STATE_CHANGED; info.handler on_state_changed; esp_service_event_subscribe(service, info); }几个值得记住的细节ESP_SERVICE_EVENT_STATE_CHANGED的定义是UINT16_MAX - 1见 esp_service.h基类在每次成功的状态迁移时自动发布payload 为esp_service_state_changed_payload_t含old_state/new_state。派生服务的域事件 ID 从 1 开始UINT16_MAX通配与UINT16_MAX - 1被保留严禁使用订阅通过esp_service_event_subscribe()包装adf_event_hub_subscribe()完成info中至少需要设置投递目标target_queue或handler之一状态迁移的发布实现位于 esp_service.c 的publish_core_state_changed_event()它 malloc 一个 payload通过adf_event_hub_publish()发布并挂载free_event_payload作为释放回调——payload 所有权在发布后即转移调用方不得再访问。构建、烧录与自动验证命令行构建示例根目录为 components/esp_service/examples/mock_services/按 README 的标准流程cd components/esp_service/examples/mock_services idf.py set-target esp32s3 idf.py build flash monitor开机后在串口监视器中应当看到ALL SMOKE TESTS PASSED横幅main.c 在failures 0时打印失败则打印N SMOKE TEST(S) FAILED。工程配置要点CMakeLists.txt 通过EXTRA_COMPONENT_DIRS同时挂载了两条组件搜索路径这是示例能直接复用esp_service框架与六个 mock 组件的关键set(EXTRA_COMPONENT_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/../../ # esp_service 组件本身 ${CMAKE_CURRENT_SOURCE_DIR}/components # 六个 mock 服务 ) include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(mock_services_example)sdkconfig.defaults 中与示例直接相关的默认配置包括CONFIG_ESP_MAIN_TASK_STACK_SIZE4096 # 主任务栈 4 KB CONFIG_FREERTOS_HZ1000 # 系统时钟节拍 1000 Hz CONFIG_ESP_CONSOLE_UART_DEFAULTy # 控制台走 UART0CP2102 / ttyUSB0CI 自动验证仓库为示例配套了 pytest 用例 pytest_esp_service_mock_services.py它把“启动 → 看到冒烟测试横幅 → 看到全部通过”固化为可重复的自动化断言pytest.mark.generic idf_parametrize(target, [esp32s3], indirect[target]) def test_esp_service_mock_services_example(dut: IdfDut) - None: dut.expect(rMock Services Example, timeout60) dut.expect(rALL SMOKE TESTS PASSED, timeout120)这从测试层面印证了示例的定位这是一个开机即自检、无需任何外部依赖无 Wi-Fi、无传输层的冒烟工程。传输层演示去哪了README 专门用一节回答这个问题凡是真正“锤炼”esp_service框架本身的内容——service manager、MCP JSON-RPC、HTTP / SSE / WebSocket / UART 传输、Python MCP 客户端、PC 端多服务模拟——都已迁移到components/esp_service/test_apps/设备端传输测试test_apps/main/如ut_http_transport.c、ut_ws_transport.c、ut_sse_transport.c、ut_uart_transport.c、ut_service_manager.c等PC 端场景test_apps/pc/service_sim_example.c、service_tool_dispatch_test.c、service_integration_test.c、service_coverage_test.c、mcp_http_server.c等Python MCP 客户端test_apps/scripts/test_mcp_http.py、test_mcp_sse.py、test_mcp_ws.py、test_mcp_uart.py分别对应设备端各传输协议的冒烟/保活测试。这些内容的运行方式统一见 test_apps/README.md。相关文档导航组件总览components/esp_service/README.md含esp_service_t状态机、service manager、MCP 服务器、六种传输层的完整说明生产风格示例真实 Wi-Fi OTA CLI 按键服务位于adf_examples/下adf_examples/services_hub/README.md六个 mock 服务各自头文件中的事件枚举与 payload 定义可作为派生自己服务的“模板”wifi_service.h、ota_service.h、button_service.h若想了解 MCP 工具描述xxx_service_mcp.json与 LLM/AI Agent 桥接的完整用法可结合 esp_service/tools/mcp_llm_bridge/ 与 test_apps/README.md 继续深入。小结从示例到自己的服务Mock Services 示例的参考价值在于它把“如何正确使用esp_service_t”压缩到了最小可运行单元。复刻它的经验可以总结为四条先选模式长操作选 ASYNC、即时操作选 SYNC、复杂命令流选 QUEUE嵌入基类esp_service_t必须作为派生结构体第一个成员填充 vtable实现所需的on_*回调不需要的留 NULL接入事件通过esp_service_publish_event()发域事件、esp_service_event_subscribe()收STATE_CHANGED与自定义事件。在此基础上即可像adf_examples/services_hub那样把 mock 服务逐步替换为真实实现接入 service manager 与 MCP 传输最终交给 LLM/AI Agent 直接调用。赞分享音视频音频处理嵌入式物联网语音【免费下载链接】esp-adfEspressif Advanced Development Framework for Multimedia Applications项目地址https://gitcode.com/gh_mirrors/es/esp-adf点击查看免费下载相关推荐esp-adf services_hub 示例实战基于 esp_service 的多服务集成与 HTTP OTA 升级全流程esp adf services_hub 示例实战基于 esp_service 的多服务集成与 HTTP OTA 升级全流程 导读 services_hub音视频音频处理嵌入式物联网语音ESP IoT Solution 实战基于 esp-iot-solution 的 BLE 健康温度计服务HTSGATT 服务端示例详解ESP IoT Solution 实战基于 esp iot solution 的 BLE 健康温度计服务HTSGATT 服务端示例详解 导读 本指南围绕物联网嵌入式驱动开发硬件开发esp-adf 的 adf_examples 示例集合从 CHANGELOG 看 ESP32 多媒体示例生态的演进与实战esp adf 的 adf_examples 示例集合从 CHANGELOG 看 ESP32 多媒体示例生态的演进与实战 本文基于 esp adfEspre音视频音频处理嵌入式物联网语音上一篇Symfony Notifier Sipgate 桥接DSN 配置、ssl 选项与底层实现解析下一篇Codenotch 开发踩坑实录SQLite WAL 只读陷阱、Keychain 签名身份、429 退避持久化等 6 个硬核问题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考