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:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View 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)

View 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;
}

View 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;
}

View 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;
}

View 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);
}
}

View File

@@ -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

View File

@@ -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);
}
}
}

View File

@@ -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