Development Example
This section uses the add_extend_driver_adapter project as an example to describe how to adapt OM SDK APIs. Before performing operations in this section, developers must carefully read the "Module Development" chapter and be familiar with the product specifications, module specifications, and API functions.
Procedure
- Go to the {project_dir}/src/app/add_extend_driver_adapter directory of the project source code.The directory structure of add_extend_driver_adapter is as follows:
├── build_extend_driver_adapter.sh // Script for building a driver ├── validate_module_def.py // Script for verifying a configuration file ├── CMakeLists.txt // CMake construction file ├── demo_module.c // Source file for module adaptation └── demo_module.h // Header file for module adaptation - Define modules and devices in the specification configuration file.
- Copy all existing configuration files from software/ibma/config/devm_configs in om-sdk.tar.gz to {project_dir}/config/module_def.
- Create the module specification configuration file module_demo.json in the {project_dir}/config/module_def directory and define key fields by referring to Key Parameters.
- (Optional) In the {project_dir}/config/module_def directory, open the product_specification.json file and add the device definition of the extended modules to the device list. (If a module can be dynamically inserted and removed, you do not need to statically define it in the configuration file.)
- Use the configuration file verification script validate_module_def.py to check the extended configuration file set and verify the correctness of the product specification configuration file and module specification configuration files.
cd src/app/add_extend_driver_adapter python3 validate_module_def.py ../../../config/module_def
The content of the validate_module_def.py script is as follows: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')The check items are as follows:- Check whether the structure of the configuration files is correct.
- Check whether key fields are missing.
- Check whether the enumerated fields are of the correct type.
- Check the security.
- Define external APIs and key fields in demo_module.h.
#ifndef __DEMO_MODULE_H__ #define __DEMO_MODULE_H__ #define DEVICE_MAP_MAX_LEN 2 /* Maximum number of module devices stored */ #define DEVICE_INFO_MAX_LEN 64 /* Maximum length of the module information stored */ #define TLV_HEADER_LENGTH 8 /* Define the attribute ID type: 0 for basic common attribute and 1 for module-specific attribute. */ #define BASE_CLASS_ID 0x00 #define MODULE_CLASS_ID 0x01 /*Define the module management attributes.*/ typedef enum demo_attribute_id { NAME = ((BASE_CLASS_ID) << 16) + 1, /* Name, string */ CLASS = ((BASE_CLASS_ID) << 16) + 2, /* Type, string */ PRESENT = ((MODULE_CLASS_ID) << 16) + 1, /* Status, int */ TEMPERATURE = ((MODULE_CLASS_ID) << 16) + 2, /* Temperature, float */ VOLTAGE = ((MODULE_CLASS_ID) << 16) + 3, /* Voltage, float */ SWITCH = ((MODULE_CLASS_ID) << 16) + 4, /* Switch, bool */ MEMORY = ((MODULE_CLASS_ID) << 16) + 5, /* Memory, long long */ VERSION = ((MODULE_CLASS_ID) << 16) + 6, /* Version, string */ SIGNAL_INFO = ((MODULE_CLASS_ID) << 16) + 7 /* Signal information, json */ } DEMO_ATTRIBUTE_ID; /*Define the sub-attribute ID of the JSON attribute.*/ typedef enum sub_attribute_id { SUB_ATTRIBUTE1 = 1, SUB_ATTRIBUTE2 = 2 } SUB_ATTRIBUTE_ID; /*Define the structure for simulating JSON attributes.*/ typedef struct demo_signal_info { char signal_type[DEVICE_INFO_MAX_LEN]; /* Type, string */ char signal_strength[DEVICE_INFO_MAX_LEN]; /* Strength, string */ } DEMO_SIGNAL_INFO; /* Define the device to be saved.*/ typedef struct demo_device_map { char name[DEVICE_INFO_MAX_LEN]; int fd; } DEMO_DEVICE_MAP; /* Define device attributes to simulate the obtaining and setting of device attributes.*/ 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; /* Define the saved module list. */ typedef struct demo_device_ctl { DEMO_DEVICE_MAP device_map[DEVICE_MAP_MAX_LEN]; } DEMO_DEVICE_CTRL; /* Define the TLV parsing structure.*/ typedef struct attr_tlv_struct { int type; int len; char value[0]; } ATTR_TLV; /*Define the external APIs provided by the driver.*/ 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 - Implement the APIs and key fields in 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 /* The structure pointer g_demo_ctrl saves the module device information. */ static DEMO_DEVICE_CTRL *g_demo_ctrl = NULL; /* Define the supported device name.*/ char* g_devices_name[DEVICE_MAP_MAX_LEN] = { "extend_device01", "extend_device02" }; /* Define the supported devices to simulate the obtaining and setting of attribute type of the module.*/ 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}; /* Driver adaptation resource loading and module initialization API */ int dev_load() { /* Allocate memory resources for storing module device information.*/ g_demo_ctrl = malloc(sizeof(DEMO_DEVICE_CTRL)); if (g_demo_ctrl == NULL) { return -1; } /* Initialize the module device information and generate different fd values for different devices. */ 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; } /* Driver adaptation resource release API */ int dev_unload() { /* Release the pointer for saving the module device information and leave it empty. */ if (g_demo_ctrl != NULL) { free(g_demo_ctrl); } g_demo_ctrl = NULL; return 0; } /* API for enabling driver-adapted device. Return the device identifier fd according to the input device name. */ int dev_open(char *name, int *fd) { /* Determine whether to initialize the loaded module and whether the input parameter is a null pointer. */ if (g_demo_ctrl == NULL || name == NULL || fd == NULL) { return -1; } /* Traverse the module device list and check whether the device information specified by device_name exists and return the device's fd value. */ 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; } /* API for disabling driver-adapted device. Shut down the corresponding device based on the input fd device identifier. */ int dev_close(int fd) { if (g_demo_ctrl == NULL) { return -1; } /* Traverse the module device list, query the device information corresponding to the fd, and set the device information to the initial value. */ 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; } /* API for obtaining and encapsulating JSON data. Parse the TLV-encoded value to obtain JSON attributes. */ 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); /* Check whether memory overwriting occurs during TLV parsing. If a parsing error occurs, an error code is returned. */ 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: /* If the input module subattribute does not support the setting, an error code is returned. */ return -1; } pos += TLV_HEADER_LENGTH + sub_item->len; } return 0; } /* API for obtaining the driver adaptation attribute. Save the module attributes to the buff parsed using TLV. */ int get_attribute(int fd, unsigned int buffSize, unsigned char *buff) { DEVICE* device = NULL; /* Determine the unique device of the module based on the fd and obtain the device attributes of the module. */ 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) { /* If the device exists, obtain the device attributes based on the TLV parsing. If the device does not exist, an error code is returned. */ return -1; } /* Parse TLV on the input memory that contains module attributes. */ ATTR_TLV *demo_attr = (ATTR_TLV *)buff; /* Use the switch case method to determine the module attributes to be obtained and save the module attributes in the buffer memory segment. */ switch (demo_attr->type) { /* The module attribute IDs in the case and in the module configuration JSON file must be the same. */ case NAME: /* Simulate the method of obtaining the attribute of the string type. The actual attribute value obtained needs to be saved in the value parsed using TLV. */ memcpy_s(demo_attr->value, demo_attr->len, device->name, DEVICE_INFO_MAX_LEN); break; case CLASS: /* Simulate the method of obtaining the attribute of the string type. The actual attribute value obtained needs to be saved in the value parsed using TLV. */ memcpy_s(demo_attr->value, demo_attr->len, device->device_class, DEVICE_INFO_MAX_LEN); break; /* The global variables in the case are only used to simulate the methods of obtaining attributes of different types. In the process of adaptation and development, replace the global variables with the corresponding module attribute APIs.*/ case PRESENT: /* Simulate the method of obtaining the attribute of the int type. The actual attribute value obtained needs to be saved in the value parsed using TLV. */ *(int *)demo_attr->value = device->present; break; case TEMPERATURE: /* Simulate the method of obtaining the attribute of the float type. The actual attribute value obtained needs to be saved in the value parsed using TLV. */ *(float *)demo_attr->value = device->temperature; break; case VOLTAGE: /* Simulate the method of obtaining the attribute of the float type. The actual attribute value obtained needs to be saved in the value parsed using TLV. */ *(float *)demo_attr->value = device->voltage; break; case SWITCH: /* Simulate the method of obtaining the attribute of the bool type. The actual attribute value obtained needs to be saved in the value parsed using TLV. */ *demo_attr->value = device->device_switch; break; case MEMORY: /* Simulate the method of obtaining the attribute of the long long type. The actual attribute value obtained needs to be saved in the value parsed using TLV. */ *(long long *)demo_attr->value = device->memory; break; case VERSION: /* Simulate the method of obtaining the attribute of the string type. The actual attribute value obtained needs to be saved in the value parsed using TLV. */ memcpy_s(demo_attr->value, demo_attr->len, device->version, DEVICE_INFO_MAX_LEN); break; case SIGNAL_INFO: /* For attributes of the JSON type, TLV parsing needs to be performed on the buffer again. The offset mode is used to obtain sub-attributes. */ return get_json_attribute(demo_attr->len, (char *)demo_attr->value, device); default: /* If the input module attribute cannot be obtained, an error code is returned. */ return -1; } return 0; } /* API for encapsulating JSON data settings. Parse the TLV-encoded value to complete the JSON attribute setting. */ 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); /* Check whether memory overwriting occurs during TLV parsing. If a parsing error occurs, an error code is returned. */ 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: /* If the input module subattribute does not support the setting, an error code is returned. */ return -1; } pos += TLV_HEADER_LENGTH + sub_item->len; } return 0; } /* API for setting driver adaptation attribute, which is used to set the attribute value in the buffer parsed by TLV encoding.*/ int set_attribute(int fd, unsigned int buffSize, unsigned char *buff) { DEVICE* device = NULL; /* Determine the unique device of the module based on the fd and obtain the device attributes of the module. */ 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) { /* If the device exists, obtain the device attributes based on the TLV parsing. If the device does not exist, an error code is returned. */ return -1; } /* Parse TLV on the input memory that contains module attributes. */ ATTR_TLV *demo_attr = (ATTR_TLV *)buff; /* Use the switch case method to determine the module attributes to be set and save the corresponding attribute values in the buffer memory segment to the module. */ switch (demo_attr->type) { /* The module attribute IDs in the case and in the module configuration JSON file must be the same. */ case PRESENT: /* Simulate the setting mode of an attribute of the int type. In the process of development, the value parsed using TLV needs to be passed to the actual attribute setting API. */ device->present = *(int *)demo_attr->value; break; case TEMPERATURE: /* Simulate the setting mode of an attribute of the float type. In the process of development, the value parsed using TLV needs to be passed to the actual attribute setting API. */ device->temperature = *(float *)demo_attr->value; break; case VOLTAGE: /* Simulate the setting mode of an attribute of the float type. In the process of development, the value parsed using TLV needs to be passed to the actual attribute setting API. */ device->voltage = *(float *)demo_attr->value; break; case SWITCH: /* Simulate the setting mode of an attribute of the bool type. In the process of development, the value parsed using TLV needs to be passed to the actual attribute setting API. */ device->device_switch = *(int *)demo_attr->value; break; case MEMORY: /* Simulate the setting mode of an attribute of the long long type. In the process of development, the value parsed using TLV needs to be passed to the actual attribute setting API. */ device->memory = *(long long *)demo_attr->value; break; case VERSION: /* Simulate the setting mode of an attribute of the string type. In the process of development, the value parsed using TLV needs to be passed to the actual attribute setting API. */ memcpy_s(device->version, DEVICE_INFO_MAX_LEN, (char *)demo_attr->value, DEVICE_INFO_MAX_LEN); break; case SIGNAL_INFO: /* For attributes of the JSON type, TLV parsing needs to be performed on the buffer again. The offset mode is used to set sub-attributes. .*/ return set_json_attribute(demo_attr->len, (char *)demo_attr->value, device); default: /* If the input module subattribute does not support the setting, an error code is returned. */ return -1; } return 0; } /* Read API reserved for driver adaptation. It is not used for development currently. */ int dev_read(int fd, unsigned int buffSize, unsigned char *buff) { return 0; } /* Write API reserved for driver adaptation. It is not used for development currently. */ int dev_write(int fd, unsigned int buffSize, unsigned char *buff) { return 0; } /* Module API of hot-swappable devices, which is used to obtain the device list of a specified device type. When the module information changes in real time, this API is used to update the module device information. */ 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; /* Save the module device information in the buffer by using the start address offset based on the module device type. */ 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 - Build the dynamic library .so.
- Define the compilation mode in the CMakeLists.txt file in add_extend_driver_adapter. The following is an example:
# Set the earliest CMake version. cmake_minimum_required(VERSION 3.16) # Cross compilation options 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() # Add the name of the project to be constructed. project(libdemo_adapter) set(TOP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../..) # Search for all source files in the directory. aux_source_directory(. SRC_LIST) # Generate a link file named libdemo_adapter.so from the source file. STATIC: static link. SHARED: dynamic link. add_library(demo_adapter SHARED ${SRC_LIST}) - Build the .so file.
- Compile the script in build_extend_driver_adapter.sh of the add_extend_driver_adapter project. The following is an example:
#!/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" # Note that this option must be enabled after the ARM64 compilation tool is installed in the x86 compilation environment. cmake -DCROSSCOMPILE_ENABLED=ON .. make echo "build demo adapter lib success" return 0 } main RESULT=$? exit "${RESULT}" - Set _NeedAdditionalDriver of {project_dir}/config/project_cfg/project.conf to yes.
- Compile the script in build_extend_driver_adapter.sh of the add_extend_driver_adapter project. The following is an example:
- Define the compilation mode in the CMakeLists.txt file in add_extend_driver_adapter. The following is an example:
- Compile the new module file. Call the compilation script of the extended module in {project_dir}/build/build.sh.
# Add the driver for the extended module. # 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
Parent topic: Module Development