mirror of
https://github.com/jackyzha0/quartz.git
synced 2025-12-24 13:24:05 -06:00
[PUBLISHER] Merge #54
* PUSH NOTE : EVerest-core.md * PUSH NOTE : EVerest-framework.md * PUSH NOTE : EVerest-timer.md
This commit is contained in:
parent
650e87436b
commit
68cb7cea8f
@ -1,6 +1,6 @@
|
||||
---
|
||||
date: 2025-03-05 10:33
|
||||
updated: 2025-03-11 11:39
|
||||
updated: 2025-03-11 14:40
|
||||
tags: Everest,ocpp
|
||||
link:
|
||||
publish: true
|
||||
@ -430,6 +430,29 @@ charging_schedules_timer->start();
|
||||
|
||||
### 启动流程
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant E as EVerest框架
|
||||
participant O as OCPP模块
|
||||
participant CP as ChargePoint核心
|
||||
participant EM as EVSE管理器
|
||||
participant S as 安全模块
|
||||
|
||||
E->>O: 构造
|
||||
O->>O: 依赖注入
|
||||
E->>O: init()
|
||||
O->>O: 初始化EVSE Ready Map
|
||||
O->>EM: 订阅EVSE Ready事件
|
||||
O->>O: 加载配置文件
|
||||
O->>O: 创建ChargePoint实例
|
||||
O->>S: 创建EvseSecurity包装
|
||||
E->>O: ready()
|
||||
O->>O: 初始化EVSE连接器映射
|
||||
O->>CP: 注册多个回调函数
|
||||
O->>CP: 启动WebSocket连接
|
||||
O->>O: 设置充电计划定时器
|
||||
```
|
||||
|
||||
1. 模块构造 - 依赖注入
|
||||
2. init() - 初始化内部状态
|
||||
3. ready() - 启动服务
|
||||
@ -437,6 +460,170 @@ charging_schedules_timer->start();
|
||||
5. 注册回调函数
|
||||
6. 开始定时任务
|
||||
|
||||
#### EVSE就绪映射初始化
|
||||
|
||||
模块使用 `evse_ready_map` 追踪所有EVSE的就绪状态:
|
||||
|
||||
```cpp
|
||||
void OCPP::init_evse_ready_map() {
|
||||
std::lock_guard<std::mutex> lk(this->evse_ready_mutex);
|
||||
for (size_t evse_id = 1; evse_id <= this->r_evse_manager.size(); evse_id++) {
|
||||
this->evse_ready_map[evse_id] = false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
在init方法中,模块订阅每个EVSE的就绪状态:
|
||||
|
||||
```cpp
|
||||
for (size_t evse_id = 1; evse_id <= this->r_evse_manager.size(); evse_id++) {
|
||||
this->r_evse_manager.at(evse_id - 1)->subscribe_ready([this, evse_id](bool ready) {
|
||||
std::lock_guard<std::mutex> lk(this->evse_ready_mutex);
|
||||
if (ready) {
|
||||
this->evse_ready_map[evse_id] = true;
|
||||
this->evse_ready_cv.notify_one();
|
||||
}
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
这种设计确保OCPP模块只有在所有EVSE都准备好后才会完全启动服务。
|
||||
|
||||
#### 配置文件处理
|
||||
|
||||
OCPP模块处理两种配置文件:
|
||||
|
||||
- 主配置文件:包含基本OCPP设置
|
||||
- 用户配置文件:包含用户自定义设置
|
||||
|
||||
从哪里传进去的?
|
||||
|
||||
```cpp
|
||||
// 寻找并加载主配置文件
|
||||
auto configured_config_path = fs::path(this->config.ChargePointConfigPath);
|
||||
if (!fs::exists(configured_config_path) && configured_config_path.is_relative()) {
|
||||
configured_config_path = this->ocpp_share_path / configured_config_path;
|
||||
}
|
||||
|
||||
// 解析JSON配置
|
||||
std::ifstream ifs(config_path.c_str());
|
||||
std::string config_file((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
auto json_config = json::parse(config_file);
|
||||
json_config.at("Core").at("NumberOfConnectors") = this->r_evse_manager.size();
|
||||
|
||||
// 合并用户配置
|
||||
if (fs::exists(user_config_path)) {
|
||||
std::ifstream ifs(user_config_path.c_str());
|
||||
std::string user_config_file((std::istreambuf_iterator<char>(ifs)), (std::istreambuf_iterator<char>()));
|
||||
const auto user_config = json::parse(user_config_file);
|
||||
json_config.merge_patch(user_config);
|
||||
}
|
||||
```
|
||||
|
||||
这种方法允许系统管理员通过两个配置文件分别维护基本设置和用户定制,增强了配置的灵活性。
|
||||
|
||||
#### EVSE-连接器映射建立
|
||||
|
||||
OCPP需要维护EVerest内部EVSE/连接器ID与OCPP协议ID的映射关系
|
||||
|
||||
```c
|
||||
void OCPP::init_evse_connector_map() {
|
||||
int32_t ocpp_connector_id = 1; // OCPP连接器ID
|
||||
int32_t evse_id = 1; // EVerest EVSE管理器ID
|
||||
|
||||
for (const auto& evse : this->r_evse_manager) {
|
||||
const auto _evse = evse->call_get_evse();
|
||||
std::map<int32_t, int32_t> connector_map; // 映射EVerest连接器ID到OCPP连接器ID
|
||||
|
||||
// 检查EVSE ID是否连续
|
||||
if (_evse.id != evse_id) {
|
||||
throw std::runtime_error("Configured evse_id(s) must be starting with 1 counting upwards");
|
||||
}
|
||||
|
||||
// 处理每个连接器
|
||||
for (const auto& connector : _evse.connectors) {
|
||||
connector_map[connector.id] = ocpp_connector_id;
|
||||
this->connector_evse_index_map[ocpp_connector_id] = evse_id - 1; // 索引对应r_evse_manager
|
||||
ocpp_connector_id++;
|
||||
}
|
||||
|
||||
// 处理没有显式连接器的EVSE
|
||||
if (connector_map.size() == 0) {
|
||||
this->connector_evse_index_map[ocpp_connector_id] = evse_id - 1;
|
||||
connector_map[1] = ocpp_connector_id;
|
||||
ocpp_connector_id++;
|
||||
}
|
||||
|
||||
this->evse_connector_map[_evse.id] = connector_map;
|
||||
evse_id++;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个映射是OCPP模块的核心数据结构之一,用于在OCPP连接器ID与EVerest内部ID之间转换,确保消息正确路由。
|
||||
|
||||
#### ChargePoint核心创建
|
||||
|
||||
在init方法结束时,模块创建了OCPP通信的核心组件
|
||||
|
||||
```cpp
|
||||
this->charge_point = std::make_unique<ocpp::v16::ChargePoint>(
|
||||
json_config.dump(), // 配置JSON
|
||||
this->ocpp_share_path, // 共享路径
|
||||
user_config_path, // 用户配置路径
|
||||
std::filesystem::path(this->config.DatabasePath), // 数据库路径
|
||||
sql_init_path, // SQL初始化脚本路径
|
||||
std::filesystem::path(this->config.MessageLogPath), // 消息日志路径
|
||||
std::make_shared<EvseSecurity>(*this->r_security)); // 安全组件
|
||||
```
|
||||
|
||||
这一步创建了处理所有OCPP通信的核心对象,并为其提供了:
|
||||
|
||||
- 配置数据
|
||||
- 持久化存储位置
|
||||
- 日志记录路径
|
||||
- 安全接口
|
||||
|
||||
EvseSecurity是一个适配器类,将EVerest安全接口转换为OCPP库需要的接口格式。
|
||||
|
||||
#### 回调函数注册
|
||||
|
||||
在ready方法中,模块注册了多个回调函数,建立了OCPP事件与EVerest动作之间的桥梁
|
||||
|
||||
##### 充电控制回调
|
||||
|
||||
```cpp
|
||||
// 暂停充电回调
|
||||
this->charge_point->register_pause_charging_callback([this](int32_t connector) {
|
||||
if (this->connector_evse_index_map.count(connector)) {
|
||||
return this->r_evse_manager.at(this->connector_evse_index_map.at(connector))->call_pause_charging();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 恢复充电回调
|
||||
this->charge_point->register_resume_charging_callback([this](int32_t connector) {
|
||||
if (this->connector_evse_index_map.count(connector)) {
|
||||
return this->r_evse_manager.at(this->connector_evse_index_map.at(connector))->call_resume_charging();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// 停止交易回调
|
||||
this->charge_point->register_stop_transaction_callback([this](int32_t connector, ocpp::v16::Reason reason) {
|
||||
if (this->connector_evse_index_map.count(connector)) {
|
||||
types::evse_manager::StopTransactionRequest req;
|
||||
req.reason = types::evse_manager::string_to_stop_transaction_reason(
|
||||
ocpp::v16::conversions::reason_to_string(reason));
|
||||
return this->r_evse_manager.at(this->connector_evse_index_map.at(connector))->call_stop_transaction(req);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### OCPP消息处理流程
|
||||
|
||||
#### 接收消息
|
||||
|
||||
@ -620,5 +620,5 @@ graph LR
|
||||
|
||||
- CodeCoverage.cmake (用于代码覆盖率测试)
|
||||
- 元编程宏库 (来自 libextobjc)
|
||||
- EVerest-timer
|
||||
- [[./EVerest-timer|EVerest-timer]]
|
||||
- EVerest-log
|
||||
|
||||
244
content/Obsidian/OCPP/Everest/EVerest-timer.md
Normal file
244
content/Obsidian/OCPP/Everest/EVerest-timer.md
Normal file
@ -0,0 +1,244 @@
|
||||
---
|
||||
date: 2025-03-05 15:51
|
||||
updated: 2025-03-11 11:42
|
||||
tags:
|
||||
- Everest
|
||||
- ocpp
|
||||
publish: true
|
||||
link:
|
||||
share: "true"
|
||||
---
|
||||
|
||||
# 项目概述
|
||||
|
||||
libtimer 是 EVerest 项目的一个组件,主要==提供定时器功能==。EVerest 是一个开源的电动汽车充电站操作系统。libtimer 是一个相对独立的库,主要目的是为了支持==日志文件回放==功能:
|
||||
|
||||
```md
|
||||
C++ timer library for the EVerest framework
|
||||
===========================================
|
||||
EVerest is aiming to enable logfile replay. All EVerest components have to utilize this library for time (and delay) related things in order to enable accelerated logfile replay speed in the future.
|
||||
|
||||
All documentation and the issue tracking can be found in our main repository here: https://github.com/EVerest/everest
|
||||
|
||||
|
||||
```
|
||||
|
||||
# libtimer 测试代码分析
|
||||
|
||||
从测试代码 <mcfile name="libtimer_unit_test.cpp" path="d:\Code\github\EVerest\libtimer\tests\libtimer_unit_test.cpp"></mcfile> 中,可以看到:
|
||||
|
||||
1. 该库使用 Google Test 框架进行单元测试
|
||||
2. 测试类 `LibTimerUnitTest` 继承自 `::testing::Test`
|
||||
3. 包含标准的 `SetUp` 和 `TearDown` 方法
|
||||
4. 目前只有一个简单的示例测试用例 `just_an_example`
|
||||
|
||||
# 与 everest-framework 的集成
|
||||
|
||||
从 [[./EVerest-framework|EVerest-framework]] 的 `Cmakelists.txt` 中,我们可以看到 libtimer 与 EVerest-framework 的关系:
|
||||
|
||||
1. EVerest-framework 依赖于 EVerest-timer(即 libtimer)
|
||||
2. 在 CMake 配置中,EVerest-framework 将 EVerest-timer 作为必需的依赖项
|
||||
3. 在打包时,EVerest-framework 会包含对 EVerest-timer 的依赖声明
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
framework[everest-framework] --> timer[everest-timer/libtimer]
|
||||
framework --> log[everest-log]
|
||||
framework --> json[nlohmann_json]
|
||||
framework --> schema[nlohmann_json_schema_validator]
|
||||
framework --> fmt[fmt库]
|
||||
framework --> date[date库]
|
||||
```
|
||||
|
||||
# libtimer 功能
|
||||
|
||||
libtimer 是一个接口库(interface library),是一个纯头文件库(header-only library),实现代码都在头文件 `timer.hpp` 中。这种设计方式在C++中很常见,特别是对于模板库来说,因为**模板必须在编译时可见**。
|
||||
|
||||
## `timer.hpp`
|
||||
|
||||
这是一个基于3.4C++模板的计时器类,默认使用`date::utc_clock`作为时钟类型:
|
||||
|
||||
```16:17:libtimer/include/everest/timer.hpp
|
||||
// template <typename TimerClock = date::steady_clock> class Timer {
|
||||
template <typename TimerClock = date::utc_clock> class Timer {
|
||||
```
|
||||
|
||||
### 主要成员变量
|
||||
|
||||
- `timer`: ASIO的定时器对象
|
||||
- `callback`: 用户定义的回调函数
|
||||
- `callback_wrapper`: 用于 interval 模式的包装回调函数
|
||||
- `io_context`: boost::asio的IO上下文
|
||||
- `timer_thread`: 定时器线程指针
|
||||
|
||||
### 构造函数
|
||||
|
||||
类提供了四个构造函数:
|
||||
|
||||
- 默认构造函数:创建自己的 io_context 和线程
|
||||
- 带回调函数的构造函数:创建自己的 io_context 和线程
|
||||
- 使用外部 io_context 的构造函数
|
||||
- 使用外部 io_context 和回调函数的构造函数
|
||||
|
||||
### 主要功能方法
|
||||
|
||||
- `at()`: 在指定时间点执行回调
|
||||
- `interval()`: 按指定时间间隔周期性执行回调
|
||||
- `timeout()`: 在指定延时后执行一次回调
|
||||
- `stop()`: 停止定时器
|
||||
|
||||
### 别名
|
||||
|
||||
类提供了两个别名:
|
||||
|
||||
```180:181:libtimer/include/everest/timer.hpp
|
||||
using SteadyTimer = Timer<date::utc_clock>;
|
||||
using SystemTimer = Timer<date::utc_clock>;
|
||||
```
|
||||
|
||||
### 主要特点
|
||||
|
||||
1. 基于boost::asio实现,支持异步操作
|
||||
2. 支持自管理和外部管理两种IO上下文模式
|
||||
3. 提供了精确的时间控制功能
|
||||
4. 线程安全设计
|
||||
5. 支持多种时间点和时间间隔的表示方式
|
||||
6. 可以方便地用于日志回放功能,这也是该库的主要目的
|
||||
|
||||
使用示例可以参考examples/main.cpp,其中展示了如何使用at、interval和timeout等功能。这个库的设计非常灵活,既可以独立使用,也可以集成到使用boost::asio的更大系统中。
|
||||
|
||||
### 代码解读
|
||||
|
||||
以`at`函数为例:
|
||||
|
||||
```c
|
||||
/// Executes the given callback at the given timepoint
|
||||
template <class Clock, class Duration = typename Clock::duration>
|
||||
void at(const std::function<void()>& callback, const std::chrono::time_point<Clock, Duration>& time_point) {
|
||||
this->stop();
|
||||
|
||||
this->callback = callback;
|
||||
|
||||
this->at(time_point);
|
||||
}
|
||||
|
||||
/// Executes the at the given timepoint
|
||||
template <class Clock, class Duration = typename Clock::duration>
|
||||
void at(const std::chrono::time_point<Clock, Duration>& time_point) {
|
||||
this->stop();
|
||||
|
||||
if (this->callback == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->timer != nullptr) {
|
||||
// use asio timer
|
||||
this->timer->expires_at(time_point);
|
||||
this->timer->async_wait([this](const boost::system::error_code& e) {
|
||||
if (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
this->callback();
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这段代码定义了一个定时器类中的两个 at() 方法,用于在指定的时间点执行回调函数。
|
||||
|
||||
```c
|
||||
template <class Clock, class Duration = typename Clock::duration>
|
||||
```
|
||||
|
||||
这一行声明了一个模板函数,**可以接受不同类型的时钟和时间间隔**。
|
||||
|
||||
```cpp
|
||||
void at(const std::function<void()>& callback,
|
||||
const std::chrono::time_point<Clock, Duration>& time_point )
|
||||
```
|
||||
|
||||
第一个参数 `const std::function<void()>& callback:`
|
||||
|
||||
- `std::function<void()>` 是一个函数包装器,可以存储任何无参数、无返回值的可调用对象(函数、lambda表达式等)
|
||||
- const 表示这个参数不会被修改
|
||||
- 1.15C++引用 表示通过引用传递,避免复制开销
|
||||
|
||||
第二个参数 `const std::chrono::time_point<Clock, Duration>& time_point:`
|
||||
|
||||
- `std::chrono::time_point` 是C++标准库中表示时间点的类型
|
||||
- Clock 是模板参数,指定使用哪种时钟(比如系统时钟或稳定时钟)
|
||||
- Duration 是时间精度的类型(如纳秒、微秒等)
|
||||
- const 和 & 的含义同上
|
||||
|
||||
### 为什么要使用模板实现
|
||||
|
||||
#### 灵活性和可扩展性
|
||||
|
||||
```16:17:libtimer/include/everest/timer.hpp
|
||||
// template <typename TimerClock = date::steady_clock> class Timer {
|
||||
template <typename TimerClock = date::utc_clock> class Timer {
|
||||
```
|
||||
|
||||
从代码中可以看到,Timer类默认使用`date::utc_clock`作为时钟类型,但通过模板参数可以灵活地更换其他时钟类型。这种设计允许用户根据需要选择不同的时钟实现,比如:
|
||||
|
||||
- `date::steady_clock`:单调递增时钟
|
||||
- `date::utc_clock`:UTC时钟
|
||||
- `std::chrono`中的其他时钟类型
|
||||
|
||||
timer是如何通过模板实现自动适配不同时间类型
|
||||
|
||||
#### 类型安全
|
||||
|
||||
```69:76:libtimer/include/everest/timer.hpp
|
||||
template <class Clock, class Duration = typename Clock::duration>
|
||||
void at(const std::function<void()>& callback, const std::chrono::time_point<Clock, Duration>& time_point) {
|
||||
this->stop();
|
||||
|
||||
this->callback = callback;
|
||||
|
||||
this->at(time_point);
|
||||
}
|
||||
```
|
||||
|
||||
在`at()`方法中使用模板参数`Clock`和`Duration`,这样可以:
|
||||
|
||||
- 在编译时确保时间点类型的正确性
|
||||
- 支持不同精度的时间单位(如秒、毫秒、微秒等)
|
||||
- 避免运行时类型转换带来的开销
|
||||
|
||||
#### 通用性
|
||||
|
||||
```101:108:libtimer/include/everest/timer.hpp
|
||||
template <class Rep, class Period>
|
||||
void interval(const std::function<void()>& callback, const std::chrono::duration<Rep, Period>& interval) {
|
||||
this->stop();
|
||||
|
||||
this->callback = callback;
|
||||
|
||||
this->interval(interval);
|
||||
}
|
||||
```
|
||||
|
||||
`interval()`方法使用模板参数`Rep`和`Period`,这使得函数可以接受任何符合`std::chrono::duration`概念的时间间隔类型,比如:
|
||||
|
||||
- `std::chrono::seconds`
|
||||
- `std::chrono::milliseconds`
|
||||
- 自定义的时间单位
|
||||
|
||||
#### 日志回放功能
|
||||
|
||||
```2:3:libtimer/README.md
|
||||
===========================================
|
||||
EVerest is aiming to enable logfile replay. All EVerest components have to utilize this library for time (and delay) related things in order to enable accelerated logfile replay speed in the future.
|
||||
```
|
||||
|
||||
从README可以看出,这个库的主要目的是支持日志回放功能。使用模板可以让时钟类型可配置,这样在回放模式下可以使用模拟时钟来加速或减速时间流逝,而在正常模式下使用实际的系统时钟。
|
||||
|
||||
#### 性能优化
|
||||
|
||||
- **模板代码在编译时展开,没有运行时开销**
|
||||
- 编译器可以针对具体的时钟类型进行优化
|
||||
- 避免了虚函数调用和类型擦除带来的性能损失
|
||||
|
||||
通过使用模板,这个Timer类实现了高度的灵活性和类型安全,同时保持了良好的性能,这对于需要精确时间控制的EVerest框架来说是非常重要的。
|
||||
Loading…
Reference in New Issue
Block a user