3.6 软件实现方案--EvseSlac
3.6.1 启动EvseSlac模块
settings:
telemetry_enabled: true
active_modules:
api:
connections:
evse_manager:
- implementation_id: evse
module_id: connector_1
module: API
auth:
config_module:
connection_timeout: 10
prioritize_authorization_over_stopping_transaction: true
selection_algorithm: FindFirst
ignore_connector_faults: true
connections:
evse_manager:
- implementation_id: evse
module_id: connector_1
token_provider:
- implementation_id: main
module_id: token_provider
token_validator:
- implementation_id: main
module_id: token_validator
module: Auth
# ev_manager:
# config_module:
# auto_enable: true
# auto_exec: false
# auto_exec_commands: sleep 1;iec_wait_pwr_ready;sleep 1;draw_power_regulated 16,3;sleep 30;unplug
# connector_id: 1
# connections:
# ev:
# - implementation_id: ev
# module_id: iso15118_car
# ev_board_support:
# - implementation_id: ev_board_support
# module_id: connector_1_powerpath
# slac:
# - implementation_id: ev
# module_id: slac
# module: JsEvManager
energy_manager:
connections:
energy_trunk:
- implementation_id: energy_grid
module_id: grid_connection_point
module: EnergyManager
connector_1:
config_module:
ac_enforce_hlc: false
ac_hlc_enabled: true
ac_hlc_use_5percent: false
ac_nominal_voltage: 230
charge_mode: AC
connector_id: 1
country_code: DE
ev_receipt_required: false
evse_id: DE*PNX*E12345*1
has_ventilation: true
max_current_import_A: 32
max_current_export_A: 32
payment_enable_contract: true
payment_enable_eim: true
session_logging: true
session_logging_path: /tmp/everest-logs
session_logging_xml: false
three_phases: true
connections:
bsp:
- implementation_id: board_support
module_id: connector_1_powerpath
hlc:
- implementation_id: charger
module_id: iso15118_charger
powermeter_grid_side:
- implementation_id: powermeter
module_id: connector_1_powerpath
slac:
# - implementation_id: evse
- implementation_id: main
module_id: slac
ac_rcd:
- implementation_id: rcd
module_id: connector_1_powerpath
connector_lock:
- implementation_id: connector_lock
module_id: connector_1_powerpath
module: EvseManager
telemetry:
id: 1
grid_connection_point:
config_module:
fuse_limit_A: 40
phase_count: 3
connections:
energy_consumer:
- implementation_id: energy_grid
module_id: connector_1
module: EnergyNode
iso15118_car:
config_module:
device: auto
supported_ISO15118_2: true
connections: {}
module: PyEvJosev
iso15118_charger:
config_module:
device: auto
tls_security: allow
connections: {}
module: EvseV2G
connections:
security:
- module_id: evse_security
implementation_id: main
evse_security:
module: EvseSecurity
config_module:
private_key_password: "123456"
persistent_store:
config_module:
sqlite_db_file_path: everest_persistent_store.db
connections: {}
module: PersistentStore
setup:
config_module:
initialized_by_default: true
localization: true
online_check_host: lfenergy.org
setup_simulation: true
setup_wifi: false
connections:
store:
- implementation_id: main
module_id: persistent_store
module: Setup
# slac:
# config_implementation:
# ev:
# ev_id: PIONIX_SAYS_HELLO
# evse:
# evse_id: PIONIX_SAYS_HELLO
# nid: pionix!
# number_of_sounds: 10
# connections: {}
# module: JsSlacSimulator
slac:
config_implementation:
main:
device: ens33
evse_id: PIONIX_SAYS_HELLO
nid: pionix!
number_of_sounds: 10
connections: {}
module: EvseSlac
token_provider:
config_implementation:
main:
timeout: 10
token: DEADBEEF
connections:
evse:
- implementation_id: evse
module_id: connector_1
module: DummyTokenProvider
token_validator:
config_implementation:
main:
sleep: 0.25
validation_reason: Token seems valid
validation_result: Accepted
connections: {}
module: DummyTokenValidator
connector_1_powerpath:
config_module:
connector_id: 1
connections: {}
module: JsYetiSimulator
telemetry:
id: 1
'x-module-layout':
api:
position:
x: 33
y: 13
terminals:
bottom: []
left:
- id: evse_manager
interface: evse_manager
type: requirement
right:
- id: main
interface: empty
type: provide
top: []
.......
Everest项目要求root权限才能运行,直接切换到root用户下,安装过此项目的依赖库:
root@tom-virtual-machine:~/checkout/everest-workspace/Josev# python3 -m pip install -r requirements.txt
终于运行成功了: 结果略。
出现上面黄色标记内容“Module slac initialized”表示已经初始化了EvseSlac模块。接下来一旦everest系统的cp状态切换A->B, 就会启动slac监听,直到车端发起slac连接,然后完成连接。
3.6.2 启动过程
main() everest-core/build/generated/modules/EvseSlac/ld-ev.cpp
创建Everest::ModuleLoader对象module_loader,
执行module_loader.initialize() everest-framework/lib/runtime.cpp
this->callbacks.everest_register() 创建出EvseSlac对象 everest-core/build/generated/modules/EvseSlac/ld-ev.cpp
this->callbacks.init()
slacImpl::init() everest-core/modules/EvseSlac/main/slacImpl.cpp 实现类的真正入口
3.6.2.1 slacImpl::init()
slac:
config_implementation:
main:
device: ens33
evse_id: PIONIX_SAYS_HELLO
nid: pionix!
number_of_sounds: 10
connections: {}
module: EvseSlac
注意这个device 指明使用的网卡。下面的线程会打开网卡进行slac通信,实际在板上应该是vc_ethspi0虚拟网卡。
std::thread(&slacImpl::run, this).detach();
3.6.2.2 slacImpl::run()slac线程主体
-
创建SlacIO对象,
-
初始化:打开网卡,建立通道
-
创建回调函数
-
启动SlacIO对象的run(): 开启内部的poll模型,持续的接收HomePlug消息包并执行回调函数。
-
启动fsm_ctrl的run(), 这也是线程。
slac_io.run([](slac::messages::HomeplugMessage& msg) { fsm_ctrl->signal_new_slac_message(msg); });
这行代码仔细看有些奇怪--- 匿名函数中使用了外部对象fsm_ctrl的函数, 而且这个函数被底层回调执行, 也就是说这个fsm_ctrl的函数被调用到了。
3.6.3 PacketSocket类
PacketSocket::PacketSocket(const InterfaceInfo& if_info, int protocol) {
// FIXME (aw): do we need to use O_NONBLOCKING?
socket_fd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(protocol));
if (socket_fd == -1) {
error = "Couldn't create the socket: ";
error += strerror(errno);
return;
}
// bind this packet socket to a specific interface
struct sockaddr_ll sock_addr = {
AF_PACKET, // sll_family
htons(protocol), // sll_protocol
if_info.get_index(), // sll_ifindex
0x00, // sll_hatype, set on receiving
0x00, // sll_pkttype, set on receiving
ETH_ALEN, // sll_halen
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // sll_addr[8]
};
if (-1 == bind(socket_fd, (struct sockaddr*)&sock_addr, sizeof(sock_addr))) {
error = "Failed to bind the socket: ";
error += strerror(errno);
close(socket_fd);
}
// everything should have worked out
valid = true;
}
【原始套接字】

