quartz/content/Obsidian/OCPP/Everest/EVerest-core.md
wangzipai 68cb7cea8f
[PUBLISHER] Merge #54
* PUSH NOTE : EVerest-core.md

* PUSH NOTE : EVerest-framework.md

* PUSH NOTE : EVerest-timer.md
2025-03-11 14:41:48 +08:00

20 KiB
Raw Blame History

date updated tags link publish share
2025-03-05 10:33 2025-03-11 14:40 Everest,ocpp true true

项目依赖

阅读CMakeLists.txt文件可以知道项目EVerest依赖于多个组件

  1. everest-cmake - 项目的构建系统工具
  2. Boost库 - 使用了filesystem、program_options、system和thread组件
  3. ./EVerest-framework - 核心框架
  4. everest-sunspec - 太阳能相关组件
  5. everest-modbus - Modbus通信协议支持
  6. everest-ocpp - 开放充电点协议(Open Charge Point Protocol)支持
  7. everest-openv2g - 电动汽车到电网(V2G)通信支持
  8. PalSigslot - 信号槽库
  9. fsm - 有限状态机
  10. slac - 可能是Signal Level Attenuation Characterization的缩写用于电力线通信
  11. pugixml - XML解析库

这些项目通过EDM管理当EDM不使能时cmake会去查找本地安装的路径。

架构图

graph LR
    A[EVerest Core] --> B[模块系统]
    A --> C[接口定义]
    A --> D[构建系统]
    
    B --> B1[通信模块]
    B --> B2[协议模块]
    B --> B3[硬件接入模块]
    B --> B4[能源管理模块]
    
    B1 --> B1a[SerialCommHub]
    B1 --> B1b[SLAC]
    
    B2 --> B2a[OCPP]
    B2 --> B2b[ISO15118]
    
    B3 --> B3a[GenericPowermeter]
    
    B2b --> B2b1[PyJosev-SECC]
    B2b --> B2b2[PyEvJosev-EVCC]
    
    C --> C1[YAML接口定义]
    C1 --> C1a[kvs.yaml]
    C1 --> C1b[energy.yaml]
    C1 --> C1c[auth.yaml]
    C1 --> C1d[slac.yaml]
    C1 --> C1e[power.yaml]
    
    D --> D1[CMake系统]
    D --> D2[ev-project-bootstrap]

核心组件解析

模块系统

EVerest采用模块化设计每个模块专注于特定功能

GenericPowermeter

# 通过ModbusRTU协议连接和读取电表数据
# 支持AC和DC电表通过配置文件描述寄存器映射
# 读取数据包括:能量、功率、电压、电流、频率等

SerialCommHub

// 串行通信中心处理Modbus通信
response = modbus.txrx(target_device_id, tiny_modbus::FunctionCode::READ_MULTIPLE_HOLDING_REGISTERS,
                       first_register_address, num_registers_to_read);

OCPP模块

// 实现开放充电点协议
bool ocpp_1_6_charge_pointImpl::handle_restart() {
    std::lock_guard<std::mutex>(this->m);
    mod->charging_schedules_timer->interval(std::chrono::seconds(this->mod->config.PublishChargingScheduleIntervalS));
    bool success = mod->charge_point->restart();
    if (success) {
        this->mod->ocpp_stopped = false;
    }
    return success;
}

ISO15118实现

PyJosev - 充电站侧实现(SECC)

async def secc_handler_main_loop(module_config: dict):
    # 启动ISO 15118 SECC控制器
    config = Config()
    patch_josev_config(config, module_config)
    sim_evse_controller = await SimEVSEController.create()
    await SECCHandler(
        exi_codec=ExificientEXICodec(), evse_controller=sim_evse_controller, config=config
    ).start(config.iface)

PyEvJosev - 车辆侧实现(EVCC)

