Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
31
tools/EVerest-main/lib/everest/slac/CMakeLists.txt
Normal file
31
tools/EVerest-main/lib/everest/slac/CMakeLists.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
add_library(slac)
|
||||
add_library(slac::slac ALIAS slac)
|
||||
|
||||
target_include_directories(slac PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:include>
|
||||
)
|
||||
|
||||
target_sources(slac
|
||||
PRIVATE
|
||||
src/channel.cpp
|
||||
src/slac.cpp
|
||||
src/packet_socket.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(slac
|
||||
PRIVATE
|
||||
everest::tls
|
||||
)
|
||||
|
||||
add_subdirectory(io)
|
||||
add_subdirectory(fsm/ev)
|
||||
add_subdirectory(fsm/evse)
|
||||
|
||||
if(BUILD_DEV_TESTS)
|
||||
add_subdirectory(test)
|
||||
endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
21
tools/EVerest-main/lib/everest/slac/fsm/ev/CMakeLists.txt
Normal file
21
tools/EVerest-main/lib/everest/slac/fsm/ev/CMakeLists.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
add_library(slac_fsm_ev)
|
||||
add_library(slac::fsm::ev ALIAS slac_fsm_ev)
|
||||
ev_register_library_target(slac_fsm_ev)
|
||||
|
||||
target_include_directories(slac_fsm_ev
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
)
|
||||
|
||||
target_sources(slac_fsm_ev
|
||||
PRIVATE
|
||||
src/context.cpp
|
||||
src/states/others.cpp
|
||||
src/states/sounding.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(slac_fsm_ev
|
||||
PUBLIC
|
||||
slac::slac
|
||||
fsm::fsm
|
||||
)
|
||||
@@ -0,0 +1,110 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EV_SLAC_CONTEXT_HPP
|
||||
#define EV_SLAC_CONTEXT_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
|
||||
#include <slac/slac.hpp>
|
||||
|
||||
namespace slac::fsm::ev {
|
||||
|
||||
namespace _context_detail {
|
||||
template <typename SlacMessageType> struct MMTYPE;
|
||||
|
||||
template <> struct MMTYPE<slac::messages::cm_slac_parm_req> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_SLAC_PARAM | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::cm_start_atten_char_ind> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_START_ATTEN_CHAR | slac::defs::MMTYPE_MODE_IND;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::cm_mnbc_sound_ind> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_MNBC_SOUND | slac::defs::MMTYPE_MODE_IND;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::cm_atten_char_rsp> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_ATTEN_CHAR | slac::defs::MMTYPE_MODE_RSP;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::cm_slac_match_req> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_SLAC_MATCH | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::cm_set_key_req> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
|
||||
template <typename SlacMessageType> struct MMV {
|
||||
// this is the default value for homeplug av 2.0 messages, which are
|
||||
// backward compatible with homeplug av 1.1 messages
|
||||
// non-backward (to 1.1) compatible message are CM_CHAN_EST,
|
||||
// CM_AMP_MAP and CM_NW_STATS, these need to use AV_2_0
|
||||
// older av 1.0 message need to use AV_1_0
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_1;
|
||||
};
|
||||
} // namespace _context_detail
|
||||
|
||||
struct ContextCallbacks {
|
||||
std::function<void(slac::messages::HomeplugMessage&)> send_raw_slac{nullptr};
|
||||
std::function<void(const std::string&)> signal_state{nullptr};
|
||||
std::function<void(const std::string&)> log_debug{nullptr};
|
||||
std::function<void(const std::string&)> log_info{nullptr};
|
||||
std::function<void(const std::string&)> log_warn{nullptr};
|
||||
std::function<void(const std::string&)> log_error{nullptr};
|
||||
};
|
||||
|
||||
struct Context {
|
||||
static constexpr std::array<uint8_t, ETH_ALEN> BROADCAST_MAC = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
static constexpr std::array<uint8_t, ETH_ALEN> EV_PLC_MAC = {0x00, 0xB0, 0x52, 0x00, 0x00, 0x01};
|
||||
|
||||
Context(const ContextCallbacks& callbacks_, const std::array<uint8_t, ETH_ALEN>& mac) :
|
||||
callbacks(callbacks_), ev_host_mac(mac) {
|
||||
}
|
||||
|
||||
const std::array<uint8_t, ETH_ALEN> ev_host_mac{};
|
||||
|
||||
// event specific payloads
|
||||
// FIXME (aw): due to the synchroneous nature of the fsm, this could be even a ptr/ref
|
||||
slac::messages::HomeplugMessage slac_message;
|
||||
|
||||
// FIXME (aw): message should be const, but libslac doesn't allow for const ptr - needs changes in libslac
|
||||
template <typename SlacMessageType>
|
||||
void send_slac_message(const uint8_t* dest_mac, SlacMessageType const& message) {
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_ethernet_header(dest_mac);
|
||||
try {
|
||||
hp_message.setup_payload(&message, sizeof(message), _context_detail::MMTYPE<SlacMessageType>::value,
|
||||
_context_detail::MMV<SlacMessageType>::value);
|
||||
} catch (const std::runtime_error& e) {
|
||||
const auto error_message = std::string("Could not setup SLAC payload: ") + std::string(e.what());
|
||||
log_error(error_message);
|
||||
}
|
||||
callbacks.send_raw_slac(hp_message);
|
||||
}
|
||||
|
||||
// signal handlers
|
||||
void signal_state(const std::string& state);
|
||||
|
||||
// logging util
|
||||
void log_debug(const std::string& text);
|
||||
void log_info(const std::string& text);
|
||||
void log_warn(const std::string& text);
|
||||
void log_error(const std::string& text);
|
||||
|
||||
private:
|
||||
const ContextCallbacks& callbacks;
|
||||
};
|
||||
|
||||
struct SessionParamaters {
|
||||
SessionParamaters(const uint8_t* run_id, const uint8_t* evse_mac);
|
||||
uint8_t run_id[slac::defs::RUN_ID_LEN];
|
||||
uint8_t evse_mac[ETH_ALEN];
|
||||
};
|
||||
|
||||
} // namespace slac::fsm::ev
|
||||
|
||||
#endif // EV_SLAC_CONTEXT_HPP
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EV_SLAC_FSM_HPP
|
||||
#define EV_SLAC_FSM_HPP
|
||||
|
||||
#include <fsm/fsm.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace slac::fsm::ev {
|
||||
|
||||
enum class Event {
|
||||
RESET,
|
||||
TRIGGER_MATCHING,
|
||||
SLAC_MESSAGE,
|
||||
|
||||
// internal events
|
||||
FAILED,
|
||||
};
|
||||
|
||||
using FSMReturnType = int;
|
||||
|
||||
using FSM = ::fsm::FSM<Event, FSMReturnType>;
|
||||
using FSMSimpleState = ::fsm::states::StateWithContext<FSM::SimpleStateType, Context>;
|
||||
using FSMCompoundState = ::fsm::states::StateWithContext<FSM::CompoundStateType, Context>;
|
||||
|
||||
} // namespace slac::fsm::ev
|
||||
|
||||
#endif // EV_SLAC_FSM_HPP
|
||||
@@ -0,0 +1,103 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EV_SLAC_STATES_OTHERS_HPP
|
||||
#define EV_SLAC_STATES_OTHERS_HPP
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <everest/slac/fsm/ev/fsm.hpp>
|
||||
|
||||
namespace slac::fsm::ev {
|
||||
|
||||
struct ResetState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
CallbackReturnType callback() final;
|
||||
};
|
||||
|
||||
struct InitSlacState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
// only returns true, if valid
|
||||
bool check_for_valid_parm_conf();
|
||||
|
||||
// sends out CM_SLAC_PARM.REQ, increases num of tries and returns the timeout until a response is expected
|
||||
int send_parm_req();
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> next_timeout;
|
||||
|
||||
int num_of_tries{0};
|
||||
|
||||
uint8_t run_id[8];
|
||||
};
|
||||
|
||||
struct MatchRequestState : public FSMSimpleState {
|
||||
MatchRequestState(Context& ctx, SessionParamaters session_parameters);
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
// return the pointer to the NMK, if valid
|
||||
const uint8_t* check_for_valid_match_req_conf();
|
||||
|
||||
// sends out CM_SLAC_PARM.REQ, increases num of tries and returns the timeout until a response is expected
|
||||
int send_match_req();
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> next_timeout;
|
||||
|
||||
int num_of_tries{0};
|
||||
|
||||
SessionParamaters session_parameters;
|
||||
};
|
||||
|
||||
struct JoinNetworkState : public FSMSimpleState {
|
||||
static constexpr auto SET_KEY_TIMEOUT_MS = 500;
|
||||
JoinNetworkState(Context& ctx, const uint8_t* nmk);
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
// only returns true, if valid
|
||||
bool check_for_valid_set_key_conf();
|
||||
|
||||
std::chrono::time_point<std::chrono::steady_clock> timeout;
|
||||
|
||||
uint8_t nmk[slac::defs::NMK_LEN];
|
||||
};
|
||||
|
||||
struct MatchedState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
void enter() final {
|
||||
ctx.log_info("Entered matched state");
|
||||
ctx.signal_state("MATCHED");
|
||||
}
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
};
|
||||
|
||||
struct FailedState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
void enter() final {
|
||||
ctx.log_info("Entered failed state");
|
||||
}
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
};
|
||||
|
||||
} // namespace slac::fsm::ev
|
||||
|
||||
#endif // EV_SLAC_STATES_OTHERS_HPP
|
||||
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EV_SLAC_STATES_SOUNDING_HPP
|
||||
#define EV_SLAC_STATES_SOUNDING_HPP
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#include <slac/slac.hpp>
|
||||
|
||||
#include <everest/slac/fsm/ev/fsm.hpp>
|
||||
|
||||
namespace slac::fsm::ev {
|
||||
|
||||
struct SoundingState : public FSMSimpleState {
|
||||
SoundingState(Context& ctx, SessionParamaters session_parameters);
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
// returns true, if CM_ATTEN_CHAR.IND is expected and fulfills expectations and response has been send
|
||||
bool handle_valid_atten_char_ind();
|
||||
|
||||
// returns true if further message will need to be send
|
||||
bool do_sounding();
|
||||
|
||||
// timepoint until we need to receive the atten_char_ind message
|
||||
std::chrono::time_point<std::chrono::steady_clock> sounding_timeout;
|
||||
|
||||
// timepoint until we internally want to send the next message
|
||||
std::chrono::time_point<std::chrono::steady_clock> next_timeout;
|
||||
|
||||
int count_start_atten_char_sent{0};
|
||||
int count_mnbc_sound_sent{0};
|
||||
|
||||
SessionParamaters session_parameters;
|
||||
};
|
||||
|
||||
} // namespace slac::fsm::ev
|
||||
|
||||
#endif // EV_SLAC_STATES_SOUNDING_HPP
|
||||
44
tools/EVerest-main/lib/everest/slac/fsm/ev/src/context.cpp
Normal file
44
tools/EVerest-main/lib/everest/slac/fsm/ev/src/context.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/slac/fsm/ev/context.hpp>
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace slac::fsm::ev {
|
||||
|
||||
void Context::signal_state(const std::string& state) {
|
||||
if (callbacks.signal_state) {
|
||||
callbacks.signal_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::log_debug(const std::string& text) {
|
||||
if (callbacks.log_debug) {
|
||||
callbacks.log_debug(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::log_info(const std::string& text) {
|
||||
if (callbacks.log_info) {
|
||||
callbacks.log_info(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::log_warn(const std::string& text) {
|
||||
if (callbacks.log_warn) {
|
||||
callbacks.log_warn(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::log_error(const std::string& text) {
|
||||
if (callbacks.log_error) {
|
||||
callbacks.log_error(text);
|
||||
}
|
||||
}
|
||||
|
||||
SessionParamaters::SessionParamaters(const uint8_t* run_id_, const uint8_t* evse_mac_) {
|
||||
memcpy(run_id, run_id_, sizeof(run_id));
|
||||
memcpy(evse_mac, evse_mac_, sizeof(evse_mac));
|
||||
}
|
||||
|
||||
} // namespace slac::fsm::ev
|
||||
303
tools/EVerest-main/lib/everest/slac/fsm/ev/src/states/others.cpp
Normal file
303
tools/EVerest-main/lib/everest/slac/fsm/ev/src/states/others.cpp
Normal file
@@ -0,0 +1,303 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/slac/fsm/ev/states/others.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
|
||||
#include <endian.h>
|
||||
|
||||
#include "timing_helper.hpp"
|
||||
|
||||
#include <everest/slac/fsm/ev/states/sounding.hpp>
|
||||
|
||||
namespace slac::fsm::ev {
|
||||
|
||||
void ResetState::enter() {
|
||||
ctx.signal_state("UNMATCHED");
|
||||
ctx.log_info("Entered Reset state");
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType ResetState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::TRIGGER_MATCHING) {
|
||||
return sa.create_simple<InitSlacState>(ctx);
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType ResetState::callback() {
|
||||
ctx.log_info("Called callback of ResetState");
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType InitSlacState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::SLAC_MESSAGE) {
|
||||
if (check_for_valid_parm_conf()) {
|
||||
ctx.signal_state("MATCHING");
|
||||
return sa.create_simple<SoundingState>(ctx, SessionParamaters{run_id, ctx.slac_message.get_src_mac()});
|
||||
}
|
||||
|
||||
return sa.HANDLED_INTERNALLY;
|
||||
} else if (ev == Event::FAILED) {
|
||||
return sa.create_simple<FailedState>(ctx);
|
||||
} else if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
void InitSlacState::enter() {
|
||||
ctx.log_info("Entered init state");
|
||||
|
||||
// generate random run_id
|
||||
std::random_device rnd_dev;
|
||||
std::mt19937 rng(rnd_dev());
|
||||
std::uniform_int_distribution<std::mt19937::result_type> dist256(0, 255);
|
||||
|
||||
for (auto& id : this->run_id) {
|
||||
id = dist256(rng);
|
||||
}
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType InitSlacState::callback() {
|
||||
if (num_of_tries == 0) {
|
||||
return send_parm_req();
|
||||
}
|
||||
|
||||
// did already send a parm req, check for timeout
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto time_left = milliseconds_left(now, next_timeout);
|
||||
|
||||
if (time_left > 0) {
|
||||
// still have time
|
||||
return time_left;
|
||||
}
|
||||
|
||||
// timeout, check if we still have retries
|
||||
if (num_of_tries < 100) {
|
||||
// FIXME (aw): the norm says num_of_tries < slac::defs::C_EV_MATCH_RETRY
|
||||
// but doesn't seem to work in 'real life'
|
||||
return send_parm_req();
|
||||
}
|
||||
|
||||
// no retries left fail
|
||||
return Event::FAILED;
|
||||
}
|
||||
|
||||
int InitSlacState::send_parm_req() {
|
||||
slac::messages::cm_slac_parm_req msg;
|
||||
msg.application_type = 0x0;
|
||||
msg.security_type = 0x0;
|
||||
memcpy(msg.run_id, run_id, sizeof(msg.run_id));
|
||||
|
||||
ctx.send_slac_message(ctx.BROADCAST_MAC.data(), msg);
|
||||
|
||||
num_of_tries++;
|
||||
|
||||
next_timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(slac::defs::TT_MATCH_RESPONSE_MS);
|
||||
|
||||
return slac::defs::TT_MATCH_RESPONSE_MS;
|
||||
}
|
||||
|
||||
bool InitSlacState::check_for_valid_parm_conf() {
|
||||
const auto mmtype = ctx.slac_message.get_mmtype();
|
||||
if (mmtype != (slac::defs::MMTYPE_CM_SLAC_PARAM | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// correct message type
|
||||
const auto& parm_cnf = ctx.slac_message.get_payload<slac::messages::cm_slac_parm_cnf>();
|
||||
|
||||
// it is not clear, whether we should use the m-sound target anyhow ... or if we should really validate any of the
|
||||
// fields except the run id
|
||||
const auto same_run_id = (memcmp(parm_cnf.run_id, run_id, sizeof(run_id)) == 0);
|
||||
|
||||
return same_run_id;
|
||||
}
|
||||
|
||||
MatchRequestState::MatchRequestState(Context& ctx, SessionParamaters session_parameters_) :
|
||||
FSMSimpleState(ctx), session_parameters(std::move(session_parameters_)) {
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType MatchRequestState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::SLAC_MESSAGE) {
|
||||
const auto nmk = check_for_valid_match_req_conf();
|
||||
if (nmk) {
|
||||
return sa.create_simple<JoinNetworkState>(ctx, nmk);
|
||||
}
|
||||
|
||||
return sa.HANDLED_INTERNALLY;
|
||||
} else if (ev == Event::FAILED) {
|
||||
return sa.create_simple<FailedState>(ctx);
|
||||
} else if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
void MatchRequestState::enter() {
|
||||
ctx.log_info("Entered MatchRequestState state");
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType MatchRequestState::callback() {
|
||||
if (num_of_tries == 0) {
|
||||
return send_match_req();
|
||||
}
|
||||
|
||||
// did already send a parm req, check for timeout
|
||||
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto time_left = milliseconds_left(now, next_timeout);
|
||||
|
||||
if (time_left > 0) {
|
||||
// still have time
|
||||
return time_left;
|
||||
}
|
||||
|
||||
// timeout, check if we still have retries
|
||||
if (num_of_tries < slac::defs::C_EV_MATCH_RETRY) {
|
||||
return send_match_req();
|
||||
}
|
||||
|
||||
// no retries left fail
|
||||
return Event::FAILED;
|
||||
}
|
||||
|
||||
const uint8_t* MatchRequestState::check_for_valid_match_req_conf() {
|
||||
if (num_of_tries == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const auto mmtype = ctx.slac_message.get_mmtype();
|
||||
if (mmtype != (slac::defs::MMTYPE_CM_SLAC_MATCH | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// correct message type
|
||||
const auto& match_cnf = ctx.slac_message.get_payload<slac::messages::cm_slac_match_cnf>();
|
||||
|
||||
const auto run_id_match =
|
||||
(memcmp(session_parameters.run_id, match_cnf.run_id, sizeof(session_parameters.run_id)) == 0);
|
||||
|
||||
if (run_id_match) {
|
||||
return match_cnf.nmk;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int MatchRequestState::send_match_req() {
|
||||
slac::messages::cm_slac_match_req msg;
|
||||
msg.application_type = 0x0;
|
||||
msg.security_type = 0x0;
|
||||
msg.mvf_length = htole16(0x3e); // FIXME (aw) fixed constant
|
||||
memset(msg.pev_id, 0, sizeof(msg.pev_id));
|
||||
memcpy(msg.pev_mac, ctx.ev_host_mac.data(), sizeof(msg.pev_mac));
|
||||
memset(msg.evse_id, 0, sizeof(msg.evse_id));
|
||||
memcpy(msg.evse_mac, session_parameters.evse_mac, sizeof(msg.evse_mac));
|
||||
memcpy(msg.run_id, session_parameters.run_id, sizeof(msg.run_id));
|
||||
memset(msg._reserved, 0, sizeof(msg._reserved));
|
||||
|
||||
ctx.send_slac_message(session_parameters.evse_mac, msg);
|
||||
|
||||
num_of_tries++;
|
||||
|
||||
next_timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(slac::defs::TT_MATCH_RESPONSE_MS);
|
||||
|
||||
return slac::defs::TT_MATCH_RESPONSE_MS;
|
||||
}
|
||||
|
||||
JoinNetworkState::JoinNetworkState(Context& ctx, const uint8_t* nmk_) : FSMSimpleState(ctx) {
|
||||
memcpy(nmk, nmk_, sizeof(nmk));
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType JoinNetworkState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::SLAC_MESSAGE) {
|
||||
if (check_for_valid_set_key_conf()) {
|
||||
// FIXME (aw): later on, we also need to distinguish between set_key failed
|
||||
return sa.create_simple<MatchedState>(ctx);
|
||||
}
|
||||
|
||||
return sa.HANDLED_INTERNALLY;
|
||||
} else if (ev == Event::FAILED) {
|
||||
return sa.create_simple<FailedState>(ctx);
|
||||
} else if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
void JoinNetworkState::enter() {
|
||||
ctx.log_info("Entered JoinNetwork state");
|
||||
|
||||
slac::messages::cm_set_key_req msg;
|
||||
msg.key_type = slac::defs::CM_SET_KEY_REQ_KEY_TYPE_NMK;
|
||||
msg.my_nonce = 0xAAAAAAAA;
|
||||
msg.your_nonce = 0x00000000;
|
||||
msg.pid = slac::defs::CM_SET_KEY_REQ_PID_HLE;
|
||||
msg.prn = htole16(slac::defs::CM_SET_KEY_REQ_PRN_UNUSED);
|
||||
msg.pmn = slac::defs::CM_SET_KEY_REQ_PMN_UNUSED;
|
||||
msg.cco_capability = slac::defs::CM_SET_KEY_REQ_CCO_CAP_NONE;
|
||||
slac::utils::generate_nid_from_nmk(msg.nid, nmk);
|
||||
msg.new_eks = slac::defs::CM_SET_KEY_REQ_PEKS_NMK_KNOWN_TO_STA;
|
||||
memcpy(msg.new_key, nmk, sizeof(msg.new_key));
|
||||
|
||||
ctx.send_slac_message(ctx.EV_PLC_MAC.data(), msg);
|
||||
|
||||
timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(SET_KEY_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType JoinNetworkState::callback() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto time_left = milliseconds_left(now, timeout);
|
||||
|
||||
if (time_left > 0) {
|
||||
// still have time
|
||||
return time_left;
|
||||
}
|
||||
|
||||
// we reached the set key timeout
|
||||
return Event::FAILED;
|
||||
}
|
||||
|
||||
bool JoinNetworkState::check_for_valid_set_key_conf() {
|
||||
const auto mmtype = ctx.slac_message.get_mmtype();
|
||||
if (mmtype != (slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// correct message type
|
||||
const auto& set_key_cnf = ctx.slac_message.get_payload<slac::messages::cm_set_key_cnf>();
|
||||
|
||||
// FIXME (aw): validation of the message?
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType MatchedState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
} else if (ev == Event::SLAC_MESSAGE) {
|
||||
return sa.HANDLED_INTERNALLY;
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType FailedState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
} else if (ev == Event::SLAC_MESSAGE) {
|
||||
return sa.HANDLED_INTERNALLY;
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace slac::fsm::ev
|
||||
@@ -0,0 +1,154 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/slac/fsm/ev/states/sounding.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <random>
|
||||
|
||||
#include "timing_helper.hpp"
|
||||
|
||||
#include <everest/slac/fsm/ev/states/others.hpp>
|
||||
|
||||
namespace slac::fsm::ev {
|
||||
|
||||
SoundingState::SoundingState(Context& ctx, SessionParamaters session_parameters_) :
|
||||
FSMSimpleState(ctx), session_parameters(std::move(session_parameters_)) {
|
||||
}
|
||||
|
||||
void SoundingState::enter() {
|
||||
sounding_timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(slac::defs::TT_EV_ATTEN_RESULTS_MS);
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType SoundingState::callback() {
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto sounding_time_left = milliseconds_left(now, sounding_timeout);
|
||||
|
||||
if (sounding_time_left <= 0) {
|
||||
return Event::FAILED;
|
||||
}
|
||||
|
||||
if (count_mnbc_sound_sent == 10) {
|
||||
// we're already done, return time left until sounding timeout
|
||||
return sounding_time_left;
|
||||
}
|
||||
|
||||
if (count_start_atten_char_sent == 0) {
|
||||
// no sounding messages have been send yet
|
||||
next_timeout = now;
|
||||
}
|
||||
|
||||
const auto next_step_time_left = milliseconds_left(now, next_timeout);
|
||||
|
||||
if (next_step_time_left > 0) {
|
||||
// just idle a bit
|
||||
return std::min(next_step_time_left, sounding_time_left);
|
||||
}
|
||||
|
||||
// need to issue the next step
|
||||
if (do_sounding()) {
|
||||
// FIXME (aw): how to setup the next timeout, we could send the everything right away, but waiting at least a
|
||||
// bit seems to be a good practice
|
||||
const auto next_step_delay_ms = 20; // FIXME (aw): which value to use? TP_EV_BATCH_MSG_INTERVAL_MS
|
||||
next_timeout = now + std::chrono::milliseconds(next_step_delay_ms);
|
||||
return std::min(next_step_delay_ms, static_cast<int>(sounding_time_left));
|
||||
}
|
||||
|
||||
return sounding_time_left;
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType SoundingState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::SLAC_MESSAGE) {
|
||||
if (handle_valid_atten_char_ind()) {
|
||||
return sa.create_simple<MatchRequestState>(ctx, session_parameters);
|
||||
}
|
||||
return sa.HANDLED_INTERNALLY;
|
||||
} else if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
}
|
||||
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
|
||||
bool SoundingState::do_sounding() {
|
||||
if (count_start_atten_char_sent < slac::defs::C_EV_START_ATTEN_CHAR_INDS) {
|
||||
|
||||
slac::messages::cm_start_atten_char_ind msg;
|
||||
msg.application_type = 0x0;
|
||||
msg.security_type = 0x0;
|
||||
msg.num_sounds = slac::defs::C_EV_MATCH_MNBC;
|
||||
msg.timeout = (slac::defs::TT_EVSE_MATCH_MNBC_MS + 99) / 100; // in multiples of 100ms!
|
||||
msg.resp_type = 0x01; // fixed value indicating 'other Green Phy station'
|
||||
memcpy(msg.forwarding_sta, ctx.ev_host_mac.data(), sizeof(msg.forwarding_sta));
|
||||
memcpy(msg.run_id, session_parameters.run_id, sizeof(msg.run_id));
|
||||
|
||||
ctx.send_slac_message(ctx.BROADCAST_MAC.data(), msg);
|
||||
|
||||
count_start_atten_char_sent++;
|
||||
|
||||
} else if (count_mnbc_sound_sent < slac::defs::C_EV_MATCH_MNBC) {
|
||||
count_mnbc_sound_sent++;
|
||||
|
||||
slac::messages::cm_mnbc_sound_ind msg;
|
||||
msg.application_type = 0x0;
|
||||
msg.security_type = 0x0;
|
||||
memset(msg.sender_id, 0, sizeof(msg.sender_id));
|
||||
msg.remaining_sound_count = slac::defs::C_EV_MATCH_MNBC - count_mnbc_sound_sent;
|
||||
memcpy(msg.run_id, session_parameters.run_id, sizeof(msg.run_id));
|
||||
memset(msg._reserved, 0, sizeof(msg._reserved));
|
||||
|
||||
// FIXME (aw): does this have any performance penalties?
|
||||
std::random_device rnd_dev;
|
||||
std::mt19937 rng(rnd_dev());
|
||||
std::uniform_int_distribution<std::mt19937::result_type> dist256(0, 255);
|
||||
|
||||
for (auto& random : msg.random) {
|
||||
random = dist256(rng);
|
||||
}
|
||||
|
||||
ctx.send_slac_message(ctx.BROADCAST_MAC.data(), msg);
|
||||
}
|
||||
|
||||
return (count_mnbc_sound_sent < slac::defs::C_EV_MATCH_MNBC);
|
||||
}
|
||||
|
||||
bool SoundingState::handle_valid_atten_char_ind() {
|
||||
if (count_mnbc_sound_sent < slac::defs::C_EV_MATCH_MNBC) {
|
||||
ctx.log_info("Received unexpected message while SOUNDING");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto mmtype = ctx.slac_message.get_mmtype();
|
||||
|
||||
if (mmtype != (slac::defs::MMTYPE_CM_ATTEN_CHAR | slac::defs::MMTYPE_MODE_IND)) {
|
||||
ctx.log_info("Received unexpected message after SOUNDING");
|
||||
return false;
|
||||
}
|
||||
|
||||
// correct message type
|
||||
const auto& atten_char = ctx.slac_message.get_payload<slac::messages::cm_atten_char_ind>();
|
||||
|
||||
const auto run_id_match =
|
||||
(memcmp(session_parameters.run_id, atten_char.run_id, sizeof(session_parameters.run_id)) == 0);
|
||||
|
||||
if (run_id_match == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// reply
|
||||
slac::messages::cm_atten_char_rsp response;
|
||||
response.application_type = 0x0;
|
||||
response.security_type = 0x0;
|
||||
memcpy(response.source_address, ctx.ev_host_mac.data(), sizeof(response.source_address));
|
||||
memcpy(response.run_id, atten_char.run_id, sizeof(response.run_id));
|
||||
memset(response.source_id, 0, sizeof(response.source_id));
|
||||
memset(response.resp_id, 0, sizeof(response.resp_id));
|
||||
response.result = 0x0;
|
||||
|
||||
ctx.send_slac_message(session_parameters.evse_mac, response);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace slac::fsm::ev
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EV_SLAC_STATES_TIMING_HELPER_HPP
|
||||
#define EV_SLAC_STATES_TIMING_HELPER_HPP
|
||||
|
||||
#include <chrono>
|
||||
|
||||
template <typename TimepointType> auto milliseconds_left(const TimepointType& from, const TimepointType& to) {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(to - from).count();
|
||||
}
|
||||
|
||||
#endif // EV_SLAC_STATES_TIMING_HELPER_HPP
|
||||
23
tools/EVerest-main/lib/everest/slac/fsm/evse/CMakeLists.txt
Normal file
23
tools/EVerest-main/lib/everest/slac/fsm/evse/CMakeLists.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
add_library(slac_fsm_evse)
|
||||
add_library(slac::fsm::evse ALIAS slac_fsm_evse)
|
||||
ev_register_library_target(slac_fsm_evse)
|
||||
|
||||
target_include_directories(slac_fsm_evse
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
)
|
||||
|
||||
target_sources(slac_fsm_evse
|
||||
PRIVATE
|
||||
src/misc.cpp
|
||||
src/context.cpp
|
||||
src/states/others.cpp
|
||||
src/states/matching.cpp
|
||||
src/states/matching_handle_slac.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(slac_fsm_evse
|
||||
PUBLIC
|
||||
slac::slac
|
||||
fsm::fsm
|
||||
)
|
||||
@@ -0,0 +1,237 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE_SLAC_CONTEXT_HPP
|
||||
#define EVSE_SLAC_CONTEXT_HPP
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include <slac/slac.hpp>
|
||||
|
||||
namespace slac::fsm::evse {
|
||||
|
||||
namespace _context_detail {
|
||||
|
||||
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;
|
||||
};
|
||||
template <> struct MMTYPE<slac::messages::cm_atten_char_ind> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_ATTEN_CHAR | slac::defs::MMTYPE_MODE_IND;
|
||||
};
|
||||
template <> struct MMTYPE<slac::messages::cm_set_key_req> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
template <> struct MMTYPE<slac::messages::cm_validate_cnf> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_VALIDATE | slac::defs::MMTYPE_MODE_CNF;
|
||||
};
|
||||
template <> struct MMTYPE<slac::messages::cm_slac_match_cnf> {
|
||||
static const uint16_t value = slac::defs::MMTYPE_CM_SLAC_MATCH | slac::defs::MMTYPE_MODE_CNF;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::qualcomm::cm_reset_device_req> {
|
||||
static const uint16_t value = slac::defs::qualcomm::MMTYPE_CM_RESET_DEVICE | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::qualcomm::cm_reset_device_cnf> {
|
||||
static const uint16_t value = slac::defs::qualcomm::MMTYPE_CM_RESET_DEVICE | slac::defs::MMTYPE_MODE_CNF;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::qualcomm::link_status_req> {
|
||||
static const uint16_t value = slac::defs::qualcomm::MMTYPE_LINK_STATUS | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::qualcomm::link_status_cnf> {
|
||||
static const uint16_t value = slac::defs::qualcomm::MMTYPE_LINK_STATUS | slac::defs::MMTYPE_MODE_CNF;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::qualcomm::op_attr_req> {
|
||||
static const uint16_t value = slac::defs::qualcomm::MMTYPE_OP_ATTR | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::qualcomm::op_attr_cnf> {
|
||||
static const uint16_t value = slac::defs::qualcomm::MMTYPE_OP_ATTR | slac::defs::MMTYPE_MODE_CNF;
|
||||
};
|
||||
|
||||
// This message has no CNF counterpart
|
||||
template <> struct MMTYPE<slac::messages::lumissil::nscm_reset_device_req> {
|
||||
static const uint16_t value = slac::defs::lumissil::MMTYPE_NSCM_RESET_DEVICE | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::lumissil::nscm_get_version_req> {
|
||||
static const uint16_t value = slac::defs::lumissil::MMTYPE_NSCM_GET_VERSION | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::lumissil::nscm_get_version_cnf> {
|
||||
static const uint16_t value = slac::defs::lumissil::MMTYPE_NSCM_GET_VERSION | slac::defs::MMTYPE_MODE_CNF;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::lumissil::nscm_get_d_link_status_req> {
|
||||
static const uint16_t value = slac::defs::lumissil::MMTYPE_NSCM_GET_D_LINK_STATUS | slac::defs::MMTYPE_MODE_REQ;
|
||||
};
|
||||
|
||||
template <> struct MMTYPE<slac::messages::lumissil::nscm_get_d_link_status_cnf> {
|
||||
static const uint16_t value = slac::defs::lumissil::MMTYPE_NSCM_GET_D_LINK_STATUS | slac::defs::MMTYPE_MODE_CNF;
|
||||
};
|
||||
|
||||
template <typename SlacMessageType> struct MMV {
|
||||
// this is the default value for homeplug av 2.0 messages, which are
|
||||
// backward compatible with homeplug av 1.1 messages
|
||||
// non-backward (to 1.1) compatible message are CM_CHAN_EST,
|
||||
// CM_AMP_MAP and CM_NW_STATS, these need to use AV_2_0
|
||||
// older av 1.0 message need to use AV_1_0
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_1;
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::qualcomm::cm_reset_device_req> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0;
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::qualcomm::cm_reset_device_cnf> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0;
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::qualcomm::link_status_req> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0;
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::qualcomm::link_status_cnf> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0;
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::qualcomm::op_attr_req> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0;
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::qualcomm::op_attr_cnf> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0;
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::lumissil::nscm_reset_device_req> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0; // FIXME this is unclear
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::lumissil::nscm_get_version_req> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0; // FIXME this is unclear
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::lumissil::nscm_get_version_cnf> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0; // FIXME this is unclear
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::lumissil::nscm_get_d_link_status_req> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0; // FIXME this is unclear
|
||||
};
|
||||
|
||||
template <> struct MMV<slac::messages::lumissil::nscm_get_d_link_status_cnf> {
|
||||
static constexpr auto value = slac::defs::MMV::AV_1_0; // FIXME this is unclear
|
||||
};
|
||||
|
||||
} // namespace _context_detail
|
||||
|
||||
// FIXME (aw): this should be moved to common headers (in libslac)
|
||||
enum class ModemVendor {
|
||||
Unknown,
|
||||
Qualcomm,
|
||||
Lumissil,
|
||||
VertexCom,
|
||||
};
|
||||
|
||||
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_debug{nullptr};
|
||||
std::function<void(const std::string&)> log_info{nullptr};
|
||||
std::function<void(const std::string&)> log_warn{nullptr};
|
||||
std::function<void(const std::string&)> log_error{nullptr};
|
||||
};
|
||||
|
||||
struct EvseSlacConfig {
|
||||
// MAC address of our (EVSE) PLC modem
|
||||
// FIXME (aw): is that used somehow?
|
||||
uint8_t plc_peer_mac[ETH_ALEN] = {0x00, 0xB0, 0x52, 0x00, 0x00, 0x01};
|
||||
|
||||
// FIXME (aw): we probably want to use std::array here
|
||||
void generate_nmk();
|
||||
uint8_t session_nmk[slac::defs::NMK_LEN]{};
|
||||
|
||||
// flag for using 5% PWM in AC mode
|
||||
bool ac_mode_five_percent{true};
|
||||
|
||||
// timeout for CM_SET_KEY.REQ
|
||||
int set_key_timeout_ms = 500;
|
||||
|
||||
// timeout for CM_SLAC_PARM.REQ
|
||||
int slac_init_timeout_ms = slac::defs::TT_EVSE_SLAC_INIT_MS;
|
||||
|
||||
// Settings CM_DEVICE_RESET.REQ
|
||||
struct chip_reset_struct {
|
||||
bool enabled = false;
|
||||
int timeout_ms = 500;
|
||||
int delay_ms = 100;
|
||||
} chip_reset;
|
||||
|
||||
// Settings for LINK_STATUS detection
|
||||
struct link_status_struct {
|
||||
bool do_detect = false;
|
||||
int retry_ms = 100;
|
||||
int poll_in_matched_state_ms = 1000;
|
||||
int timeout_ms = 5000;
|
||||
bool debug_simulate_failed_matching = false;
|
||||
} link_status;
|
||||
|
||||
int request_info_delay_ms = 100;
|
||||
|
||||
// offset for adjusting the calculated sounding attenuation
|
||||
int sounding_atten_adjustment = 0;
|
||||
|
||||
bool reset_instead_of_fail{false};
|
||||
|
||||
bool regenerate_key_on_reset{true};
|
||||
};
|
||||
|
||||
struct Context {
|
||||
explicit Context(const ContextCallbacks& callbacks_) : callbacks(callbacks_){};
|
||||
|
||||
EvseSlacConfig slac_config{};
|
||||
|
||||
// event specific payloads
|
||||
// FIXME (aw): due to the synchroneous nature of the fsm, this could be even a ptr/ref
|
||||
slac::messages::HomeplugMessage slac_message_payload;
|
||||
|
||||
// FIXME (aw): message should be const, but libslac doesn't allow for const ptr - needs changes in libslac
|
||||
template <typename SlacMessageType> void send_slac_message(const uint8_t* mac, SlacMessageType const& message) {
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_ethernet_header(mac);
|
||||
hp_message.setup_payload(&message, sizeof(message), _context_detail::MMTYPE<SlacMessageType>::value,
|
||||
_context_detail::MMV<SlacMessageType>::value);
|
||||
callbacks.send_raw_slac(hp_message);
|
||||
}
|
||||
|
||||
// signal handlers
|
||||
void signal_cm_slac_parm_req(const uint8_t* ev_mac);
|
||||
void signal_cm_slac_match_cnf(const uint8_t* ev_mac);
|
||||
void signal_dlink_ready(bool value);
|
||||
void signal_error_routine_request();
|
||||
void signal_state(const std::string& state);
|
||||
|
||||
// logging util
|
||||
void log_debug(const std::string& text);
|
||||
void log_info(const std::string& text);
|
||||
void log_warn(const std::string& text);
|
||||
void log_error(const std::string& text);
|
||||
|
||||
ModemVendor modem_vendor{ModemVendor::Unknown};
|
||||
uint8_t evse_mac[ETH_ALEN];
|
||||
|
||||
private:
|
||||
const ContextCallbacks& callbacks;
|
||||
};
|
||||
|
||||
} // namespace slac::fsm::evse
|
||||
|
||||
#endif // EVSE_SLAC_CONTEXT_HPP
|
||||
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE_SLAC_FSM_HPP
|
||||
#define EVSE_SLAC_FSM_HPP
|
||||
|
||||
#include <fsm/fsm.hpp>
|
||||
|
||||
#include "context.hpp"
|
||||
|
||||
namespace slac::fsm::evse {
|
||||
|
||||
enum class Event {
|
||||
RESET,
|
||||
ENTER_BCD,
|
||||
LEAVE_BCD,
|
||||
SLAC_MESSAGE,
|
||||
|
||||
// internal events
|
||||
RETRY_MATCHING,
|
||||
MATCH_COMPLETE,
|
||||
FAILED,
|
||||
SUCCESS,
|
||||
};
|
||||
|
||||
using FSMReturnType = int;
|
||||
|
||||
using FSM = ::fsm::FSM<Event, FSMReturnType>;
|
||||
using FSMSimpleState = ::fsm::states::StateWithContext<FSM::SimpleStateType, Context>;
|
||||
using FSMCompoundState = ::fsm::states::StateWithContext<FSM::CompoundStateType, Context>;
|
||||
|
||||
} // namespace slac::fsm::evse
|
||||
|
||||
#endif // EVSE_SLAC_FSM_HPP
|
||||
@@ -0,0 +1,93 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE_SLAC_STATES_MATCHING_HPP
|
||||
#define EVSE_SLAC_STATES_MATCHING_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include <slac/slac.hpp>
|
||||
|
||||
#include "../fsm.hpp"
|
||||
|
||||
namespace slac::fsm::evse {
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
constexpr auto FINALIZE_SOUNDING_DELAY_MS = 45;
|
||||
|
||||
using MatchingTimepoint = std::chrono::time_point<std::chrono::steady_clock>;
|
||||
|
||||
struct MatchingSession {
|
||||
MatchingSession(const uint8_t* ev_mac, const uint8_t* run_id);
|
||||
|
||||
// common session related
|
||||
MatchingSubState state{MatchingSubState::WAIT_FOR_START_ATTEN_CHAR};
|
||||
uint8_t ev_mac[ETH_ALEN];
|
||||
uint8_t run_id[slac::defs::RUN_ID_LEN];
|
||||
|
||||
// timeout related
|
||||
MatchingTimepoint next_timeout;
|
||||
bool timeout_active{false};
|
||||
|
||||
// sounding related
|
||||
int captured_sounds{0};
|
||||
int captured_aags[slac::defs::AAG_LIST_LEN];
|
||||
bool received_mnbc_sound{false};
|
||||
|
||||
int num_retries{0};
|
||||
|
||||
// helper functions
|
||||
void set_next_timeout(int delay_ms);
|
||||
void ack_timeout();
|
||||
bool is_identified_by(const uint8_t* ev_mac, const uint8_t* run_id) const;
|
||||
slac::messages::cm_atten_char_ind calculate_avg() const;
|
||||
};
|
||||
|
||||
struct MatchingState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
std::vector<MatchingSession> sessions;
|
||||
|
||||
// FIXME (aw): this should be const ref, but some of the member functions of HomeplugMessage are not const'd
|
||||
void handle_slac_message(slac::messages::HomeplugMessage&);
|
||||
|
||||
void handle_cm_slac_parm_req(const slac::messages::cm_slac_parm_req&);
|
||||
void handle_cm_start_atten_char_ind(const slac::messages::cm_start_atten_char_ind&);
|
||||
void handle_cm_mnbc_sound_ind(const slac::messages::cm_mnbc_sound_ind&);
|
||||
void handle_cm_atten_profile_ind(const slac::messages::cm_atten_profile_ind&);
|
||||
void handle_cm_atten_char_rsp(const slac::messages::cm_atten_char_rsp&);
|
||||
void handle_cm_validate_req(const slac::messages::cm_validate_req&);
|
||||
void handle_cm_slac_match_req(const slac::messages::cm_slac_match_req&);
|
||||
|
||||
void finalize_sounding(MatchingSession& session);
|
||||
|
||||
// FIXME (aw): this should be wrapped somewhere else
|
||||
const uint8_t* tmp_ev_mac;
|
||||
|
||||
MatchingTimepoint timeout_slac_parm_req;
|
||||
|
||||
bool seen_slac_parm_req{false};
|
||||
int num_retries{0};
|
||||
|
||||
std::unique_ptr<slac::messages::cm_slac_match_cnf> match_cnf_message;
|
||||
int failed_count{0};
|
||||
};
|
||||
|
||||
} // namespace slac::fsm::evse
|
||||
|
||||
#endif // EVSE_SLAC_STATES_MATCHING_HPP
|
||||
@@ -0,0 +1,112 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE_SLAC_STATES_OTHERS_HPP
|
||||
#define EVSE_SLAC_STATES_OTHERS_HPP
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include "../fsm.hpp"
|
||||
|
||||
namespace slac::fsm::evse {
|
||||
|
||||
struct ResetState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
// for now returns true if CM_SET_KEY_CNF is received
|
||||
bool handle_slac_message(slac::messages::HomeplugMessage&);
|
||||
|
||||
bool setup_has_been_send{false};
|
||||
};
|
||||
|
||||
struct ResetChipState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
// for now returns true if CM_RESET_CNF is received
|
||||
bool handle_slac_message(slac::messages::HomeplugMessage&);
|
||||
|
||||
bool reset_delay_done{false};
|
||||
bool chip_reset_has_been_sent{false};
|
||||
|
||||
enum class SubState {
|
||||
DELAY,
|
||||
SEND_RESET,
|
||||
DONE,
|
||||
} sub_state{SubState::DELAY};
|
||||
};
|
||||
|
||||
struct IdleState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
};
|
||||
|
||||
struct MatchedState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
void leave() final;
|
||||
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
bool link_status_req_sent{false};
|
||||
};
|
||||
|
||||
struct FailedState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
};
|
||||
|
||||
struct WaitForLinkState : public FSMSimpleState {
|
||||
WaitForLinkState(Context& ctx, std::unique_ptr<slac::messages::cm_slac_match_cnf> sent_match_cnf_message);
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
void enter() final;
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
// for now returns true if link up detected is received
|
||||
bool handle_slac_message(slac::messages::HomeplugMessage&);
|
||||
|
||||
bool link_status_req_sent{false};
|
||||
std::chrono::steady_clock::time_point start_time;
|
||||
std::unique_ptr<slac::messages::cm_slac_match_cnf> match_cnf_message;
|
||||
};
|
||||
|
||||
struct InitState : public FSMSimpleState {
|
||||
using FSMSimpleState::FSMSimpleState;
|
||||
|
||||
HandleEventReturnType handle_event(AllocatorType&, Event) final;
|
||||
|
||||
CallbackReturnType callback() final;
|
||||
|
||||
void handle_slac_message(slac::messages::HomeplugMessage&);
|
||||
|
||||
// For now we are requesting only one version info packet, but probably there will be more in the future.
|
||||
enum class SubState {
|
||||
QUALCOMM_OP_ATTR,
|
||||
LUMISSIL_GET_VERSION,
|
||||
DONE,
|
||||
} sub_state{SubState::QUALCOMM_OP_ATTR};
|
||||
};
|
||||
|
||||
} // namespace slac::fsm::evse
|
||||
|
||||
#endif // EVSE_SLAC_STATES_OTHERS_HPP
|
||||
79
tools/EVerest-main/lib/everest/slac/fsm/evse/src/context.cpp
Normal file
79
tools/EVerest-main/lib/everest/slac/fsm/evse/src/context.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/slac/fsm/evse/context.hpp>
|
||||
|
||||
#include <random>
|
||||
|
||||
#include "misc.hpp"
|
||||
|
||||
namespace slac::fsm::evse {
|
||||
|
||||
void EvseSlacConfig::generate_nmk() {
|
||||
const std::string CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
std::random_device random_device;
|
||||
std::mt19937 generator(random_device());
|
||||
std::uniform_int_distribution<> distribution(0, CHARACTERS.size() - 1);
|
||||
|
||||
for (std::size_t i = 0; i < slac::defs::NMK_LEN; ++i) {
|
||||
session_nmk[i] = (uint8_t)CHARACTERS[distribution(generator)];
|
||||
}
|
||||
}
|
||||
|
||||
void Context::signal_cm_slac_parm_req(const uint8_t* mac) {
|
||||
if (callbacks.signal_ev_mac_address_parm_req) {
|
||||
const auto mac_string = format_mac_addr(mac);
|
||||
callbacks.signal_ev_mac_address_parm_req(mac_string);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::signal_cm_slac_match_cnf(const uint8_t* mac) {
|
||||
if (callbacks.signal_ev_mac_address_match_cnf) {
|
||||
const auto mac_string = format_mac_addr(mac);
|
||||
callbacks.signal_ev_mac_address_match_cnf(mac_string);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::signal_dlink_ready(bool value) {
|
||||
if (callbacks.signal_dlink_ready) {
|
||||
callbacks.signal_dlink_ready(value);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::signal_error_routine_request() {
|
||||
if (callbacks.signal_error_routine_request) {
|
||||
callbacks.signal_error_routine_request();
|
||||
}
|
||||
}
|
||||
|
||||
void Context::signal_state(const std::string& state) {
|
||||
if (callbacks.signal_state) {
|
||||
callbacks.signal_state(state);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::log_debug(const std::string& text) {
|
||||
if (callbacks.log_debug) {
|
||||
callbacks.log_debug(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::log_info(const std::string& text) {
|
||||
if (callbacks.log_info) {
|
||||
callbacks.log_info(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::log_warn(const std::string& text) {
|
||||
if (callbacks.log_warn) {
|
||||
callbacks.log_warn(text);
|
||||
}
|
||||
}
|
||||
|
||||
void Context::log_error(const std::string& text) {
|
||||
if (callbacks.log_error) {
|
||||
callbacks.log_error(text);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace slac::fsm::evse
|
||||
35
tools/EVerest-main/lib/everest/slac/fsm/evse/src/misc.cpp
Normal file
35
tools/EVerest-main/lib/everest/slac/fsm/evse/src/misc.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include "misc.hpp"
|
||||
|
||||
#include <net/ethernet.h>
|
||||
|
||||
#include <slac/slac.hpp>
|
||||
|
||||
std::string format_mac_addr(const uint8_t* mac) {
|
||||
char string_buffer[ETH_ALEN * 2 + (ETH_ALEN - 1) + 1];
|
||||
snprintf(string_buffer, sizeof(string_buffer), "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3],
|
||||
mac[4], mac[5]);
|
||||
return string_buffer;
|
||||
}
|
||||
|
||||
std::string format_nmk(const uint8_t* nmk) {
|
||||
char string_buffer[slac::defs::NMK_LEN * 3];
|
||||
for (int i = 0; i < slac::defs::NMK_LEN; i++) {
|
||||
snprintf(string_buffer + (i * 3), sizeof(string_buffer) - (i * 3), "%02X:", nmk[i]);
|
||||
}
|
||||
return string_buffer;
|
||||
}
|
||||
|
||||
std::string format_run_id(const uint8_t* run_id) {
|
||||
char string_buffer[2 * slac::defs::RUN_ID_LEN + 1];
|
||||
snprintf(string_buffer, sizeof(string_buffer), "%02X%02X%02X%02X%02X%02X%02X%02X", run_id[0], run_id[1], run_id[2],
|
||||
run_id[3], run_id[4], run_id[5], run_id[6], run_id[7]);
|
||||
return string_buffer;
|
||||
}
|
||||
|
||||
std::string format_mmtype(const uint16_t mmtype) {
|
||||
char string_buffer[2 + 2 * 2 + 1];
|
||||
snprintf(string_buffer, sizeof(string_buffer), "0x%04hX", mmtype);
|
||||
return string_buffer;
|
||||
}
|
||||
17
tools/EVerest-main/lib/everest/slac/fsm/evse/src/misc.hpp
Normal file
17
tools/EVerest-main/lib/everest/slac/fsm/evse/src/misc.hpp
Normal file
@@ -0,0 +1,17 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE_SLAC_MISC_HPP
|
||||
#define EVSE_SLAC_MISC_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
std::string format_nmk(const uint8_t* nmk);
|
||||
|
||||
std::string format_mac_addr(const uint8_t* mac);
|
||||
|
||||
std::string format_run_id(const uint8_t* run_id);
|
||||
|
||||
std::string format_mmtype(const uint16_t mmtype);
|
||||
|
||||
#endif // EVSE_SLAC_MISC_HPP
|
||||
@@ -0,0 +1,187 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/slac/fsm/evse/states/matching.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
|
||||
#include "matching_handle_slac.hpp"
|
||||
#include <everest/slac/fsm/evse/states/others.hpp>
|
||||
|
||||
namespace slac::fsm::evse {
|
||||
|
||||
//
|
||||
// Helper functions
|
||||
//
|
||||
static inline auto remaining_milliseconds(const MatchingTimepoint& timeout, const MatchingTimepoint& now) {
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(timeout - now).count();
|
||||
}
|
||||
|
||||
MatchingSession::MatchingSession(const uint8_t* ev_mac, const uint8_t* run_id) {
|
||||
memcpy(this->ev_mac, ev_mac, sizeof(this->ev_mac));
|
||||
memcpy(this->run_id, run_id, sizeof(this->run_id));
|
||||
memset(captured_aags, 0, sizeof(captured_aags));
|
||||
}
|
||||
|
||||
void MatchingSession::set_next_timeout(int delay_ms) {
|
||||
next_timeout = std::chrono::steady_clock::now() + std::chrono::milliseconds(delay_ms);
|
||||
timeout_active = true;
|
||||
}
|
||||
|
||||
void MatchingSession::ack_timeout() {
|
||||
timeout_active = false;
|
||||
}
|
||||
|
||||
bool MatchingSession::is_identified_by(const uint8_t* ev_mac, const uint8_t* run_id) const {
|
||||
if (0 != memcmp(run_id, this->run_id, sizeof(this->run_id))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (0 != memcmp(ev_mac, this->ev_mac, sizeof(this->ev_mac))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool all_sessions_failed(const std::vector<MatchingSession>& sessions) {
|
||||
for (const auto& session : sessions) {
|
||||
if (session.state != MatchingSubState::FAILED) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//
|
||||
// Matching state related
|
||||
//
|
||||
void MatchingState::enter() {
|
||||
ctx.signal_state("MATCHING");
|
||||
ctx.log_info("Entered Matching state, waiting for CM_SLAC_PARM_REQ");
|
||||
// timeout for getting CM_SLAC_PARM_REQ
|
||||
timeout_slac_parm_req =
|
||||
std::chrono::steady_clock::now() + std::chrono::milliseconds(ctx.slac_config.slac_init_timeout_ms);
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType MatchingState::callback() {
|
||||
// check timeouts
|
||||
auto now_tp = std::chrono::steady_clock::now();
|
||||
std::optional<FSMReturnType> call_back_ms;
|
||||
|
||||
if (!seen_slac_parm_req) {
|
||||
if (now_tp >= timeout_slac_parm_req) {
|
||||
ctx.log_error("CM_SLAC_PARM_REQ timed out -> FAILED");
|
||||
return Event::FAILED;
|
||||
}
|
||||
|
||||
call_back_ms = remaining_milliseconds(timeout_slac_parm_req, now_tp);
|
||||
}
|
||||
|
||||
// fallthrough: CM_SLAC_PARM_REQ has been seen, check individual sessions
|
||||
|
||||
for (auto& session : sessions) {
|
||||
// there should always be an active timeout, right?
|
||||
// FIXME (aw)
|
||||
while (session.timeout_active) {
|
||||
// FIXME (aw): this way we only take the first one
|
||||
if (session.state == MatchingSubState::MATCH_COMPLETE) {
|
||||
return Event::MATCH_COMPLETE;
|
||||
}
|
||||
|
||||
auto remaining_ms = remaining_milliseconds(session.next_timeout, now_tp);
|
||||
if (remaining_ms > 0) {
|
||||
if (call_back_ms.has_value() == false || *call_back_ms > remaining_ms) {
|
||||
call_back_ms = remaining_ms;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// fall-through, timeout should be handled now
|
||||
session.ack_timeout();
|
||||
|
||||
if (session.state == MatchingSubState::WAIT_FOR_START_ATTEN_CHAR) {
|
||||
session_log(ctx, session, LogLevel::ERROR, "Waiting for CM_START_ATTEN_CHAR_IND timed out -> failed");
|
||||
session.state = MatchingSubState::FAILED;
|
||||
} else if (session.state == MatchingSubState::SOUNDING) {
|
||||
session_log(ctx, session, LogLevel::WARN,
|
||||
"Sounding not yet complete but timed out, going to sub-state FINALIZE_SOUNDING");
|
||||
session.state = MatchingSubState::FINALIZE_SOUNDING;
|
||||
session.set_next_timeout(FINALIZE_SOUNDING_DELAY_MS);
|
||||
} else if (session.state == MatchingSubState::FINALIZE_SOUNDING) {
|
||||
finalize_sounding(session);
|
||||
session.num_retries = 0;
|
||||
} else if (session.state == MatchingSubState::WAIT_FOR_ATTEN_CHAR_RSP) {
|
||||
session.num_retries++;
|
||||
if (session.num_retries <= slac::defs::C_EV_MATCH_RETRY) {
|
||||
session_log(ctx, session, LogLevel::WARN,
|
||||
"Waiting for CM_ATTEN_CHAR_RSP timed out -> retry matching");
|
||||
finalize_sounding(session);
|
||||
} else {
|
||||
session_log(ctx, session, LogLevel::ERROR, "Waiting for CM_ATTEN_CHAR_RSP timed out -> failed");
|
||||
session.state = MatchingSubState::FAILED;
|
||||
}
|
||||
} else if (session.state == MatchingSubState::WAIT_FOR_SLAC_MATCH) {
|
||||
session_log(ctx, session, LogLevel::ERROR, "Wating for CM_SLAC_MATCH_REQ timed out -> failed");
|
||||
session.state = MatchingSubState::FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
if (all_sessions_failed(sessions)) {
|
||||
return Event::FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
if (call_back_ms.has_value() == false) {
|
||||
// FIXME (aw): this should not happen, should we assert here or something similar?
|
||||
}
|
||||
return *call_back_ms;
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType MatchingState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::SLAC_MESSAGE) {
|
||||
handle_slac_message(ctx.slac_message_payload);
|
||||
return sa.HANDLED_INTERNALLY;
|
||||
} else if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
} else if (ev == Event::MATCH_COMPLETE) {
|
||||
// Wait for link up to be confirmed before going to MATCHED state if enabled in config
|
||||
if (ctx.slac_config.link_status.do_detect) {
|
||||
return sa.create_simple<WaitForLinkState>(ctx, std::move(match_cnf_message));
|
||||
} else {
|
||||
return sa.create_simple<MatchedState>(ctx);
|
||||
}
|
||||
} else if (ev == Event::RETRY_MATCHING) {
|
||||
num_retries++;
|
||||
if (num_retries == slac::defs::C_EV_MATCH_RETRY) {
|
||||
ctx.log_error("Reached retry limit for matching");
|
||||
return sa.create_simple<FailedState>(ctx);
|
||||
}
|
||||
|
||||
// otherwise, reset timeout
|
||||
timeout_slac_parm_req =
|
||||
std::chrono::steady_clock::now() + std::chrono::milliseconds(ctx.slac_config.slac_init_timeout_ms);
|
||||
return sa.HANDLED_INTERNALLY;
|
||||
} else if (ev == Event::FAILED) {
|
||||
failed_count++;
|
||||
if (ctx.slac_config.reset_instead_of_fail and failed_count < 2) {
|
||||
ctx.log_error("Resetting MatchingState. Waiting for the next CM_SLAC_PARAM.REQ message.");
|
||||
|
||||
// Resetting all relevant MatchingState members
|
||||
sessions.clear();
|
||||
// timeout for getting CM_SLAC_PARM_REQ
|
||||
timeout_slac_parm_req =
|
||||
std::chrono::steady_clock::now() + std::chrono::milliseconds(ctx.slac_config.slac_init_timeout_ms);
|
||||
seen_slac_parm_req = false;
|
||||
num_retries = 0;
|
||||
|
||||
return sa.HANDLED_INTERNALLY;
|
||||
}
|
||||
return sa.create_simple<FailedState>(ctx);
|
||||
}
|
||||
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
|
||||
} // namespace slac::fsm::evse
|
||||
@@ -0,0 +1,482 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include "matching_handle_slac.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "../misc.hpp"
|
||||
|
||||
namespace slac::fsm::evse {
|
||||
|
||||
void session_log(Context& ctx, MatchingSession& session, const LogLevel level, const std::string& text) {
|
||||
const auto run_id = format_run_id(session.run_id);
|
||||
const auto mac = format_mac_addr(session.ev_mac);
|
||||
std::stringstream ss;
|
||||
|
||||
ss << "Session (run_id=" << run_id << ", ev_mac=" << mac << "): " << text;
|
||||
|
||||
switch (level) {
|
||||
case LogLevel::DEBUG:
|
||||
ctx.log_debug(ss.str());
|
||||
break;
|
||||
case LogLevel::INFO:
|
||||
ctx.log_info(ss.str());
|
||||
break;
|
||||
case LogLevel::WARN:
|
||||
ctx.log_warn(ss.str());
|
||||
break;
|
||||
case LogLevel::ERROR:
|
||||
ctx.log_error(ss.str());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// MatchingSession related
|
||||
//
|
||||
static MatchingSession* find_session(std::vector<MatchingSession>& sessions, const uint8_t* ev_mac,
|
||||
const uint8_t* run_id) {
|
||||
for (auto& session : sessions) {
|
||||
if (session.is_identified_by(ev_mac, run_id)) {
|
||||
return &session;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
static auto create_cm_slac_parm_cnf(const MatchingSession& session) {
|
||||
slac::messages::cm_slac_parm_cnf param_confirm;
|
||||
|
||||
memcpy(param_confirm.m_sound_target, slac::defs::BROADCAST_MAC_ADDRESS, sizeof(slac::defs::BROADCAST_MAC_ADDRESS));
|
||||
param_confirm.num_sounds = slac::defs::CM_SLAC_PARM_CNF_NUM_SOUNDS;
|
||||
param_confirm.timeout = slac::defs::CM_SLAC_PARM_CNF_TIMEOUT;
|
||||
param_confirm.resp_type = slac::defs::CM_SLAC_PARM_CNF_RESP_TYPE;
|
||||
memcpy(param_confirm.forwarding_sta, session.ev_mac, sizeof(param_confirm.forwarding_sta));
|
||||
param_confirm.application_type = slac::defs::COMMON_APPLICATION_TYPE;
|
||||
param_confirm.security_type = slac::defs::COMMON_SECURITY_TYPE;
|
||||
memcpy(param_confirm.run_id, session.run_id, sizeof(param_confirm.run_id));
|
||||
|
||||
return param_confirm;
|
||||
}
|
||||
|
||||
static auto create_cm_atten_char_ind(const MatchingSession& session, int atten_offset = 0) {
|
||||
slac::messages::cm_atten_char_ind atten_char_ind;
|
||||
|
||||
atten_char_ind.application_type = slac::defs::COMMON_APPLICATION_TYPE;
|
||||
atten_char_ind.security_type = slac::defs::COMMON_SECURITY_TYPE;
|
||||
memcpy(atten_char_ind.source_address, session.ev_mac, sizeof(atten_char_ind.source_address));
|
||||
memcpy(atten_char_ind.run_id, session.run_id, sizeof(atten_char_ind.run_id));
|
||||
// memcpy(atten_char_ind.source_id, session_ev_id, sizeof(atten_char_ind.source_id));
|
||||
memset(atten_char_ind.source_id, 0, sizeof(atten_char_ind.source_id));
|
||||
// memcpy(atten_char_ind.resp_id, sample_evse_vin, sizeof(atten_char_ind.resp_id));
|
||||
memset(atten_char_ind.resp_id, 0, sizeof(atten_char_ind.resp_id));
|
||||
atten_char_ind.num_sounds = session.captured_sounds;
|
||||
atten_char_ind.attenuation_profile.num_groups = slac::defs::AAG_LIST_LEN;
|
||||
if (session.captured_sounds != 0) {
|
||||
for (int i = 0; i < slac::defs::AAG_LIST_LEN; ++i) {
|
||||
atten_char_ind.attenuation_profile.aag[i] =
|
||||
session.captured_aags[i] / session.captured_sounds + atten_offset;
|
||||
}
|
||||
} else {
|
||||
// FIXME (aw): what to do here, if we didn't receive any sounds?
|
||||
memset(atten_char_ind.attenuation_profile.aag, 0x01, sizeof(atten_char_ind.attenuation_profile.aag));
|
||||
}
|
||||
|
||||
return atten_char_ind;
|
||||
}
|
||||
|
||||
// Note (aw): this function doesn't return by value in order to optimize for fewer copies
|
||||
static void create_cm_slac_match_cnf(slac::messages::cm_slac_match_cnf& match_cnf, const MatchingSession& session,
|
||||
const slac::messages::cm_slac_match_req& match_req, const uint8_t* session_nmk) {
|
||||
match_cnf.application_type = slac::defs::COMMON_APPLICATION_TYPE;
|
||||
match_cnf.security_type = slac::defs::COMMON_SECURITY_TYPE;
|
||||
match_cnf.mvf_length = htole16(slac::defs::CM_SLAC_MATCH_CNF_MVF_LENGTH);
|
||||
memcpy(match_cnf.pev_id, match_req.pev_id, sizeof(match_cnf.pev_id));
|
||||
memcpy(match_cnf.pev_mac, match_req.pev_mac, sizeof(match_cnf.pev_mac));
|
||||
memcpy(match_cnf.evse_id, match_req.evse_id, sizeof(match_cnf.evse_id));
|
||||
memcpy(match_cnf.evse_mac, match_req.evse_mac, sizeof(match_cnf.evse_mac));
|
||||
memcpy(match_cnf.run_id, match_req.run_id, sizeof(match_cnf.run_id));
|
||||
memset(match_cnf._rerserved, 0, 8);
|
||||
match_cnf._reserved2 = 0;
|
||||
slac::utils::generate_nid_from_nmk(match_cnf.nid, session_nmk);
|
||||
memcpy(match_cnf.nmk, session_nmk, sizeof(match_cnf.nmk));
|
||||
}
|
||||
|
||||
void MatchingState::handle_slac_message(slac::messages::HomeplugMessage& msg) {
|
||||
const auto mmtype = msg.get_mmtype();
|
||||
tmp_ev_mac = msg.get_src_mac();
|
||||
|
||||
switch (mmtype) {
|
||||
case (slac::defs::MMTYPE_CM_SLAC_PARAM | slac::defs::MMTYPE_MODE_REQ):
|
||||
handle_cm_slac_parm_req(msg.get_payload<slac::messages::cm_slac_parm_req>());
|
||||
break;
|
||||
case (slac::defs::MMTYPE_CM_START_ATTEN_CHAR | slac::defs::MMTYPE_MODE_IND):
|
||||
handle_cm_start_atten_char_ind(msg.get_payload<slac::messages::cm_start_atten_char_ind>());
|
||||
break;
|
||||
case (slac::defs::MMTYPE_CM_MNBC_SOUND | slac::defs::MMTYPE_MODE_IND):
|
||||
handle_cm_mnbc_sound_ind(msg.get_payload<slac::messages::cm_mnbc_sound_ind>());
|
||||
break;
|
||||
case (slac::defs::MMTYPE_CM_ATTEN_PROFILE | slac::defs::MMTYPE_MODE_IND):
|
||||
handle_cm_atten_profile_ind(msg.get_payload<slac::messages::cm_atten_profile_ind>());
|
||||
break;
|
||||
case (slac::defs::MMTYPE_CM_ATTEN_CHAR | slac::defs::MMTYPE_MODE_RSP):
|
||||
handle_cm_atten_char_rsp(msg.get_payload<slac::messages::cm_atten_char_rsp>());
|
||||
break;
|
||||
case (slac::defs::MMTYPE_CM_SLAC_MATCH | slac::defs::MMTYPE_MODE_REQ):
|
||||
handle_cm_slac_match_req(msg.get_payload<slac::messages::cm_slac_match_req>());
|
||||
break;
|
||||
case (slac::defs::MMTYPE_CM_VALIDATE | slac::defs::MMTYPE_MODE_REQ):
|
||||
handle_cm_validate_req(msg.get_payload<slac::messages::cm_validate_req>());
|
||||
break;
|
||||
default:
|
||||
ctx.log_warn("Received non-expected SLAC message of type " + format_mmtype(mmtype));
|
||||
}
|
||||
}
|
||||
|
||||
static bool validate_cm_slac_parm_req(const slac::messages::cm_slac_parm_req& msg) {
|
||||
|
||||
if (msg.application_type not_eq slac::defs::COMMON_APPLICATION_TYPE) {
|
||||
return false;
|
||||
}
|
||||
if (msg.security_type not_eq slac::defs::COMMON_SECURITY_TYPE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MatchingState::handle_cm_slac_parm_req(const slac::messages::cm_slac_parm_req& msg) {
|
||||
|
||||
if (not validate_cm_slac_parm_req(msg)) {
|
||||
ctx.log_warn("Invalid CM_SLAC_PARM.REQ received, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
// set this flag to true, to disable the retry timeout
|
||||
seen_slac_parm_req = true;
|
||||
|
||||
auto session = find_session(sessions, tmp_ev_mac, msg.run_id);
|
||||
if (session) {
|
||||
// the matching session existed already, according to [V2G3-A09-16] we should restart
|
||||
*session = MatchingSession(tmp_ev_mac, msg.run_id);
|
||||
} else {
|
||||
// the session didn't exist, lets create it
|
||||
sessions.emplace_back(MatchingSession{tmp_ev_mac, msg.run_id});
|
||||
session = &sessions.back();
|
||||
}
|
||||
|
||||
session_log(ctx, *session, LogLevel::INFO, "initialized, waiting for CM_START_ATTEN_CHAR_IND");
|
||||
|
||||
// timeout until we need to get cm_start_atten_char_ind
|
||||
session->set_next_timeout(slac::defs::TT_MATCH_SEQUENCE_MS);
|
||||
|
||||
auto param_confirm = create_cm_slac_parm_cnf(*session);
|
||||
|
||||
ctx.send_slac_message(param_confirm.forwarding_sta, param_confirm);
|
||||
ctx.signal_cm_slac_parm_req(tmp_ev_mac);
|
||||
}
|
||||
|
||||
static bool validate_cm_start_atten_char_ind(const slac::messages::cm_start_atten_char_ind& msg) {
|
||||
|
||||
if (msg.application_type not_eq slac::defs::COMMON_APPLICATION_TYPE) {
|
||||
return false;
|
||||
}
|
||||
if (msg.security_type not_eq slac::defs::COMMON_SECURITY_TYPE) {
|
||||
return false;
|
||||
}
|
||||
if (msg.num_sounds == 0) { // Don't be strict to the ISO 15118-3
|
||||
return false;
|
||||
}
|
||||
if (msg.timeout == 0) { // Don't be strict to the ISO 15118-3
|
||||
return false;
|
||||
}
|
||||
if (msg.resp_type not_eq slac::defs::CM_SLAC_PARM_CNF_RESP_TYPE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MatchingState::handle_cm_start_atten_char_ind(const slac::messages::cm_start_atten_char_ind& msg) {
|
||||
|
||||
if (not validate_cm_start_atten_char_ind(msg)) {
|
||||
ctx.log_warn("Invalid CM_START_ATTEN_CHAR_IND received, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
auto session = find_session(sessions, tmp_ev_mac, msg.run_id);
|
||||
if (!session) {
|
||||
ctx.log_warn("No session found for CM_START_ATTEN_CHAR_IND");
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->state != MatchingSubState::WAIT_FOR_START_ATTEN_CHAR) {
|
||||
if (session->state != MatchingSubState::SOUNDING)
|
||||
session_log(ctx, *session, LogLevel::WARN,
|
||||
"needs to be in state WAIT_FOR_START_ATTEN_CHAR for CM_START_ATTEN_CHAR_IND");
|
||||
return;
|
||||
}
|
||||
|
||||
// go to sounding
|
||||
session_log(ctx, *session, LogLevel::INFO, "received CM_START_ATTEN_CHAR_IND, going to substate SOUNDING");
|
||||
session->state = MatchingSubState::SOUNDING;
|
||||
session->set_next_timeout(slac::defs::TT_EVSE_MATCH_MNBC_MS);
|
||||
}
|
||||
|
||||
static bool validate_cm_mnbc_sound_ind(const slac::messages::cm_mnbc_sound_ind& msg) {
|
||||
|
||||
if (msg.application_type not_eq slac::defs::COMMON_APPLICATION_TYPE) {
|
||||
return false;
|
||||
}
|
||||
if (msg.security_type not_eq slac::defs::COMMON_SECURITY_TYPE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MatchingState::handle_cm_mnbc_sound_ind(const slac::messages::cm_mnbc_sound_ind& msg) {
|
||||
|
||||
if (not validate_cm_mnbc_sound_ind(msg)) {
|
||||
ctx.log_warn("Invalid CM_MNBC_SOUND_IND received, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
auto session = find_session(sessions, tmp_ev_mac, msg.run_id);
|
||||
if (!session) {
|
||||
ctx.log_warn("No session found for CM_MNBC_SOUND_IND");
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->state != MatchingSubState::SOUNDING) {
|
||||
session_log(ctx, *session, LogLevel::WARN, "needs to be in state SOUNDING for CM_MNBC_SOUND_IND");
|
||||
return;
|
||||
}
|
||||
|
||||
session_log(ctx, *session, LogLevel::INFO, "received CM_MNBC_SOUND_IND");
|
||||
|
||||
session->received_mnbc_sound = true;
|
||||
}
|
||||
|
||||
void MatchingState::handle_cm_atten_profile_ind(const slac::messages::cm_atten_profile_ind& msg) {
|
||||
// cm_atten_profile_ind does not carry a run_id, so we can't exactly identify the session
|
||||
// FIXME (aw): for now, we only take the first one found
|
||||
MatchingSession* session = nullptr;
|
||||
|
||||
for (auto& session_i : sessions) {
|
||||
if (memcmp(msg.pev_mac, session_i.ev_mac, sizeof(msg.pev_mac)) == 0) {
|
||||
session = &session_i;
|
||||
}
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
ctx.log_warn("No session found for CM_ATTEN_PROFILE_IND");
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->state != MatchingSubState::SOUNDING) {
|
||||
session_log(ctx, *session, LogLevel::WARN, "needs to be in state SOUNDING for CM_ATTEN_PROFILE_IND");
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.num_groups != slac::defs::AAG_LIST_LEN) {
|
||||
session_log(ctx, *session, LogLevel::WARN, "mismatch in number of AAG groups");
|
||||
return;
|
||||
}
|
||||
|
||||
session_log(ctx, *session, LogLevel::INFO, "received CM_ATTEN_PROFILE_IND");
|
||||
|
||||
for (int i = 0; i < slac::defs::AAG_LIST_LEN; ++i) {
|
||||
session->captured_aags[i] += msg.aag[i];
|
||||
}
|
||||
|
||||
session->captured_sounds++;
|
||||
|
||||
if (session->captured_sounds < slac::defs::CM_SLAC_PARM_CNF_NUM_SOUNDS) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fall-through: all sounds captured
|
||||
session_log(ctx, *session, LogLevel::INFO, "received all sounds, going to substate FINALIZE_SOUNDING");
|
||||
session->state = MatchingSubState::FINALIZE_SOUNDING;
|
||||
session->set_next_timeout(FINALIZE_SOUNDING_DELAY_MS);
|
||||
}
|
||||
|
||||
static bool validate_cm_atten_char_rsp(const slac::messages::cm_atten_char_rsp& msg, const MatchingSession& session) {
|
||||
|
||||
if (msg.application_type not_eq slac::defs::COMMON_APPLICATION_TYPE) {
|
||||
return false;
|
||||
}
|
||||
if (msg.security_type not_eq slac::defs::COMMON_SECURITY_TYPE) {
|
||||
return false;
|
||||
}
|
||||
if (memcmp(msg.source_address, session.ev_mac, ETH_ALEN)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t source_id_ref[slac::messages::SOURCE_ID_LEN];
|
||||
memset(source_id_ref, 0, sizeof(source_id_ref));
|
||||
if (memcmp(source_id_ref, msg.source_id, slac::messages::SOURCE_ID_LEN)) {
|
||||
return false;
|
||||
}
|
||||
uint8_t resp_id_ref[slac::messages::RESP_ID_LEN];
|
||||
memset(resp_id_ref, 0, sizeof(resp_id_ref));
|
||||
if (memcmp(resp_id_ref, msg.resp_id, slac::messages::RESP_ID_LEN)) {
|
||||
return false;
|
||||
}
|
||||
if (msg.result not_eq slac::defs::CM_ATTEN_CHAR_RSP_RESULT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MatchingState::handle_cm_atten_char_rsp(const slac::messages::cm_atten_char_rsp& msg) {
|
||||
|
||||
auto session = find_session(sessions, tmp_ev_mac, msg.run_id);
|
||||
if (!session) {
|
||||
ctx.log_warn("No session found for CM_ATTEN_CHAR_RSP");
|
||||
return;
|
||||
}
|
||||
|
||||
if (not validate_cm_atten_char_rsp(msg, *session)) {
|
||||
session_log(ctx, *session, LogLevel::WARN, "Invalid CM_ATTEN_CHAR_RSP received, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->state != MatchingSubState::WAIT_FOR_ATTEN_CHAR_RSP) {
|
||||
session_log(ctx, *session, LogLevel::WARN,
|
||||
"needs to be in state WAIT_FOR_ATTEN_CHAR_RSP for CM_ATTEN_CHAR_RSP");
|
||||
return;
|
||||
}
|
||||
|
||||
session_log(ctx, *session, LogLevel::INFO, "received CM_ATTEN_CHAR_RSP, going to substate WAIT_FOR_SLAC_MATCH");
|
||||
session->state = MatchingSubState::WAIT_FOR_SLAC_MATCH;
|
||||
|
||||
// FIXME (aw): referring to the standard, it is not clear here, if we should offset from TT_EVSE_MATCH_MNBC
|
||||
session->set_next_timeout(slac::defs::TT_EVSE_MATCH_SESSION_MS);
|
||||
}
|
||||
|
||||
void MatchingState::handle_cm_validate_req(const slac::messages::cm_validate_req& msg) {
|
||||
// NOTE: CM_VALIDATE.REQ does not specify its session
|
||||
// EVSE allowed to not implement: [V2G3-A09-51]
|
||||
ctx.log_warn("Received CM_VALIDATE.REQ / not implemented - will return failure code");
|
||||
|
||||
slac::messages::cm_validate_cnf validate_cnf;
|
||||
validate_cnf.signal_type = slac::defs::CM_VALIDATE_REQ_SIGNAL_TYPE;
|
||||
validate_cnf.toggle_num = 0;
|
||||
validate_cnf.result = slac::defs::CM_VALIDATE_REQ_RESULT_FAILURE;
|
||||
|
||||
ctx.send_slac_message(tmp_ev_mac, validate_cnf);
|
||||
}
|
||||
|
||||
static bool validate_cm_slac_match_req(const slac::messages::cm_slac_match_req& msg, const MatchingSession& session,
|
||||
const Context& ctx) {
|
||||
|
||||
if (msg.application_type not_eq slac::defs::COMMON_APPLICATION_TYPE) {
|
||||
return false;
|
||||
}
|
||||
if (msg.security_type not_eq slac::defs::COMMON_SECURITY_TYPE) {
|
||||
return false;
|
||||
}
|
||||
if (msg.mvf_length not_eq slac::defs::CM_SLAC_MATCH_REQ_MVF_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
// PEV ID = 0x00 TC_SECC_CMN_VTB_CmSlacMatch_013/014(?)
|
||||
uint8_t pev_id_ref[slac::messages::PEV_ID_LEN];
|
||||
memset(pev_id_ref, 0, sizeof(pev_id_ref));
|
||||
if (memcmp(pev_id_ref, msg.pev_id, slac::messages::PEV_ID_LEN)) {
|
||||
return false;
|
||||
}
|
||||
// PEV MAC TC_SECC_CMN_VTB_CmSlacMatch_015/016(?)
|
||||
if (memcmp(msg.pev_mac, session.ev_mac, ETH_ALEN)) {
|
||||
return false;
|
||||
}
|
||||
// EVSE ID = 0x00 TC_SECC_CMN_VTB_CmSlacMatch_017/018(?)
|
||||
uint8_t evse_id_ref[slac::messages::EVSE_ID_LEN];
|
||||
memset(evse_id_ref, 0, sizeof(evse_id_ref));
|
||||
if (memcmp(evse_id_ref, msg.evse_id, slac::messages::EVSE_ID_LEN)) {
|
||||
return false;
|
||||
}
|
||||
// EVSE MAC TC_SECC_CMN_VTB_CmSlacMatch_019/020
|
||||
if (memcmp(ctx.evse_mac, msg.evse_mac, ETH_ALEN)) {
|
||||
return false;
|
||||
}
|
||||
// RunID TC_SECC_CMN_VTB_CmSlacMatch_021/022
|
||||
if (memcmp(msg.run_id, session.run_id, slac::defs::RUN_ID_LEN)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void MatchingState::handle_cm_slac_match_req(const slac::messages::cm_slac_match_req& msg) {
|
||||
|
||||
auto session = find_session(sessions, tmp_ev_mac, msg.run_id);
|
||||
if (!session) {
|
||||
ctx.log_warn("No session found for CM_SLAC_MATCH_REQ");
|
||||
return;
|
||||
}
|
||||
|
||||
if (not validate_cm_slac_match_req(msg, *session, ctx)) {
|
||||
session_log(ctx, *session, LogLevel::WARN, "Invalid CM_SLAC_MATCH_REQ received, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if (session->state != MatchingSubState::WAIT_FOR_SLAC_MATCH && session->state != MatchingSubState::MATCH_COMPLETE) {
|
||||
session_log(ctx, *session, LogLevel::WARN,
|
||||
"needs to be in state WAIT_FOR_SLAC_MATCH or MATCH_COMPLETE for CM_SLAC_MATCH_REQ");
|
||||
return;
|
||||
}
|
||||
|
||||
session_log(ctx, *session, LogLevel::INFO,
|
||||
"Received CM_SLAC_MATCH_REQ, sending CM_SLAC_MATCH_CNF -> session complete");
|
||||
|
||||
static constexpr uint8_t wrong_session_nmk[16] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10};
|
||||
|
||||
auto const* session_nmk = ctx.slac_config.session_nmk;
|
||||
|
||||
if (ctx.slac_config.link_status.debug_simulate_failed_matching) {
|
||||
ctx.log_info("Sending wrong NMK to EV to simulate a failed link setup after match request");
|
||||
session_nmk = wrong_session_nmk;
|
||||
}
|
||||
|
||||
match_cnf_message = std::make_unique<slac::messages::cm_slac_match_cnf>();
|
||||
create_cm_slac_match_cnf(*match_cnf_message, *session, msg, session_nmk);
|
||||
|
||||
ctx.send_slac_message(tmp_ev_mac, *match_cnf_message);
|
||||
|
||||
session->state = MatchingSubState::MATCH_COMPLETE;
|
||||
|
||||
// call this immediately again in MatchedState::callback to handle things
|
||||
session->set_next_timeout(0);
|
||||
ctx.signal_cm_slac_match_cnf(tmp_ev_mac);
|
||||
}
|
||||
|
||||
void MatchingState::finalize_sounding(MatchingSession& session) {
|
||||
session_log(ctx, session, LogLevel::INFO, "Finalize sounding, sending CM_ATTEN_CHAR_IND");
|
||||
session.state = MatchingSubState::WAIT_FOR_ATTEN_CHAR_RSP;
|
||||
|
||||
auto atten_char = create_cm_atten_char_ind(session, ctx.slac_config.sounding_atten_adjustment);
|
||||
|
||||
ctx.send_slac_message(session.ev_mac, atten_char);
|
||||
|
||||
session.set_next_timeout(slac::defs::TT_MATCH_RESPONSE_MS);
|
||||
|
||||
int aag_overall_sum = 0;
|
||||
for (size_t i = 0; i < slac::defs::AAG_LIST_LEN; ++i) {
|
||||
aag_overall_sum += atten_char.attenuation_profile.aag[i];
|
||||
}
|
||||
std::ostringstream ss;
|
||||
ss << "Avg atten.: " << std::fixed << std::setprecision(1)
|
||||
<< (static_cast<double>(aag_overall_sum) / slac::defs::AAG_LIST_LEN) << " dB";
|
||||
if (ctx.slac_config.sounding_atten_adjustment != 0) {
|
||||
ss << " plus offset " << std::to_string(ctx.slac_config.sounding_atten_adjustment) << " dB";
|
||||
}
|
||||
ss << ", from " << std::to_string(slac::defs::AAG_LIST_LEN) << " groups, " << session.captured_sounds << " sounds";
|
||||
session_log(ctx, session, LogLevel::INFO, ss.str());
|
||||
}
|
||||
|
||||
} // namespace slac::fsm::evse
|
||||
@@ -0,0 +1,21 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef EVSE_SLAC_STATES_MATCHING_HANDLE_SLAC_HPP
|
||||
#define EVSE_SLAC_STATES_MATCHING_HANDLE_SLAC_HPP
|
||||
|
||||
#include <everest/slac/fsm/evse/states/matching.hpp>
|
||||
|
||||
namespace slac::fsm::evse {
|
||||
|
||||
enum class LogLevel {
|
||||
DEBUG,
|
||||
INFO,
|
||||
WARN,
|
||||
ERROR
|
||||
};
|
||||
|
||||
void session_log(Context& ctx, MatchingSession& session, const LogLevel level, const std::string& text);
|
||||
|
||||
} // namespace slac::fsm::evse
|
||||
|
||||
#endif // EVSE_SLAC_STATES_MATCHING_HANDLE_SLAC_HPP
|
||||
@@ -0,0 +1,421 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/slac/fsm/evse/states/others.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
|
||||
#include <everest/slac/fsm/evse/states/matching.hpp>
|
||||
|
||||
#include "../misc.hpp"
|
||||
|
||||
namespace slac::fsm::evse {
|
||||
|
||||
static auto create_cm_set_key_req(uint8_t const* session_nmk) {
|
||||
slac::messages::cm_set_key_req set_key_req;
|
||||
|
||||
set_key_req.key_type = slac::defs::CM_SET_KEY_REQ_KEY_TYPE_NMK;
|
||||
set_key_req.my_nonce = 0x00000000;
|
||||
set_key_req.your_nonce = 0x00000000;
|
||||
set_key_req.pid = slac::defs::CM_SET_KEY_REQ_PID_HLE;
|
||||
set_key_req.prn = htole16(slac::defs::CM_SET_KEY_REQ_PRN_UNUSED);
|
||||
set_key_req.pmn = slac::defs::CM_SET_KEY_REQ_PMN_UNUSED;
|
||||
set_key_req.cco_capability = slac::defs::CM_SET_KEY_REQ_CCO_CAP_NONE;
|
||||
slac::utils::generate_nid_from_nmk(set_key_req.nid, session_nmk);
|
||||
set_key_req.new_eks = slac::defs::CM_SET_KEY_REQ_PEKS_NMK_KNOWN_TO_STA;
|
||||
memcpy(set_key_req.new_key, session_nmk, sizeof(set_key_req.new_key));
|
||||
|
||||
return set_key_req;
|
||||
}
|
||||
|
||||
void ResetState::enter() {
|
||||
ctx.log_info("Entered Reset state");
|
||||
if (ctx.slac_config.regenerate_key_on_reset) {
|
||||
ctx.slac_config.generate_nmk();
|
||||
}
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType ResetState::handle_event(AllocatorType& sa, Event ev) {
|
||||
const auto& cfg = ctx.slac_config;
|
||||
if (ev == Event::SLAC_MESSAGE) {
|
||||
if (handle_slac_message(ctx.slac_message_payload)) {
|
||||
if (cfg.chip_reset.enabled) {
|
||||
// If chip reset is enabled in config, go to ResetChipState and from there to IdleState
|
||||
return sa.create_simple<ResetChipState>(ctx);
|
||||
} else {
|
||||
// If chip reset is disabled, go to IdleState directly
|
||||
return sa.create_simple<IdleState>(ctx);
|
||||
}
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
} else if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType ResetState::callback() {
|
||||
const auto& cfg = ctx.slac_config;
|
||||
if (setup_has_been_send == false) {
|
||||
auto set_key_req = create_cm_set_key_req(cfg.session_nmk);
|
||||
|
||||
ctx.log_info("New NMK key: " + format_nmk(cfg.session_nmk));
|
||||
|
||||
ctx.send_slac_message(cfg.plc_peer_mac, set_key_req);
|
||||
|
||||
setup_has_been_send = true;
|
||||
|
||||
return cfg.set_key_timeout_ms;
|
||||
} else {
|
||||
ctx.log_error("CM_SET_KEY_REQ timeout - failed to setup NMK key");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool ResetState::handle_slac_message(slac::messages::HomeplugMessage& message) {
|
||||
const auto mmtype = message.get_mmtype();
|
||||
if (mmtype != (slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
// unexpected message
|
||||
// FIXME (aw): need to also deal with CM_VALIDATE.REQ. It is optional in the standard.
|
||||
ctx.log_warn("Received non-expected SLAC message of type " + format_mmtype(mmtype));
|
||||
return false;
|
||||
} else {
|
||||
ctx.log_info("Received CM_SET_KEY_CNF");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void ResetChipState::enter() {
|
||||
ctx.log_info("Entered HW Chip Reset state");
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType ResetChipState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::SLAC_MESSAGE) {
|
||||
if (handle_slac_message(ctx.slac_message_payload)) {
|
||||
return sa.create_simple<IdleState>(ctx);
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
} else if (ev == Event::SUCCESS) {
|
||||
return sa.create_simple<IdleState>(ctx);
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType ResetChipState::callback() {
|
||||
const auto& cfg = ctx.slac_config;
|
||||
if (sub_state == SubState::DELAY) {
|
||||
sub_state = SubState::SEND_RESET;
|
||||
return cfg.chip_reset.delay_ms;
|
||||
|
||||
} else if (sub_state == SubState::SEND_RESET) {
|
||||
|
||||
if (ctx.modem_vendor == ModemVendor::Qualcomm) {
|
||||
slac::messages::qualcomm::cm_reset_device_req reset_req;
|
||||
ctx.log_info("Resetting HW Chip using RS_DEV.REQ");
|
||||
ctx.send_slac_message(cfg.plc_peer_mac, reset_req);
|
||||
sub_state = SubState::DONE;
|
||||
return cfg.chip_reset.timeout_ms;
|
||||
|
||||
} else if (ctx.modem_vendor == ModemVendor::Lumissil) {
|
||||
slac::messages::lumissil::nscm_reset_device_req reset_req;
|
||||
ctx.log_info("Resetting HW Chip using NSCM_RESET_DEVICE.REQ");
|
||||
sub_state = SubState::DONE;
|
||||
ctx.send_slac_message(cfg.plc_peer_mac, reset_req);
|
||||
// CG5317 does not reply to the reset packet
|
||||
return Event::SUCCESS;
|
||||
|
||||
} else {
|
||||
ctx.log_info("Chip reset not supported on this chip");
|
||||
}
|
||||
} else {
|
||||
ctx.log_error("Reset timeout, no response received - failed to reset the chip");
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ResetChipState::handle_slac_message(slac::messages::HomeplugMessage& message) {
|
||||
const auto mmtype = message.get_mmtype();
|
||||
if (mmtype != (slac::defs::qualcomm::MMTYPE_CM_RESET_DEVICE | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
// unexpected message
|
||||
ctx.log_warn("Received non-expected SLAC message of type " + format_mmtype(mmtype));
|
||||
return false;
|
||||
} else {
|
||||
ctx.log_info("Received RS_DEV.CNF");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void IdleState::enter() {
|
||||
ctx.signal_state("UNMATCHED");
|
||||
ctx.log_info("Entered Idle state");
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType IdleState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::ENTER_BCD) {
|
||||
return sa.create_simple<MatchingState>(ctx);
|
||||
} else if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
static std::optional<bool> check_link_status_cnf(const slac::fsm::evse::ModemVendor modem_vendor,
|
||||
slac::messages::HomeplugMessage& message) {
|
||||
const auto mmtype = message.get_mmtype();
|
||||
if (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 (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};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static bool send_link_status_req(slac::fsm::evse::Context& ctx) {
|
||||
if (ctx.modem_vendor == ModemVendor::Qualcomm) {
|
||||
slac::messages::qualcomm::link_status_req link_status_req;
|
||||
ctx.send_slac_message(ctx.slac_config.plc_peer_mac, link_status_req);
|
||||
return true;
|
||||
} else if (ctx.modem_vendor == ModemVendor::Lumissil) {
|
||||
slac::messages::lumissil::nscm_get_d_link_status_req link_status_req;
|
||||
ctx.send_slac_message(ctx.slac_config.plc_peer_mac, link_status_req);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void MatchedState::enter() {
|
||||
ctx.signal_state("MATCHED");
|
||||
ctx.signal_dlink_ready(true);
|
||||
ctx.log_info("Entered Matched state");
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType MatchedState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::SLAC_MESSAGE) {
|
||||
auto link_ok = check_link_status_cnf(ctx.modem_vendor, ctx.slac_message_payload);
|
||||
if (link_ok.has_value()) {
|
||||
if (link_ok.value()) {
|
||||
return sa.PASS_ON;
|
||||
} else {
|
||||
ctx.log_error("Connection lost in matched state");
|
||||
ctx.signal_error_routine_request();
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
} else if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
}
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType MatchedState::callback() {
|
||||
const auto& link_status = ctx.slac_config.link_status;
|
||||
|
||||
if (not link_status.do_detect) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (not link_status_req_sent) {
|
||||
link_status_req_sent = send_link_status_req(ctx);
|
||||
} else {
|
||||
// Link is confirmed not up yet, query again
|
||||
link_status_req_sent = false;
|
||||
}
|
||||
|
||||
return link_status.poll_in_matched_state_ms;
|
||||
}
|
||||
|
||||
void MatchedState::leave() {
|
||||
ctx.signal_dlink_ready(false);
|
||||
}
|
||||
|
||||
void FailedState::enter() {
|
||||
if (ctx.slac_config.ac_mode_five_percent) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForLinkState::enter() {
|
||||
ctx.log_info("Waiting for Link to be ready...");
|
||||
start_time = std::chrono::steady_clock::now();
|
||||
}
|
||||
|
||||
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_error("Link could not be established");
|
||||
// Notify higher layers to on CP signal
|
||||
return sa.create_simple<FailedState>(ctx);
|
||||
} else if (ev == Event::RESET) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
} else {
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType WaitForLinkState::callback() {
|
||||
const auto& cfg = ctx.slac_config;
|
||||
if (not link_status_req_sent) {
|
||||
link_status_req_sent = send_link_status_req(ctx);
|
||||
return cfg.link_status.retry_ms;
|
||||
} else {
|
||||
// Did we timeout?
|
||||
if (std::chrono::steady_clock::now() - start_time > std::chrono::milliseconds(cfg.link_status.timeout_ms)) {
|
||||
return Event::RETRY_MATCHING;
|
||||
}
|
||||
// Link is confirmed not up yet, query again
|
||||
link_status_req_sent = false;
|
||||
return cfg.link_status.retry_ms;
|
||||
}
|
||||
}
|
||||
|
||||
WaitForLinkState::WaitForLinkState(Context& ctx,
|
||||
std::unique_ptr<slac::messages::cm_slac_match_cnf> sent_match_cnf_message) :
|
||||
FSMSimpleState(ctx), match_cnf_message(std::move(sent_match_cnf_message)) {
|
||||
}
|
||||
|
||||
bool WaitForLinkState::handle_slac_message(slac::messages::HomeplugMessage& message) {
|
||||
const auto mmtype = message.get_mmtype();
|
||||
|
||||
auto link_ok = check_link_status_cnf(ctx.modem_vendor, message);
|
||||
|
||||
if (link_ok.has_value() and link_ok.value()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (mmtype == (slac::defs::MMTYPE_CM_SLAC_MATCH | slac::defs::MMTYPE_MODE_REQ)) {
|
||||
// EV retries MATCH_REQ, so we send the CNF again
|
||||
ctx.log_info("Received CM_SLAC_MATCH.REQ retry from EV, sending out CM_SLAC_MATCH.CNF again.");
|
||||
ctx.send_slac_message(message.get_src_mac(), *match_cnf_message);
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
FSMSimpleState::HandleEventReturnType InitState::handle_event(AllocatorType& sa, Event ev) {
|
||||
if (ev == Event::SLAC_MESSAGE) {
|
||||
handle_slac_message(ctx.slac_message_payload);
|
||||
return sa.PASS_ON;
|
||||
} else if (ev == Event::SUCCESS) {
|
||||
return sa.create_simple<ResetState>(ctx);
|
||||
}
|
||||
return sa.PASS_ON;
|
||||
}
|
||||
|
||||
FSMSimpleState::CallbackReturnType InitState::callback() {
|
||||
const auto& cfg = ctx.slac_config;
|
||||
|
||||
if (sub_state == SubState::QUALCOMM_OP_ATTR) {
|
||||
sub_state = SubState::LUMISSIL_GET_VERSION;
|
||||
slac::messages::qualcomm::op_attr_req op_attr_req;
|
||||
ctx.send_slac_message(cfg.plc_peer_mac, op_attr_req);
|
||||
return cfg.request_info_delay_ms;
|
||||
|
||||
} else if (sub_state == SubState::LUMISSIL_GET_VERSION) {
|
||||
sub_state = SubState::DONE;
|
||||
slac::messages::lumissil::nscm_get_version_req version_req;
|
||||
ctx.send_slac_message(cfg.plc_peer_mac, version_req);
|
||||
return cfg.request_info_delay_ms;
|
||||
|
||||
} else if (sub_state == SubState::DONE) {
|
||||
// the requested info may or may not be implemented by the chip,
|
||||
// so we ignore timeouts here.
|
||||
return Event::SUCCESS;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static std::string get_qualcomm_device_info(slac::messages::qualcomm::op_attr_cnf const& msg) {
|
||||
const auto get_string_view = [](auto const& raw) constexpr {
|
||||
static_assert(sizeof(uint8_t) == sizeof(char));
|
||||
return std::string_view(reinterpret_cast<char const*>(raw), sizeof(raw));
|
||||
};
|
||||
|
||||
std::string result("Qualcomm PLC Device Attributes:");
|
||||
result += "\n HW Platform: ";
|
||||
result += get_string_view(msg.hw_platform);
|
||||
result += "\n SW Platform: ";
|
||||
result += get_string_view(msg.sw_platform);
|
||||
result += ("\n Firmware: " + std::to_string(msg.version_major) + "." + std::to_string(msg.version_minor) + "." +
|
||||
std::to_string(msg.version_pib) + "." + std::to_string(msg.reserved) + "-" +
|
||||
std::to_string(msg.version_build));
|
||||
result += "\n Build date: ";
|
||||
result += get_string_view(msg.build_date);
|
||||
|
||||
result += "\n ZC signal: ";
|
||||
|
||||
// FIXME: no magic numbers
|
||||
const auto zc_signal = (msg.line_freq_zc >> 2) & 0x03;
|
||||
if (zc_signal == 0x01) {
|
||||
result += "Detected";
|
||||
} else if (zc_signal == 0x02) {
|
||||
result += "Missing";
|
||||
} else {
|
||||
result += ("Unknown (" + std::to_string(zc_signal) + ")");
|
||||
}
|
||||
|
||||
result += "\n Line frequency: ";
|
||||
|
||||
const auto line_freq = (msg.line_freq_zc) & 0x03;
|
||||
if (line_freq == 0x01) {
|
||||
result += "50Hz";
|
||||
} else if (line_freq == 0x02) {
|
||||
result += "60Hz";
|
||||
} else {
|
||||
result += ("Unknown (" + std::to_string(line_freq) + ")");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string get_lumissil_device_info(slac::messages::lumissil::nscm_get_version_cnf const& msg) {
|
||||
return "Lumissil PLC Device Firmware version: " + std::to_string(msg.version_major) + "." +
|
||||
std::to_string(msg.version_minor) + "." + std::to_string(msg.version_patch) + "." +
|
||||
std::to_string(msg.version_build);
|
||||
}
|
||||
|
||||
void InitState::handle_slac_message(slac::messages::HomeplugMessage& message) {
|
||||
const auto mmtype = message.get_mmtype();
|
||||
if (mmtype == (slac::defs::qualcomm::MMTYPE_OP_ATTR | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
const auto msg = message.get_payload<slac::messages::qualcomm::op_attr_cnf>();
|
||||
const auto device_info = get_qualcomm_device_info(msg);
|
||||
ctx.log_info(device_info);
|
||||
// This message is only supported on Qualcomm, so we can use it to detect the Vendor
|
||||
ctx.modem_vendor = ModemVendor::Qualcomm;
|
||||
|
||||
} else if (mmtype == (slac::defs::lumissil::MMTYPE_NSCM_GET_VERSION | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
const auto msg = message.get_payload<slac::messages::lumissil::nscm_get_version_cnf>();
|
||||
const auto device_info = get_lumissil_device_info(msg);
|
||||
ctx.log_info(device_info);
|
||||
// This message is only supported on Qualcomm, so we can use it to detect the Vendor
|
||||
ctx.modem_vendor = ModemVendor::Lumissil;
|
||||
}
|
||||
}
|
||||
} // namespace slac::fsm::evse
|
||||
56
tools/EVerest-main/lib/everest/slac/include/slac/channel.hpp
Normal file
56
tools/EVerest-main/lib/everest/slac/include/slac/channel.hpp
Normal file
@@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef SLAC_CHANNEL_HPP
|
||||
#define SLAC_CHANNEL_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2021 Pionix GmbH and Contributors to EVerest
|
||||
#include <slac/slac.hpp>
|
||||
|
||||
namespace utils {
|
||||
class PacketSocket;
|
||||
}
|
||||
|
||||
namespace slac {
|
||||
|
||||
// TODO (aw):
|
||||
// - do we need to handle VLAN tags?
|
||||
// - probably we need to handle different sessions ...
|
||||
// - channel could own the interface handle and pass it to the packet
|
||||
// socket
|
||||
|
||||
class Channel {
|
||||
public:
|
||||
Channel();
|
||||
// Channel(const std::string& interface_name);
|
||||
~Channel();
|
||||
|
||||
bool open(const std::string& interface_name);
|
||||
bool read(slac::messages::HomeplugMessage& msg, int timeout);
|
||||
bool write(slac::messages::HomeplugMessage& msg, int timeout);
|
||||
|
||||
const std::string& get_error() const {
|
||||
return error;
|
||||
}
|
||||
|
||||
bool got_timeout() const {
|
||||
return did_timeout;
|
||||
}
|
||||
|
||||
const uint8_t* get_mac_addr();
|
||||
|
||||
private:
|
||||
// for debugging only, should be removed
|
||||
std::unique_ptr<::utils::PacketSocket> socket;
|
||||
uint8_t orig_if_mac[ETH_ALEN];
|
||||
|
||||
std::string error;
|
||||
bool did_timeout{false};
|
||||
};
|
||||
|
||||
} // namespace slac
|
||||
|
||||
#endif // SLAC_CHANNEL_HPP
|
||||
434
tools/EVerest-main/lib/everest/slac/include/slac/slac.hpp
Normal file
434
tools/EVerest-main/lib/everest/slac/include/slac/slac.hpp
Normal file
@@ -0,0 +1,434 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2026 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef SLAC_SLAC_HPP
|
||||
#define SLAC_SLAC_HPP
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <utility>
|
||||
|
||||
#include <net/ethernet.h>
|
||||
|
||||
namespace slac {
|
||||
|
||||
// TODO (aw):
|
||||
// - is run_id 8 or 16 bytes?
|
||||
// - is nid 7 or 8 bytes?
|
||||
|
||||
namespace defs {
|
||||
|
||||
const uint16_t ETH_P_HOMEPLUG_GREENPHY = 0x88E1;
|
||||
|
||||
enum class MMV : uint8_t {
|
||||
AV_1_0 = 0x0,
|
||||
AV_1_1 = 0x1,
|
||||
AV_2_0 = 0x2,
|
||||
};
|
||||
|
||||
const int MME_MIN_LENGTH = 60;
|
||||
|
||||
const int STATION_ID_LEN = 17;
|
||||
const int NID_LEN = 7;
|
||||
const int NID_MOST_SIGNIFANT_BYTE_SHIFT = 4;
|
||||
const uint8_t NID_SECURITY_LEVEL_SIMPLE_CONNECT = 0b00;
|
||||
const int NID_SECURITY_LEVEL_OFFSET = 4;
|
||||
|
||||
const uint8_t DAKS_HASH[] = {0x08, 0x85, 0x6d, 0xaf, 0x7c, 0xf5, 0x81, 0x85};
|
||||
const uint8_t NMK_HASH[] = {0x08, 0x85, 0x6d, 0xaf, 0x7c, 0xf5, 0x81, 0x86};
|
||||
const std::array<std::uint8_t, 8> NMK_HASH_ARR = {0x08, 0x85, 0x6d, 0xaf, 0x7c, 0xf5, 0x81, 0x86};
|
||||
|
||||
const int NMK_LEN = 16;
|
||||
|
||||
const int AAG_LIST_LEN = 58;
|
||||
const int RUN_ID_LEN = 8;
|
||||
|
||||
// FIXME (aw): where to put these iso15118/3 consts?
|
||||
const int C_EV_START_ATTEN_CHAR_INDS = 3;
|
||||
const int C_EV_MATCH_RETRY = 2;
|
||||
const int C_EV_MATCH_MNBC = 10;
|
||||
const int TP_EV_BATCH_MSG_INTERVAL_MS = 40; // 20ms - 50ms, interval between start_atten_char and mnbc_sound msgs
|
||||
const int TT_EV_ATTEN_RESULTS_MS = 1200; // max. 1200ms
|
||||
const int TT_EVSE_MATCH_MNBC_MS = 600;
|
||||
const int TT_MATCH_SEQUENCE_MS = 400;
|
||||
const int TT_MATCH_RESPONSE_MS = 200;
|
||||
const int TT_EVSE_MATCH_SESSION_MS = 10000;
|
||||
const int TT_EVSE_SLAC_INIT_MS = 40000; // (20s - 50s)
|
||||
const int TT_MATCH_JOIN_MS = 12000; // max. 12s
|
||||
const int T_STEP_EF_MS = 4000; // min. 4s
|
||||
|
||||
const uint16_t MMTYPE_CM_SET_KEY = 0x6008;
|
||||
const uint16_t MMTYPE_CM_SLAC_PARAM = 0x6064;
|
||||
const uint16_t MMTYPE_CM_START_ATTEN_CHAR = 0x6068;
|
||||
const uint16_t MMTYPE_CM_ATTEN_CHAR = 0x606C;
|
||||
const uint16_t MMTYPE_CM_MNBC_SOUND = 0x6074;
|
||||
const uint16_t MMTYPE_CM_VALIDATE = 0x6078;
|
||||
const uint16_t MMTYPE_CM_SLAC_MATCH = 0x607C;
|
||||
const uint16_t MMTYPE_CM_ATTEN_PROFILE = 0x6084;
|
||||
|
||||
// Qualcomm Vendor MMEs
|
||||
namespace qualcomm {
|
||||
const uint16_t MMTYPE_CM_RESET_DEVICE = 0xA01C;
|
||||
const uint16_t MMTYPE_LINK_STATUS = 0xA0B8;
|
||||
const uint16_t MMTYPE_OP_ATTR = 0xA068;
|
||||
const uint16_t MMTYPE_NW_INFO = 0xA038;
|
||||
const uint16_t MMTYPE_GET_SW = 0xA000;
|
||||
const uint16_t MMTYPE_VS_ATTENUATION_CHARACTERISTICS = 0xA14E;
|
||||
} // namespace qualcomm
|
||||
|
||||
// Lumissil Vendor MMEs
|
||||
namespace lumissil {
|
||||
const uint16_t MMTYPE_NSCM_RESET_DEVICE = 0xAC70;
|
||||
const uint16_t MMTYPE_NSCM_GET_VERSION = 0xAC6C;
|
||||
const uint16_t MMTYPE_NSCM_GET_D_LINK_STATUS = 0xAC9C;
|
||||
} // namespace lumissil
|
||||
|
||||
// Standard mmtypes
|
||||
const uint16_t MMTYPE_MODE_REQ = 0x0000;
|
||||
const uint16_t MMTYPE_MODE_CNF = 0x0001;
|
||||
const uint16_t MMTYPE_MODE_IND = 0x0002;
|
||||
const uint16_t MMTYPE_MODE_RSP = 0x0003;
|
||||
const uint16_t MMTYPE_MODE_MASK = 0x0003;
|
||||
|
||||
const uint16_t MMTYPE_CATEGORY_STA_CCO = 0x0000;
|
||||
const uint16_t MMTYPE_CATEGORY_PROXY = 0x2000;
|
||||
const uint16_t MMTYPE_CATEGORY_CCO_CCO = 0x4000;
|
||||
const uint16_t MMTYPE_CATEGORY_STA_STA = 0x6000;
|
||||
const uint16_t MMTYPE_CATEGORY_MANUFACTOR_SPECIFIC = 0x8000;
|
||||
const uint16_t MMTYPE_CATEGORY_VENDOR_SPECIFIC = 0xA000;
|
||||
const uint16_t MMTYPE_CATEGORY_MASK = 0xE000;
|
||||
|
||||
const uint8_t COMMON_APPLICATION_TYPE = 0x00;
|
||||
const uint8_t COMMON_SECURITY_TYPE = 0x00;
|
||||
|
||||
const uint8_t CM_VALIDATE_REQ_SIGNAL_TYPE = 0x00;
|
||||
const uint8_t CM_VALIDATE_REQ_RESULT_READY = 0x01;
|
||||
const uint8_t CM_VALIDATE_REQ_RESULT_FAILURE = 0x03;
|
||||
|
||||
const uint16_t CM_SLAC_MATCH_REQ_MVF_LENGTH = 0x3e;
|
||||
|
||||
const uint16_t CM_SLAC_MATCH_CNF_MVF_LENGTH = 0x56;
|
||||
|
||||
const uint8_t CM_SLAC_PARM_CNF_RESP_TYPE = 0x01; // = other GP station
|
||||
const uint8_t CM_SLAC_PARM_CNF_NUM_SOUNDS = 10; // typical value
|
||||
const uint8_t CM_SLAC_PARM_CNF_TIMEOUT = 0x06; // 600ms
|
||||
|
||||
const uint8_t CM_SET_KEY_REQ_KEY_TYPE_NMK = 0x01; // NMK (AES-128), Network Management Key
|
||||
const uint8_t CM_SET_KEY_REQ_PID_HLE = 0x04;
|
||||
const uint16_t CM_SET_KEY_REQ_PRN_UNUSED = 0x0000;
|
||||
const uint8_t CM_SET_KEY_REQ_PMN_UNUSED = 0x00;
|
||||
const uint8_t CM_SET_KEY_REQ_CCO_CAP_NONE = 0x00; // Level-0 CCo Capable, neither QoS nor TDMA
|
||||
const uint8_t CM_SET_KEY_REQ_PEKS_NMK_KNOWN_TO_STA = 0x01;
|
||||
|
||||
const uint8_t CM_SET_KEY_CNF_RESULT_SUCCESS = 0x0;
|
||||
|
||||
const uint8_t CM_ATTEN_CHAR_RSP_RESULT = 0x00;
|
||||
|
||||
const uint8_t BROADCAST_MAC_ADDRESS[] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
|
||||
|
||||
} // namespace defs
|
||||
|
||||
namespace utils {
|
||||
void generate_nmk_hs(uint8_t nmk_hs[slac::defs::NMK_LEN], const char* plain_password, int password_len);
|
||||
void generate_nid_from_nmk(uint8_t nid[slac::defs::NID_LEN], const uint8_t nmk[slac::defs::NMK_LEN]);
|
||||
} // namespace utils
|
||||
|
||||
namespace messages {
|
||||
|
||||
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};
|
||||
};
|
||||
|
||||
const int M_SOUND_TARGET_LEN = 6;
|
||||
const int SENDER_ID_LEN = defs::STATION_ID_LEN;
|
||||
const int SOURCE_ID_LEN = defs::STATION_ID_LEN;
|
||||
const int RESP_ID_LEN = defs::STATION_ID_LEN;
|
||||
const int PEV_ID_LEN = defs::STATION_ID_LEN;
|
||||
const int EVSE_ID_LEN = defs::STATION_ID_LEN;
|
||||
|
||||
typedef struct {
|
||||
uint8_t application_type; // fixed to 0x00, indicating 'pev-evse matching'
|
||||
uint8_t security_type; // fixed to 0x00, indicating 'no security'
|
||||
uint8_t run_id[defs::RUN_ID_LEN]; // indentifier for a matching run
|
||||
// cipher fields are missing, because we restrict to security_type = 0x00
|
||||
} __attribute__((packed)) cm_slac_parm_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t m_sound_target[M_SOUND_TARGET_LEN]; // fixed to 0xFFFFFFFFFFFF
|
||||
uint8_t num_sounds; // number of expected m-sounds
|
||||
uint8_t timeout; // corresponds to TT_EVSE_match_MNBC, in units of 100ms
|
||||
uint8_t resp_type; // fixed to 0x01, indicating 'other gp station'
|
||||
uint8_t forwarding_sta[ETH_ALEN]; // ev host mac address
|
||||
uint8_t application_type; // fixed to 0x00, indicating 'pev-evse matching'
|
||||
uint8_t security_type; // fixed to 0x00, indicating 'no security'
|
||||
uint8_t run_id[defs::RUN_ID_LEN]; // matching run identifier, corresponding to the request
|
||||
// cipher field is missing, because we restrict to security_type = 0x00
|
||||
} __attribute__((packed)) cm_slac_parm_cnf;
|
||||
|
||||
typedef struct {
|
||||
uint8_t application_type; // fixed to 0x00, indicating 'pev-evse matching'
|
||||
uint8_t security_type; // fixed to 0x00, indicating 'no security'
|
||||
uint8_t num_sounds; // number of expected m-sounds
|
||||
uint8_t timeout; // corresponds to TT_EVSE_match_MNBC
|
||||
uint8_t resp_type; // fixed to 0x01, indicating 'other gp station'
|
||||
uint8_t forwarding_sta[ETH_ALEN]; // ev host mac address
|
||||
uint8_t run_id[defs::RUN_ID_LEN]; // indentifier for a matching run
|
||||
} __attribute__((packed)) cm_start_atten_char_ind;
|
||||
|
||||
typedef struct {
|
||||
uint8_t application_type; // fixed to 0x00, indicating 'pev-evse matching'
|
||||
uint8_t security_type; // fixed to 0x00, indicating 'no security'
|
||||
uint8_t source_address[ETH_ALEN]; // mac address of EV host, which initiates matching
|
||||
uint8_t run_id[defs::RUN_ID_LEN]; // indentifier for a matching run
|
||||
uint8_t source_id[SOURCE_ID_LEN]; // unique id of the station, that sent the m-sounds
|
||||
uint8_t resp_id[RESP_ID_LEN]; // unique id of the station, that is sending this message
|
||||
uint8_t num_sounds; // number of sounds used for attenuation profile
|
||||
struct {
|
||||
uint8_t num_groups; // number of OFDM carrier groups
|
||||
uint8_t aag[defs::AAG_LIST_LEN]; // AAG_1 .. AAG_N
|
||||
} __attribute__((packed)) attenuation_profile;
|
||||
} __attribute__((packed)) cm_atten_char_ind;
|
||||
|
||||
typedef struct {
|
||||
uint8_t application_type; // fixed to 0x00, indicating 'pev-evse matching'
|
||||
uint8_t security_type; // fixed to 0x00, indicating 'no security'
|
||||
uint8_t source_address[ETH_ALEN]; // mac address of EV host, which initiates matching
|
||||
uint8_t run_id[defs::RUN_ID_LEN]; // indentifier for a matching run
|
||||
uint8_t source_id[SOURCE_ID_LEN]; // unique id of the station, that sent the m-sounds
|
||||
uint8_t resp_id[RESP_ID_LEN]; // unique id of the station, that is sending this message
|
||||
uint8_t result; // fixed to 0x00, indicates successful SLAC process
|
||||
} __attribute__((packed)) cm_atten_char_rsp;
|
||||
|
||||
typedef struct {
|
||||
uint8_t application_type; // fixed to 0x00, indicating 'pev-evse matching'
|
||||
uint8_t security_type; // fixed to 0x00, indicating 'no security'
|
||||
uint8_t sender_id[SENDER_ID_LEN]; // sender id, if application_type = 0x00, it should be the pev's vin code
|
||||
uint8_t remaining_sound_count; // count of remaining sound messages
|
||||
uint8_t run_id[defs::RUN_ID_LEN]; // indentifier for a matching run
|
||||
uint8_t _reserved[8]; // note: this is to pad the run_id, which is defined to be 16 bytes for this message
|
||||
uint8_t random[16]; // random value
|
||||
} __attribute__((packed)) cm_mnbc_sound_ind;
|
||||
|
||||
// note: this message doesn't seem to part of hpgp, it is defined in ISO15118-3
|
||||
typedef struct {
|
||||
uint8_t pev_mac[ETH_ALEN]; // mac address of the EV host
|
||||
uint8_t num_groups; // number of OFDM carrier groups
|
||||
uint8_t _reserved;
|
||||
uint8_t aag[defs::AAG_LIST_LEN]; // list of average attenuation for each group
|
||||
} __attribute__((packed)) cm_atten_profile_ind;
|
||||
|
||||
typedef struct {
|
||||
uint8_t application_type; // fixed to 0x00, indicating 'pev-evse matching'
|
||||
uint8_t security_type; // fixed to 0x00, indicating 'no security'
|
||||
uint16_t mvf_length; // fixed to 0x3e = 62 bytes following
|
||||
uint8_t pev_id[PEV_ID_LEN]; // vin code of PEV
|
||||
uint8_t pev_mac[ETH_ALEN]; // mac address of the EV host
|
||||
uint8_t evse_id[EVSE_ID_LEN]; // EVSE id
|
||||
uint8_t evse_mac[ETH_ALEN]; // mac address of the EVSE
|
||||
uint8_t run_id[defs::RUN_ID_LEN]; // indentifier for a matching run
|
||||
uint8_t _reserved[8]; // note: this is to pad the run_id, which is defined to be 16 bytes for this message
|
||||
} __attribute__((packed)) cm_slac_match_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t application_type; // fixed to 0x00, indicating 'pev-evse matching'
|
||||
uint8_t security_type; // fixed to 0x00, indicating 'no security'
|
||||
uint16_t mvf_length; // fixed to 0x56 = 86 bytes following
|
||||
uint8_t pev_id[PEV_ID_LEN]; // vin code of PEV
|
||||
uint8_t pev_mac[ETH_ALEN]; // mac address of the EV host
|
||||
uint8_t evse_id[EVSE_ID_LEN]; // EVSE id
|
||||
uint8_t evse_mac[ETH_ALEN]; // mac address of the EVSE
|
||||
uint8_t run_id[defs::RUN_ID_LEN]; // indentifier for a matching run
|
||||
uint8_t _rerserved[8]; // note: this is to pad the run_id, which is defined to be 16 bytes for this message
|
||||
uint8_t nid[defs::NID_LEN]; // network id derived from the nmk
|
||||
uint8_t _reserved2; // note: this is to pad the nid, which is defined to be 8 bytes for this message
|
||||
uint8_t nmk[defs::NMK_LEN]; // private nmk of the EVSE
|
||||
} __attribute__((packed)) cm_slac_match_cnf;
|
||||
|
||||
typedef struct {
|
||||
uint8_t signal_type; // fixed to 0x00: PEV S2 toggles on control pilot line
|
||||
uint8_t timer; // in the first request response exchange: should be set to 0x00
|
||||
// in the second request response exchange: 0x00 = 100ms, 0x01 = 200ms TT_EVSE_vald_toggle
|
||||
uint8_t result; // in the first request response exchange: should be set to 0x01 = ready
|
||||
// in the second request response exchange: should be set to 0x01 = ready
|
||||
} __attribute__((packed)) cm_validate_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t signal_type; // fixed to 0x00: PEV S2 toggles on control pilot line
|
||||
uint8_t toggle_num; // in the first request response exchange: should be set to 0x00
|
||||
// in the second request response exchange: number of detected BC
|
||||
// edges during TT_EVSE_vald_toggle
|
||||
uint8_t result; // 0x00 = not ready, 0x01 = ready, 0x02 = success, 0x03 = failure, 0x04 = not required
|
||||
} __attribute__((packed)) cm_validate_cnf;
|
||||
|
||||
typedef struct {
|
||||
uint8_t key_type; // fixed to 0x01, indicating NMK
|
||||
uint32_t my_nonce; // fixed to 0x00000000: encrypted payload not used
|
||||
uint32_t your_nonce; // fixed to 0x00000000: encrypted payload not used
|
||||
uint8_t pid; // fixed to 0x04: HLE protocol
|
||||
uint16_t prn; // fixed to 0x0000: encrypted payload not used
|
||||
uint8_t pmn; // fixed to 0x00: encrypted payload not used
|
||||
uint8_t cco_capability; // CCo capability according to the station role
|
||||
uint8_t nid[defs::NID_LEN]; // 54 LSBs = NID, 2 MSBs = 0b00
|
||||
uint8_t new_eks; // fixed to 0x01: NMK
|
||||
uint8_t new_key[defs::NMK_LEN]; // new NMK
|
||||
} __attribute__((packed)) cm_set_key_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t result; // 0x00 = success, 0x01 = failure, 0x02 - 0xFF = reserved
|
||||
uint32_t my_nonce;
|
||||
uint32_t your_nonce;
|
||||
uint8_t pid;
|
||||
uint16_t prn;
|
||||
uint8_t pmn;
|
||||
uint8_t cco_capability;
|
||||
} __attribute__((packed)) cm_set_key_cnf;
|
||||
|
||||
namespace qualcomm {
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3] = {0x00, 0xb0, 0x52}; // Qualcomm Vendor MME code
|
||||
} __attribute__((packed)) cm_reset_device_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3]; // Vendor MME code
|
||||
uint8_t success;
|
||||
} __attribute__((packed)) cm_reset_device_cnf;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3] = {0x00, 0xb0, 0x52}; // Qualcomm Vendor MME code
|
||||
} __attribute__((packed)) link_status_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3]; // Vendor MME code
|
||||
uint8_t reserved;
|
||||
uint8_t link_status;
|
||||
} __attribute__((packed)) link_status_cnf;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3] = {0x00, 0xb0, 0x52}; // Qualcomm Vendor MME code
|
||||
uint32_t cookie{0x12345}; // some cookie we will also get in the reply
|
||||
uint8_t report_type{0}; // binary report
|
||||
} __attribute__((packed)) op_attr_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3]; // Vendor MME code
|
||||
uint16_t success; // 0x00 means success
|
||||
uint32_t cookie;
|
||||
uint8_t report_type; // should be 0x00 (binary)
|
||||
uint16_t size; // should be 118, otherwise we do not know the structure
|
||||
uint8_t hw_platform[16];
|
||||
uint8_t sw_platform[16];
|
||||
uint32_t version_major;
|
||||
uint32_t version_minor;
|
||||
uint32_t version_pib;
|
||||
uint32_t version_build;
|
||||
uint32_t reserved;
|
||||
uint8_t build_date[8];
|
||||
uint8_t release_type[12];
|
||||
uint8_t sdram_type;
|
||||
uint8_t reserved2;
|
||||
uint8_t line_freq_zc;
|
||||
uint32_t sdram_size;
|
||||
uint8_t authorization_mode;
|
||||
} __attribute__((packed)) op_attr_cnf;
|
||||
|
||||
} // namespace qualcomm
|
||||
|
||||
namespace lumissil {
|
||||
|
||||
typedef struct {
|
||||
uint16_t version;
|
||||
uint32_t reserved;
|
||||
uint8_t request_id;
|
||||
uint8_t reserved2[12];
|
||||
} __attribute__((packed)) lms_header;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3] = {0x00, 0x16, 0xE8}; // Lumissil Vendor MME code
|
||||
lms_header lms;
|
||||
uint8_t mode{0}; // Normal reset
|
||||
} __attribute__((packed)) nscm_reset_device_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3] = {0x00, 0x16, 0xE8}; // Lumissil Vendor MME code
|
||||
lms_header lms;
|
||||
} __attribute__((packed)) nscm_get_version_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3] = {0x00, 0x16, 0xE8}; // Lumissil Vendor MME code
|
||||
lms_header lms;
|
||||
uint16_t version_major;
|
||||
uint16_t version_minor;
|
||||
uint16_t version_patch;
|
||||
uint16_t version_build;
|
||||
uint16_t reserved;
|
||||
} __attribute__((packed)) nscm_get_version_cnf;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3] = {0x00, 0x16, 0xE8}; // Lumissil Vendor MME code
|
||||
lms_header lms;
|
||||
} __attribute__((packed)) nscm_get_d_link_status_req;
|
||||
|
||||
typedef struct {
|
||||
uint8_t vendor_mme[3] = {0x00, 0x16, 0xE8}; // Lumissil Vendor MME code
|
||||
lms_header lms;
|
||||
uint8_t link_status;
|
||||
} __attribute__((packed)) nscm_get_d_link_status_cnf;
|
||||
|
||||
// There is no CNF for this reset packet
|
||||
|
||||
} // namespace lumissil
|
||||
|
||||
} // namespace messages
|
||||
} // namespace slac
|
||||
|
||||
#endif // SLAC_SLAC_HPP
|
||||
18
tools/EVerest-main/lib/everest/slac/io/CMakeLists.txt
Normal file
18
tools/EVerest-main/lib/everest/slac/io/CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
add_library(slac_io)
|
||||
add_library(slac::io ALIAS slac_io)
|
||||
ev_register_library_target(slac_io)
|
||||
|
||||
target_include_directories(slac_io
|
||||
PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
|
||||
)
|
||||
|
||||
target_sources(slac_io
|
||||
PRIVATE
|
||||
src/io.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(slac_io
|
||||
PUBLIC
|
||||
slac::slac
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef SLAC_IO_HPP
|
||||
#define SLAC_IO_HPP
|
||||
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <slac/channel.hpp>
|
||||
|
||||
class SlacIO {
|
||||
public:
|
||||
using InputHandlerFnType = void(slac::messages::HomeplugMessage&);
|
||||
void init(const std::string& if_name);
|
||||
|
||||
void run(std::function<InputHandlerFnType> callback);
|
||||
void send(slac::messages::HomeplugMessage& msg);
|
||||
void quit();
|
||||
|
||||
// cannot be const while libslac's SlacChannel::get_mac_addr() isn't const
|
||||
const uint8_t* get_mac_addr() /* const */;
|
||||
|
||||
private:
|
||||
void loop();
|
||||
slac::Channel slac_channel;
|
||||
slac::messages::HomeplugMessage incoming_msg;
|
||||
std::function<InputHandlerFnType> input_handler;
|
||||
std::thread loop_thread;
|
||||
|
||||
bool running{false};
|
||||
};
|
||||
|
||||
#endif // SLAC_IO_HPP
|
||||
48
tools/EVerest-main/lib/everest/slac/io/src/io.cpp
Normal file
48
tools/EVerest-main/lib/everest/slac/io/src/io.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#include <everest/slac/io.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
const uint8_t* SlacIO::get_mac_addr() /* const */ {
|
||||
return slac_channel.get_mac_addr();
|
||||
}
|
||||
90
tools/EVerest-main/lib/everest/slac/src/channel.cpp
Normal file
90
tools/EVerest-main/lib/everest/slac/src/channel.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#include <slac/channel.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include "packet_socket.hpp"
|
||||
|
||||
namespace slac {
|
||||
|
||||
Channel::Channel() : socket(nullptr){};
|
||||
|
||||
bool Channel::open(const std::string& interface_name) {
|
||||
did_timeout = false;
|
||||
|
||||
auto if_info = ::utils::InterfaceInfo(interface_name);
|
||||
if (!if_info.is_valid()) {
|
||||
error = if_info.get_error();
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(orig_if_mac, if_info.get_mac(), sizeof(orig_if_mac));
|
||||
|
||||
socket = std::make_unique<::utils::PacketSocket>(if_info, defs::ETH_P_HOMEPLUG_GREENPHY);
|
||||
if (!socket->is_valid()) {
|
||||
error = socket->get_error();
|
||||
socket.reset();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Channel::~Channel() = default;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool Channel::write(slac::messages::HomeplugMessage& msg, int timeout) {
|
||||
using IOResult = ::utils::PacketSocket::IOResult;
|
||||
|
||||
assert(("Homeplug message is not valid\n", msg.is_valid()));
|
||||
|
||||
did_timeout = false;
|
||||
|
||||
if (socket) {
|
||||
auto raw_msg_ether_shost = msg.get_src_mac();
|
||||
if (!msg.keep_source_mac()) {
|
||||
memcpy(raw_msg_ether_shost, orig_if_mac, sizeof(orig_if_mac));
|
||||
}
|
||||
switch (socket->write(msg.get_raw_message_ptr(), msg.get_raw_msg_len(), timeout)) {
|
||||
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;
|
||||
}
|
||||
|
||||
const uint8_t* Channel::get_mac_addr() {
|
||||
return orig_if_mac;
|
||||
}
|
||||
|
||||
} // namespace slac
|
||||
148
tools/EVerest-main/lib/everest/slac/src/packet_socket.cpp
Normal file
148
tools/EVerest-main/lib/everest/slac/src/packet_socket.cpp
Normal file
@@ -0,0 +1,148 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#include "packet_socket.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <linux/if_packet.h>
|
||||
#include <poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace utils {
|
||||
InterfaceInfo::InterfaceInfo(const std::string& interface_name) {
|
||||
// fetch all interfaces
|
||||
struct ifaddrs* if_addrs;
|
||||
int ret = getifaddrs(&if_addrs);
|
||||
if (ret == -1) {
|
||||
error = "Error while calling getifaddrs(): ";
|
||||
error += strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
// iterate through them and list them
|
||||
struct ifaddrs* cur_if_addr = if_addrs;
|
||||
while (cur_if_addr) {
|
||||
if (cur_if_addr->ifa_addr && cur_if_addr->ifa_addr->sa_family == AF_PACKET) {
|
||||
if (0 == interface_name.compare(cur_if_addr->ifa_name)) {
|
||||
const auto* addr_info = reinterpret_cast<struct sockaddr_ll*>(cur_if_addr->ifa_addr);
|
||||
memcpy(mac, addr_info->sll_addr, 6);
|
||||
interface_index = addr_info->sll_ifindex;
|
||||
valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cur_if_addr = cur_if_addr->ifa_next;
|
||||
}
|
||||
|
||||
freeifaddrs(if_addrs);
|
||||
|
||||
if (!valid) {
|
||||
error = "Interface " + interface_name + " not found";
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
PacketSocket::IOResult PacketSocket::write(const void* buf, size_t size, int timeout) {
|
||||
struct pollfd poll_fd = {
|
||||
socket_fd, // file descriptor
|
||||
POLLOUT, // requested event
|
||||
0 // returned event
|
||||
};
|
||||
|
||||
int ret = poll(&poll_fd, 1, timeout);
|
||||
|
||||
if (ret == -1) {
|
||||
error = std::string("poll() failed with: ") + strerror(errno);
|
||||
return IOResult::Failure;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
return IOResult::Timeout;
|
||||
}
|
||||
|
||||
if ((poll_fd.revents & POLLOUT) == 0) {
|
||||
error = "poll() set other flag than POLLOUT";
|
||||
return IOResult::Failure;
|
||||
}
|
||||
|
||||
auto bytes_written = ::write(socket_fd, buf, size);
|
||||
|
||||
if (-1 == bytes_written) {
|
||||
error = std::string("write() failed with: ") + strerror(errno);
|
||||
return IOResult::Failure;
|
||||
}
|
||||
|
||||
if (bytes_written != size) {
|
||||
error = "write() only send part of the datagram - this should not happen";
|
||||
return IOResult::Failure;
|
||||
}
|
||||
|
||||
return IOResult::Ok;
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
72
tools/EVerest-main/lib/everest/slac/src/packet_socket.hpp
Normal file
72
tools/EVerest-main/lib/everest/slac/src/packet_socket.hpp
Normal file
@@ -0,0 +1,72 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef SRC_PACKET_SOCKET_HPP
|
||||
#define SRC_PACKET_SOCKET_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <linux/if_ether.h>
|
||||
|
||||
namespace utils {
|
||||
class InterfaceInfo {
|
||||
public:
|
||||
explicit InterfaceInfo(const std::string& interface_name);
|
||||
bool is_valid() {
|
||||
return valid;
|
||||
};
|
||||
const std::string& get_error() const {
|
||||
return error;
|
||||
};
|
||||
|
||||
const int get_index() const {
|
||||
return interface_index;
|
||||
};
|
||||
const uint8_t* get_mac() const {
|
||||
return mac;
|
||||
};
|
||||
|
||||
private:
|
||||
bool valid{false};
|
||||
int interface_index{0};
|
||||
std::string error;
|
||||
uint8_t mac[ETH_ALEN];
|
||||
};
|
||||
|
||||
class PacketSocket {
|
||||
public:
|
||||
enum class IOResult {
|
||||
Ok,
|
||||
Failure,
|
||||
Timeout
|
||||
};
|
||||
|
||||
PacketSocket(const InterfaceInfo& if_info, int protocol);
|
||||
|
||||
bool is_valid() {
|
||||
return valid;
|
||||
};
|
||||
|
||||
const std::string& get_error() {
|
||||
return error;
|
||||
}
|
||||
|
||||
IOResult read(uint8_t* buffer, int timeout);
|
||||
|
||||
int get_last_read_size() const {
|
||||
return bytes_read;
|
||||
};
|
||||
|
||||
IOResult write(const void* buf, size_t count, int timeout);
|
||||
|
||||
static const int MIN_BUFFER_SIZE = ETH_FRAME_LEN;
|
||||
|
||||
private:
|
||||
int bytes_read{-1};
|
||||
bool valid{false};
|
||||
std::string error;
|
||||
int socket_fd{-1};
|
||||
};
|
||||
} // namespace utils
|
||||
|
||||
#endif // SRC_PACKET_SOCKET_HPP
|
||||
135
tools/EVerest-main/lib/everest/slac/src/slac.cpp
Normal file
135
tools/EVerest-main/lib/everest/slac/src/slac.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2022 - 2022 Pionix GmbH and Contributors to EVerest
|
||||
#include <slac/slac.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <endian.h>
|
||||
|
||||
#include <everest/tls/openssl_util.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace slac {
|
||||
namespace utils {
|
||||
|
||||
// note on byte order:
|
||||
// - sha256 takes the most significant byte first from the lowest
|
||||
// memory address
|
||||
// - for the generation of the aes-128, or NMK-HS, the first octet of
|
||||
// the sha256 output is taken as the zero octet for the NMK-HS
|
||||
// - for the generation of NID, the NMK is fed into sha256, so having
|
||||
// a const char* as input should be the proper byte ordering already
|
||||
void generate_nmk_hs(uint8_t nmk_hs[slac::defs::NMK_LEN], const char* plain_password, int password_len) {
|
||||
// do pbkdf1 (use sha256 as hashing function, iterate 1000 times,
|
||||
// use salt)
|
||||
std::vector<std::uint8_t> input(plain_password, plain_password + password_len);
|
||||
input.insert(input.end(), slac::defs::NMK_HASH_ARR.begin(), slac::defs::NMK_HASH_ARR.end());
|
||||
openssl::sha_256_digest_t digest;
|
||||
openssl::sha_256(input.data(), input.size(), digest);
|
||||
for (int i = 0; i < 1000 - 1; ++i) {
|
||||
openssl::sha_256(digest.data(), openssl::sha_256_digest_size, digest);
|
||||
}
|
||||
|
||||
memcpy(nmk_hs, digest.data(), slac::defs::NMK_LEN);
|
||||
}
|
||||
|
||||
void generate_nid_from_nmk(uint8_t nid[slac::defs::NID_LEN], const uint8_t nmk[slac::defs::NMK_LEN]) {
|
||||
// msb of least significant octet of NMK should be the leftmost bit
|
||||
// of the input, which corresponds to the usual const char* order
|
||||
|
||||
// do pbkdf1 (use sha256 as hashing function, iterate 5 times, no
|
||||
// salt)
|
||||
openssl::sha_256_digest_t digest;
|
||||
openssl::sha_256(nmk, slac::defs::NMK_LEN, digest);
|
||||
for (int i = 0; i < 5 - 1; ++i) {
|
||||
openssl::sha_256(digest.data(), openssl::sha_256_digest_size, digest);
|
||||
}
|
||||
|
||||
// use leftmost 52 bits of the hash output
|
||||
// left most bit should be bit 7 of the nid
|
||||
memcpy(nid, digest.data(), slac::defs::NID_LEN - 1); // (bits 52 - 5)
|
||||
nid[slac::defs::NID_LEN - 1] =
|
||||
(slac::defs::NID_SECURITY_LEVEL_SIMPLE_CONNECT << slac::defs::NID_SECURITY_LEVEL_OFFSET) |
|
||||
((static_cast<uint8_t>(digest.data()[6])) >> slac::defs::NID_MOST_SIGNIFANT_BYTE_SHIFT);
|
||||
}
|
||||
|
||||
} // namespace utils
|
||||
|
||||
namespace messages {
|
||||
|
||||
static constexpr auto effective_payload_length(const defs::MMV mmv) {
|
||||
if (mmv == defs::MMV::AV_1_0) {
|
||||
return sizeof(homeplug_message::payload);
|
||||
} else {
|
||||
return sizeof(homeplug_message::payload) - sizeof(homeplug_fragmentation_part);
|
||||
}
|
||||
}
|
||||
|
||||
void HomeplugMessage::setup_payload(void const* payload, int len, uint16_t mmtype, const defs::MMV mmv) {
|
||||
if (len > effective_payload_length(mmv)) {
|
||||
throw std::runtime_error("Homeplug Payload length too long");
|
||||
}
|
||||
raw_msg.homeplug_header.mmv = static_cast<std::underlying_type_t<defs::MMV>>(mmv);
|
||||
raw_msg.homeplug_header.mmtype = htole16(mmtype);
|
||||
|
||||
uint8_t* dst = raw_msg.payload;
|
||||
|
||||
if (mmv != defs::MMV::AV_1_0) {
|
||||
homeplug_fragmentation_part fragmentation_part{};
|
||||
fragmentation_part.fmni = 0; // not implemented
|
||||
fragmentation_part.fmsn = 0; // not implemented
|
||||
memcpy(dst, &fragmentation_part, sizeof(fragmentation_part));
|
||||
dst += sizeof(fragmentation_part); // adjust effective payload start
|
||||
}
|
||||
|
||||
// copy payload into place
|
||||
memcpy(dst, payload, len);
|
||||
|
||||
// get pointer to the end of buffer
|
||||
uint8_t* dst_end = dst + len;
|
||||
|
||||
// calculate raw message length
|
||||
raw_msg_len = dst_end - reinterpret_cast<uint8_t*>(&raw_msg);
|
||||
|
||||
// do padding
|
||||
auto padding_len = defs::MME_MIN_LENGTH - raw_msg_len;
|
||||
if (padding_len > 0) {
|
||||
memset(dst_end, 0x00, padding_len);
|
||||
raw_msg_len = defs::MME_MIN_LENGTH;
|
||||
}
|
||||
}
|
||||
|
||||
void HomeplugMessage::setup_ethernet_header(const uint8_t dst_mac_addr[ETH_ALEN],
|
||||
const uint8_t src_mac_addr[ETH_ALEN]) {
|
||||
|
||||
// ethernet frame byte order is big endian
|
||||
raw_msg.ethernet_header.ether_type = htons(defs::ETH_P_HOMEPLUG_GREENPHY);
|
||||
if (dst_mac_addr) {
|
||||
memcpy(raw_msg.ethernet_header.ether_dhost, dst_mac_addr, ETH_ALEN);
|
||||
}
|
||||
|
||||
if (src_mac_addr) {
|
||||
memcpy(raw_msg.ethernet_header.ether_shost, src_mac_addr, ETH_ALEN);
|
||||
keep_src_mac = true;
|
||||
} else {
|
||||
keep_src_mac = false;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t HomeplugMessage::get_mmtype() const {
|
||||
return le16toh(raw_msg.homeplug_header.mmtype);
|
||||
}
|
||||
|
||||
uint8_t* HomeplugMessage::get_src_mac() {
|
||||
return raw_msg.ethernet_header.ether_shost;
|
||||
}
|
||||
|
||||
bool HomeplugMessage::is_valid() const {
|
||||
return raw_msg_len >= defs::MME_MIN_LENGTH;
|
||||
}
|
||||
|
||||
} // namespace messages
|
||||
} // namespace slac
|
||||
41
tools/EVerest-main/lib/everest/slac/test/CMakeLists.txt
Normal file
41
tools/EVerest-main/lib/everest/slac/test/CMakeLists.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
add_executable(evse_slac_test)
|
||||
target_sources(evse_slac_test
|
||||
PRIVATE
|
||||
evse_slac_test.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(evse_slac_test
|
||||
PRIVATE
|
||||
slac_fsm_evse
|
||||
fmt::fmt
|
||||
)
|
||||
target_compile_features(evse_slac_test PRIVATE cxx_std_17)
|
||||
|
||||
add_executable(evse_vs_ev)
|
||||
target_sources(evse_vs_ev
|
||||
PRIVATE
|
||||
evse_vs_ev/socket_pair_bridge.cpp
|
||||
evse_vs_ev/plc_emu.cpp
|
||||
evse_vs_ev/main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(evse_vs_ev
|
||||
PRIVATE
|
||||
slac::fsm::evse
|
||||
slac::fsm::ev
|
||||
Threads::Threads
|
||||
)
|
||||
target_compile_features(evse_vs_ev PRIVATE cxx_std_17)
|
||||
|
||||
add_executable(bridger)
|
||||
target_sources(bridger
|
||||
PRIVATE
|
||||
evse_vs_ev/plc_emu.cpp
|
||||
bridger.cpp
|
||||
)
|
||||
target_link_libraries(bridger
|
||||
PRIVATE
|
||||
slac::slac
|
||||
fmt::fmt
|
||||
)
|
||||
target_compile_features(bridger PRIVATE cxx_std_17)
|
||||
130
tools/EVerest-main/lib/everest/slac/test/bridger.cpp
Normal file
130
tools/EVerest-main/lib/everest/slac/test/bridger.cpp
Normal file
@@ -0,0 +1,130 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <linux/if_packet.h>
|
||||
#include <net/ethernet.h>
|
||||
#include <poll.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <slac/slac.hpp>
|
||||
|
||||
#include "evse_vs_ev/plc_emu.hpp"
|
||||
|
||||
void print_mac(const uint8_t* mac) {
|
||||
printf("%2X:%2X:%2X:%2X:%2X:%2X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||
}
|
||||
|
||||
// FIXME (aw): this helper doesn't really belong here
|
||||
static void exit_with_error(const char* msg) {
|
||||
fprintf(stderr, "%s (%s)\n", msg, strerror(errno));
|
||||
exit(-EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct InterfaceInfo {
|
||||
uint8_t mac[ETH_ALEN];
|
||||
int interface_index{-1};
|
||||
};
|
||||
|
||||
InterfaceInfo get_interface_info(const std::string& interface_name) {
|
||||
InterfaceInfo if_info;
|
||||
|
||||
struct ifaddrs* if_addrs;
|
||||
if (-1 == getifaddrs(&if_addrs)) {
|
||||
// FIXME (aw): proper error handling?
|
||||
return if_info;
|
||||
}
|
||||
|
||||
// iterate through them and list them
|
||||
struct ifaddrs* cur_if_addr = if_addrs;
|
||||
while (cur_if_addr) {
|
||||
if (cur_if_addr->ifa_addr && cur_if_addr->ifa_addr->sa_family == AF_PACKET) {
|
||||
if (0 == interface_name.compare(cur_if_addr->ifa_name)) {
|
||||
const auto* addr_info = reinterpret_cast<struct sockaddr_ll*>(cur_if_addr->ifa_addr);
|
||||
memcpy(if_info.mac, addr_info->sll_addr, sizeof(if_info.mac));
|
||||
if_info.interface_index = addr_info->sll_ifindex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
cur_if_addr = cur_if_addr->ifa_next;
|
||||
}
|
||||
|
||||
freeifaddrs(if_addrs);
|
||||
|
||||
return if_info;
|
||||
}
|
||||
|
||||
int create_raw_homeplug_socket(const InterfaceInfo& interface_info) {
|
||||
const uint16_t homeplug_protocol = slac::defs::ETH_P_HOMEPLUG_GREENPHY;
|
||||
const auto socket_fd = socket(AF_PACKET, SOCK_RAW | SOCK_NONBLOCK, htons(homeplug_protocol));
|
||||
|
||||
if (socket_fd == -1) {
|
||||
exit_with_error("Couldn't create the socket");
|
||||
}
|
||||
|
||||
// bind this packet socket to a specific interface
|
||||
struct sockaddr_ll sock_addr = {
|
||||
AF_PACKET, // sll_family
|
||||
htons(homeplug_protocol), // sll_protocol
|
||||
interface_info.interface_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))) {
|
||||
exit_with_error("Failed to bind the socket");
|
||||
}
|
||||
|
||||
return socket_fd;
|
||||
}
|
||||
|
||||
void loop(int ev_fd, int evse_fd) {
|
||||
constexpr static auto EV_INDEX = 0;
|
||||
constexpr static auto EVSE_INDEX = 1;
|
||||
|
||||
struct pollfd pollfds[] = {
|
||||
{ev_fd, POLLIN, 0},
|
||||
{evse_fd, POLLIN, 0},
|
||||
};
|
||||
|
||||
static constexpr auto num_fds = sizeof(pollfds) / sizeof(struct pollfd);
|
||||
|
||||
while (true) {
|
||||
const auto status = poll(pollfds, num_fds, -1);
|
||||
|
||||
if (status == -1) {
|
||||
exit_with_error("bridge poll");
|
||||
}
|
||||
|
||||
if (pollfds[EV_INDEX].revents & POLLIN) {
|
||||
printf("Received ev input\n");
|
||||
handle_ev_input(ev_fd, evse_fd);
|
||||
}
|
||||
|
||||
if (pollfds[EVSE_INDEX].revents & POLLIN) {
|
||||
printf("Received evse input\n");
|
||||
handle_evse_input(evse_fd, ev_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
const auto ev_bridge_device_info = get_interface_info("vev-bridge");
|
||||
const auto evse_bridge_device_info = get_interface_info("vevse-bridge");
|
||||
const auto ev_fd = create_raw_homeplug_socket(ev_bridge_device_info);
|
||||
const auto evse_fd = create_raw_homeplug_socket(evse_bridge_device_info);
|
||||
|
||||
loop(ev_fd, evse_fd);
|
||||
|
||||
// printf("mac: ");
|
||||
// print_mac(info.mac);
|
||||
// printf("index: %d\n", info.interface_index);
|
||||
|
||||
return 0;
|
||||
}
|
||||
311
tools/EVerest-main/lib/everest/slac/test/evse_slac_test.cpp
Normal file
311
tools/EVerest-main/lib/everest/slac/test/evse_slac_test.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#include <everest/slac/fsm/evse/fsm.hpp>
|
||||
#include <everest/slac/fsm/evse/states/others.hpp>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
static auto create_cm_set_key_cnf() {
|
||||
// FIXME (aw): needs to be fully implemented!
|
||||
slac::messages::cm_set_key_cnf set_key_cnf;
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_payload(&set_key_cnf, sizeof(set_key_cnf),
|
||||
(slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_CNF), slac::defs::MMV::AV_1_1);
|
||||
return hp_message;
|
||||
}
|
||||
|
||||
static auto create_cm_validate_req() {
|
||||
slac::messages::cm_validate_req validate_req;
|
||||
validate_req.signal_type = slac::defs::CM_VALIDATE_REQ_SIGNAL_TYPE;
|
||||
validate_req.timer = 0;
|
||||
validate_req.result = slac::defs::CM_VALIDATE_REQ_RESULT_READY;
|
||||
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_payload(&validate_req, sizeof(validate_req),
|
||||
(slac::defs::MMTYPE_CM_VALIDATE | slac::defs::MMTYPE_MODE_REQ), slac::defs::MMV::AV_1_1);
|
||||
|
||||
return hp_message;
|
||||
}
|
||||
|
||||
struct EVSession {
|
||||
EVSession(const std::array<uint8_t, 8>& run_id_, const std::array<uint8_t, 6>& mac_) : run_id(run_id_), mac(mac_){};
|
||||
|
||||
// FIXME (aw): all these create_cm_* need to be fully implemented!
|
||||
auto create_cm_slac_parm_req() {
|
||||
slac::messages::cm_slac_parm_req parm_req;
|
||||
std::copy(run_id.begin(), run_id.end(), parm_req.run_id);
|
||||
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_ethernet_header(mac.data(), mac.data());
|
||||
hp_message.setup_payload(&parm_req, sizeof(parm_req),
|
||||
(slac::defs::MMTYPE_CM_SLAC_PARAM | slac::defs::MMTYPE_MODE_REQ),
|
||||
slac::defs::MMV::AV_1_1);
|
||||
|
||||
return hp_message;
|
||||
}
|
||||
|
||||
auto create_cm_start_atten_char_ind() {
|
||||
slac::messages::cm_start_atten_char_ind atten_char_ind;
|
||||
std::copy(run_id.begin(), run_id.end(), atten_char_ind.run_id);
|
||||
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_ethernet_header(mac.data(), mac.data());
|
||||
hp_message.setup_payload(&atten_char_ind, sizeof(atten_char_ind),
|
||||
(slac::defs::MMTYPE_CM_START_ATTEN_CHAR | slac::defs::MMTYPE_MODE_IND),
|
||||
slac::defs::MMV::AV_1_1);
|
||||
|
||||
return hp_message;
|
||||
}
|
||||
|
||||
auto create_cm_mnbc_sound_ind() {
|
||||
slac::messages::cm_mnbc_sound_ind sound_ind;
|
||||
std::copy(run_id.begin(), run_id.end(), sound_ind.run_id);
|
||||
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_ethernet_header(mac.data(), mac.data());
|
||||
hp_message.setup_payload(&sound_ind, sizeof(sound_ind),
|
||||
(slac::defs::MMTYPE_CM_MNBC_SOUND | slac::defs::MMTYPE_MODE_IND),
|
||||
slac::defs::MMV::AV_1_1);
|
||||
|
||||
return hp_message;
|
||||
}
|
||||
|
||||
auto create_cm_atten_profile_ind() {
|
||||
slac::messages::cm_atten_profile_ind profile_ind;
|
||||
std::copy(mac.begin(), mac.end(), profile_ind.pev_mac);
|
||||
profile_ind.num_groups = slac::defs::AAG_LIST_LEN;
|
||||
|
||||
for (int i = 0; i < slac::defs::AAG_LIST_LEN; ++i) {
|
||||
profile_ind.aag[i] = i;
|
||||
}
|
||||
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_ethernet_header(mac.data(), mac.data());
|
||||
hp_message.setup_payload(&profile_ind, sizeof(profile_ind),
|
||||
(slac::defs::MMTYPE_CM_ATTEN_PROFILE | slac::defs::MMTYPE_MODE_IND),
|
||||
slac::defs::MMV::AV_1_1);
|
||||
|
||||
return hp_message;
|
||||
}
|
||||
|
||||
auto create_cm_atten_char_rsp() {
|
||||
slac::messages::cm_atten_char_rsp atten_char;
|
||||
std::copy(run_id.begin(), run_id.end(), atten_char.run_id);
|
||||
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_ethernet_header(mac.data(), mac.data());
|
||||
hp_message.setup_payload(&atten_char, sizeof(atten_char),
|
||||
(slac::defs::MMTYPE_CM_ATTEN_CHAR | slac::defs::MMTYPE_MODE_RSP),
|
||||
slac::defs::MMV::AV_1_1);
|
||||
|
||||
return hp_message;
|
||||
}
|
||||
|
||||
auto create_cm_slac_match_req() {
|
||||
slac::messages::cm_slac_match_req match_req;
|
||||
std::copy(run_id.begin(), run_id.end(), match_req.run_id);
|
||||
|
||||
slac::messages::HomeplugMessage hp_message;
|
||||
hp_message.setup_ethernet_header(mac.data(), mac.data());
|
||||
hp_message.setup_payload(&match_req, sizeof(match_req),
|
||||
(slac::defs::MMTYPE_CM_SLAC_MATCH | slac::defs::MMTYPE_MODE_REQ),
|
||||
slac::defs::MMV::AV_1_1);
|
||||
|
||||
return hp_message;
|
||||
}
|
||||
|
||||
private:
|
||||
std::array<uint8_t, 8> run_id;
|
||||
const std::array<uint8_t, ETH_ALEN>& mac;
|
||||
};
|
||||
|
||||
void feed_machine_for(slac::fsm::evse::FSM& machine, int period_ms,
|
||||
fsm::FeedResult<slac::fsm::evse::FSMReturnType> feed_result) {
|
||||
using namespace std::chrono;
|
||||
|
||||
auto end_tp = steady_clock::now() + milliseconds(period_ms);
|
||||
|
||||
while (true) {
|
||||
if (feed_result.transition()) {
|
||||
// need to call feed
|
||||
} else if (feed_result.unhandled_event()) {
|
||||
printf("DEBUG: got an unhandled event\n");
|
||||
break;
|
||||
} else if (feed_result.internal_error()) {
|
||||
printf("ERROR: internal FSM error\n");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (feed_result.has_value() == false) {
|
||||
// nothing to do
|
||||
break;
|
||||
} else if (feed_result.has_value() == true) {
|
||||
const auto timeout = *feed_result;
|
||||
if (timeout == 0) {
|
||||
// need to call feed directly
|
||||
} else {
|
||||
auto next_tp = steady_clock::now() + milliseconds(timeout);
|
||||
if (next_tp > end_tp) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_until(next_tp);
|
||||
}
|
||||
} else {
|
||||
printf("ERROR: unknown feed result case\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
feed_result = machine.feed();
|
||||
}
|
||||
|
||||
std::this_thread::sleep_until(end_tp);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
const auto ATTENUATION_ADJUSTMENT = 10;
|
||||
printf("Hi from SLAC!\n");
|
||||
|
||||
std::optional<slac::messages::HomeplugMessage> msg_in;
|
||||
|
||||
slac::fsm::evse::ContextCallbacks callbacks;
|
||||
callbacks.log = [](const std::string& text) { fmt::print("SLAC LOG: {}\n", text); };
|
||||
|
||||
callbacks.send_raw_slac = [&msg_in](slac::messages::HomeplugMessage& hp_message) { msg_in = hp_message; };
|
||||
|
||||
auto ctx = slac::fsm::evse::Context(callbacks);
|
||||
ctx.slac_config.sounding_atten_adjustment = ATTENUATION_ADJUSTMENT;
|
||||
ctx.slac_config.chip_reset.enabled = false;
|
||||
|
||||
auto machine = slac::fsm::evse::FSM();
|
||||
|
||||
//
|
||||
// reset machine
|
||||
//
|
||||
machine.reset<slac::fsm::evse::ResetState>(ctx);
|
||||
auto fr = machine.feed();
|
||||
|
||||
// assert that CM_SET_KEY_REQ gets set!
|
||||
if (!msg_in.has_value() || msg_in->get_mmtype() != (slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_REQ)) {
|
||||
printf("Expected CM_SET_KEY_REQ!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
msg_in.reset();
|
||||
}
|
||||
|
||||
feed_machine_for(machine, 230, fr);
|
||||
|
||||
// feed in CM_SET_KEY_CNF
|
||||
ctx.slac_message_payload = create_cm_set_key_cnf();
|
||||
machine.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
fr = machine.feed();
|
||||
|
||||
// should be idle state now, send ENTER_BCD, to enter MATCHING
|
||||
machine.handle_event(slac::fsm::evse::Event::ENTER_BCD);
|
||||
fr = machine.feed();
|
||||
|
||||
feed_machine_for(machine, 300, fr);
|
||||
|
||||
// create session 1 and inject CM_SLAC_PARM_REQ
|
||||
auto session_1 = EVSession({0, 1, 2, 3, 4, 5, 6, 7}, {0xca, 0xfe, 0xca, 0xfe, 0xca, 0xfe});
|
||||
ctx.slac_message_payload = session_1.create_cm_slac_parm_req();
|
||||
machine.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
fr = machine.feed();
|
||||
|
||||
// assert that CM_SLAC_PARM_CNF gets set!
|
||||
if (!msg_in.has_value() ||
|
||||
msg_in->get_mmtype() != (slac::defs::MMTYPE_CM_SLAC_PARAM | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
printf("Expected CM_SLAC_PARM_CNF!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
msg_in.reset();
|
||||
}
|
||||
|
||||
feed_machine_for(machine, 233, fr);
|
||||
|
||||
// inject CM_START_ATTEN_CHAR_IND
|
||||
ctx.slac_message_payload = session_1.create_cm_start_atten_char_ind();
|
||||
machine.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
fr = machine.feed();
|
||||
|
||||
// inject all the soundings ...
|
||||
for (int i = 0; i < slac::defs::CM_SLAC_PARM_CNF_NUM_SOUNDS - 1; i++) {
|
||||
ctx.slac_message_payload = session_1.create_cm_mnbc_sound_ind();
|
||||
machine.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
fr = machine.feed();
|
||||
|
||||
ctx.slac_message_payload = session_1.create_cm_atten_profile_ind();
|
||||
machine.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
fr = machine.feed();
|
||||
}
|
||||
|
||||
feed_machine_for(machine, 700 + 1 * 45, fr);
|
||||
|
||||
// assert that CM_ATTEN_CHAR_IND gets set!
|
||||
if (!msg_in.has_value() ||
|
||||
msg_in->get_mmtype() != (slac::defs::MMTYPE_CM_ATTEN_CHAR | slac::defs::MMTYPE_MODE_IND)) {
|
||||
printf("Expected CM_ATTEN_CHAR_IND!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
auto atten_char_ind = msg_in->get_payload<slac::messages::cm_atten_char_ind>();
|
||||
for (int i = 0; i < slac::defs::AAG_LIST_LEN; ++i) {
|
||||
if (atten_char_ind.attenuation_profile.aag[i] != i + ATTENUATION_ADJUSTMENT) {
|
||||
printf("Averaging not correct in ATTEN_CHAR_IND\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
msg_in.reset();
|
||||
}
|
||||
|
||||
// "async" insert an CM_VALIDATE.REQ
|
||||
ctx.slac_message_payload = create_cm_validate_req();
|
||||
machine.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
fr = machine.feed();
|
||||
|
||||
// assert that CM_VALIDATE.CNF gets set!
|
||||
if (!msg_in.has_value() || msg_in->get_mmtype() != (slac::defs::MMTYPE_CM_VALIDATE | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
printf("Expected CM_VALIDATE.CNF!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
// check for correct "failure" result
|
||||
auto validate_cnf = msg_in->get_payload<slac::messages::cm_validate_cnf>();
|
||||
if (validate_cnf.result != slac::defs::CM_VALIDATE_REQ_RESULT_FAILURE) {
|
||||
printf("Expected result field of CM_VALIDATE.CNF to be set to failure\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
// inject CM_ATTEN_CHAR_RSP
|
||||
ctx.slac_message_payload = session_1.create_cm_atten_char_rsp();
|
||||
machine.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
fr = machine.feed();
|
||||
|
||||
feed_machine_for(machine, 1000, fr);
|
||||
|
||||
// inject messages from a second session
|
||||
auto session_2 = EVSession({9, 1, 2, 3, 4, 5, 6, 7}, {0xbe, 0xaf, 0xbe, 0xaf, 0xbe, 0xaf});
|
||||
ctx.slac_message_payload = session_2.create_cm_slac_parm_req();
|
||||
machine.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
fr = machine.feed();
|
||||
|
||||
feed_machine_for(machine, 1000, fr);
|
||||
|
||||
// inject CM_SLAC_MATCH_REQ
|
||||
ctx.slac_message_payload = session_1.create_cm_slac_match_req();
|
||||
machine.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
fr = machine.feed();
|
||||
|
||||
// assert that CM_SLAC_MATCH_CNF gets set!
|
||||
if (!msg_in.has_value() ||
|
||||
msg_in->get_mmtype() != (slac::defs::MMTYPE_CM_SLAC_MATCH | slac::defs::MMTYPE_MODE_CNF)) {
|
||||
printf("Expected CM_ATTEN_CHAR_IND!\n");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
msg_in.reset();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
190
tools/EVerest-main/lib/everest/slac/test/evse_vs_ev/main.cpp
Normal file
190
tools/EVerest-main/lib/everest/slac/test/evse_vs_ev/main.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
|
||||
#include <poll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <everest/slac/fsm/ev/fsm.hpp>
|
||||
#include <everest/slac/fsm/ev/states/others.hpp>
|
||||
#include <everest/slac/fsm/evse/fsm.hpp>
|
||||
#include <everest/slac/fsm/evse/states/others.hpp>
|
||||
|
||||
#include "plc_emu.hpp"
|
||||
#include "socket_pair_bridge.hpp"
|
||||
|
||||
class EvseFsmController {
|
||||
public:
|
||||
explicit EvseFsmController(int evse_fd);
|
||||
|
||||
void trigger_enter_bcd();
|
||||
|
||||
void run();
|
||||
|
||||
private:
|
||||
static constexpr uint64_t ENTER_BCD_EVENT_CODE = 1;
|
||||
slac::fsm::evse::ContextCallbacks callbacks;
|
||||
slac::fsm::evse::Context ctx{callbacks};
|
||||
slac::fsm::evse::FSM fsm;
|
||||
|
||||
std::array<struct pollfd, 2> pollfds;
|
||||
};
|
||||
|
||||
EvseFsmController::EvseFsmController(int evse_fd) :
|
||||
pollfds({{
|
||||
{evse_fd, POLLIN, 0},
|
||||
{eventfd(0, 0), POLLIN, 0},
|
||||
}}) {
|
||||
|
||||
ctx.slac_config.chip_reset.enabled = false;
|
||||
|
||||
callbacks.log = [](const std::string& msg) { printf("EVSE log: %s\n", msg.c_str()); };
|
||||
callbacks.send_raw_slac = [evse_fd](slac::messages::HomeplugMessage& msg) {
|
||||
write(evse_fd, msg.get_raw_message_ptr(), msg.get_raw_msg_len());
|
||||
};
|
||||
|
||||
fsm.reset<slac::fsm::evse::ResetState>(ctx);
|
||||
}
|
||||
|
||||
void EvseFsmController::trigger_enter_bcd() {
|
||||
write(pollfds[1].fd, &ENTER_BCD_EVENT_CODE, sizeof(ENTER_BCD_EVENT_CODE));
|
||||
}
|
||||
|
||||
void EvseFsmController::run() {
|
||||
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()) {
|
||||
throw std::runtime_error("Evse fsm: internal error / unhandled event");
|
||||
// FIXME (aw): would need to log here!
|
||||
}
|
||||
const auto timeout = (feed_result.has_value()) ? *feed_result : -1;
|
||||
if (timeout == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto poll_result = poll(pollfds.data(), pollfds.size(), timeout);
|
||||
|
||||
if (poll_result == 0) {
|
||||
// timeout
|
||||
continue;
|
||||
}
|
||||
|
||||
// check for fds
|
||||
if (pollfds[0].revents & POLLIN) {
|
||||
auto raw_msg = ctx.slac_message_payload.get_raw_message_ptr();
|
||||
read(pollfds[0].fd, raw_msg, sizeof(slac::messages::homeplug_message));
|
||||
fsm.handle_event(slac::fsm::evse::Event::SLAC_MESSAGE);
|
||||
}
|
||||
|
||||
if (pollfds[1].revents & POLLIN) {
|
||||
uint64_t tmp;
|
||||
read(pollfds[1].fd, &tmp, sizeof(tmp));
|
||||
|
||||
if (tmp == ENTER_BCD_EVENT_CODE) {
|
||||
fsm.handle_event(slac::fsm::evse::Event::ENTER_BCD);
|
||||
}
|
||||
// new event, for now, we do not care, later on we could check, if it is an exit event code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EvFsmController {
|
||||
public:
|
||||
explicit EvFsmController(int ev_fd);
|
||||
|
||||
void run();
|
||||
|
||||
private:
|
||||
slac::fsm::ev::ContextCallbacks callbacks;
|
||||
slac::fsm::ev::Context ctx{callbacks};
|
||||
slac::fsm::ev::FSM fsm;
|
||||
|
||||
std::array<struct pollfd, 2> pollfds;
|
||||
};
|
||||
|
||||
EvFsmController::EvFsmController(int ev_fd) {
|
||||
pollfds = {{
|
||||
{ev_fd, POLLIN, 0},
|
||||
{eventfd(0, 0), POLLIN, 0},
|
||||
}};
|
||||
|
||||
callbacks.log = [](const std::string& msg) { printf("EV log: %s\n", msg.c_str()); };
|
||||
callbacks.send_raw_slac = [ev_fd](slac::messages::HomeplugMessage& msg) {
|
||||
write(ev_fd, msg.get_raw_message_ptr(), msg.get_raw_msg_len());
|
||||
};
|
||||
|
||||
fsm.reset<slac::fsm::ev::ResetState>(ctx);
|
||||
}
|
||||
|
||||
void EvFsmController::run() {
|
||||
// start connecting
|
||||
fsm.handle_event(slac::fsm::ev::Event::TRIGGER_MATCHING);
|
||||
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()) {
|
||||
throw std::runtime_error("Evse fsm: internal error / unhandled event");
|
||||
// FIXME (aw): would need to log here!
|
||||
}
|
||||
const auto timeout = (feed_result.has_value()) ? *feed_result : -1;
|
||||
|
||||
if (timeout == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto poll_result = poll(pollfds.data(), pollfds.size(), timeout);
|
||||
|
||||
if (poll_result == 0) {
|
||||
// timeout
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pollfds[0].revents & POLLIN) {
|
||||
auto raw_msg = ctx.slac_message.get_raw_message_ptr();
|
||||
read(pollfds[0].fd, raw_msg, sizeof(slac::messages::homeplug_message));
|
||||
fsm.handle_event(slac::fsm::ev::Event::SLAC_MESSAGE);
|
||||
}
|
||||
|
||||
if (pollfds[1].revents & POLLIN) {
|
||||
uint64_t tmp;
|
||||
read(pollfds[1].fd, &tmp, sizeof(tmp));
|
||||
// new event, for now, we do not care, later on we could check, if it is an exit event code
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto main(int argc, char* argv[]) -> int {
|
||||
SocketPairBridge spb{handle_ev_input, handle_evse_input};
|
||||
|
||||
EvseFsmController evse_ctrl{spb.get_evse_socket()};
|
||||
|
||||
EvFsmController ev_ctrl{spb.get_ev_socket()};
|
||||
|
||||
std::thread evse_thread(&EvseFsmController::run, &evse_ctrl);
|
||||
|
||||
// give the EVSE some time to get ready
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
evse_ctrl.trigger_enter_bcd();
|
||||
std::thread ev_thread(&EvFsmController::run, &ev_ctrl);
|
||||
|
||||
// the fsm controller could poll on its fd by itself and look for new slac messages
|
||||
|
||||
evse_thread.join();
|
||||
ev_thread.join();
|
||||
|
||||
// each controller needs to have a feeding thread, and an async input function for the slac message
|
||||
|
||||
return 0;
|
||||
}
|
||||
101
tools/EVerest-main/lib/everest/slac/test/evse_vs_ev/plc_emu.cpp
Normal file
101
tools/EVerest-main/lib/everest/slac/test/evse_vs_ev/plc_emu.cpp
Normal file
@@ -0,0 +1,101 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include "plc_emu.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <slac/slac.hpp>
|
||||
|
||||
// NOTE (aw): this is only intended to be used for this test, if multiple instances use these handle functions, they
|
||||
// will race the buffer
|
||||
static slac::messages::HomeplugMessage homeplug_message;
|
||||
|
||||
constexpr static uint8_t EV_MAC_ADDR[ETH_ALEN] = {0x00, 0x7d, 0xfa, 0x09, 0xfe, 0x76};
|
||||
constexpr static uint8_t EVSE_MAC_ADDR[ETH_ALEN] = {0x6e, 0x3f, 0x46, 0x32, 0xbf, 0xc6};
|
||||
|
||||
constexpr static uint8_t PLC_SRC_MAC_ADDR[ETH_ALEN] = {0x00, 0x01, 0x87, 0x0e, 0xa3, 0x55};
|
||||
|
||||
static void handle_set_key_req(int origin_fd) {
|
||||
slac::messages::cm_set_key_cnf set_key_cnf;
|
||||
// FIXME (aw): proper message and mac header setup!
|
||||
homeplug_message.setup_payload(&set_key_cnf, sizeof(set_key_cnf),
|
||||
(slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_CNF),
|
||||
slac::defs::MMV::AV_1_1);
|
||||
|
||||
auto raw = homeplug_message.get_raw_message_ptr();
|
||||
memcpy(raw->ethernet_header.ether_dhost, raw->ethernet_header.ether_shost,
|
||||
sizeof(raw->ethernet_header.ether_dhost));
|
||||
memcpy(raw->ethernet_header.ether_shost, PLC_SRC_MAC_ADDR, sizeof(PLC_SRC_MAC_ADDR));
|
||||
|
||||
write(origin_fd, raw, homeplug_message.get_raw_msg_len());
|
||||
}
|
||||
|
||||
static void attach_atten_profile(int evse_bridge_fd) {
|
||||
slac::messages::cm_atten_profile_ind atten_profile;
|
||||
|
||||
memcpy(atten_profile.pev_mac, homeplug_message.get_src_mac(), sizeof(atten_profile.pev_mac));
|
||||
atten_profile.num_groups = slac::defs::AAG_LIST_LEN;
|
||||
|
||||
std::random_device rnd_dev;
|
||||
std::mt19937 rng(rnd_dev());
|
||||
std::uniform_int_distribution<std::mt19937::result_type> db_dist(20, 27);
|
||||
|
||||
for (auto i = 0; i < atten_profile.num_groups; ++i) {
|
||||
atten_profile.aag[i] = db_dist(rng);
|
||||
}
|
||||
|
||||
homeplug_message.setup_payload(&atten_profile, sizeof(atten_profile),
|
||||
(slac::defs::MMTYPE_CM_ATTEN_PROFILE | slac::defs::MMTYPE_MODE_IND),
|
||||
slac::defs::MMV::AV_1_1);
|
||||
|
||||
auto raw = homeplug_message.get_raw_message_ptr();
|
||||
|
||||
memcpy(raw->ethernet_header.ether_shost, PLC_SRC_MAC_ADDR, sizeof(PLC_SRC_MAC_ADDR));
|
||||
|
||||
write(evse_bridge_fd, raw, homeplug_message.get_raw_msg_len());
|
||||
}
|
||||
|
||||
void handle_ev_input(int ev_bridge_fd, int evse_bridge_fd) {
|
||||
auto raw_hp_message = homeplug_message.get_raw_message_ptr();
|
||||
auto bytes_read = read(ev_bridge_fd, raw_hp_message, sizeof(slac::messages::homeplug_message));
|
||||
|
||||
const auto mmtype = homeplug_message.get_mmtype();
|
||||
|
||||
// printf("EV send message of size %d and type 0x%hx\n", bytes_read, mmtype);
|
||||
|
||||
// patch in "our" mac address
|
||||
memcpy(raw_hp_message->ethernet_header.ether_shost, EV_MAC_ADDR, sizeof(EV_MAC_ADDR));
|
||||
|
||||
if (mmtype == (slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_REQ)) {
|
||||
handle_set_key_req(ev_bridge_fd);
|
||||
} else {
|
||||
// default: forward message
|
||||
write(evse_bridge_fd, raw_hp_message, bytes_read);
|
||||
}
|
||||
|
||||
if (mmtype == (slac::defs::MMTYPE_CM_MNBC_SOUND | slac::defs::MMTYPE_MODE_IND)) {
|
||||
// also attach CM_ATTEN_PROFILE.IND
|
||||
attach_atten_profile(evse_bridge_fd);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_evse_input(int evse_bridge_fd, int ev_bridge_fd) {
|
||||
auto raw_hp_message = homeplug_message.get_raw_message_ptr();
|
||||
auto bytes_read = read(evse_bridge_fd, raw_hp_message, sizeof(slac::messages::homeplug_message));
|
||||
|
||||
const auto mmtype = homeplug_message.get_mmtype();
|
||||
|
||||
// patch in "our" mac address
|
||||
memcpy(raw_hp_message->ethernet_header.ether_shost, EVSE_MAC_ADDR, sizeof(EVSE_MAC_ADDR));
|
||||
|
||||
if (mmtype == (slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_REQ)) {
|
||||
handle_set_key_req(evse_bridge_fd);
|
||||
} else {
|
||||
// default: forward message
|
||||
write(ev_bridge_fd, raw_hp_message, bytes_read);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef TESTS_EVSE_VS_EV_PLC_EMU_HPP
|
||||
#define TESTS_EVSE_VS_EV_PLC_EMU_HPP
|
||||
|
||||
#include "socket_pair_bridge.hpp"
|
||||
|
||||
void handle_ev_input(int ev_bridge_fd, int evse_bridge_fd);
|
||||
|
||||
void handle_evse_input(int evse_bridge_fd, int ev_bridge_fd);
|
||||
|
||||
#endif // TESTS_EVSE_VS_EV_PLC_EMU_HPP
|
||||
@@ -0,0 +1,100 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#include "socket_pair_bridge.hpp"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <errno.h>
|
||||
#include <poll.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// FIXME (aw): this helper doesn't really belong here
|
||||
static void exit_with_error(const char* msg) {
|
||||
fprintf(stderr, "%s (%s)\n", msg, strerror(errno));
|
||||
exit(-EXIT_FAILURE);
|
||||
}
|
||||
|
||||
SocketPairBridge::SocketPairBridge(const SocketInputHandler& ev_input_handler,
|
||||
const SocketInputHandler& evse_input_handler) :
|
||||
handle_ev_input(ev_input_handler), handle_evse_input(evse_input_handler) {
|
||||
std::array<int, 2> fd_pair;
|
||||
|
||||
auto ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, fd_pair.data());
|
||||
if (ret) {
|
||||
exit_with_error("ev socketpair creation failed");
|
||||
}
|
||||
|
||||
sockets.ev_fd = fd_pair.at(0);
|
||||
sockets.ev_bridge_fd = fd_pair.at(1);
|
||||
|
||||
ret = socketpair(AF_UNIX, SOCK_DGRAM, 0, fd_pair.data());
|
||||
if (ret) {
|
||||
exit_with_error("evse socketpair creation failed");
|
||||
}
|
||||
|
||||
sockets.evse_fd = fd_pair.at(0);
|
||||
sockets.evse_bridge_fd = fd_pair.at(1);
|
||||
|
||||
event_fd = eventfd(0, 0);
|
||||
|
||||
if (event_fd == -1) {
|
||||
exit_with_error("eventfd failed");
|
||||
}
|
||||
|
||||
// NOTE (aw): using 'this' here is safe
|
||||
loop_thread = std::thread(&SocketPairBridge::loop, this);
|
||||
}
|
||||
|
||||
SocketPairBridge::~SocketPairBridge() {
|
||||
uint64_t event_value = 0x1;
|
||||
write(event_fd, &event_value, sizeof(event_value));
|
||||
|
||||
loop_thread.join();
|
||||
|
||||
close(event_fd);
|
||||
close(sockets.evse_bridge_fd);
|
||||
close(sockets.evse_fd);
|
||||
close(sockets.ev_bridge_fd);
|
||||
close(sockets.ev_fd);
|
||||
}
|
||||
|
||||
void SocketPairBridge::loop() {
|
||||
constexpr static auto EV_INDEX = 0;
|
||||
constexpr static auto EVSE_INDEX = 1;
|
||||
constexpr static auto ABORT_INDEX = 2;
|
||||
|
||||
struct pollfd pollfds[] = {
|
||||
{sockets.ev_bridge_fd, POLLIN, 0},
|
||||
{sockets.evse_bridge_fd, POLLIN, 0},
|
||||
{event_fd, POLLIN, 0},
|
||||
};
|
||||
|
||||
static constexpr auto num_fds = sizeof(pollfds) / sizeof(struct pollfd);
|
||||
|
||||
while (true) {
|
||||
const auto status = poll(pollfds, num_fds, -1);
|
||||
|
||||
if (status == -1) {
|
||||
exit_with_error("bridge poll");
|
||||
}
|
||||
|
||||
if (pollfds[ABORT_INDEX].revents & POLLIN) {
|
||||
uint64_t tmp;
|
||||
read(event_fd, &tmp, sizeof(tmp));
|
||||
printf("Received shutdown event, exiting\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pollfds[EV_INDEX].revents & POLLIN) {
|
||||
handle_ev_input(sockets.ev_bridge_fd, sockets.evse_bridge_fd);
|
||||
}
|
||||
|
||||
if (pollfds[EVSE_INDEX].revents & POLLIN) {
|
||||
handle_evse_input(sockets.evse_bridge_fd, sockets.ev_bridge_fd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2023 - 2023 Pionix GmbH and Contributors to EVerest
|
||||
#ifndef TESTS_EVSE_VS_EV_SOCKET_PAIR_BRIDGE_HPP
|
||||
#define TESTS_EVSE_VS_EV_SOCKET_PAIR_BRIDGE_HPP
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
|
||||
class SocketPairBridge {
|
||||
public:
|
||||
struct Sockets {
|
||||
int ev_fd;
|
||||
int ev_bridge_fd;
|
||||
|
||||
int evse_fd;
|
||||
int evse_bridge_fd;
|
||||
};
|
||||
using SocketInputHandler = std::function<void(int, int)>;
|
||||
|
||||
SocketPairBridge(const SocketInputHandler& ev_input_handler, const SocketInputHandler& evse_input_handler);
|
||||
// FIXME (aw): disable copy constructors!
|
||||
|
||||
int get_ev_socket() {
|
||||
return sockets.ev_fd;
|
||||
};
|
||||
|
||||
int get_evse_socket() {
|
||||
return sockets.evse_fd;
|
||||
}
|
||||
|
||||
~SocketPairBridge();
|
||||
|
||||
private:
|
||||
SocketInputHandler handle_ev_input;
|
||||
SocketInputHandler handle_evse_input;
|
||||
|
||||
void loop();
|
||||
|
||||
Sockets sockets{};
|
||||
|
||||
int event_fd;
|
||||
|
||||
std::thread loop_thread;
|
||||
|
||||
uint8_t transfer_buffer[1024];
|
||||
};
|
||||
|
||||
#endif // TESTS_EVSE_VS_EV_SOCKET_PAIR_BRIDGE_HPP
|
||||
24
tools/EVerest-main/lib/everest/slac/tests/CMakeLists.txt
Normal file
24
tools/EVerest-main/lib/everest/slac/tests/CMakeLists.txt
Normal file
@@ -0,0 +1,24 @@
|
||||
set(TEST_TARGET_NAME slac_unit_test)
|
||||
add_executable(${TEST_TARGET_NAME} libslac_unit_test.cpp)
|
||||
|
||||
target_include_directories(${TEST_TARGET_NAME} PUBLIC${GTEST_INCLUDE_DIRS})
|
||||
|
||||
if(DISABLE_EDM)
|
||||
find_package(GTest REQUIRED)
|
||||
else()
|
||||
set(GTEST_LIBRARIES
|
||||
GTest::gmock
|
||||
GTest::gtest
|
||||
GTest::gtest_main
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${TEST_TARGET_NAME} PRIVATE
|
||||
${GTEST_LIBRARIES}
|
||||
slac::slac
|
||||
)
|
||||
|
||||
include(GoogleTest)
|
||||
gtest_discover_tests(${TEST_TARGET_NAME})
|
||||
|
||||
ev_register_test_target(${TEST_TARGET_NAME})
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
|
||||
|
||||
#include <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <slac/slac.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace libslac {
|
||||
class LibSLACUnitTest : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(LibSLACUnitTest, test_generate_nmk_hs) {
|
||||
uint8_t nmk_hs[slac::defs::NMK_LEN] = {0};
|
||||
std::string plain_password = "EVerest";
|
||||
|
||||
slac::utils::generate_nmk_hs(nmk_hs, plain_password.c_str(), plain_password.length());
|
||||
ASSERT_THAT(nmk_hs, testing::ElementsAre(0xf7, 0x0f, 0x4e, 0x02, 0x2b, 0xb1, 0x08, 0x1b, 0xcb, 0xa1, 0xa0, 0x9d,
|
||||
0xf6, 0xe0, 0x8f, 0x6b));
|
||||
}
|
||||
|
||||
TEST_F(LibSLACUnitTest, test_generate_nid_from_nmk) {
|
||||
uint8_t nid[slac::defs::NID_LEN] = {0};
|
||||
const uint8_t sample_nmk[] = {0x34, 0x52, 0x23, 0x54, 0x45, 0xae, 0xf2, 0xd4,
|
||||
0x55, 0xfe, 0xff, 0x31, 0xa3, 0xb3, 0x03, 0xad};
|
||||
|
||||
slac::utils::generate_nid_from_nmk(nid, sample_nmk);
|
||||
ASSERT_THAT(nid, testing::ElementsAre(0xb1, 0xc9, 0x13, 0xb5, 0xaa, 0x07, 0x02));
|
||||
}
|
||||
|
||||
TEST_F(LibSLACUnitTest, test_generate_nid_from_nmk_check_security_bits) {
|
||||
uint8_t nid[slac::defs::NID_LEN] = {0};
|
||||
const uint8_t sample_nmk[] = {0x34, 0x52, 0x23, 0x54, 0x45, 0xae, 0xf2, 0xd4,
|
||||
0x55, 0xfe, 0xff, 0x31, 0xa3, 0xb3, 0x03, 0xad};
|
||||
|
||||
slac::utils::generate_nid_from_nmk(nid, sample_nmk);
|
||||
ASSERT_TRUE((nid[6] >> 4) == 0x00);
|
||||
}
|
||||
|
||||
TEST_F(LibSLACUnitTest, test_setup_payload) {
|
||||
slac::messages::cm_set_key_cnf set_key_cnf;
|
||||
slac::messages::HomeplugMessage message;
|
||||
ASSERT_THROW(message.setup_payload(&set_key_cnf, 2000, // this is way too large
|
||||
(slac::defs::MMTYPE_CM_SET_KEY | slac::defs::MMTYPE_MODE_CNF),
|
||||
slac::defs::MMV::AV_1_1),
|
||||
std::runtime_error);
|
||||
}
|
||||
} // namespace libslac
|
||||
Reference in New Issue
Block a user