# 液冷储能系统NCCU 软件系统架构分析文档

# 一、工程概述

本工程是一套运行于嵌入式 Linux 平台（AM335x ARM Cortex-A8，IMMU2/IMMU3 硬件平台）的储能充电站控制系统（NCCU，Network Communication Control Unit），采用纯 C语言编写，面向工业实时控制场景，支持多种工业通信协议，兼顾高可靠性与高可扩展性。

* * *

# 二、顶层目录结构与职责划分

项目根目录

├── main/ # 程序入口与系统主控

├── publib/ # 公共基础库（线程、队列、互斥、日志、数据库）

├── include/ # 全局头文件（数据模型、基础类型、第三方库）

│ ├── base\_defines/ # 基础类型与工具宏

│ ├── data\_model/ # 核心数据模型定义（CHARGER/DEVICE/SAMPLER/REPORTER等）

│ └── 3rd\_party/ # 第三方库（SQLite、curl、MQTT、IEC61850等）

├── comm\_ports/ # 通信端口硬件抽象层（HAL），以动态库形式存在

├── data\_interfaces/ # 数据访问接口层（DAI），Reporter 访问数据的统一入口

├── moduals/ # 核心业务模块

│ ├── data\_processor/ # 数据处理器（DPR）：系统核心总线

│ ├── samplers/ # 采样器插件（设备数据采集）

│ └── reporters/ # 报告器插件（对外协议上报）

├── config/ # 系统配置（SQLite格式 System.cfg）

├── extensions/ # 驱动内核模块与系统扩展

├── utilities/ # 辅助工具（日志查看、系统监控、USB升级等）

└── shells/ # 系统启动/停止/运维脚本

* * *

# 三、整体软件架构

## 3.1 架构模式

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/e1cd7b44-16e2-4a97-b9bc-6225e3e2e724.png align="center")

本系统采用分层 + 插件化的事件驱动架构，整体可分为五个主要层次：

┌─────────────────────────────────────────────────────────────────┐ │ 外部协议层（Reporter 层） │ │ OCPP1.6 | Modbus TCP | IEC104 | IEC101 | IEC61850 | MQTT | │ │ WebSocket | HTTP/REST | GUI(Qt) | 云平台 | 运维后台 ... │ ├─────────────────────────────────────────────────────────────────┤ │ 数据访问接口层（DAI 层） │ │ DAI\_Get / DAI\_Set / DAI\_Query / DAI\_Signal / DAI\_EvHandler │ ├─────────────────────────────────────────────────────────────────┤ │ 数据处理器层（DPR 层） │ │ 参数更新 / 报警判定 / 事件分发 / 逻辑计算 / 历史数据持久化 │ ├─────────────────────────────────────────────────────────────────┤ │ 设备采样层（Sampler 层） │ │ Charger | KWH Meter | System | CCU | BMS | PV | AEMS | ... │ ├─────────────────────────────────────────────────────────────────┤ │ 通信端口硬件抽象层（CommPort HAL 层） │ │ 串口 Serial | TCP/IP | UDP | WebSocket | CAN | HTTP/FTP | IPC │ └─────────────────────────────────────────────────────────────────┘ ▲ 公共基础库（publib） iThread | iQueue | iMutex | iLog | iDataBase | iNew

* * *

# 四、核心模块深度分析

## 4.1 主控模块（main/）

主控模块是整个系统的启动器与调度中心，入口为 main/main.c。

### 4.1.1 启动流程（main.c）

```plaintext
int main(int argc, char *argv[]) { 
  // Step 1: 命令行参数处理（-pid, -version） 
  Main_ProcessCmdLine(argc, argv); 

  // Step 2: 内存池初始化
  MemoryPool_Init();

  // Step 3: 等待 SQLite 数据库文件锁释放（防止多进程冲突）
  Lock_SqlDB_FileLocker(1000);

  // Step 4: 初始化 SQLite（序列化模式，支持多线程共享同一 handle）
  sqlite3_config(SQLITE_CONFIG_SERIALIZED);

  sqlite3_open_v2(szDBFile, &db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL);

  // Step 5: 创建核心数据结构 CHARGER，挂载事件队列
  Create_Charger(db);

  // Step 6: 打开硬件看门狗
  WatchDog_Open();

  // Step 7: 安装信号处理器
  Install_SigHandlers();

  // Step 8: 设置 longjmp 跳出点（用于异常快速退出）
  int nExitCode = setjmp(s_JumpBufFaultError);

  // Step 9: 初始化线程管理器
  RunThread_ManagerInit(ThreadManager_EventHandler);

  // Step 10: 加载所有业务模块（DPR → DAI → CommPorts → Samplers → Reporters）
  Main_LoadModuals();

  // Step 11: 进入主事件循环
  while(!s_pChargeThis->bExit)
  {
      Do_Proc_HeartBeat();   // 进程心跳
      Queue_Get(hqEvent, &ev_arg, FALSE, 100); // 取事件
      dataprocessor.DoEvent(&ev_arg);           // 分发事件
  }
}
```

