开发示例
本章节指导开发者根据add_extend_driver_adapter工程示例适配OM SDK的接口。在实际操作本章节前,开发人员应已经仔细阅读模组开发章节,熟悉产品规格、模组规格、接口功能等信息。
操作步骤
- 进入工程源码“{project_dir}/src/app/add_extend_driver_adapter”路径下。add_extend_driver_adapter的目录结构如下所示。
├── build_extend_driver_adapter.sh // 驱动构建脚本 ├── validate_module_def.py // 配置文件校验脚本 ├── CMakeLists.txt // CMake构建文件 ├── demo_module.c // 模组适配源文件 └── demo_module.h // 模组适配头文件
- 在规格配置文件中定义模组和设备。
- 从om-sdk.tar.gz中“software/ibma/config/devm_configs”下拷贝所有已有配置文件到“{project_dir}/config/module_def”目录下。
- 在“{project_dir}/config/module_def”目录下新建模组规格配置文件module_demo.json,根据关键字段说明章节定义出关键字段。
- (可选)在“{project_dir}/config/module_def”路径下,打开product_specification.json文件,在文件的设备列表中添加属于拓展模组的设备定义。(如果模组的设备属于动态插拔类型则无需在配置文件中静态定义)。
- 使用配置文件检查脚本validate_module_def.py对拓展后的配置文件集进行检查,验证产品规格配置文件和模组规格配置文件的正确性。
cd src/app/add_extend_driver_adapter python3 validate_module_def.py ../../../config/module_def
validate_module_def.py脚本内容如下:import json import os import sys PRODUCT_NAME = 'product_specification.json' MAX_ATTRIBUTE_LEVEL = 2 MAX_ATTRIBUTE_NUMBER = 50 MODULE_CATEGORY = ('internal', 'extend', 'addition') ATTRIBUTE_VALUE_TYPE = ('bool', 'float', 'int', 'json', 'long long', 'string') ATTRIBUTE_ACCESS_MODE = ('Read', 'Write', 'ReadWrite','ReadHide', 'WriteHide', 'ReadWriteHide') def check(config_file_dir: str): """ :param config_file_dir: :return: None Check the correctness of all the configuration files in the directory """ for filename in os.listdir(config_file_dir): if not filename.endswith('json'): continue with open(os.path.join(config_file_dir, filename)) as stream: document = json.load(stream) if filename == PRODUCT_NAME: check_product(document) elif filename.startswith('module'): check_module(document) else: raise AssertionError(f'Unknown file: "{filename}"') print(f'The collection of configurations in {config_file_dir} is correct. ') def check_product(doc: dict) -> int: """ :param doc: product specification :return: total number of device """ assert isinstance(doc, dict), 'product specification file structure error: outermost layer should be a map' modules = doc.get('modules') if modules: assert isinstance(modules, dict), 'modules should be a map' for module, module_spec in modules.items(): assert isinstance(module, str), 'key in modules should be a string' assert isinstance(module_spec, dict), 'value in modules should be a dict' devices = module_spec.get('devices') if devices is None: raise AssertionError(f'devices not exist in module {module}') else: assert isinstance(devices, list), 'devices in module spec should be a list' for device in devices: assert isinstance(device, str), 'device should be a string' return len(modules) else: raise AssertionError('no modules found in product specification') def check_module(doc: dict) -> str: """ :param doc: module specification :return: module name """ assert isinstance(doc, dict), 'module specification file structure error: outermost layer should be a map' module_name = doc.get('name') assert module_name is not None, f'no name in {doc}' assert isinstance(module_name, str), 'module name should be a string' _id = doc.get('id') assert _id is not None, f'no id in module {module_name}' assert isinstance(_id, int), 'module id should be an integer' category = doc.get('category') if category: assert isinstance(category, str), 'module category should be a string' assert category in MODULE_CATEGORY, f'invalid category: {category}' else: raise AssertionError(f'no category found in module {module_name}') driver = doc.get('driver') assert driver is not None, f'no driver found in module {module_name}' assert isinstance(driver, str), 'module driver should be a string' dynamic = doc.get('dynamic') assert dynamic is not None, f'no dynamic found in module {module_name}' assert isinstance(dynamic, bool), 'dynamic should be a boolean value' attributes = doc.get('attributes') if attributes: check_attributes(attributes) else: raise AssertionError(f'no attributes found in module {module_name}') return module_name def check_attributes(attributes: dict, level=1): """ :param attributes: a map whose keys are attribute names and values are attribute specifications :param level: the level of recursive subAttribute :return: None """ assert isinstance(attributes, dict), 'module specification file structure error: attributes should be a map' assert len(attributes) <= MAX_ATTRIBUTE_NUMBER, f'number of attributes exceeds limit ({MAX_ATTRIBUTE_NUMBER})' assert level <= MAX_ATTRIBUTE_LEVEL, 'recursive subAttributes level exceeds limit' for attr_name, doc in attributes.items(): _id = doc.get('id') assert _id is not None, f'no id in attribute {attr_name}' assert isinstance(_id, int), 'attribute id should be an integer' value_type = doc.get('type') if value_type: assert isinstance(value_type, str), 'attribute value type should be a string' assert value_type in ATTRIBUTE_VALUE_TYPE, f'invalid value type {value_type}' else: raise AssertionError(f'no type in attribute {attr_name}') access_mode = doc.get('accessMode') if access_mode: assert isinstance(access_mode, str), 'attribute access mode should be a string' assert access_mode in ATTRIBUTE_ACCESS_MODE, f'invalid access mode {access_mode}' else: raise AssertionError(f'no accessMode in attribute {attr_name}') attributes = doc.get('subAttributes') if attributes: check_attributes(attributes, level=level + 1) if __name__ == '__main__': if len(sys.argv) == 2: check(sys.argv[1]) else: print('The input argument should be the directory of the configuration files')
检查内容包括:- 配置文件结构是否正确。
- 关键字段是否有缺失。
- 枚举类型字段是否属于正确类型的一种。
- 安全性检查。
- 在demo_module.h定义对外接口以及接口关键字段。
#ifndef __DEMO_MODULE_H__ #define __DEMO_MODULE_H__ #define DEVICE_MAP_MAX_LEN 2 /* 保存模组设备的最大个数 */ #define DEVICE_INFO_MAX_LEN 64 /* 保存模组信息的最大长度 */ #define TLV_HEADER_LENGTH 8 /* 定义属性ID类型 0:基础通用属性 1:模组特有属性 */ #define BASE_CLASS_ID 0x00 #define MODULE_CLASS_ID 0x01 /* 定义模组管理的属性 */ typedef enum demo_attribute_id { NAME = ((BASE_CLASS_ID) << 16) + 1, /* 名称 string */ CLASS = ((BASE_CLASS_ID) << 16) + 2, /* 类型 string */ PRESENT = ((MODULE_CLASS_ID) << 16) + 1, /* 状态 int */ TEMPERATURE = ((MODULE_CLASS_ID) << 16) + 2, /* 温度 float */ VOLTAGE = ((MODULE_CLASS_ID) << 16) + 3, /* 电压 float */ SWITCH = ((MODULE_CLASS_ID) << 16) + 4, /* 开关 bool */ MEMORY = ((MODULE_CLASS_ID) << 16) + 5, /* 内存 long long */ VERSION = ((MODULE_CLASS_ID) << 16) + 6, /* 版本 string */ SIGNAL_INFO = ((MODULE_CLASS_ID) << 16) + 7 /* 信号信息 json */ } DEMO_ATTRIBUTE_ID; /* 定义json属性中的子属性ID */ typedef enum sub_attribute_id { SUB_ATTRIBUTE1 = 1, SUB_ATTRIBUTE2 = 2 } SUB_ATTRIBUTE_ID; /* 定义模拟json属性的结构体 */ typedef struct demo_signal_info { char signal_type[DEVICE_INFO_MAX_LEN]; /* 类型 string */ char signal_strength[DEVICE_INFO_MAX_LEN]; /* 强度 string */ } DEMO_SIGNAL_INFO; /* 定义保存的设备 */ typedef struct demo_device_map { char name[DEVICE_INFO_MAX_LEN]; int fd; } DEMO_DEVICE_MAP; /* 定义设备各属性,用于模拟设备属性的获取和设置 */ typedef struct device { char name[DEVICE_INFO_MAX_LEN]; char device_class[DEVICE_INFO_MAX_LEN]; int present; float temperature; float voltage; int device_switch; long long memory; char version[DEVICE_INFO_MAX_LEN]; DEMO_SIGNAL_INFO* signal_info; } DEVICE; /* 定义保存的模组列表 */ typedef struct demo_device_ctl { DEMO_DEVICE_MAP device_map[DEVICE_MAP_MAX_LEN]; } DEMO_DEVICE_CTRL; /* 定义TLV解析结构体 */ typedef struct attr_tlv_struct { int type; int len; char value[0]; } ATTR_TLV; /* 定义驱动提供的对外接口 */ int dev_load(); int dev_unload(); int dev_open(char *name, int *fd); int dev_close(int fd); int get_attribute(int fd, unsigned int buffSize, unsigned char *buff); int set_attribute(int fd, unsigned int buffSize, unsigned char *buff); int dev_read(int fd, unsigned int buffSize, unsigned char *buff); int dev_write(int fd, unsigned int buffSize, unsigned char *buff); int dev_get_device_list(int type, unsigned int buffSize, unsigned char *buff, unsigned int device_name_len); #endif
- 在demo_module.c中实现接口以及接口关键字段。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <dlfcn.h> #include <pthread.h> #include <unistd.h> #include "demo_module.h" #ifdef __cplusplus #if __cplusplus extern "C" { #endif #endif /* 结构体指针g_demo_ctrl保存模组设备信息 */ static DEMO_DEVICE_CTRL *g_demo_ctrl = NULL; /* 定义支持的设备名 */ char* g_devices_name[DEVICE_MAP_MAX_LEN] = { "extend_device01", "extend_device02" }; /* 定义支持的设备, 用于模拟模组各属性类型的获取与设置 */ DEMO_SIGNAL_INFO signal1 = {"4g", "strong"}; DEMO_SIGNAL_INFO signal2 = {"5g", "weak"}; DEVICE device1 = {"extend_name", "extend_class", 1, 52, 1.0, 0, 51200000, "extend 1.0", &signal1}; DEVICE device2 = {"extend_name", "extend_class", 1, 52, 1.0, 1, 51200000, "extend 1.0", &signal2}; DEVICE* g_devices[DEVICE_MAP_MAX_LEN] = {&device1, &device2}; /* 驱动适配资源加载以及模组初始化接口 */ int dev_load() { /* 申请用于保存模组设备信息的内存资源 */ g_demo_ctrl = malloc(sizeof(DEMO_DEVICE_CTRL)); if (g_demo_ctrl == NULL) { return -1; } /* 初始化模组设备信息,生成区分设备的不同fd值以及 */ for (int index = 0; index < DEVICE_MAP_MAX_LEN; index++) { g_demo_ctrl->device_map[index].fd = index; int ret = memcpy_s(g_demo_ctrl->device_map[index].name, DEVICE_INFO_MAX_LEN, g_devices_name[index], DEVICE_INFO_MAX_LEN); if (ret != 0) { return -1; } } return 0; } /* 驱动适配资源释放接口 */ int dev_unload() { /* 释放保存模组设备信息的指针并置空 */ if (g_demo_ctrl != NULL) { free(g_demo_ctrl); } g_demo_ctrl = NULL; return 0; } /* 驱动适配设备打开接口,根据输入的设备名name,返回得到设备标识符fd */ int dev_open(char *name, int *fd) { /* 判断是否初始化加载模组,以及输入参数是否为空指针 */ if (g_demo_ctrl == NULL || name == NULL || fd == NULL) { return -1; } /* 遍历模组设备列表,查询是否已存在device_name的设备信息,若存在返回对应fd值 */ for (int index = 0; index < DEVICE_MAP_MAX_LEN; index++) { if (strcmp(g_demo_ctrl->device_map[index].name, name) == 0) { *fd = g_demo_ctrl->device_map[index].fd; return 0; } } return -1; } /* 驱动适配设备关闭接口,根据输入的fd设备标识符关闭对应设备 */ int dev_close(int fd) { if (g_demo_ctrl == NULL) { return -1; } /* 遍历模组设备列表,查询fd对应设备信息并置为初始值 */ for (int index = 0; index < DEVICE_MAP_MAX_LEN; index++) { if (g_demo_ctrl->device_map[index].fd == fd) { memset_s(g_demo_ctrl->device_map[index].name, DEVICE_INFO_MAX_LEN, 0, DEVICE_INFO_MAX_LEN); } } return 0; } /* json类型数据获取封装接口,将TLV编码解析的value进行TLV解析完成json属性获取 */ static int get_json_attribute(int buffSize, char *buff, DEVICE* device) { int pos = 0; while (pos < buffSize) { ATTR_TLV *sub_item = (ATTR_TLV *)(buff + pos); /* 判断TLV解析是否会内存越界,若解析错误则返回错误码 */ if (pos + TLV_HEADER_LENGTH + sub_item->len > buffSize) { return -1; } switch (sub_item->type) { case SUB_ATTRIBUTE1: { memcpy_s((char *)sub_item->value, DEVICE_INFO_MAX_LEN, device->signal_info->signal_type, DEVICE_INFO_MAX_LEN); break; } case SUB_ATTRIBUTE2: { memcpy_s((char *)sub_item->value, DEVICE_INFO_MAX_LEN, device->signal_info->signal_strength, DEVICE_INFO_MAX_LEN); break; } default: /* 若输入的模组子属性不支持设置,返回错误码 */ return -1; } pos += TLV_HEADER_LENGTH + sub_item->len; } return 0; } /* 驱动适配属性获取接口,将模组属性保存到TLV编码解析的buff中 */ int get_attribute(int fd, unsigned int buffSize, unsigned char *buff) { DEVICE* device = NULL; /* 根据fd确定模组唯一设备,确认后对该模组设备属性获取 */ for (int index = 0; index < DEVICE_MAP_MAX_LEN; index++) { if (g_demo_ctrl->device_map[index].fd == fd) { device = g_devices[fd]; break; } } if (device == NULL) { /* 若存在该设备,则根据TLV解析获取该设备属性,若不存在该设备,返回错误码 */ return -1; } /* 将输入的获取到模组属性的内存进行TLV转换进行解析 */ ATTR_TLV *demo_attr = (ATTR_TLV *)buff; /* 使用switch case方法确定需要获取的模组属性,并将对应模组属性保存在buff内存段中 */ switch (demo_attr->type) { /* case中模组属性ID与模组配置json文件保持一致 */ case NAME: /* 模拟string类型属性的获取方式,实际开发中:需将实际接口获取的属性值保存在TLV解析的value中 */ memcpy_s(demo_attr->value, demo_attr->len, device->name, DEVICE_INFO_MAX_LEN); break; case CLASS: /* 模拟string类型属性的获取方式,实际开发中:需将实际接口获取的属性值保存在TLV解析的value中 */ memcpy_s(demo_attr->value, demo_attr->len, device->device_class, DEVICE_INFO_MAX_LEN); break; /* case中的全局变量仅是模拟不同类型的属性获取方式,实际适配开发中需修改为实际的模组属性接口 */ case PRESENT: /* 模拟int类型属性的获取方式,实际开发中:需将实际接口获取的属性值保存在TLV解析的value中 */ *(int *)demo_attr->value = device->present; break; case TEMPERATURE: /* 模拟float类型属性的获取方式,实际开发中:需将实际接口获取的属性值保存在TLV解析的value中 */ *(float *)demo_attr->value = device->temperature; break; case VOLTAGE: /* 模拟float类型属性的获取方式,实际开发中:需将实际接口获取的属性值保存在TLV解析的value中 */ *(float *)demo_attr->value = device->voltage; break; case SWITCH: /* 模拟bool类型属性的获取方式,实际开发中:需将实际接口获取的属性值保存在TLV解析的value中 */ *demo_attr->value = device->device_switch; break; case MEMORY: /* 模拟long long类型属性的获取方式,实际开发中:需将实际接口获取的属性值保存在TLV解析的value中 */ *(long long *)demo_attr->value = device->memory; break; case VERSION: /* 模拟string类型属性的获取方式,实际开发中:需将实际接口获取的属性值保存在TLV解析的value中 */ memcpy_s(demo_attr->value, demo_attr->len, device->version, DEVICE_INFO_MAX_LEN); break; case SIGNAL_INFO: /* json类型的属性需要对buff再次进行TLV解析,使用偏移量的方式确定子属性的获取 */ return get_json_attribute(demo_attr->len, (char *)demo_attr->value, device); default: /* 若输入的模组属性不支持获取,返回错误码 */ return -1; } return 0; } /* json类型数据设置封装接口,将TLV编码解析的value进行TLV解析完成json属性设置 */ static int set_json_attribute(int buffSize, char *buff, DEVICE* device) { int pos = 0; while (pos < buffSize) { ATTR_TLV *sub_item = (ATTR_TLV *)(buff + pos); /* 判断TLV解析是否会内存越界,若解析错误则返回错误码 */ if (pos + TLV_HEADER_LENGTH + sub_item->len > buffSize) { return -1; } switch (sub_item->type) { case SUB_ATTRIBUTE1: memcpy_s(device->signal_info->signal_type, DEVICE_INFO_MAX_LEN, (char *)sub_item->value, DEVICE_INFO_MAX_LEN); break; case SUB_ATTRIBUTE2: memcpy_s(device->signal_info->signal_strength, DEVICE_INFO_MAX_LEN, (char *)sub_item->value, DEVICE_INFO_MAX_LEN); break; default: /* 若输入的模组子属性不支持设置,返回错误码 */ return -1; } pos += TLV_HEADER_LENGTH + sub_item->len; } return 0; } /* 驱动适配属性设置接口,将TLV编码解析的buff中的属性值进行设置 */ int set_attribute(int fd, unsigned int buffSize, unsigned char *buff) { DEVICE* device = NULL; /* 根据fd确定模组唯一设备,确认后对该模组设备属性获取 */ for (int index = 0; index < DEVICE_MAP_MAX_LEN; index++) { if (g_demo_ctrl->device_map[index].fd == fd) { device = g_devices[fd]; break; } } if (device == NULL) { /* 若存在该设备,则根据TLV解析获取该设备属性,若不存在该设备,返回错误码 */ return -1; } /* 将输入的获取到模组属性的内存进行TLV转换进行解析 */ ATTR_TLV *demo_attr = (ATTR_TLV *)buff; /* 使用switch case方法确定需要设置的模组属性,并将buff内存段中对应的属性值保存在模组中 */ switch (demo_attr->type) { /* case中模组属性ID与模组配置json文件保持一致 */ case PRESENT: /* 模拟int类型属性的设置方式,实际开发中:需将保存在TLV解析的value值传入实际接口设置属性值 */ device->present = *(int *)demo_attr->value; break; case TEMPERATURE: /* 模拟float类型属性的设置方式,实际开发中:需将保存在TLV解析的value值传入实际接口设置属性值 */ device->temperature = *(float *)demo_attr->value; break; case VOLTAGE: /* 模拟float类型属性的设置方式,实际开发中:需将保存在TLV解析的value值传入实际接口设置属性值 */ device->voltage = *(float *)demo_attr->value; break; case SWITCH: /* 模拟bool类型属性的设置方式,实际开发中:需将保存在TLV解析的value值传入实际接口设置属性值 */ device->device_switch = *(int *)demo_attr->value; break; case MEMORY: /* 模拟long long类型属性的设置方式,实际开发中:需将保存在TLV解析的value值传入实际接口设置属性值 */ device->memory = *(long long *)demo_attr->value; break; case VERSION: /* 模拟string类型属性的设置方式,实际开发中:需将保存在TLV解析的value值传入实际接口设置属性值 */ memcpy_s(device->version, DEVICE_INFO_MAX_LEN, (char *)demo_attr->value, DEVICE_INFO_MAX_LEN); break; case SIGNAL_INFO: /* json类型的属性需要对buff再次进行TLV解析,使用偏移量的方式进行子属性的设置 */ return set_json_attribute(demo_attr->len, (char *)demo_attr->value, device); default: /* 若输入的模组子属性不支持设置,返回错误码 */ return -1; } return 0; } /* 驱动适配预留读接口,暂不设计开发使用 */ int dev_read(int fd, unsigned int buffSize, unsigned char *buff) { return 0; } /* 驱动适配预留写接口,暂不设计开发使用 */ int dev_write(int fd, unsigned int buffSize, unsigned char *buff) { return 0; } /* 可热插拔设备的模组接口,用于获取指定设备类型的设备列表。当模组信息会实时变化的情况下,使用此接口刷新模组设备信息 */ int dev_get_device_list(int type, unsigned int buffSize, unsigned char *buff, unsigned int device_name_len) { if (g_demo_ctrl == NULL) { return -1; } int pos = 0; int ret; /* 根据不同的模组设备类型,使用首地址偏移的方式将模组设备信息保存在buff中 */ ret = memcpy_s((char *)(buff + pos), device_name_len, "extend_device01", device_name_len); if (ret != 0) { return -1; } pos += device_name_len; ret = memcpy_s((char *)(buff + pos), device_name_len, "extend_device02", device_name_len); if (ret != 0) { return -1; } return 0; } #ifdef __cplusplus #if __cplusplus } #endif #endif
- 编译构建动态库so。
- 在“add_extend_driver_adapter”路径的CMakeLists.txt文件中编写构建方式,示例如下。
#设置CMake的最低版本 cmake_minimum_required(VERSION 3.16) #交叉编译选项 if (CROSSCOMPILE_ENABLED) set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR aarch64) set(target_arch aarch64-linux-gnu) set(CMAKE_C_COMPILER /usr/bin/aarch64-linux-gnu-gcc) set(CMAKE_CXX_COMPILER /usr/bin/aarch64-linux-gnu-g++) set(CMAKE_LIBRARY_ARCHITECTURE ${target_arch} CACHE STRING "" FORCE) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) endif() #添加构建的项目名称 project(libdemo_adapter) set(TOP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..) #在目录中查找所有源文件 aux_source_directory(. SRC_LIST) #将源文件生成名为libdemo_adapter.so链接文件 STATIC:静态链接 SHARED:动态链接 add_library(demo_adapter SHARED ${SRC_LIST})
- 构建.so文件。
- 在“add_extend_driver_adapter”工程下的build_extend_driver_adapter.sh中编写构建脚本,代码编写示例如下。
#!/bin/bash CUR_DIR=$(dirname "$(readlink -f "$0")") function main() { echo "build demo adapter lib..." if [ ! -d "${CUR_DIR}/build" ];then mkdir -p "${CUR_DIR}/build" else rm -rf "${CUR_DIR}/build"/* fi cd "${CUR_DIR}/build" # 注意x86编译环境需要安装Arm64编译工具后,开启该选项 cmake -DCROSSCOMPILE_ENABLED=ON .. make echo "build demo adapter lib success" return 0 } main RESULT=$? exit "${RESULT}"
- 将“{project_dir}/config/project_cfg/project.conf”的“_NeedAdditionalDriver”设置为“yes”。
- 在“add_extend_driver_adapter”工程下的build_extend_driver_adapter.sh中编写构建脚本,代码编写示例如下。
- 在“add_extend_driver_adapter”路径的CMakeLists.txt文件中编写构建方式,示例如下。
- 编译新增模组文件。在{project_dir}/build/build.sh中,实现调用扩展模组的编译脚本。
# 添加扩展模组驱动 # TOP_DIR={project_dir} # OMSDK_TAR_PATH={project_dir}/platform/omsdk if [[ "${_NeedAdditionalDriver}" == "yes" ]]; then if ! bash "${TOP_DIR}"/src/app/add_extend_driver_adapter/build_extend_driver_adapter.sh;then return 1 fi cp -rf "${TOP_DIR}"/src/app/add_extend_driver_adapter/build/libdemo_adapter.so "${OMSDK_TAR_PATH}"/lib/ # copy additional configurations cp -rf "${TOP_DIR}"/config/module_def/*.json "${OMSDK_TAR_PATH}"/software/ibma/config/devm_configs/ fi
父主题: 模组开发