# lb_example_app **Repository Path**: litebmc/lb_example_app ## Basic Information - **Project Name**: lb_example_app - **Description**: 演示litebmc应用实现 - **Primary Language**: Unknown - **License**: LGPL-2.1 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2025-04-03 - **Last Updated**: 2026-07-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## lb_example_app示例代码说明 示例代码存储在 [gitee.com](https://gitee.com/litebmc/lb_example_app.git),组件演示了`com.litebmc.Test`接口的服务端功能,同时`test_package`目录下`test_service.c`演示了`com.litebmc.Test`接口的客户端代码,分别实现的功能包括: 1. 服务端:`src/service.c`演示了`com.litebmc.Test`接口的方法回调注册、属性查询设置、属性变更前后事件、信号发送代码等代码,将构建出`.temp/rootfs/opt/litebmc/apps/lb_example_app/lb_example_app`可执行程序。 2. 客户端:`test_package/test_service.c`演示了`com.litebmc.Test`接口的方法调用、属性查询设置、信号监听等代码,同时还演示了调用`lb_subscribe_interface("com.litebmc.Test");`方法借助`lb_interface_manager`订阅`com.litebmc.Test`接口的实例化对象功能,该功能将在监听到总线上`com.litebmc.Test`接口实例化新对象时自动创建本地对象。`test_service.c`将构建出`.temp/rootfs/opt/litebmc/apps/lb_example_app/lb_example_app_test`可执行程序。 开发者可以执行`lbk test`命令,该命令将拉起`lb_example_app`进程后,调用`lb_example_app_test`测试进程,通过调用dbus接口完成服务端功能验证(和演示)。 lb_example_app支持开发环境、仿真环境和真机环境三种方法运行: 1. 参考[安装工具](https://litebmc.com/docs/#/docs/lbkit/readme)文档在你的linux开发主机上运行示例代码,参考工具安装和系统初始化章节,完成环境准备,之后,需要执行命令运行lb_example_app服务端程序: ```bash git clone https://gitee.com/litebmc/lb_example_app.git lbk test -e ``` 如果通过busctl访问会话总线,需要首先设置环境变量,执行命令: ```bash eval `cat /dev/shm/session-dbus` ``` 2. 参考[快速开始](https://litebmc.com/docs/#/quick_start)创建仿真镜像,直接登录或ssh登录仿真环境,系统自动在dbus的系统总线(system)和会话总线(session)各启动了一个`lb_example_app`服务。`busctl`命令指定`--user`参数时测试会话总线上的服务,`busctl`命令不指定`--user`参数时测试系统总线上的服务。 3. 参考[快速开始](https://litebmc.com/docs/#/quick_start)将镜像烧录到`luckfox lyra plus B`开发板,实用方法与方法2相同。 lb_example_app的代码主要有dbus接口申明、组件依赖管理、编译脚本适配、对象实例化、属性值读写、属性值变更事件、方法(method)实现、信号(signal)发送组成。 ### com.litebmc.Test接口申明 com.litebmc.Test接口由 [interfaces](https://gitee.com/litebmc/interfaces/tree/master/dbus)仓定义,该目录存储了所有litebmc支持的dbus接口定义。 在interfaces仓的dbus目录下执行`./build.py -i lbTest.yaml`会自动生成以下3个conan包: 1. `lbTest-public/1.0.1`:组件公共代码,提供了错误消息、对象原型、请求响应申明及编解码函数、方法申明、插件注册调用、信号申明等代码。 2. `lbTest-server/1.0.1`:服务端代码,用于管理服务端接口和对象操作,包括属性设置、信号发送、对象创建删除事件、在位状态管理、私有数据绑定、属性变更回调、对象创建删除事件、对象查询、锁定解锁、对象列表查询、属性变更回调等代码。 2. `lbTest-client/1.0.1`:客户端代码,用于实现客户端接口和对象操作,包括属性查询设置、方法调用、信号订阅、对象创建删除事件、在位状态管理、私有数据绑定、属性变更回调、对象创建删除事件、对象查询、锁定解锁、对象列表查询、属性变更回调等代码。 ?> 文件名中的`lbTest`就是类名,之后在C代码中操作对象的属性、方法、信号等函数、变更或类型都将以`lbTest_`开头。 ### 组件依赖管理 服务端主要是实例化对象,基于实例化的对象实现方法回调、信号发送,按需关注属性的变更事件,为此我们需要在组件的`metadata/package.yml`配置文件的`requres.compile`配置项下新增`- conan: lbTest-server/[>=1.0.0 <2.0.0]`,该配置会在构建时的依赖注入到组件构建代码中。 metadata/package.yml配置示例: ```yaml requires: compile: - conan: lbTest-server/[>=1.0.0 <2.0.0] - conan: lb_core/[>=0.6.0 <0.7.0] test: - conan: lbTest-client/[>=1.0.0 <2.0.0] ``` `requires`用于定义依赖,`compile`是编译时依赖,`test`是开发者(DT: DeveloperTest)测试的依赖。 conan组件的`package_info`方法(见conanfile.py)可以配置`pkg-config`元数据,供依赖方查询组件的头文件、库等信息。 `lbTest-public`的`package_info`示例: ```python def package_info(self): self.cpp_info.includedirs = ["usr/include"] self.cpp_info.libdirs = ["usr/lib"] self.cpp_info.libs = [self.name] self.cpp_info.set_property("cmake_find_mode", "both") self.cpp_info.set_property("cmake_target_name", "lbTest-public:lbTest-public") self.cpp_info.set_property("pkg_config_name", "lbTest-public") self.runenv_info.define("LD_LIBRARY_PATH", os.path.join(self.package_folder, "lib")) ``` `lbTest-server`的`package_info`示例: ```python def package_info(self): self.cpp_info.includedirs = ["usr/include"] self.cpp_info.libdirs = ["usr/lib"] self.cpp_info.libs = [self.name] self.cpp_info.set_property("cmake_find_mode", "both") self.cpp_info.set_property("cmake_target_name", "lbTest-server:lbTest-server") self.cpp_info.set_property("pkg_config_name", "lbTest-server") self.runenv_info.define("LD_LIBRARY_PATH", os.path.join(self.package_folder, "lib")) ``` `lbTest-client`的`package_info`示例: ```python def package_info(self): self.cpp_info.includedirs = ["usr/include"] self.cpp_info.libdirs = ["usr/lib"] self.cpp_info.libs = [self.name] self.cpp_info.set_property("cmake_find_mode", "both") self.cpp_info.set_property("cmake_target_name", "lbTest-client:lbTest-client") self.cpp_info.set_property("pkg_config_name", "lbTest-client") self.runenv_info.define("LD_LIBRARY_PATH", os.path.join(self.package_folder, "lib")) ``` ### 组件特性管理 litebmc 组件通过 `metadata/package.yml` 的 `options` 字段声明可配置的编译期特性(如 DBus 总线类型、测试开关等),使用 `/lb-component-option-mgmt` 技能统一管理。 **特性声明(metadata/package.yml):** ```yaml options: enable_dbus_system: description: 是否是DBus系统总线运行 option: - true - false default: false enable_dbus_session: description: 是否是DBus会话总线运行 option: - true - false default: true ``` `option` 列表定义允许的值,支持 bool(`true`/`false`)、字符串、整数、浮点数。`default` 指定默认值。 **数据流:** `lbk gen`/`lbk build` 读取 `package.yml` → conanbase.mako 模板生成 `conanfile.py` → `CMakeToolchain` 将 option 转为 cmake 变量和 C 预处理器宏。 每条 option 自动生成 4 个符号(以 `enable_dbus_session` 为例): | 符号 | 用途 | |------|------| | cmake 变量 `BUILD_ENABLE_DBUS_SESSION` | CMakeLists.txt 中 `if(BUILD_ENABLE_DBUS_SESSION)` 条件判断 | | cmake 变量 `LB_ENABLE_DBUS_SESSION` | 备用别名 | | C 宏 `BUILD_ENABLE_DBUS_SESSION` | 源码中 `#ifdef BUILD_ENABLE_DBUS_SESSION` 条件编译 | | C 宏 `LB_ENABLE_DBUS_SESSION` | 备用别名 | **CMakeLists.txt 中使用:** ```cmake option(BUILD_ENABLE_DBUS_SYSTEM "Run with DBus system bus" OFF) option(BUILD_ENABLE_DBUS_SESSION "Run with DBus session bus" ON) if (BUILD_ENABLE_DBUS_SESSION) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink /usr/lib/systemd/system/${PROJECT_NAME}@.service ${CMAKE_INSTALL_PREFIX}/etc/systemd/system/multi-user.target.wants/${PROJECT_NAME}@session.service)") endif() ``` `option()` 命令仅在 cmake 变量未定义时设置回退默认值——conan 已传入时不会覆盖。 **用户控制特性:** ```bash # 构建时关闭 DBus 系统总线 lbk build -o *:enable_dbus_system=false # 测试时自定义 option lbk test -o *:enable_dbus_session=false ``` **使用 `/lb-component-option-mgmt` 管理特性:** 1. 在对话中输入 `/lb-component-option-mgmt` 加载技能 2. 按提示描述需要新增、修改或删除的特性(如"新增 enable_dbus_session,默认 true") 3. 技能自动修改 `metadata/package.yml` 和 `CMakeLists.txt`,保持两处默认值一致 4. 执行 `lbk build` 验证,conanbase.mako 自动将新 option 转换为 cmake 变量 **产品中传递 option:** 当组件作为依赖被产品集成时,产品可在 `manifest.yml` 的 `dependencies.options` 中传递 option 值,覆盖组件默认值。 Schema 定义(来自 pdf.v1.json): ```json "options": { "description": "conan包的option,用于使能或配置特性", "type": "object", "patternProperties": { "^[a-zA-Z][a-zA-Z0-9_-]*$": { "type": ["string", "number", "boolean"] } } } ``` > **依赖优先级:** options 遵守"产品依赖优先调试依赖,调试依赖优于公共依赖"的优先级规则。 产品 manifest.yml 示例: ```yaml # manifest.yml dependencies: - package: lb_example_app/[>=0.0.2] options: enable_dbus_system: false # bool 类型,覆盖组件默认值 true enable_dbus_session: true - package: some_library/[>=1.0.0] options: log_level: "debug" # 字符串类型 max_connections: 100 # 整数类型 ``` **完整链路:** ``` 产品 manifest.yml 组件 package.yml lbk build cmake/C dependencies: options: ┌──────────────┐ ┌─────────────┐ - package: lb_example_app enable_dbus_system: │ conan install │ │ CMakeLists: │ options: ──────> default: false ──────>│ -o *:option │───>│ if(BUILD_) │ enable_dbus_system: false enable_dbus_session: │ =value │ │ #ifdef │ default: true └──────────────┘ └─────────────┘ ``` 产品侧的 `options` 值通过 conan 依赖解析时注入,优先级高于组件 `package.yml` 中的 `default`,实现产品级特性开关控制。 ### 编译脚本适配 组件需要查询查找被依赖组件的头文件和库文件时,可以在`CMakeLists.txt`文调用cmake的[pkg_check_modules](https://cmake.org/cmake/help/latest/module/FindPkgConfig.html)方法,示例: ```cmake find_package(PkgConfig REQUIRED) # ... others pkg_check_modules(TEST_PUB REQUIRED lbTest-public) pkg_check_modules(TEST_SRV REQUIRED lbTest-server) # 增加头文件查找目录 target_include_directories(${PROJECT_NAME} PUBLIC # ... others ${TEST_PUB_INCLUDE_DIRS} ${TEST_SRV_INCLUDE_DIRS} ) # 链接库 target_link_libraries(${PROJECT_NAME} PUBLIC # ... others ${TEST_PUB_LIBRARIES} ${TEST_SRV_LIBRARIES} ) # 链接库查找目录 target_link_directories(${PROJECT_NAME} PUBLIC # ... others ${TEST_PUB_LIBRARY_DIRS} ${TEST_SRV_LIBRARY_DIRS} ) ``` ### 对象实例化 服务端对象实例化有两种方式,一种是基于odf文件的自动加载模式,一种是业务代码实现的自定义模式。 自定义模式中,只需要调用以下代码即可创建对象: ```c void lb_example_app_start(void) { // ... others lbTest t = lbTest_new("/com/litebmc/test", NULL); lbTest_present_set(t, TRUE); // ... others } ``` 上述代码中: 1. `lbTest_new`:由自动生成代码工具生成的对象创建函数,由`lbTest-server`组件提供。函数的第一个参数是对象名,类型为`object_path`,第二个参数是一个boolean型指针,用于获取对象是否已经存在。 2. `lbTest_present_set`: 自动生成的对象在位状态操作函数,默认创建时对象不在位,无法在dbus总线上看到对象,当设置为TRUE后可以在dbus总线上看到对象。 #### 在dbus中查询对象信息 可以使用busctl命令查看dbus树以及对象详细信息,busctl操作示例: ```bash busctl --user tree com.litebmc.lb_example_app # 输出 # └─/com # └─/com/litebmc # └─/com/litebmc/test # 查看对象详细信息 busctl --user introspect com.litebmc.lb_example_app /com/litebmc/test ``` ### 属性值读写 litebmc的所有对象都是一个const的结构体指针,读取属性值只需要按指针引用属性即可,如果需要设置属性值,则需要调用`server`组件提供的`[alias]_set_[prop]`函数,如`lbTest_set_y`函数。 属性读写示例: ```c static void _test_read_write(lbTest obj, guint8 val) { if (obj->b) { lbTest_set_y(obj, val); } } ``` 上述代码中: 1. `obj->b`查询属性b的值。 2. `lbTest_set_y(obj, val)`设置属性y的值,范式为`[alias]_set_[name]`,`alias`是接口别名,`name`是属性名。 为什么必须调用`lbTest_set_y`设置属性值,而不是`obj->y = val`?dbus对象不只是记录属性值,还需要按需对外广播属性值变更事件`PropertiesChanged`。`lbTest_set_y`提供了捕获属性值事件变更的机会,为**迫使**开发者正确设置属性值,litebmc将对象句柄定义为const类型的对象指针。 ### 属性值变更事件 服务端可以配置属性变更前和变更后的事件钩子函数(可以设置多个钩子),并在事件发生时自动调用钓子。 * 变更前:一般用于条件检查,当钓子函数返回非0值表示检查失败,可以通过error返回错误消息;返回0表示允许设置。 * 变更后:一般用于记录日志或触发其它事件,无返回值。 示例: ```c // 变更后事件,prop是属性信息,value是属性值,user_data是注册时提供的指针 static void _y_changed_after(lbTest obj, const LBProperty *prop, GVariant *value, gpointer user_data) { // 变更后回调,记录事件 log_info("Property y changed, new_value: %u", g_variant_get_byte(value)); } // 变更前事件,prop是属性信息,value是属性值,user_data是注册时提供的指针,error是异常时返回的错误消息 static gint _y_change_before(lbTest obj, const LBProperty *prop, GVariant *value, gpointer user_data, GError **error) { guint8 new_value = g_variant_get_byte(value); if (new_value & 1) { // 假设不允许设置奇数,当判断为奇数时返回非0 *error = lbTest_Error_PropertyYChangedFailed_new(new_value); return -1; } return 0; } // 服务启动函数 void lb_example_app_start(void) { // ... others lbTest_y_hook(_y_change_before, _y_changed_after, NULL); } ``` 在接口描述中,属性y的flags描述`flags: readwrite`,表示可以读写,以下是busctl操作示例: ```bash # 查询属性y的值,在Test.yaml中描述默认值为123,在未做任何变更的情况下下面命令返回的值也是123,命令行打印:y 123 busctl --user get-property com.litebmc.lb_example_app /com/litebmc/test com.litebmc.Test y # 将y设置为124,是偶数,可以设置,执行成功 busctl --user set-property com.litebmc.lb_example_app /com/litebmc/test com.litebmc.Test y y 124 # 查询属性y的值,命令行打印:y 124 busctl --user get-property com.litebmc.lb_example_app /com/litebmc/test com.litebmc.Test y # 将y设置为123,是奇数,不可以设置,返回`PropertyYChangedFailed`错误,命令行打印:Failed to set property y on interface com.litebmc.Test: The value of property Y must be an even number, get 123 # 如果钓子函数返回非零且未设置error时,命令行打印示例:`Failed to set property y on interface com.litebmc.Test: Property /com/litebmc/test.y set failed because before_check return -1` busctl --user set-property com.litebmc.lb_example_app /com/litebmc/test com.litebmc.Test y y 123 # 查询属性y的值,因为设置失败,y的值依然是124,命令行打印:y 124 busctl --user get-property com.litebmc.lb_example_app /com/litebmc/test com.litebmc.Test y ``` * 上述示例的y的默认值是奇数,说明系统在设置y的默认值时不会调用change_before钓子函数。 ### 方法(method)实现 有两种方法实现接口的方法。 方法一是查询`lbTest_methods`获取接口的方法结构体并设置方法的回调句柄,示例: ```c static int _set_bool(lbTest obj, const lbTest_SetBool_Req *req, lbTest_SetBool_Rsp **rsp, GError **error, LBMethodExtData *ext_data) { *rsp = g_new0(lbTest_SetBool_Rsp, 1); (*rsp)->out = req->in; (*rsp)->n_out_array = req->n_in_array; if (req->n_in_array) { (*rsp)->out_array = g_new0(gboolean, req->n_in_array + 1); memcpy((*rsp)->out_array, req->in_array, sizeof(gboolean) * req->n_in_array); } lbTest_set_b(obj, req->in); return 0; } void lb_example_app_start(void) { // 方法回调设置方案一:设置方法回调 lbTest_Methods *methods = lbTest_methods(); methods->SetBool.handler = _set_bool; // ... others } ``` 方法二是因为系统为每个方法都定义了一个弱符号(请了解`__attribute__((__weak__))`)的默认实现,以下是自动生成的代码示例: ```c /* 方法lbTest_SetByte的默认实现 */ __weak int lbTest_SetByte(lbTest obj, const lbTest_SetByte_Req *req, lbTest_SetByte_Rsp **rsp, GError **error, LBMethodExtData *ext_data) { *error = LbError_MethodNotImplament_new("SetByte"); return -1; } ``` * 注:函数的原型定义在public组件的头文件中。 弱符号函数可以被覆盖,因此,在业务代码实现中只需要定义一个固定的名为lbTest_SetByte的函数,示例: ```c // 方法回调设置方案二:实现名为`_`的非static的函数 int lbTest_SetByte(lbTest obj, const lbTest_SetByte_Req *req, lbTest_SetByte_Rsp **rsp, GError **error, LBMethodExtData *ext_data) { // 当b为True时返回属性被锁定错误 if (obj->b) { *error = lbTest_Error_PropertyYIsLocked_new(); return -1; } // 输入值转换成输出值 *rsp = g_new0(lbTest_SetByte_Rsp, 1); (*rsp)->out = req->in; (*rsp)->n_out_array = req->n_in_array; if (req->n_in_array) { (*rsp)->out_array = g_new0(guint8, req->n_in_array + 1); memcpy((*rsp)->out_array, req->in_array, sizeof(guint8) * req->n_in_array); } return 0; } ``` busctl操作示例: ```bash busctl --user call com.litebmc.lb_example_app /com/litebmc/test com.litebmc.Test SetBool bab true 2 true false # 在_set_bool回调函数中实际是原样返回,因此返回值为: bab true 2 true false ``` ### 信号(signal)发送 属性对外发送的信号由lb_core处理,开发者在idf文件的属性flgas中申明的`const`、`emits_false`、`emits_invalidation`会影响属性变更时发送的信号,具体定义请参考官方文档: [org.freedesktop.DBus.Property.EmitsChangedSignal](https://dbus.freedesktop.org/doc/dbus-specification.html)。 在《属性值变更事件》章节中,y属性未申明EmitsChangedSignal标记,因此当属性值变更时会发送`PropertiesChanged`信号,`dbus-monitor --session sender='com.litebmc.lb_example_app',type='signal',member='PropertiesChanged'`抓取的信号示例: ``` signal time=1743608873.230032 sender=:1.391 -> destination=(null destination) serial=253 path=/com/litebmc/test; interface=org.freedesktop.DBus.Properties; member=PropertiesChanged string "com.litebmc.Test" array [ dict entry( string "y" variant byte 128 ) ] array [ ] ``` 开发者可以在idf中申明需要发送的信号,代码自动生成工具会在server组件中生成信号发送函数,如idf文件中申明以下信号: ```yaml signals: - name: TestSignalA description: 测试信号A flags: deprecated properties: - name: count description: 发送一个计数 type: uint32 ``` `server`组件会生成`lbTest_Emit_TestSignalA`信号发送函数,开发者调用发送函数即可,代码示例: ```c static gboolean _send_signal_a(gpointer user_data) { static guint count = 0; lbTest obj = lbTest_get("/com/litebmc/test"); if (!obj) { return FALSE; } lbTest_TestSignalA_Msg msg = { .count = count++ }; return lbTest_Emit_TestSignalA(obj, NULL, &msg, NULL); } ``` 使用`dbus-monitor --session sender='com.litebmc.lb_example_app',type='signal',member='TestSignalA'`监控总线,信号示例: ``` signal time=1743609147.376633 sender=:1.419 -> destination=(null destination) serial=158 path=/com/litebmc/test; interface=com.litebmc.Test; member=TestSignalA uint32 2 ```