关键设计要点：

*   使用 setjmp/longjmp 实现从任意深度调用栈中安全退出，避免层层返回的复杂性
    
*   主循环以 事件队列（hqEvent） 为驱动，每 100ms 轮询一次
    
*   使用 SQLite 文件锁 防止多进程同时操作数据库
    

### 4.1.2 模块加载顺序（Main\_LoadModuals）

> `static BOOL Main_LoadModuals(void) {`
> 
> `Init_CreateDataProcessor(); // 1. 数据处理器（核心总线）`
> 
> `dataprocessor.LoadConfigAndData(); // 2. 从 SQLite 加载配置`
> 
> `Init_LoadDAI(); // 3. 数据访问接口`
> 
> `Init_Load_AllCommPorts(); // 4. 通信端口（动态库）`
> 
> `Set_SqliteDB_2RunTime_Mode(); // 5. 切换数据库到运行时模式`
> 
> `Init_Load_AllSamplers(); // 6. 采样器（动态库）`
> 
> `Init_Load_AllReporters(); // 7. 报告器（动态库）`
> 
> `}`

加载顺序体现了清晰的依赖关系：数据层先于通信层，通信层先于业务层。

### 4.1.3 看门狗（watch\_dog.c）

```plaintext
//打开硬件看门狗设备 
s_hWatchDog = open("/dev/wd", O_RDWR);
ioctl(s_hWatchDog, IOCTL_SET_TIMER, 60); // 60秒超时
ioctl(s_hWatchDog, IOCTL_EN_WD);

// 喂狗（由 ThreadManager 每次心跳时调用）
void WatchDog_Feed(void) {
    ioctl(s_hWatchDog, IOCTL_FEED_WD);
}
```

同时支持新旧两代 Linux Kernel 的 ioctl 接口（新 WDOG\_MAJOR=236，旧 WDOG\_MAJOR=241）。

### 4.1.4 信号处理（sig\_handler.c）

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/ffc47a82-d6e2-4b06-a2bc-716847f73d4a.png align="center")

* * *

## 4.2 核心数据模型（include/data\_model/）

数据模型是整个架构的数据契约，所有模块共享同一套类型定义。

### 4.2.1 顶层数据结构：CHARGER

```plaintext
  typedef struct tagCharger { 
      CHARGER_INFO info;             // 软件版本、设备模型、参数定义、报警定义 
      CLUSTER_DEVICE devices;        // 设备列表（含参数、报警实例） 
      CLUSTER_SAMPLER samplers;      // 采样器列表 
      CLUSTER_REPORTER reporters;    // 报告器列表 
      CLUSTER_COMMPORT commports;    // 通信端口列表
      BOOL            bExit;         // 全局退出标志（所有线程监听此变量）
      int             nExitCode;     // 退出原因码
      HANDLE          hRunDB;        // 运行时 SQLite 数据库句柄
      HANDLE          hqEvent;       // 主事件队列（MAX=96条）
      BOOL            bIsInitOK;     // 初始化完成标志
      DATA_PROCESSOR  dataprocessor; // 数据处理器（函数指针集合）
      DAI             dai;           // 数据访问接口（函数指针集合）
} CHARGER;
```

CHARGER 是整个系统的"大脑"，贯穿所有模块，通过指针在各层传递。

### 4.2.2 设备模型层次