async def evcc_handler_main_loop(module_config: dict):
    # 启动ISO 15118 EVCC控制器
    iface = determine_network_interface(module_config['device'])
    evcc_config = EVCCConfig()
    patch_josev_config(evcc_config, module_config)
    await EVCCHandler(
        evcc_config=evcc_config,
        iface=iface,
        exi_codec=ExificientEXICodec(),
        ev_controller=SimEVController(evcc_config),
    ).start()

接口定义系统

EVerest使用YAML文件定义模块间接口采用统一的格式定义命令和变量

# 接口示例 (kvs.yaml - 键值存储接口)
description: This interface defines a simple key-value-store interface
cmds:
  store:
    description: This command stores a value under a given key
    arguments:
      key:
        description: Key to store the value for
        type: string
        pattern: ^[A-Za-z0-9_.]*$

OCPP模块

以OCPP模块为例学习EVerest-core的构建到执行的过程。

架构流程图

graph TD
    A[OCPP模块初始化] --> B[模块构造与依赖注入]
    B --> C[init调用]
    C --> D[ready调用]
    D --> E[启动ChargePoint服务]
    
    E --> F1[处理OCPP核心功能]
    E --> F2[提供令牌验证]
    E --> F3[提供令牌授权]
    
    F1 --> G1[通信控制]
    F1 --> G2[配置管理]
    F1 --> G3[数据传输]
    F1 --> G4[安全事件]

    G1 --> H1[启动/停止连接]
    G1 --> H2[重新连接]
    
    G2 --> I1[获取配置]
    G2 --> I2[设置配置]
    G2 --> I3[监控配置变更]
    
    G3 --> J1[DataTransfer请求/响应]
    
    G4 --> K1[安全事件通知]
    
    subgraph 定时任务
    L1[充电计划定时器]
    end
    
    E --> L1

核心组件解析

模块结构与初始化

依赖注入机制

OCPP模块的初始化过程包括 构造函数中的依赖注入和init/ready方法

// OCPP.hpp - 模块构造函数
OCPP(const ModuleInfo& info, Everest::MqttProvider& mqtt_provider,
     std::unique_ptr<ocpp_1_6_charge_pointImplBase> p_main,
     std::unique_ptr<auth_token_validatorImplBase> p_auth_validator,
     std::unique_ptr<auth_token_providerImplBase> p_auth_provider,
     std::vector<std::unique_ptr<evse_managerIntf>> r_evse_manager,
     std::vector<std::unique_ptr<external_energy_limitsIntf>> r_connector_zero_sink,
     std::unique_ptr<reservationIntf> r_reservation, std::unique_ptr<authIntf> r_auth,
     std::unique_ptr<systemIntf> r_system, std::unique_ptr<evse_securityIntf> r_security, 
     Conf& config) :
    ModuleBase(info),
    mqtt(mqtt_provider),
    p_main(std::move(p_main)),
    p_auth_validator(std::move(p_auth_validator)),
    p_auth_provider(std::move(p_auth_provider)),
    r_evse_manager(std::move(r_evse_manager)),
    r_connector_zero_sink(std::move(r_connector_zero_sink)),
    r_reservation(std::move(r_reservation)),
    r_auth(std::move(r_auth)),
    r_system(std::move(r_system)),
    r_security(std::move(r_security)),
    config(config){};

这个构造函数显示该模块的复杂性,它需要多个接口交互,包括:

  • 提供的接口: OCPP充电点、认证令牌验证、认证令牌提供
  • 依赖的接口: EVSE管理器、能源限制、预约、认证、系统和安全

这种注入方式遵循依赖倒置原则,通过接口而非具体实现进行交互,提高了系统的解耦性和可测试性。

初始化流程

OCPP模块的初始化分为两个阶段

初始化阶段 (init)
  • 创建ChargePoint实例
  • 配置数据库路径
  • 设置安全参数
  • 建立MQTT连接
  • 初始化定时器
就绪阶段 (ready)
  • 启动WebSocket连接
  • 设置充电计划定时器
  • 注册回调函数
  • 订阅相关主题