-
超时返回失败,
-
有POLLIN事件,就read数据;
-
有POLLOUT事件,就write数据。
PacketSocket::IOResult PacketSocket::read(uint8_t* buffer, int timeout) {
struct pollfd poll_fd = {
socket_fd, // file descriptor
POLLIN, // requested event
0 // returned event
};
int ret = poll(&poll_fd, 1, timeout);
if (-1 == ret) {
error = std::string("poll() failed with: ") + strerror(errno);
return IOResult::Failure;
}
if (0 == ret) {
return IOResult::Timeout;
}
if ((poll_fd.revents & POLLIN) == 0) {
error = "poll() set other flag than POLLIN";
return IOResult::Failure;
}
bytes_read = ::read(socket_fd, buffer, MIN_BUFFER_SIZE);
if (bytes_read == -1) {
error = std::string("read() failed with: ") + strerror(errno);
return IOResult::Failure;
}
return IOResult::Ok;
}
3.6.4 Channel类
bool Channel::read(slac::messages::HomeplugMessage& msg, int timeout) {
did_timeout = false;
using IOResult = ::utils::PacketSocket::IOResult;
if (socket) {
switch (socket->read(reinterpret_cast<uint8_t*>(msg.get_raw_message_ptr()), timeout)) {
// FIXME (aw): this enum conversion looks ugly
case IOResult::Failure:
error = socket->get_error();
return false;
case IOResult::Timeout:
did_timeout = true;
return false;
case IOResult::Ok:
return true;
}
}
error = "No IO socket available\n";
return false;
}
HomeplugMessage定义文件: /home/vboxuser/checkout/everest-workspace/libslac/include/slac/slac.hpp
typedef struct {
struct ether_header ethernet_header;
struct {
uint8_t mmv; // management message version
uint16_t mmtype; // management message type
} __attribute__((packed)) homeplug_header;
// the rest of this message is potentially payload data
uint8_t payload[ETH_FRAME_LEN - ETH_HLEN - sizeof(homeplug_header)];
} __attribute__((packed)) homeplug_message;
typedef struct {
uint8_t fmni; // fragmentation management number information
uint8_t fmsn; // fragmentation message sequence number
} __attribute__((packed)) homeplug_fragmentation_part;
class HomeplugMessage {
public:
homeplug_message* get_raw_message_ptr() {
return &raw_msg;
};
int get_raw_msg_len() const {
return raw_msg_len;
}
void setup_payload(void const* payload, int len, uint16_t mmtype, const defs::MMV mmv);
void setup_ethernet_header(const uint8_t dst_mac_addr[ETH_ALEN], const uint8_t src_mac_addr[ETH_ALEN] = nullptr);
uint16_t get_mmtype() const;
uint8_t* get_src_mac();
template <typename T> const T& get_payload() {
if (raw_msg.homeplug_header.mmv == static_cast<std::underlying_type_t<defs::MMV>>(defs::MMV::AV_1_0)) {
return *reinterpret_cast<T*>(raw_msg.payload);
}
// if not av 1.0 message, we need to shift by the fragmentation part
return *reinterpret_cast<T*>(raw_msg.payload + sizeof(homeplug_fragmentation_part));
}
bool is_valid() const;
bool keep_source_mac() const {
return keep_src_mac;
}
private:
homeplug_message raw_msg;
int raw_msg_len{-1};
bool keep_src_mac{false};
};
3.6.5 SlacIO类
void SlacIO::init(const std::string& if_name) {
if (!slac_channel.open(if_name)) {
throw std::runtime_error(slac_channel.get_error());
}
}
void SlacIO::run(std::function<InputHandlerFnType> callback) {
input_handler = callback;
running = true;
loop_thread = std::thread(&SlacIO::loop, this);
}
void SlacIO::quit() {
if (!running) {
return;
}
running = false;
loop_thread.join();
}
void SlacIO::loop() {
while (running) {
if (slac_channel.read(incoming_msg, 10)) {
input_handler(incoming_msg);
}
}
}
void SlacIO::send(slac::messages::HomeplugMessage& msg) {
// FIXME (aw): handle errors
slac_channel.write(msg, 1);
}
从SlacIO实现代码来看, 应用层应该调用顺序:
-
SlacIO.init(), 打开网卡,建立通道。
-
SlacIO.run(), 设置回调函数,启动命令接收线程,持续的进行poll轮转,读取报文并执行回调函数。
-
当退出时执行SlacIO::quit(),会停止线程并等待线程结束。 【警告】这一步没找到调用代码。
3.6.6 关于Slac的回调函数类
3.6.6.1 template <> 模版特殊化
template <typename SlacMessageType> struct MMTYPE;
template <> struct MMTYPE<slac::messages::cm_slac_parm_cnf> {
static const uint16_t value = slac::defs::MMTYPE_CM_SLAC_PARAM | slac::defs::MMTYPE_MODE_CNF;
};
对于模版实例化时候需要指定具体类型,如果模版实例类只能对某一种类型有效或者部分代码要求指定具体类型,就称为模版完全特化或模版部分特化。
-
关键字tempalte后面接一对空的尖括号(< >)
-
函数名后接一对尖括号,尖括号中指定这个特化定义的模板形参, 然后是函数形参表、函数体
-
类名、结构体名后接一对尖括号,尖括号中指定这个特化定义的模板形参
3.6.6.2 ContextCallbacks
/home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/include/slac/fsm/evse/context.hpp
结构体定义了一些回调函数:
struct ContextCallbacks {
std::function<void(slac::messages::HomeplugMessage&)> send_raw_slac{nullptr};
std::function<void(const std::string&)> signal_state{nullptr};
std::function<void(bool)> signal_dlink_ready{nullptr};
std::function<void()> signal_error_routine_request{nullptr};
std::function<void(const std::string&)> signal_ev_mac_address_parm_req{nullptr};
std::function<void(const std::string&)> signal_ev_mac_address_match_cnf{nullptr};
std::function<void(const std::string&)> log{nullptr};
};
// setup callbacks
slac::fsm::evse::ContextCallbacks callbacks;
callbacks.send_raw_slac = [&slac_io](slac::messages::HomeplugMessage& msg) { slac_io.send(msg); }; 发送命令
callbacks.signal_dlink_ready = [this](bool value) { publish_dlink_ready(value); }; 广播dlink_ready
callbacks.signal_state = [this](const std::string& value) { publish_state(value); }; 广播state, 什么状态?
callbacks.signal_error_routine_request = [this]() { publish_request_error_routine(nullptr); }; 广播错误
callbacks.log = [](const std::string& text) { EVLOG_info << text; }; 写log
if (config.publish_mac_on_first_parm_req) {
callbacks.signal_ev_mac_address_parm_req = [this](const std::string& mac) { publish_ev_mac_address(mac); }; 收到第一个请求时广播ev的mac地址
}
if (config.publish_mac_on_match_cnf) {
callbacks.signal_ev_mac_address_match_cnf = [this](const std::string& mac) { publish_ev_mac_address(mac); }; 完成匹配时广播ev的mac地址
}
3.6.6.3 FSM类定义
/home/vboxuser/checkout/everest-workspace/libfsm/include/fsm/fsm.hpp
/home/vboxuser/checkout/everest-workspace/libfsm/examples/light_switch/light_switch_state_diagram.svg
3.6.7 vendor相关代码
/home/vboxuser/checkout/everest-workspace/libslac/include/slac/slac.hpp
qualcomm: vendor_mme[3] = {0x00, 0xb0, 0x52};
lumissil: vendor_mme[3] = {0x00, 0x16, 0xE8};
实际上,运行这个代码时,如果启动了一个参数设置项就会强制检查PLC芯片的厂家参数,如果不是qualcomm和lumissil就导致失败退出。 这样的话就不能运行在其他芯片的板子上了。默认情况下并不会检查芯片厂家vendor。
3.6.8 Slac层代码MatchingSession、MatchingState
/home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/include/slac/fsm/evse/states/matching.hpp
/home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/src/states/matching.cpp
/home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/src/states/matching_handle_slac.hpp
/home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/src/states/matching_handle_slac.cpp
MatchingSession类: 本机是evse桩端,可能收到多个ev端的slac信号(可能是电力线路耦合干扰, 但是信号强度会有强弱区分)。对每一个新的请求都建立一个会话,测量线路上的信号强度,确定会话状态。每个会话独立保存在MatchingSession类中,区别是ev_mac地址不同。
/home/vboxuser/checkout/everest-workspace/everest-core/modules/EvseSlac/main/fsm_controller.cpp
void FSMController::run() {
ctx.log_info("Starting the SLAC state machine");
fsm.reset<slac::fsm::evse::InitState>(ctx);
std::unique_lock<std::mutex> feed_lck(feed_mtx);
running = true;
while (true) {
auto feed_result = fsm.feed();
if (feed_result.transition()) {
// call immediately again
continue;
} else if (feed_result.internal_error() || feed_result.unhandled_event()) {
// FIXME (aw): would need to log here!
} else if (feed_result.has_value() == true) {
const auto timeout = *feed_result;
if (timeout == 0) {
// call feed directly again
continue;
}
new_event_cv.wait_for(feed_lck, std::chrono::milliseconds(timeout), [this] { return new_event; });
} else {
// nothing happened, no return value -> wait for new event
new_event_cv.wait(feed_lck, [this] { return new_event; });
}
if (new_event) {
// we got a new event, reset it and let run feed again
new_event = false;
}
}
}
fsm.feed() ,InitState类中没有重写feed()函数,此处调用模版类FSM中的feed()。该函数把callback()的返回结果转换了类型:FeedResultState::TRANSITION、UNHANDLED_EVENT、INTERNAL_ERROR。
/home/vboxuser/checkout/everest-workspace/libfsm/include/fsm/fsm.hpp
FeedResult<ReturnType> feed() {
using FeedResultState = _impl::FeedResultState;
if (current_state == nullptr) {
return FeedResultState::INTERNAL_ERROR;
}
const auto result = current_state->callback();
if (result.is_event) {
switch (handle_event(result.event)) {
case HandleEventResult::SUCCESS:
return FeedResultState::TRANSITION;
case HandleEventResult::UNHANDLED:
return FeedResultState::UNHANDLED_EVENT;
default:
// NOTE: everything else should be an internal error
return FeedResultState::INTERNAL_ERROR;
}
} else if (result.is_value_set) {
return result.value;
} else {
return FeedResultState::NO_VALUE;
}
}
current_state->callback(); 此时调用的是子类InitState中的callback(); 文件 /home/vboxuser/checkout/everest-workspace/libfsm/include/fsm/fsm.hpp
/home/vboxuser/checkout/everest-workspace/libfsm/include/fsm/fsm.hpp
FeedResult<ReturnType> feed() {
using FeedResultState = _impl::FeedResultState;
if (current_state == nullptr) {
return FeedResultState::INTERNAL_ERROR;
}
const auto result = current_state->callback();
if (result.is_event) {
switch (handle_event(result.event)) {
case HandleEventResult::SUCCESS:
return FeedResultState::TRANSITION;
case HandleEventResult::UNHANDLED:
return FeedResultState::UNHANDLED_EVENT;
default:
// NOTE: everything else should be an internal error
return FeedResultState::INTERNAL_ERROR;
}
} else if (result.is_value_set) {
return result.value;
} else {
return FeedResultState::NO_VALUE;
}
}
这个callback()函数会反复执行3次,执行一次后就把 sub_state设置为下一步状态值, sub_state的初始值是QUALCOMM_OP_ATTR。
3.6.10 FSMController工作原理
3.6.10.1 InitState状态机
---- 000 handle_event: template <typename EventType, typename ReturnType> class FSM
2024-08-28 18:33:04.818437 [INFO] slac:EvseSlac :: Entered Reset state
2024-08-28 18:33:04.818465 [INFO] slac:EvseSlac :: --feed_result.transition() ... 000
2024-08-28 18:33:04.818487 [INFO] slac:EvseSlac :: ----feed_result.transition() ... 1111
2024-08-28 18:33:04.818542 [INFO] slac:EvseSlac :: New NMK key: 44:43:46:54:52:4F:47:38:59:4E:51:32:38:37:48:46
如果PLC芯片发送了应答CM_SET_KEY_CNF, 那么就被送给 ResetState::handle_event() , 随之切换到 IdleState状态机。
3.6.10.3 IdleState状态机
/home/vboxuser/checkout/everest-workspace/everest-core/build/generated/include/generated/interfaces/slac/Implementation.hpp
class slacImplBase : public Everest::ImplementationBase {
void _gather_cmds(std::vector<Everest::cmd>& cmds){
// enter_bcd command
Everest::cmd enter_bcd_cmd;
enter_bcd_cmd.impl_id = _name;
enter_bcd_cmd.cmd_name = "enter_bcd";
// cmd enter_bcd has no arguments
enter_bcd_cmd.cmd = [this](Parameters args) -> Result {
(void) args; // no arguments used for this callback
auto result = this->handle_enter_bcd();
return result;
};
enter_bcd_cmd.return_type = {"boolean"};
cmds.emplace_back(std::move(enter_bcd_cmd));
3.6.10.4 MatchingState状态机
/home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/include/slac/fsm/evse/states/matching.hpp
enum class MatchingSubState {
WAIT_FOR_START_ATTEN_CHAR,
SOUNDING,
FINALIZE_SOUNDING,
WAIT_FOR_ATTEN_CHAR_RSP,
WAIT_FOR_SLAC_MATCH,
RECEIVED_SLAC_MATCH,
MATCH_COMPLETE,
FAILED,
};
ctx.signal_cm_slac_parm_req(tmp_ev_mac);
ctx.signal_cm_slac_match_cnf(tmp_ev_mac); /home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/src/states/matching_handle_slac.cpp
callbacks.signal_ev_mac_address_match_cnf(mac_string); /home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/src/context.cpp
callbacks.signal_ev_mac_address_parm_req = [this](const std::string& mac) { publish_ev_mac_address(mac); /home/vboxuser/checkout/everest-workspace/everest-core/modules/EvseSlac/main/slacImpl.cpp
3.6.10.5 MatchedState状态机
/home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/include/slac/fsm/evse/states/others.hpp
void MatchedState::enter() {
ctx.signal_state("MATCHED");
ctx.signal_dlink_ready(true);
ctx.log_info("Entered Matched state");
}
void MatchedState::leave() {
ctx.signal_dlink_ready(false);
}
在已连接状态下,没有任何需要处理的slac命令,因此,MatchedState::handle_event很简单,只需要处理Event::RESET事件即可。
FSMSimpleState::HandleEventReturnType MatchedState::handle_event(AllocatorType& sa, Event ev) {
if (ev == Event::RESET) {
return sa.create_simple<ResetState>(ctx);
} else {
return sa.PASS_ON;
}
}
当外部拔枪时,Manager模块调用EvseSlac模块的接口命令reset, 产生事件Event::RESET, 驱动状态切换成ResetState。
3.6.10.6 WaitForLinkState状态机
link_status_detection:
description: After matching.cnf, wait for link to come up before sending out d_link_ready=connected using LINK_STATUS Vendor MME Extension (Works on Qualcomm and Lumissil chips)
type: boolean
default: false
如果该参数设置为true,那么就会进入WaitForLinkState状态机,对slac命令处理过程如下。
/home/vboxuser/checkout/everest-workspace/everest-core/lib/staging/slac/fsm/evse/src/states/others.cpp
FSMSimpleState::HandleEventReturnType WaitForLinkState::handle_event(AllocatorType& sa, Event ev) {
if (ev == Event::SLAC_MESSAGE) {
if (handle_slac_message(ctx.slac_message_payload)) {
return sa.create_simple<MatchedState>(ctx);
} else {
return sa.PASS_ON;
}
} else if (ev == Event::RETRY_MATCHING) {
ctx.log_info("Link could not be established, resetting...");
// Notify higher layers to on CP signal
return sa.create_simple<FailedState>(ctx);
} else {
return sa.PASS_ON;
}
}
bool WaitForLinkState::handle_slac_message(slac::messages::HomeplugMessage& message) {
const auto mmtype = message.get_mmtype();
if (ctx.modem_vendor == ModemVendor::Qualcomm &&
mmtype == (slac::defs::qualcomm::MMTYPE_LINK_STATUS | slac::defs::MMTYPE_MODE_CNF)) {
const auto success = message.get_payload<slac::messages::qualcomm::link_status_cnf>().link_status == 0x01;
return success;
} else if (ctx.modem_vendor == ModemVendor::Lumissil &&
mmtype == (slac::defs::lumissil::MMTYPE_NSCM_GET_D_LINK_STATUS | slac::defs::MMTYPE_MODE_CNF)) {
const auto success =
message.get_payload<slac::messages::lumissil::nscm_get_d_link_status_cnf>().link_status == 0x01;
return success;
} else {
// unexpected message
ctx.log_info("Received non-expected SLAC message of type " + format_mmtype(mmtype));
return false;
}
}
【注意】启动这个参数就意味着进行PLC芯片厂家Vendor检查。如果检测失败,就一直停留在WaitForLinkState状态。
3.6.10.7 FailedState状态机
void FailedState::enter() {
ctx.signal_error_routine_request();
ctx.log_info("Entered Failed state");
}
FSMSimpleState::HandleEventReturnType FailedState::handle_event(AllocatorType& sa, Event ev) {
if (ev == Event::RESET) {
return sa.create_simple<ResetState>(ctx);
} else {
return sa.PASS_ON;
}
}
3.6.11 状态机的切换顺序
3.6.12 EvseSlac模块的接口文件slac.yaml
description: Implementation of SLAC data link negotiation according to ISO15118-3.
provides:
main:
interface: slac
description: SLAC interface implementation.
config:
device:
description: Ethernet device used for PLC.
type: string
default: eth1
......
这个slac接口文件是:everest-core/build/dist/share/everest/interfaces/slac.yaml
description: ISO15118-3 SLAC interface for EVSE side
cmds:
reset:
description: Reset SLAC
arguments:
enable:
description: 'true: start SLAC after reset, false: stop SLAC'
type: boolean
enter_bcd:
description: Signal pilot state change to B/C/D from A/E/F.
result:
type: boolean
description: >-
True on success, returns False if transition was unexpected and
cannot be handled by SLAC state machine.
leave_bcd:
description: Signal pilot state change to A/E/F from B/C/D.
result:
type: boolean
description: >-
True on success, returns False if transition was unexpected and
cannot be handled by SLAC state machine.
dlink_terminate:
description: Terminate the data link and become UNMATCHED.
result:
type: boolean
description: True on success.
dlink_error:
description: Terminate the data link and restart the matching process.
result:
type: boolean
description: True on success.
dlink_pause:
description: Request power saving mode, while staying MATCHED.
result:
type: boolean
description: True on success.
vars:
state:
description: Provides the state enum.
type: string
enum:
- UNMATCHED
- MATCHING
- MATCHED
dlink_ready:
description: >-
Inform higher layers about a change in data link status. Emits true
if link was set up and false when the link is shut down.
type: boolean
request_error_routine:
description: >-
Inform the higher layer to execute the error routine for a SLAC connection
retry
type: 'null'
ev_mac_address:
description: >-
Inform higher layers about the MAC address of the vehicle (upper case)
type: string
pattern: ^[A-F0-9]{2}(:[A-F0-9]{2}){5}$
这个yaml编译时自动生成接口文件:
3.6.13 调用接口cmd
everest-core/modules/EvseManager/EvseManager.cpp
void EvseManager::ready() {
bsp->signal_event.connect([this](const CPEvent event) {
// Forward events from BSP to SLAC module before we process the events in the charger
if (slac_enabled) {
if (event == CPEvent::EFtoBCD) {
// this means entering BCD from E|F
r_slac[0]->call_enter_bcd();
} else if (event == CPEvent::BCDtoEF) {
r_slac[0]->call_leave_bcd();
} else if (event == CPEvent::CarPluggedIn) {
// CC: right now we dont support energy saving mode, so no need to reset slac here.
// It is more important to start slac as early as possible to avoid unneccesary retries
// e.g. by Tesla cars which send the first SLAC_PARM_REQ directly after plugin.
// If we start slac too late, Tesla will do a B->C->DF->B sequence for each retry which
// may confuse the PWM state machine in some implementations.
// r_slac[0]->call_reset(true);
// This is entering BCD from state A
car_manufacturer = types::evse_manager::CarManufacturer::Unknown;
r_slac[0]->call_enter_bcd();
} else if (event == CPEvent::CarUnplugged) {
r_slac[0]->call_leave_bcd();
r_slac[0]->call_reset(false);
}
}
对应关系:
CP事件
|
调用的接口命令
|
说明
|
CPEvent::EFtoBCD
|
enter_bcd
| |
CPEvent::BCDtoEF
|
leave_bcd
| |
CPEvent::CarPluggedIn
|
enter_bcd
|
插枪
|
CPEvent::CarUnplugged
|
leave_bcd
reset
|
拔枪,调用两个命令
|
状态机
|
mqtt命令
|
说明
|
MatchedState
|
发送 state=MATCHED
|
匹配成功
|
MatchedState
|
发送dlink_ready=true
|
DLink建立成功
|
MatchedState
|
发送dlink_ready=false
|
MatchedState状态机下收到Reset命令,
通知上层DLink断开。
|
MatchingState
|
收到CM_SLAC_PARM.REQ:
收到CM_SLAC_MATCH.REQ
发送对方的mac地址
|
匹配开始:广播ev的mac地址。
加入逻辑网络:广播ev的mac地址。
|
FailedState
|
request_error_routine
|
进入失败状态,广播错误请求。
|
void IdleState::enter() {
ctx.signal_state("UNMATCHED");
ctx.log_info("Entered Idle state");
}
实际捕捉到的消息:
everest/slac/evse/var {"data":"MATCHING","name":"state"}
everest/slac/evse/var {"data":"UNMATCHED","name":"state"}
EVSEManager模块中订阅消息, 接收上面广播的state、dlink_ready消息, 触发具体的处理过程。
everest-core/modules/EvseManager/EvseManager.cpp
EvseManager::ready()
if (slac_enabled) {
r_slac[0]->subscribe_state([this](const std::string& s) {
session_log.evse(true, fmt::format("SLAC {}", s));
// Notify charger whether matching was started (or is done) or not
if (s == "UNMATCHED") {
charger->set_matching_started(false);
} else {
charger->set_matching_started(true);
}
});
r_slac[0]->subscribe_request_error_routine([this]() {
EVLOG_info << "Received request error routine from SLAC in evsemanager\n";
charger->request_error_sequence();
});
r_slac[0]->subscribe_dlink_ready([this](const bool value) {
session_log.evse(true, fmt::format("D-LINK_READY ({})", value));
if (hlc_enabled) {
r_hlc[0]->call_dlink_ready(value);
}
});
}
3.6.14 EvseSlac运行框架总结
- SlacMessage模型: poll模型,收发PLC线路上的slac命令。
- FSMController模型:事件等待模型,监听Event事件,通过条件变量唤醒。
- Mqtt模型:poll模型, 监听3种socket: mqtt_socket, event_fd, desconnect_event_fd