> CHARGER\_INFO
> 
> └── CLUSTER\_DEVICE\_MODEL\_INFO 设备模型定义
> 
> └── CLUSTER\_STATUS\_PARAM\_INFO 状态参数定义（含显示表达式、范围、枚举）
> 
> └── CLUSTER\_CONTROL\_PARAM\_INFO 控制参数定义（含控制条件表达式）
> 
> └── CLUSTER\_SETTING\_PARAM\_INFO 设置参数定义（含默认值、同步策略）
> 
> └── CLUSTER\_ALARM\_INFO 报警定义（含触发/屏蔽表达式）
> 
> CLUSTER\_DEVICE (devices)
> 
> └── DEVICE
> 
> ├── DEVICE\_INFO 设备标识（ID、名称、所属模型、所属采样器）
> 
> ├── CLUSTER\_PARAMETER params\_Status 运行时状态参数值
> 
> ├── CLUSTER\_PARAMETER params\_Control 运行时控制参数值
> 
> ├── CLUSTER\_PARAMETER params\_Setting 运行时设置参数值
> 
> └── CLUSTER\_ALARM alarms 运行时报警状态

### 4.2.3 参数值（PARAMETER）

> typedef struct tagA\_Param {
> 
> PARAM\_BASIC\_INFO\* pParamInfo; // 指向参数定义（静态配置）
> 
> HANDLE hDevice; // 所属设备
> 
> VAR\_VALUE currValue; // 当前值
> 
> BOOL bIsValueInvalid; // 当前值是否有效
> 
> VAR\_VALUE lastValue; // 上一次值（用于变化检测）
> 
> time\_t tmCurrValueRefreshed; // 最后更新时间
> 
> time\_t tmLastValueChanged; // 最后变化时间
> 
> } PARAMETER;

* * *

## 4.3 公共基础库（publib/）

publib 是整个系统的"地基"，编译为 libpub.so 动态库，供所有模块链接使用。

### 4.3.1 线程管理（iThread.c）

系统实现了一套 带心跳监控的线程管理框架：

线程管理器内部结构

> typedef struct tagThreadManager {
> 
> int nThreadEntries; // 当前分配的线程槽数（动态扩展，步长32）
> 
> RUN\_THREAD\_ENTRY \*pThreadEntries; // 线程条目数组
> 
> int volatile nRunningThreads; // 当前运行中的线程数
> 
> RUN\_THREAD\_EVENT\_HANDLER pfnEventHandler; // 事件处理回调（main 注册）
> 
> pthread\_mutex\_t hSyncLock; // 保护线程表的互斥锁
> 
> } RUN\_THREAD\_MANAGER;

每个线程条目

> typedef struct tagThreaEntry {
> 
> DWORD dwThreadId; // 高16位为魔术字，低16位为索引
> 
> char szThreadName\[16\];
> 
> pthread\_t hSystemThread;
> 
> DWORD volatile dwThreadHeartbeat; // 心跳计数器
> 
> int volatile nStatus; // RUN\_THREAD\_IS\_RUNNING / TO\_QUIT / IS\_INVALID
> 
> } RUN\_THREAD\_ENTRY;

心跳机制：

*   每个业务线程必须在循环中调用 RUN\_THREAD\_HEARTBEAT()
    
*   ThreadManager 每 500ms 检查一次所有线程心跳
    
*   若某线程 60 秒无心跳响应，触发 THREAD\_EVENT\_NO\_RESPONSE 回调
    
*   主控接收到回调后设置 bExit=TRUE，触发系统重启
    

main.c 中的线程事件处理器

> `static DWORD ThreadManager_EventHandler(DWORD dwThreadEvent, HANDLE hThread, const char *pszThreadName)`
> 
> `{`
> 
> `WatchDog_Feed(); // 每次心跳时喂狗`
> 
> `if(THREAD_EVENT_NO_RESPONSE == dwThreadEvent) {`
> 
> `s_pChargeThis->bExit = TRUE; s_pChargeThis->nExitCode = SYS_EXIT_BY_THREAD;`
> 
> `}`
> 
> `return THREAD_CONTINUE_RUN;`
> 
> `}`

### 4.3.2 线程安全队列（iQueue.c）

> `typedef struct tagQueue {`
> 
> `int nSize; // 单个元素大小`
> 
> `int nCapacity; // 队列容量`
> 
> `char *pBuffer; // 环形缓冲区`
> 
> `pthread_mutex_t hSyncLock;// 互斥锁`
> 
> `int nCount; // 当前元素数 int nHead; // 头指针`
> 
> `int nTail; // 尾指针`
> 
> `HANDLE hCountSem; // 信号量（用于阻塞等待）`
> 
> `} QUEUE;`

队列支持：

*   阻塞模式（bBlock=TRUE，带超时等待）
    