初始化代码示例
void OCPP::init() {
    // 配置文件路径处理
    ocpp_share_path = std::filesystem::path(this->info.paths.share);
    
    // 创建ChargePoint核心对象
    charge_point = std::make_unique<ocpp::v16::ChargePoint>(
        /* 配置参数 */
    );
}

void OCPP::ready() {
	// 设置充电计划定时器
    charging_schedules_timer = std::make_unique<Everest::SteadyTimer>(
        [this](){
            // 定时发布充电计划
            this->publish_charging_schedules(
                this->charge_point->get_composite_schedule(
                    std::chrono::seconds(this->config.PublishChargingScheduleDurationS)
                )
            );
        }
    );
}

ChargePoint核心组件

ChargePoint是OCPP模块的核心组件负责所有OCPP通信

  • WebSocket管理建立和维护与CSMS的WebSocket连接
  • 消息序列化将OCPP消息转换为JSON格式
  • 会话管理:处理认证和会话状态
  • 事务处理:管理充电事务的开始、进行和结束
  • 配置管理:处理配置请求和更新

接口实现

ocpp_1_6_charge_pointImpl

ocpp_1_6_charge_pointImpl类实现了OCPP 1.6协议的主要功能:

bool ocpp_1_6_charge_pointImpl::handle_stop() {
    std::lock_guard<std::mutex>(this->m);
    mod->charging_schedules_timer->stop();
    bool success = mod->charge_point->stop();
    if (success) {
        this->mod->ocpp_stopped = true;
    }
    return success;
}

bool ocpp_1_6_charge_pointImpl::handle_restart() {
    std::lock_guard<std::mutex>(this->m);
    mod->charging_schedules_timer->interval(std::chrono::seconds(this->mod->config.PublishChargingScheduleIntervalS));
    bool success = mod->charge_point->restart();
    if (success) {
        this->mod->ocpp_stopped = false;
    }
    return success;
}

数据转换函数示例:

types::ocpp::KeyValue to_everest(const ocpp::v16::KeyValue& key_value) {
    types::ocpp::KeyValue _key_value;
    _key_value.key = key_value.key.get();
    _key_value.read_only = key_value.readonly;
    if (key_value.value.has_value()) {
        _key_value.value = key_value.value.value().get();
    }
    return _key_value;
}

auth_token_validatorImpl

实现令牌验证功能将验证请求传递给OCPP后端

auth_token_validatorImpl::handle_validate_token(types::authorization::ProvidedIdToken& provided_token) {
    if (provided_token.authorization_type == types::authorization::AuthorizationType::PlugAndCharge) {
        return validate_pnc_request(provided_token);
    } else {
        return validate_standard_request(provided_token);
    }
};

auth_token_providerImpl

提供令牌信息给其他模块:

// 伪代码
void auth_token_providerImpl::handle_get_token_info(const std::string& id_token, types::authorization::TokenInfo& token_info) {
    auto response = mod->charge_point->get_token_info(id_token);
    token_info.auth_status = convert_auth_status(response.status);
    // 填充其他令牌信息
}

这里为什么没有具体的实现代码?

配置管理功能

模块支持全面的配置管理,包括获取、设置和监控配置:

// 获取配置
types::ocpp::GetConfigurationResponse ocpp_1_6_charge_pointImpl::handle_get_configuration_key(Array& keys) {
    ocpp::v16::GetConfigurationRequest request;
    std::vector<ocpp::CiString<50>> _keys;
    for (const auto& key : keys) {
        _keys.push_back(key);
    }
    request.key = _keys;

    const auto response = this->mod->charge_point->get_configuration_key(request);
    return to_everest(response);
}

// 监控配置变更
void ocpp_1_6_charge_pointImpl::handle_monitor_configuration_keys(Array& keys) {
    for (const auto& key : keys) {
        this->mod->charge_point->register_configuration_key_changed_callback(
            key,
            [this](const ocpp::v16::KeyValue key_value) { 
                this->publish_configuration_key(to_everest(key_value)); 
            });
    }
}

