23 KiB
1. 设计文档
1.1. 产品概述
一款低功耗的IPC产品设计方案。
1.2. 结构设计
- 迷彩外观;
- 抽屉式电池箱组;
- 捆绑/相机支架安装方式;
- 案件翻盖保护;
- 可拓展4G模块;
- 户外防水;
1.3. 硬件设计
1.3.1. 关键外设
- 摄像头;
- PIR sensor;
- IR leds;
- 电池(箱体);
- 启动模式拨键(三键);
- 按键:同步/复位/格式化;
- SD card;
- usb接口;
- NFC(绑定APP);
- 光感传感器:白天/黑夜;
- 指示灯:设备状态/SD卡状态/电量/无线信号灯/账号状态;
1.4. 软件设计
1.4.1. 多态单例设计模式
多态单例模式包含两部分:抽象接口 + 抽象接口实例,代码基于抽象接口存在,业务基于抽象接口实例存在。
1.4.1.1. 多态单例图示
下述图示分别为:类图 / 依赖关系图 / 编译链接关系图
classDiagram
APP --> AbstractInterface:使用
AbstractInterface <|.. Instance:实现
应用 --> 抽象接口库:依赖
抽象接口库 ..> 实例库:依赖
main线程 --> 抽象接口代码库:链接
main线程 --> 实例代码库:链接
1.4.1.2. 多态单例模块使用时序图
在使用多态单例设计模式开发功能模块时,使用统一的命名规则:
- 抽象接口命名:xxx + Abstract,例如:libLogAbstract.a;
- 实例库命名:xxx + 具体实例名称,例如:libLogEasylogging.a;
调用关系如下图
sequenceDiagram
User ->> +libLogAbstract.a:调用抽象接口
libLogAbstract.a --> +libLogEasylogging.a:实际调用实例接口
libLogEasylogging.a --> -libLogAbstract.a:return
libLogAbstract.a -->> -User:return
只有main函数实际调用实例库的实例化接口时,log功能才会生效。
1.4.1.3. 多态单例目录结构规范
根据多态单例设计模式,指定使用多态单例模式开发的模块的源码目录结构,耦合思路清晰。
└── Log // 多态单例模式模块的目录
├── include // 多态单例模块对外暴露的头文件目录,抽象接口定义
│ └── ILogAbstract.h
├── abstract // 多态单例模式抽象接口声明
│ └── ILogAbstract.cpp
└── src // 多态单例接口实例代码,目录名称可根据实际功能定义
├── LogAstract.cpp
└── LogAstract.h
构建时把abstract目录和src目录的源码分别编译成库,main线程根据实际需要链接并实例化即可。
1.4.1.4. 混合多态单例
多态单例的分类有C++版本多态单例,C语言版本多态单例,混合(C/C++)多态单例。混合多态单例的提出是由于纯C语言多态单例开发难度较高,纯C++语言的多态单例不适合底层(特指本文的分层结构中的适配层)接口为C++时,很多C语言代码调用困难。
混合多态单例,内部多态实现使用C++,保留C++开发便捷性和易维护性(智能指针),对外同时提供C语言多态抽象接口和C++多态抽象接口,满足C/C++混编时易用性。最大特点是include文件夹里面会包含两个头文件。
1.4.1.4.1. 多态单例目录结构
hal
├── abstract // 抽象接口库的基础代码
│ ├── IHal.cpp
│ └── IHalCpp.cpp
├── include
│ ├── IHalCpp.h // C++抽象接口头文件
│ └── IHal.h // C语言抽象接口头文件
└── src // 抽象接口实例库代码
├── Hal.c // C语言接口实例
├── HalCpp.cpp // C++接口实例
├── HalCpp.h
├── Hal.h
├── HalMakePtr.cpp // 负责创建内部实例
└── HalMakePtr.h
1.4.1.5. 多态单例总结:
- 应用代码只使用抽象接口,禁止直接依赖实例代码;
- 应用代码只有在main线程初始化时实例化实例模块即可;
- 多态单例模块由两个库组成,一个是抽象接口库,一个是实例代码库,支持静态多态和动态多态;
1.4.2. 双核业务设计
基于芯片大小核架构的启动设计。
1.4.2.1. 红外触发启动
红外触发启动作为产品正常工作时最经常最重要的启动状态。启动后快速抓拍/录像,快速关机。
启动时序图
sequenceDiagram
participant MCU
participant 小核
participant 大核
MCU ->> MCU:待机
MCU ->> MCU:检测到PIR信号
MCU ->> +小核:上电
小核 ->> 小核:抓拍 / 录像
小核 ->> 小核:保存到sd卡
小核 -->> -MCU:关机
MCU ->> 小核:断电
1.4.2.2. 定时触发状态启动
1.4.2.3. 设置状态启动
特殊的启动状态,可以长时间通电完成其它功能,此时功耗较高。
- 如果没有设置状态按键,在物理电源上电时,首先进入设置状态启动,5分钟后自动切换到工作状态。
设置状态启动时序图
sequenceDiagram
participant MCU
participant 小核
participant 大核
MCU ->> MCU:物理上电
MCU ->> 小核:上电
小核 ->> 小核:?
小核 ->> 大核:设置状态启动
大核 ->> 大核:设置状态任务
大核 ->> MCU:关机
MCU ->> 小核:断电
大核设置状态启动时序图
sequenceDiagram
participant 大核
participant app
大核 ->> 大核:物理上电
大核 ->> app:启动脚本启动app
大核 ->> 大核:断电
1.4.3. 根据软件模块作用域分层
1.4.3.1. 应用层(application)
1.4.3.1.1. 应用层概述
应用层负责处理产品级的复杂业务关系,是产品功能的直接体现,应用层模块全部使用C++接口的多态单例模式设计,各模块之间可以互相调用接口,应用层各库可以随意任意调用中间件或者工具类接口。
1.4.3.1.2. 网络服务模块
1.4.3.1.2.1. 网络服务概述
根据产品联网属性,网络服务模块分为不联网 / 联网(B端)/ 联网(自研)三个多态属性。联网时,IPC的图片 / 视频资源通过网络服务器进行管理。
- 不联网版本:网络服务模块不实例化即可;
- 联网(B端):媒体资源由三方服务器管理;
- 联网(自研):媒体资源由自研服务器管理;
1.4.3.1.2.2. 网络服务多态设计模式
通过构建配置文件选择需要实例化的网络服务模块代码。
1.4.3.1.3. 相机状态管理
相机主业务逻辑使用状态机机制进行管理。
1.4.3.1.3.1. 任务状态
任务状态是指相机启动需要执行的任务,可能是拍照 / 视频,可能是其它任务。
例如:
- 移动物体侦测启动;
- 定时启动拍照 / 录像;
- 测试启动;
1.4.3.2. 中间件(middleware)
1.4.3.2.1. 中间件概述
一些相对中性的业务功能库,这些库可以提供给不同的产品需求使用,在应用层不同的调用方式可实现不同的产品功能。中间件只能被应用层调用或者向下调用适配层或者调用工具库,中间件各模块之间不能互相调用。中间件库接口可以使用C或者C++接口。
1.4.3.2.2. 外设管理模块
应用层唯一的硬件外设接口库。包含灯 / 按键 / GPIO / SD卡等。
1.4.3.2.3. 相机管理模块
应用层唯一的摄像头接口库。
1.4.3.2.4. MCU管理模块
MCU通信接口库,一般使用串口进行通信,需要考虑多态其它接口(例如I2C),考虑多态协议数据结构。MCU负责管理外设的电源控制 / 充当硬狗等。
1.4.3.2.4.1. MCU管理模块设计模式
使用C++接口的多态单例模式。
基本功能:
- 使用utils当中的串口功能模块,支持多态切换到其它串口功能模块;
- 设置MCU管理的监视器monitor,用于回调处理MCU接受到的业务事件;
- 通信协议不暴露,内部处理基于协议的数据解析 / 组包;
- 通信协议支持多态拓展,通信协议独立成库;
1.4.3.2.5. IPC配置库
负责管理IPC产品相关的配置数据。
1.4.3.2.5.1. IPC配置库设计模式
使用多态单例设计模式,对外提供C语言接口,内部不局限使用C或者C++。
基本功能
- 敏感数据(例如:账号 / 密码)需要加密处理;
- 读到内存使用二进制数据,缓存到数据结构体;
- 调用utils工具里面的配置库,对配置文件进行读 / 写;
- 使用二进制结构体 + 明文配置文件结合的模式,既可减少内存消耗,又可以规避二进制数据升级迭代数据匹配困难问题;
- 使用枚举方式管理IPC配置数据定义,当使用纯16进制保存数据时,可不链接utils工具里面的配置库,直接保存16进制数据到文件系统即可;
- 应用程序全局唯一可以操作IPC配置文件的库,保证配置文件正确读写;
1.4.3.2.5.2. IPC配置库类图
classDiagram
i_ipc_config <.. ipc_config:实现
ipc_config --> i_config_manager:依赖
ipc_config --> IHal:依赖
i_config_manager <.. config_manager:实现
config_manager --> libconfig开源库:依赖
1.4.3.2.5.3. 关键业务时序图
时序图会忽略抽象接口直接使用实例接口表示。
- IPC配置库初始化 / 解初始化
sequenceDiagram
participant User
participant ipc_config
participant i_config
participant IHal
User ->> +ipc_config:初始化
ipc_config ->> +IHal:获取文件系统路径
IHal -->> -ipc_config:return
ipc_config ->> +i_config:打开配置文件
i_config -->> -ipc_config:return
alt 打开成功
rect rgba(255,220,200,1)
loop 读取所有数据到IPC数据结构体
ipc_config ->> +i_config:读取一个数据到IPC数据结构体
i_config -->> -ipc_config:return
rect rgba(255,255,200,1)
opt 读取失败
ipc_config ->> ipc_config:生成默认数据
ipc_config ->> +i_config:设置默认数据
i_config ->> -ipc_config:return
end
end
end
ipc_config ->> +i_config:关闭配置文件
i_config -->> -ipc_config:return
end
else 打开失败
rect rgba(255,200,200,1)
ipc_config ->> ipc_config:生产默认数据
ipc_config ->> +i_config:创建配置文件
i_config -->> -ipc_config:return
ipc_config ->> +i_config:设置默认数据
i_config -->> -ipc_config:return
ipc_config ->> +i_config:关闭配置文件
i_config -->> -ipc_config:return
end
end
ipc_config -->> -User:return
User ->> +ipc_config:解初始化
ipc_config ->> ipc_config:释放内存中的数据
ipc_config -->> -User:return
- 读 / 写(修改)数据
sequenceDiagram
participant User
participant ipc_config
User ->> +ipc_config:读取数据
ipc_config ->> ipc_config:返回内存保存的数据
ipc_config -->> -User:return
User ->> +ipc_config:修改数据
ipc_config ->> ipc_config:修改内存保存的数据
ipc_config -->> -User:return
- 保存数据
sequenceDiagram
participant User
participant ipc_config
participant i_config
User ->> +ipc_config:保存
ipc_config ->> +i_config:打开配置文件
i_config -->> -ipc_config:return
ipc_config ->> +i_config:同步数据
i_config -->> -ipc_config:return
ipc_config ->> +i_config:保存配置文件
i_config -->> -ipc_config:return
ipc_config ->> +i_config:关闭配置文件
i_config -->> -ipc_config:return
ipc_config -->> -User:return
1.4.3.2.6. 高级配置库
对配置库的二级封装,提供更便捷的功能服务,例如:可以监控文件的修改事件 / 可以直接捕获某个配置文件或者数据的操作对象。
1.4.3.2.6.1. 高级配置库设计
对外暴露C++接口,使用多态单例设计模式。
1.4.3.2.7. 状态机管理
提供实现状态机管理机制C++接口,使用鸿蒙状态机开源源码进行改造封装。
1.4.3.2.7.1. 状态机管理设计模式
使用多态单例设计模式,暂定使用鸿蒙状态机开源代码改造实现,后续可替换其它源码或者自研代码。
1.4.3.2.8. 文件数据库
文件数据库负责管理设备的媒体资源(图片 / 视频等)。
1.4.3.2.8.1. 文件数据设计模式
使用混合多态单例模式开发。
1.4.3.2.8.2. 开源库选型
sqlite3开源库,编译成工具类库提供给文件数据库使用。
1.4.3.2.8.3. 基本功能和业务需求
- 对媒体资源文件的增删改查处理;
- 记录媒体资源文件的生成时间,任务(是否发送);
- 业务根据需要,通过数据库判断哪些文件已经发送完成,哪些文件发送失败,哪些文件待发送;
1.4.3.3. 硬件适配层(hal)
负责适配不同的硬件平台。
1.4.3.3.1. 硬件适配层设计模式
基于C语言接口的多态单例模式,编译时静态多态链接对应的芯片平台适配代码,实现芯片接口的标准功能定义。
1.4.3.3.2. 媒体适配方案:
IPC应用在适配芯片平台的多媒体接口时,使用多进程的方式实现。满足IPC应用可以快起(无需等待媒体相关的初始化)的需求。
媒体基本需求
- 图片抓拍;
- 视频抓拍;
多进程通信方案
使用本地socket的方式进行多进程通信,媒体进程为客户端,IPC应用为服务端(IPC先启动)。
- 客户端可自动重连;
- 服务端可多次关闭和开启,满足gtest资源回收需求;
1.4.3.3.3. 适配层多进程类图
classDiagram
i_hal <.. hal:实现
hal --> v_media_handle:依赖
v_media_handle --> local_socket:依赖
local_socket .. media进程:跨进程
media进程 --> 芯片媒体API:依赖
1.4.3.3.4. 跨进程业务时序图
- 本地socket链接
sequenceDiagram
participant hal
participant v_media_handle
participant local_socket_s
participant local_socket_c
participant media进程
participant 芯片媒体API
hal ->> +v_media_handle:初始化
v_media_handle ->> +local_socket_s:本地socket服务端启动
par 本地socket初始化
local_socket_s -->> -v_media_handle:return
v_media_handle -->> -hal:return
and
media进程 ->> media进程:启动
media进程 ->> +local_socket_c:初始化服务端
local_socket_c -->> local_socket_s:链接
local_socket_c -->> -media进程:return
local_socket_s -->> local_socket_c:初始化媒体
local_socket_c ->> media进程:回调回传协议事件
media进程 ->> +芯片媒体API:初始化媒体
芯片媒体API -->> -media进程:return
end
- 存在问题: 使用C语言开发时如何解决智能指针问题?
1.4.3.4. 工具库(utils)
1.4.3.4.1. 工具库概述
工具库是功能单一的不依赖任何三方库的独立库(日志库和返回码管理库除外),必须提供C语言接口,内部实现不限于C或者C++。工具类库可以被任意的其它模块调用,特别指hal/component/application三大层级。
1.4.3.4.2. 日志库
1.4.3.4.2.1. 日志库概述
提供程序的日志管理功能,含日志的实时打印/保存/跟踪(实时上传云端)。
1.4.3.4.2.2. 日志库设计模式
C语言接口的多态单例模式,可动态/静态加载多态实例。
1.4.3.4.2.3. 日志库启动
日志库是否启用一般来讲是dubug版本启用日志功能,release版本禁用日志功能,考虑到release版本的维护问题,标准启动时,main线程在启动时使用dlopen系列函数去加载日志库(多态),特殊版本仍可在main线程加载日志库后,二次实例化日志库(多态)来实现不同的日志功能。
- 标准流程:main线程加载sd卡动态库,如有即可动态实现日志功能,正常出货sd卡不带日志库,此时没有日志功能;
- 可以通过配置参数决定是否启用日志;
- sd卡的日志动态,根据实际售后维护,可以是实时打印log/保存本地log/云log的多态实例库;
- 多态日志功能,可以忽略debug和release版本的区别,只发布一个版本即可;
1.4.3.4.3. 状态码管理库
提供整个应用程序的返回码管理功能,例如:打印返回码的字符串含义。提供C语言接口,纯C语言开发的模块,形成项目内部唯一返回码标准。
1.4.3.4.3.1. 状态码功能
- 创建返回码操作“句柄”;
- 打印返回码/获取返回码(字符串);
- 不同模块可继承实现各自的返回码处理接口;
- 不同模块之间可透传状态码,避免错误码的转换麻烦;
- 状态码的定义是跟着模块走的,独立模块的状态码定义在模块内部,不链接时不占用程序空间;
1.4.3.4.3.2. 基础状态码定义
基础状态码是全局的基础状态码,枚举值全局唯一,其它独立模块必须继承基础状态码累加枚举值,但是,独立模块之间的枚举值可重复,状态码在使用时,日志只关注状态码的字符串,不关心状态码枚举值,代码逻辑使用枚举值。
enum STATUS_CODE
{
STATUS_CODE_OK = 0,
STATUS_CODE_NOT_OK,
STATUS_CODE_VIRTUAL_FUNCTION,
STATUS_CODE_INVALID_PARAMENTER,
STATUS_CODE_END
};
枚举名 | 字符串 | 描述 |
---|---|---|
STATUS_CODE_OK | STATUS_CODE_OK | 成功 |
STATUS_CODE_NOT_OK | STATUS_CODE_NOT_OK | 失败 |
STATUS_CODE_VIRTUAL_FUNCTION | STATUS_CODE_VIRTUAL_FUNCTION | 抽象接口,提示抽象接口未实例化 |
STATUS_CODE_INVALID_PARAMENTER | STATUS_CODE_INVALID_PARAMENTER | 无效的参数 |
STATUS_CODE_END | STATUS_CODE_END | 结束,无意义 |
其它 | ? | 其它状态码在各自模块定义 |
1.4.3.4.3.3. 已知漏洞
状态码在代码层面是存在重复的可能性的,代码逻辑在使用状态码枚举值时,可能会出现逻辑错误。
- 解决方案 在使用状态码进行逻辑判断时,使用状态码枚举值的字符串。 Example:
static inline bool StatusCodeEqual(const StatusCode code, const char *value)
1.4.3.4.4. 系统标准接口库
对系统标准接口的套壳封装,主要是为了对系统标准打桩满足测试需求。
使用普通的C语言接口封装即可,通过使用gcc编译参数在Linux x86系统中满足打桩需求,在交叉编译(担心工具链兼容问题)测试程序中无法对系统标准接口进行打桩。
1.4.3.4.5. 通用配置库
1.4.3.4.5.1. 通用配置库概述
配置库负责管理软件配置参数,对配置数据进行设置 / 获取 / 存储 / 备份 / 升级等功能;通用配置库不限制使用场景,是一个通用的配置文件管理库。
1.4.3.4.5.2. 配置库设计模式
对外提供C语言接口,内部不局限使用C或者C++。整个软件唯一可以直接操作文件系统配置文件的库。配置库可以理解为简单的三方库的接口直接封装,使用多态单例设计模式实现静态或者动态切换三方库的使用。
基本功能
- 使用三方库保存明文格式的配置文件到文件系统;
- 可注册回调函数,监听文件的操作事件;
- 使用字符串名字key + 值的方式管理配置文件,作为通用的配置文件管理库;
1.4.3.4.5.3. 开源库
两种方案:
- 使用libconfig作为文件操作的开源库,实现文件和数据的读 / 写。
- 使用sqlite3作为文件操作的开源库,作为数据库文件处理。
1.4.3.4.5.4. 通用配置库类图
多态单例设计模式,main线程静态链接多态库。
classDiagram
i_config_manager <.. config_manager:实现
config_manager --> libconfig开源库:依赖
i_config_manager <.. sqlite_manager:实现
sqlite_manager --> sqlite3数据库:依赖
1.4.3.4.5.5. 备份机制
备份数据用于数据异常时可还原旧数据。
方案选择:
- 出厂默认配置文件为只读文件,在数据破坏时还原;
1.4.3.4.5.6. 升级机制
程序升级后配置数据发生增 / 删时如何兼容和还原。
1.4.3.4.6. 串口功能模块
串口的打开 / 关闭 / 数据读 / 数据写 功能。
1.4.3.4.6.1. 串口开源库
使用下述开源库对串口数据进行收发。
https://gitee.com/RT-Thread-Mirror/TinyFrame
1.4.3.4.7. MCU协议库
负责MCU通信协议的组包 / 拆包 / 事件转换。
1.4.3.4.7.1. MCU协议库设计模式
基于C语言的多态单例设计模式。
1.4.3.4.7.2. 协议数据结构
1.4.3.4.8. 多进程协议库
负责IPC应用和媒体进程之间的协议组包 / 拆包,在协议和业务之间进行转换接口的封装。
1.5. 生产测试/研发调试
基于公版代码派生出来的特定的定制版本,用于辅助生产和测试。
1.6. 自动化测试
1.6.1. 自动化测试概述
自动化测试是该产品设计的一大特点,需要严格执行。自动化测试指使用纯代码对业务设计进行测试用例设计,实现业务集成测试的能力。
1.6.2. 自动化测试规范
- 每个源码文件在开发时,均要写调试的example,用于验证该文件的接口功能;测试文件的命名规则为:文件名 + “_Test.c(pp)”; 例如:
- C语言:log_impl.c对应的测试文件为log_impl_Test.cpp;
- C++:LogImpl.cpp对应的测试文件为LogImpl_Test.cpp;
1.7. 编码规范
- 文件命名统一使用大驼峰命名规则;
- 混合多态单例的C++抽象接口头文件需要有Cpp关键字标识;
- 多单单例的头文件 / 类名 统一使用“I”前缀,interface的单词首字母;
- 抽象对象接口类统一使用“V”前缀, virtual的单词首字母;
- 所有函数统一使用大驼峰命名规则;
- 其它遵循华为的编码规范要求;