*   非阻塞模式（bBlock=FALSE，立即返回）
    
*   可配置的自动扩容（nIncStep > 0）
    

### 4.3.3 互斥锁与信号量（iMutex.c）

对 pthread\_mutex\_t 和 POSIX 信号量进行封装，提供带超时的 Mutex\_Lock，避免死锁。

* * *

## 4.4 通信端口硬件抽象层（comm\_ports/）

### 4.4.1 HAL 接口定义（comm\_port.h）

所有通信端口驱动必须导出统一的 6 个函数接口：

动态库必须导出的符号名称

> #define SYM\_HAL\_COMMOPEN "HAL\_CommOpen"
> 
> #define SYM\_HAL\_COMMACCEPT "HAL\_CommAccept"
> 
> #define SYM\_HAL\_COMMREAD "HAL\_CommRead"
> 
> #define SYM\_HAL\_COMMWRITE "HAL\_CommWrite"
> 
> #define SYM\_HAL\_COMMCONTROL "HAL\_CommControl"
> 
> #define SYM\_HAL\_COMMCLOSE "HAL\_CommClose"

通信端口对象（函数指针集合）

> typedef struct tagCommPort {
> 
> COMMPORT\_INFO info;
> 
> COMM\_OPEN\_PROC Open;
> 
> COMM\_CLOSE\_PROC Close;
> 
> COMM\_ACCEPT\_PROC Accept;
> 
> COMM\_READ\_PROC Read;
> 
> COMM\_WRITE\_PROC Write;
> 
> COMM\_CONTROL\_PROC Control;
> 
> HANDLE hLib; // dlopen 句柄
> 
> } COMMPORT;

### 4.4.2 动态加载机制（comm\_loader.c）

> static BOOL Load\_ACommPort(COMMPORT\* pCommPort) {
> 
> // 通过 dlopen 加载 .so 文件，路径由配置数据库决定
> 
> pCommPort->hLib = LoadDynamicLibrary( MakeFullFileName(szFullLibPath, getenv("NCCU\_APP\_PATH"), "comm\_port", pCommPort->info.szPortDriver),
> 
> ITEM\_OF(pszSymName),
> 
> pszSymName, // 需要查找的符号名数组
> 
> pfnProc, // 将函数地址写入 COMMPORT 对应成员
> 
> TRUE // 要求所有符号必须存在
> 
> );
> 
> }

### 4.4.3 支持的通信端口类型

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/864f19d4-9843-486e-91f2-47aa9037b0e3.png align="center")

* * *

## 4.5 采样器层（moduals/samplers/）

采样器负责从物理设备采集数据并回写到数据处理器，每个采样器运行在独立线程中。

### 4.5.1 采样器数据模型（sampler\_model.h）

```plaintext
typedef struct tagSampler { 

  SAMPLER_INFO info; // ID、名称、驱动库名、采样周期(ms) 
  COMMPORT *pUsedCommPort; // 使用的通信端口 
  HANDLE hSamplerLib; // dlopen 动态库句柄 
  HANDLE hSampleThread; // 采样线程句柄 
  HANDLE hDPR; // 数据处理器句柄（用于 Get/Set） 
  void *pDataHeap; // 采样器私有数据堆  
  // 动态加载的函数指针（插件接口）
  SAMPLER_INIT_PROC    Init;        // 初始化
  SAMPLER_SAMPLE_PROC  Sample;      // 采样主函数
  SAMPLER_CONTROL_PROC Control;     // 控制执行
  SAMPLER_NOTIFY_PROC  Notify;      // 接收 DPR 通知
  HANDLE          hqControlCommand; // 控制命令队列（异步接收来自 DPR 的控制命令）
  BYTE            emWorkStatus;     // 工作状态（工作中/断线/不工作）
  double          tmLastSampled;    // 最后采样时间
  BOOL            bJustControlled;  // 刚执行了控制命令，需立即重采样
  BOOL            bExit;            // 线程退出标志
} SAMPLER;
```

### 4.5.2 采样器线程主循环（sampler\_loader.c）