安全机制

OCPP模块通过EvseSecurity类实现OCPP安全功能

class evse_securityImpl : public evse_securityImplBase {
public:
    evse_securityImpl() = delete;
    evse_securityImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<EvseSecurity>& mod, Conf& config) :
        evse_securityImplBase(ev, "main"), mod(mod), config(config){};
protected:
//伪代码
    EvseSecurity(evse_securityIntf& r_security);
    
    // 证书安装
    ocpp::InstallCertificateResult install_ca_certificate(const std::string& certificate,
                                                         const ocpp::CaCertificateType& certificate_type) override;
    
    // 证书删除
    ocpp::DeleteCertificateResult 
    delete_certificate(const ocpp::CertificateHashDataType& certificate_hash_data) override;
    
    // 证书验证
    ocpp::InstallCertificateResult verify_certificate(const std::string& certificate_chain,
                                                     const ocpp::CertificateSigningUseEnum& certificate_type) override;
    
    // 更多安全操作...
};

定时任务

模块使用定时器定期执行任务:

// 初始化定时器
charging_schedules_timer = std::make_unique<Everest::SteadyTimer>();

// 设置定时任务
charging_schedules_timer->interval(std::chrono::seconds(config.PublishChargingScheduleIntervalS));
charging_schedules_timer->start();

模块执行流程

启动流程

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() - 启动服务
  4. 建立WebSocket连接
  5. 注册回调函数
  6. 开始定时任务

EVSE就绪映射初始化

模块使用 evse_ready_map 追踪所有EVSE的就绪状态

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的就绪状态

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设置
  • 用户配置文件:包含用户自定义设置

从哪里传进去的?

// 寻找并加载主配置文件
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的映射关系

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通信的核心组件

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动作之间的桥梁

充电控制回调
// 暂停充电回调
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消息处理流程

接收消息

WebSocket接收CSMS消息 JSON解析为OCPP对象

消息分发

根据消息类型调用对应处理函数 执行业务逻辑

响应生成

创建响应对象 序列化为JSON 通过WebSocket发送

状态更新

更新内部状态 发布相关事件

认证流程

  1. 接收认证令牌
  2. 发送Authorize.req消息到CSMS
  3. 接收Authorize.conf响应
  4. 转换OCPP认证状态为EVerest认证状态
  5. 返回认证结果

充电计划处理

  1. 定时器触发
  2. 请求最新充电计划
  3. 转换为EVerest格式
  4. 发布到MQTT
  5. 传递给能源限制接口

与其他模块交互

EVSE管理器交互

// 伪代码
void handle_evse_status_change(int32_t evse_id, evse_manager::EVSEState state) {
    // 转换状态
    ocpp::v16::ChargePointStatus cp_status = convert_evse_state(state);
    
    // 向CSMS发送状态变更
    charge_point->status_notification(evse_id, cp_status);
}

安全模块交互

// 初始化安全组件
auto evse_security = std::make_shared<EvseSecurity>(*r_security);

// 配置ChargePoint使用安全组件
charge_point_config.evse_security = evse_security;

OCPPExtensionExample扩展

通过OCPPExtensionExample模式可以扩展OCPP功能:

class OCPPExtensionExample : public Everest::ModuleBase {
public:
    OCPPExtensionExample(const ModuleInfo& info, 
                        std::unique_ptr<emptyImplBase> p_empty,
                        std::unique_ptr<ocpp_1_6_charge_pointIntf> r_ocpp, 
                        Conf& config);
                        
    void ready() {
        // 监控配置变更
        std::vector<std::string> keys_to_monitor = parse_keys(config.keys_to_monitor);
        r_ocpp->monitor_configuration_keys(keys_to_monitor);
        
        // 订阅变更通知
        r_ocpp->subscribe_configuration_key([this](types::ocpp::KeyValue key_value) {
            // 处理配置变更
        });
    }
};

OCPP 2.0.1

todo