# example_app_server **Repository Path**: xu_415/example_app_server ## Basic Information - **Project Name**: example_app_server - **Description**: 演示lbkit服务端组件实现 - **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**: 2025-04-20 ## 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/toolkit/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")) ``` ### 编译脚本适配 组件需要查询查找被依赖组件的头文件和库文件时,可以在`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 ```