```plaintext
static DWORD __Thread_ASampler(void* pArgs) { 
    SAMPLER* pSampler = (SAMPLER*)pArgs; 
    while(!pSampler->bExit) { RUN_THREAD_HEARTBEAT(); // 向 ThreadManager 汇报心跳      
    // 1. 优先处理控制命令队列（最多连续处理6条）
    while(Queue_Get(pSampler->hqControlCommand, &cmd, FALSE, 100) == OK)
      {
          pSampler->Control(hSamplerSelf, PARAM_TYPE_CONTROL,
                            cmd.data.nDevId, cmd.data.nParamId,
                            &cmd.data.varValue, cmd.data.nValueSize, INFINITE);
          // 控制成功后，回写结果到 DPR
          pDPR->Set(pSampler, SETTYPE_RAW_BACK_CTRL, &cmd.data, sizeof(INTERSET_DATA));
      }

      // 2. 按采样周期触发采样
      if(IsTimeToSample(pSampler, GetCurrentTime()))
      {
          pSampler->Sample(hSamplerSelf);  // 调用插件的 Sample 函数
      }
  }
}
```

### 4.5.3 已实现的采样器插件

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/d8c14aab-181e-40b5-b320-1d2b3f7998ee.png align="center")

* * *

## 4.6 数据处理器层（moduals/data\_processor/dpr/）

数据处理器（DPR）是系统的中央总线与大脑，负责：

1.  配置加载：从 SQLite 读取所有设备模型、参数定义、报警定义
    
2.  数据管理：维护所有设备参数的实时值（线程安全）
    
3.  事件分发：向注册了感兴趣事件的所有模块广播事件
    
4.  报警判定：基于表达式引擎计算报警触发/屏蔽条件
    
5.  历史数据持久化：将关键数据写入 SQLite
    

### 4.6.1 DPR 接口（dataprocessor\_model.h）

```plaintext
typedef struct tagDataProc { 
  DATAPROC_INFO info; 
  HANDLE hThread; 
  BOOL bExit;  
  // 供 main 调用的接口
  LOADCFG_PROC        LoadConfigAndData;  // 从 DB 加载所有配置
  STARTSTOPDATA_PROC  DoDataProcessing;   // 启动数据处理线程
  PROCESSEVENT_PROC   DoEvent;            // 处理来自主队列的事件

  // 供 Sampler 调用的接口
  INTER_GET           Get;   // 采样器读取参数值
  INTER_SET           Set;   // 采样器推送采样结果
  CALCULATE_EXPRESSION CalculateExp; // 计算逻辑表达式
  REGISTER_EVHAND     Register_EvHandler; // 注册事件处理器
} DATA_PROCESSOR;
```

### 4.6.2 内部数据流向（Set 接口的类型枚举）

```plaintext
typedef enum tagInternalSetType { 
    SETTYPE_RAW_SP, // 采样器推送原始状态参数值 
    SETTYPE_LOGIC_SP, // 逻辑计算出的状态参数值 
    SETTYPE_INTERN_SETT, // 设置参数同步到其他采样器 
    SETTYPE_RAW_ST, // 设置参数本地刷新（不同步） 
    SETTYPE_INTERN_CTRL, // 控制参数分发到对应采样器 
    SETTYPE_SENDMSG, // 发送消息到 Reporter/DPR 
    SETTYPE_RAW_BACK_CTRL, // 控制执行结果回写 
    SETTYPE_RAW_BACK_SETT, // 设置执行结果回写
    SETTYPE_RESTORE_SETTINGS_FROMDB, // 从 SD 卡恢复配置
    SETTYPE_CLEAR_FACTOY_DEBUG_DATA, // 清除工厂调试数据 
} INTERSET_TYPE;
```

* * *

## 4.7 数据访问接口层（data\_interfaces/DAI）

DAI 是 Reporter 访问系统数据的标准接口，将 DPR 的内部实现与 Reporter 完全解耦。

### 4.7.1 DAI 接口集合（dai\_model.h）

```plaintext
typedef struct tagDAI { 
    DAI_GET Get; // 读取设备信息、参数值、报警状态 
    DAI_SET Set; // 设置参数（控制命令/设置参数） 
    DAI_QUERY Query; // 历史数据查询（充电记录、报警、事件） 
    DAI_SIGNAL Signal; // 向主事件队列投递事件 
    DAI_REGISTER_EVHAND Regsiter_EvHandler; // 注册事件监听器 
} DAI;
```

### 4.7.2 DAI\_GET 支持的查询类型

```plaintext
typedef enum tagDAIType { 
    DAITYPE_DEV_TOTAL_R, // 设备总数 
    DAITYPE_DEV_INFO_R, // 设备信息（按索引） 
    DAITYPE_PARAM_TOTAL_R, // 参数总数（状态/控制/设置） 
    DAITYPE_PARAM_INFO_R, // 参数定义信息 
    DAITYPE_ALARM_TOTAL_R, // 报警总数 
    DAITYPE_ALARM_INFO_R, // 报警定义信息 
    DAITYPE_STAT_PARAMVALUE_R, // 读取实时状态参数值 
    DAITYPE_SETT_PARAMVALUE_RW, // 读写设置参数 
    DAITYPE_CTRL_PARAMVALUE_RW, // 写控制参数（发控制命令） 
    DAITYPE_ALARMSTAT_R, // 读取报警状态 
    DAITYPE_GETLOCALLIST_VERSION, // OCPP 本地授权列表版本 
    DAITYPE_COMMPORT_R, // 读取通信端口信息 
    DAITYPE_VIRTUAL_PARAMVALUE_W, // Reporter 写入虚拟参数                    
    DAITYPE_COMM_DATA_DUMP, // 通信数据转储 
    ... 
} DAI_TYPE;
```

* * *

## 4.8 报告器层（moduals/reporters/）

报告器负责实现对外协议，向上级系统、后台或用户界面上报数据和处理指令。每个报告器运行在独立线程，通过 DAI 接口访问数据，通过事件机制感知系统状态变化。

### 4.8.1 报告器数据模型（reporter\_model.h）

```plaintext
typedef struct tagReporter { 
    REPORTER_INFO info; // ID、名称、驱动库名、配置字符串 
    void *pDataHeap; // 报告器私有数据 
    HANDLE hSamplerLib; // 动态库句柄 
    COMMPORT *pUsedCommPort; // 使用的通信端口 
    HANDLE hThread; // 报告器主线程 
    BOOL bExit; // 退出标志 
    HANDLE hqEvent; // 事件队列（接收 DPR 的事件推送） 
    DAI *pDAI; // 数据访问接口指针 
    INIT_PROC Init; // 初始化函数（启动线程） 
} REPORTER;
```

### 4.8.2 已实现的报告器插件

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/28e9385c-2288-4412-8a00-b9f018f053e8.png align="center")

* * *

## 4.9 事件驱动机制

事件机制是各层间解耦通信的核心手段。

### 4.9.1 事件类型（event\_model.h）

```plaintext
typedef enum tagEvent_Type { 
    EVENT_TYPE_ALARM, // 报警触发/消除 
    EVENT_TYPE_CFG_CHANGED, // 配置变更 
    EVENT_TYPE_CHARGING, // 充电状态变化 
    EVENT_TYPE_USER_AUTHORIZE, // 用户授权 
    EVENT_TYPE_SYSTEM_EXIT, // 系统退出 
    EVENT_TYPE_COMM_FAIL, // 通信失败 
    EVENT_TYPE_CHARGEREC_UPDATE, // 充电记录更新 
    EVENT_TYPE_FIRMWARE_UPDATE, // 固件升级状态 
    EVENT_TYPE_DIAGNOSTIC_UPLOAD, // 诊断日志上传         
    EVENT_TYPE_METERVAL_UPDATE_IN_CHARGING,// 充电过程中电表值更新     
    EVENT_TYPE_BMSRUNDATA_UPDATE, // BMS 运行数据更新 
    EVENT_TYPE_HEALTHDATA_UPDATE, // 健康数据更新 
    EVENT_TYPE_REFRESH_ALARM, // 刷新报警 
    ... 
} _EVENT_TYPE;
```

### 4.9.2 事件对象（EVENT\_ARG）

```plaintext
typedef struct tagEventArg { 
    _EVENT_TYPE emEvType; // 事件类型 
    _EVENT_MSG emMsgType; // 消息阶段（BEGIN/STATUS_CHANGED/END/RESTORE） 
    UINT nEvDataSize; // 数据大小 
    void *pEvData; // 事件数据指针 
    BOOL bHighPriority; // 高优先级（插队到队头） 
    BOOL bBlockMode; // 阻塞模式（等待处理完成） 
    HANDLE hSender; // 发送者句柄 
    EVENT_RECEIPT pfnReceipt; // 处理完成回调 
} EVENT_ARG;
```

### 4.9.3 事件流向

```plaintext
Sampler (数据变化/报警触发) 
│ DPR->Set(SETTYPE_SENDMSG) 
▼ 
DPR 检测到状态变化 → 生成 EVENT_ARG 
│ Queue_Put(s_pChargeThis->hqEvent) 
▼ 
主事件循环 (main.c) 取出事件 
│ dataprocessor.DoEvent(&ev_arg) 
▼ 
DPR 广播给所有已注册的 EVENT_HANDLER 
│ 遍历 EVENT_HANDLER_REGISTER 列表 
├──▶ Reporter A 的 hqEvent (OCPP 关心 CHARGING 事件) 
├──▶ Reporter B 的 hqEvent (IEC104 关心 ALARM 事件) 
└──▶ Reporter C 的 hqEvent (GUI 关心所有事件)
```

* * *

## 4.10 配置数据库（SQLite）

系统配置采用 SQLite 数据库文件（config/System.cfg），核心表结构如下：

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/a6a8cf7a-5814-4b70-84cf-50907a9ee613.png align="center")

SQLite 运行策略：

*   启动阶段用 序列化模式（SQLITE\_CONFIG\_SERIALIZED）安全加载配置
    
*   切换到运行时后调用 Set\_SqliteDB\_2RunTime\_Mode()，优化为高并发模式
    
*   通过 文件锁 防止多进程冲突（系统升级时其他进程可能操作数据库）
    

* * *

# 五、系统可靠性设计

## 5.1 多重故障保护机制

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/1cbb480d-8cdf-445b-992e-9252ed663af0.png align="center")

硬件看门狗（WDT）

│ 每次 ThreadManager 心跳时喂狗（60秒内必须喂一次）

│ ThreadManager 心跳监控（每 60 秒检查所有线程）

│ 每个业务线程循环调用 RUN\_THREAD\_HEARTBEAT()

│

信号处理（SIGSEGV/SIGILL/SIGBUS 等） → 记录日志 → longjmp 退出 → WDT 超时 → 系统 reboot

进程心跳（Do\_Proc\_HeartBeat） → 通知 sys\_hand 进程，app 进程仍在存活 → sys\_hand 若检测 app 消失，可触发 SIGUSR1 信号通知

## 5.2 优雅退出机制

```plaintext
longjmp 机制：从任意深度快速返回主函数 
void Main_Exit(int nExitCode) { 
    // 通知所有 Sampler/Reporter 退出 
    for(n = 0; n < nTotal; n++) pSampler->bExit = TRUE;  
    WatchDog_Close(FALSE); // 不禁用看门狗（让 WDT 触发重启）
    longjmp(s_JumpBufFaultError, nExitCode); // 跳回 main 的 setjmp 点
}

// 在 setjmp 接收点： 
    int nExitCode = setjmp(s_JumpBufFaultError); 
    if(nExitCode != 0) { 
        RunThread_ManagerExit(10000, TRUE); // 等待最多 10 秒让所有线程退出         
        _exit(nExitCode); 
}
```

* * *

# 六、构建系统

## 6.1 构建体系（Makefile + include.mk）

交叉编译工具链

> GCC\_PREFIX = arm-linux-gnueabihf- # ARM 目标平台
> 
> CC = ccache $(GCC\_PREFIX)gcc # 使用 ccache 加速编译

**构建目标：**

**\- libpub.so 公共基础库**

**\- libcomm\_xxx.so 各通信端口驱动（8种）**

**\- sampler\_xxx.so 各采样器插件（12种）**

**\- reporter\_xxx.so 各报告器插件（15种）**

**\- nccu\_app 主程序可执行文件**

**平台差异**

> ifeq ($(NCCU\_CROSS\_PLATFORM), arm)
> 
> CFLAGS += -D\_HAS\_FLASH\_MEM=1 # 启用看门狗、闪存支持
> 
> endif
> 
> ifeq ($(MU\_TYPE), mu3)
> 
> CFLAGS += -DIMMU3 # IMMU3 硬件平台差异
> 
> endif

## 6.2 第三方依赖

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/f7de1433-a424-4348-8046-596ebed1603b.png align="center")

* * *

# 七、架构优缺点总结

## 优点

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/9baec8e7-0103-466d-ae8a-251d09beca67.png align="center")

不足与值得关注的点

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/ef63ba80-94c0-46ff-87cc-d907a13d5b1d.png align="center")

* * *

# 八、架构图总览

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/e36df2c5-41c8-49b5-a51b-27491f4326cc.png align="center")

┌────────────────────────────────────────────────────────────────────────┐ │ NCCU 软件系统架构 │ │ │ │ ┌──────────────────────── 硬件平台 ─────────────────────────────┐ │ │ │ AM335x ARM Cortex-A8 | IMMU2 / IMMU3 | Linux 内核 │ │ │ │ 硬件看门狗 /dev/wd | CAN Bus | RS485 | 触摸屏 │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ ┌── main/ 主控层 ────────────────────────────────────────────────┐ │ │ │ main.c: 启动序列 | 事件主循环 | longjmp 退出机制 │ │ │ │ sig\_handler.c: 信号处理 watch\_dog.c: 硬件看门狗控制 │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ 统一使用

●P│ ┌── publib/ 基础库层（libpub.so）────────────────────────────────┐ │ │ │ iThread: 线程管理+心跳 iQueue: 线程安全队列 iMutex: 互斥锁 │ │ │ │ iLog/iLog4c: 日志框架 iDataBase: SQLite封装 iNew: 内存池 │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌── include/data\_model/ 数据模型层（共享类型定义）─────────────────┐ │ │ │ CHARGER → DEVICE → PARAMETER(状态/控制/设置) → ALARM │ │ │ │ DATA\_PROCESSOR(函数指针集) │ DAI(函数指针集) │ EVENT\_ARG │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ ┌──────────────┬───────────────────┬──────────────────┐ │ │ ┌──▼──────────┐ ┌─▼──────────────┐ ┌──▼────────────────┐│ │ │ │ DPR（核心 │ │ Sampler 层 │ │ Reporter 层 ││ │ │ │ 数据总线） │ │ 采样器插件 │ │ 报告器插件 ││ │ │ │ 参数管理 │ │ charger.so │ │ ocpp.so ││ │ │ │ 事件分发 │ │ kwhmeter.so │ │ modbus.so ││ │ │ │ 报警判定 │ │ system.so │ │ iec104.so ││ │ │ │ 历史持久化 │ │ ccu.so ... │ │ mqtt.so ... ││ │ │ └──────────┘ └────────┬───────┘ └────────┬──────────┘│ │ │ ▲ DPR.Get/Set │ │ DAI.Get/Set/Query │ │ ┌──┴─────────── data\_interfaces/DAI 层 ──┘ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ │ ┌── comm\_ports/ 通信端口 HAL 层（动态库）────────────────────────┐ │ │ │ serial.so │ tcpip.so │ udp.so │ websocket.so │ │ │ │ httpftp.so │ can.so │ ipc.so │ websocket\_ex.so │ │ │ └──────────────────────────────────────────────────────────────┘ │ └────────────────────────────────────────────────────────────────────────┘

* * *

# 九、关键设计模式总结

![](https://cdn.hashnode.com/uploads/covers/697987795e43af610b063eaa/3d237af1-503e-4d4d-8828-a1cc5a193c9e.png align="center")

┌────────────┬────────────────────────────────────────────────────────────────────────────────────┐ │ 设计模式 │ 在系统中的体现 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 插件模式 │ Sampler/Reporter/CommPort 均以 .so 形式存在，dlopen 动态加载，主程序不依赖具体实现 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ HAL 抽象 │ CommPort 统一 6 个函数接口（Open/Accept/Read/Write/Control/Close），屏蔽硬件差异 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 观察者模式 │ Reporter 通过 Register\_EvHandler 向 DPR 注册感兴趣的事件类型，DPR 广播时精准投递 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 命令模式 │ 控制命令封装为 INTER\_QUEUE\_CMD 投入采样器命令队列，异步执行 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 门面模式 │ DAI 作为统一门面，Reporter 无需了解 DPR 内部实现，仅调用 Get/Set/Query/Signal │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 策略模式 │ DATA\_PROCESSOR 和 DAI 均为函数指针结构体，可替换不同实现策略 │ ├────────────┼────────────────────────────────────────────────────────────────────────────────────┤ │ 心跳守卫 │ ThreadManager + WDT 构成双重"死锁/卡死"守卫，确保系统自愈 │ └────────────┴────────────────────────────────────────────────────────────────────────────────────┘

* * *

以上即为本工程的完整软件架构分析文档。整个系统体现了工业嵌入式软件的典型设计哲学：以稳定性为第一优先级，以可扩展性为第 二优先级，通过插件化和接口抽象实现协议的灵活扩展，同时通过多重硬件与软件守卫机制保障 7×24 小时不间断运行。
