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 @@
add_subdirectory(ieee2030)

View File

@@ -0,0 +1,43 @@
find_package(Threads REQUIRED)
add_library(ieee2030)
add_library(ieee2030::ieee2030 ALIAS ieee2030)
target_sources(ieee2030
PRIVATE
# Common
common/io/logging.cpp
common/io/time.cpp
common/messages/messages.cpp
# Charger
charger/charger_controller.cpp
charger/session/callback.cpp
charger/session/session.cpp
charger/v20/context.cpp
charger/v20/state/state_b.cpp
charger/v20/state/state_c.cpp
charger/io/can_broker_charger.cpp
# EV
ev/io/can_broker_ev.cpp
)
target_include_directories(ieee2030
PUBLIC
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
)
target_link_libraries(ieee2030
PUBLIC
everest::util
PRIVATE
Threads::Threads
)
target_compile_features(ieee2030 PUBLIC cxx_std_17)

View File

@@ -0,0 +1,87 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ieee2030/charger/charger_controller.hpp>
#include <chrono>
#include <memory>
#include <thread>
#include <ieee2030/charger/io/can_broker_charger.hpp>
namespace ieee2030::charger {
Controller::Controller(std::string can_interface_, callback::Callbacks callbacks_) :
can_interface(can_interface_), callbacks(std::move(callbacks_)) {
}
void Controller::start_session(bool welding_detection, float available_voltage, float available_current,
float threshold_voltage, defs::ProtocolNumber protocol) {
static constexpr auto SESSION_UPDATE_TIMEOUT_MS = 25;
auto can_broker = std::make_unique<io::CanBrokerCharger>(can_interface);
can_broker->init_charger_messages(welding_detection, available_voltage, available_current, threshold_voltage,
protocol);
auto session = Session(std::move(can_broker), callbacks);
while (session.is_session_active()) {
std::vector<events::Event> current_events;
while (auto event = event_queue.pop()) {
current_events.push_back(event.value());
}
session.update(current_events);
std::this_thread::sleep_for(std::chrono::milliseconds(SESSION_UPDATE_TIMEOUT_MS));
}
}
// Hardware signals
void Controller::update_hw_signal(HwSignal signal, bool status) {
switch (signal) {
case HwSignal::CS1:
event_queue.push(events::CS1{status});
break;
case HwSignal::CS2:
event_queue.push(events::CS2{status});
break;
case HwSignal::PROX:
event_queue.push(events::ProximityDetection{status});
break;
case HwSignal::CHARGE_PERMISSION:
event_queue.push(events::ChargePermission{status});
break;
}
}
// Update physical values
void Controller::update_isolation_status(events::IsolationStatus status) {
event_queue.push(status);
}
void Controller::update_present_voltage_current(float voltage, float current) {
event_queue.push(events::PresentVoltageCurrent{voltage, current});
}
void Controller::update_available_voltage_current(float voltage, float current) {
event_queue.push(events::AvailableVoltageCurrent{voltage, current});
}
// Events
void Controller::cable_check_finished() {
event_queue.push(events::CableCheckFinished{true});
}
void Controller::stop() {
event_queue.push(events::StopCharging{true});
}
// Errors
void Controller::send_error() {
}
void Controller::reset_errors() {
}
} // namespace ieee2030::charger

View File

@@ -0,0 +1,211 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ieee2030/charger/io/can_broker_charger.hpp>
#include <ieee2030/common/io/time.hpp>
#include <cstring>
#include <linux/can.h>
#include <net/if.h>
#include <poll.h>
#include <stdexcept>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
// Todo: should be in a helper file
static void throw_with_error(const std::string& msg) {
throw std::runtime_error(msg + ": (" + std::string(strerror(errno)) + ")");
}
namespace ieee2030::charger::io {
CanBrokerCharger::CanBrokerCharger() {
}
CanBrokerCharger::CanBrokerCharger(const std::string& interface_name) : tx_active(false), rx_active{false} {
can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (can_fd == -1) {
throw_with_error("Failed to open socket");
}
// retrieve interface index from interface name
struct ifreq ifr;
if (interface_name.size() >= sizeof(ifr.ifr_name)) {
throw_with_error("Interface name too long: " + interface_name);
} else {
strcpy(ifr.ifr_name, interface_name.c_str());
}
if (ioctl(can_fd, SIOCGIFINDEX, &ifr) == -1) {
throw_with_error("Failed with ioctl/SIOCGIFINDEX on interface " + interface_name);
}
// bind to the interface
struct sockaddr_can addr;
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(can_fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1) {
throw_with_error("Failed with bind");
}
rx_loop_thread = std::thread(&CanBrokerCharger::rx_loop, this);
tx_loop_thread = std::thread(&CanBrokerCharger::tx_loop, this);
}
CanBrokerCharger::~CanBrokerCharger() {
tx_active = false;
rx_active = false;
exit_rx_loop = true;
exit_tx_loop = true;
rx_loop_thread.join();
tx_loop_thread.join();
close(can_fd);
}
void CanBrokerCharger::set_event_callback(const CanEventCallback& callback) {
event_callback = callback;
}
void CanBrokerCharger::rx_loop() {
struct pollfd pfds = {can_fd, POLLIN, 0};
while (!exit_rx_loop) {
const auto poll_result = poll(&pfds, 1, 1);
if (poll_result == 0) {
continue;
}
if (pfds.revents & POLLIN) {
struct can_frame frame;
read(can_fd, &frame, sizeof(frame));
std::vector<uint8_t> payload;
payload.assign(frame.data, frame.data + frame.can_dlc);
handle_can_input(frame.can_id, payload);
}
}
}
void CanBrokerCharger::handle_can_input(uint32_t can_id, const std::vector<uint8_t>& payload) {
if (!(can_id == messages::EV_ID_100 || can_id == messages::EV_ID_101 || can_id == messages::EV_ID_102)) {
// Todo: Check standard if a error is provided
return;
}
// Todo: How to detect ev can is active -> Active means the first 100, 101, 102 are received?
// Todo: How to detect ev can is not active? -> Receiving nothing over a certain time?
// Todo: Check if correct sequence 100 -> 101 -> 102 -> 100 ...
// Todo: Check Timer for message 100, 101, 102
if (can_id == messages::EV_ID_100) {
message_100 = messages::EV100(payload);
} else if (can_id == messages::EV_ID_101) {
message_101 = messages::EV101(payload);
} else if (can_id == messages::EV_ID_102) {
message_102 = messages::EV102(payload);
}
publish_event(CanEvent::NEW_DATA);
}
void CanBrokerCharger::tx_loop() {
static constexpr auto SEND_CAN_TIMEOUT_MS = 100;
auto actual_time_108 = ieee2030::io::get_current_time_point();
auto actual_time_109 = ieee2030::io::get_current_time_point();
while (!exit_tx_loop) {
if (tx_active) {
switch (tx_state) {
case SendState::ID_108:
if (ieee2030::io::get_current_time_point() >=
ieee2030::io::offset_time_point_by_ms(actual_time_108, SEND_CAN_TIMEOUT_MS)) {
send(messages::CHARGER_ID_108, message_108);
actual_time_108 = ieee2030::io::get_current_time_point();
tx_state = SendState::ID_109;
}
break;
case SendState::ID_109:
if (ieee2030::io::get_current_time_point() >=
ieee2030::io::offset_time_point_by_ms(actual_time_109, SEND_CAN_TIMEOUT_MS)) {
send(messages::CHARGER_ID_109, message_109);
actual_time_109 = ieee2030::io::get_current_time_point();
tx_state = SendState::ID_108;
}
break;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
void CanBrokerCharger::send(uint32_t id, const std::vector<uint8_t>& payload) {
struct can_frame frame;
// Check payload length
if (payload.size() > sizeof(frame.data)) {
throw_with_error("Payload data length is too large");
}
frame.can_id = id;
frame.can_dlc = payload.size();
memcpy(frame.data, payload.data(), payload.size());
const auto wrote_bytes = write(can_fd, &frame, sizeof(frame));
if (wrote_bytes != sizeof(frame)) {
throw_with_error("Failed to send can packet!");
}
}
void CanBrokerCharger::update_status_error_flag(defs::ChargerStatusError status, bool active) {
switch (status) {
case defs::ChargerStatusError::CHARGER_STATUS:
message_109.charger_status = active;
break;
case defs::ChargerStatusError::CHARGER_MALFUNCTION:
message_109.charger_malfunction = active;
break;
case defs::ChargerStatusError::CONNECTOR_LOCK:
message_109.connector_lock = active;
break;
case defs::ChargerStatusError::BATTERY_INCOMPATIBILITY:
message_109.battery_incompatibility = active;
break;
case defs::ChargerStatusError::SYSTEM_MALFUNCTION:
message_109.system_malfunction = active;
break;
case defs::ChargerStatusError::STOP_CONTROL:
message_109.stop_control = active;
break;
}
}
void CanBrokerCharger::update_reamining_time_10s(uint16_t time_10s) {
if (time_10s < 0xFF) {
message_109.reamining_time_10s = (uint8_t)time_10s;
message_109.reamining_time_1min = 0;
} else {
message_109.reamining_time_10s = 0xFF;
message_109.reamining_time_1min = (uint8_t)(time_10s / 6);
}
}
} // namespace ieee2030::charger::io

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ieee2030/charger/session/callback.hpp>
#include <ieee2030/common/detail/helper.hpp>
namespace ieee2030::charger {
Callback::Callback(callback::Callbacks callbacks_) : callbacks(std::move(callbacks_)) {
}
void Callback::signal(callback::Signal signal) const {
call_if_available(callbacks.signal, signal);
}
void Callback::hw_signal(const callback::HwSignal& hw_signal) const {
call_if_available(callbacks.hw_signal, hw_signal);
}
} // namespace ieee2030::charger

View File

@@ -0,0 +1,94 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2023 - 2026 Pionix GmbH and Contributors to EVerest
#include <ieee2030/charger/session/session.hpp>
#include <ieee2030/charger/v20/state/state_b.hpp>
namespace ieee2030::charger {
Session::Session(std::unique_ptr<io::CanBrokerCharger> can_broker_, const callback::Callbacks& callbacks) :
can_broker(std::move(can_broker_)),
session_is_active(true),
ctx(active_event, callbacks, message_100, message_101, message_102),
fsm(ctx.create_state<v20::state::StateB>()) {
can_broker->set_event_callback([this](io::CanEvent event) { this->handle_can_event(event); });
v20::CanBrokerContext can_broker_callbacks;
can_broker_callbacks.enable_can = [this] { can_broker->enable_tx_can(); };
can_broker_callbacks.disable_can = [this] { can_broker->disable_tx_can(); };
can_broker_callbacks.update_status_error = [this](defs::ChargerStatusError status, bool active) {
can_broker->update_status_error_flag(status, active);
};
can_broker_callbacks.update_reamining_time = [this](uint16_t seconds) {
const auto time_10s = static_cast<uint16_t>(seconds / 10);
can_broker->update_reamining_time_10s(time_10s);
};
ctx.set_can_broker_callback(can_broker_callbacks);
}
Session::~Session() = default;
void Session::update(const std::vector<events::Event>& current_events) {
// Handle all events before state machine
if (!current_events.empty()) {
for (auto event : current_events) {
if (std::holds_alternative<events::PresentVoltageCurrent>(event)) {
const auto& present_values = std::get<events::PresentVoltageCurrent>(event);
can_broker->update_present_voltage(present_values.voltage);
can_broker->update_present_current(present_values.current);
} else if (std::holds_alternative<events::AvailableVoltageCurrent>(event)) {
const auto& available_values = std::get<events::AvailableVoltageCurrent>(event);
can_broker->update_available_voltage(available_values.voltage);
can_broker->update_available_current(available_values.current);
} else {
active_event = event;
if (std::holds_alternative<events::CS1>(event) || std::holds_alternative<events::CS2>(event) ||
std::holds_alternative<events::ProximityDetection>(event) ||
std::holds_alternative<events::ChargePermission>(event)) {
// Todo(sl): check result!
[[maybe_unused]] const auto res = fsm.feed(v20::Event::HW_SIGNAL);
} else {
// Todo(sl): check result!
[[maybe_unused]] const auto res = fsm.feed(v20::Event::EVENT);
}
}
}
}
if (auto timeout_reached = ctx.timeout.timeout_reached()) {
if (*timeout_reached) {
// Todo(sl): check result!
[[maybe_unused]] const auto res = fsm.feed(v20::Event::TIMEOUT);
}
}
if (state.new_data) {
message_100 = can_broker->get_can_100_message();
message_101 = can_broker->get_can_101_message();
message_102 = can_broker->get_can_102_message();
// Todo(sl): Check return value
[[maybe_unused]] const auto res = fsm.feed(v20::Event::CAN_MESSAGE);
}
}
void Session::handle_can_event(io::CanEvent event) {
using Event = io::CanEvent;
switch (event) {
case Event::ACTIVE:
state.can_active = true;
break;
case Event::INACTIVE:
state.can_active = false;
break;
case Event::NEW_DATA:
state.new_data = true;
break;
}
};
} // namespace ieee2030::charger

View File

@@ -0,0 +1,17 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ieee2030/charger/v20/context.hpp>
namespace ieee2030::charger::v20 {
Context::Context(const std::optional<events::Event>& current_event_, callback::Callbacks callbacks_,
const messages::EV100& message_100_, const messages::EV101& message_101_,
const messages::EV102& message_102_) :
current_event{current_event_},
callbacks(std::move(callbacks_)),
message_100(message_100_),
message_101(message_101_),
message_102(message_102_) {
}
} // namespace ieee2030::charger::v20

View File

@@ -0,0 +1,40 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ieee2030/charger/v20/state/state_b.hpp>
#include <ieee2030/charger/v20/state/state_c.hpp>
namespace ieee2030::charger::v20::state {
void StateB::enter() {
// ctx.log.enter_state("State B");
m_ctx.callbacks.hw_signal({callback::ChargerSequence::CS1, callback::Status::ON});
m_ctx.timeout.start(6);
// Todo: Start second timeout
}
Result StateB::feed(Event ev) {
if (ev == Event::CAN_MESSAGE) {
return m_ctx.create_state<StateC>();
} else if (ev == Event::TIMEOUT) {
// Todo: Adding Log -> Timeout error
stop = true;
} else if (ev == Event::EVENT) {
if (const auto event = m_ctx.get_event<events::StopCharging>()) {
if (*event) {
// Todo: Adding Log -> Stop button is pressed
stop = true;
}
}
}
if (stop) {
m_ctx.callbacks.hw_signal({callback::ChargerSequence::CS1, callback::Status::OFF});
// Todo: Stop the session
return {};
}
return {};
}
} // namespace ieee2030::charger::v20::state

View File

@@ -0,0 +1,83 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ieee2030/charger/detail/v20/state/state_c.hpp>
#include <ieee2030/charger/v20/state/state_c.hpp>
namespace ieee2030::charger::v20::state {
bool state_c_1(const messages::EV100& ev_100, const messages::EV101& ev_101, const messages::EV102& ev_102) {
// Check ev messages if sended
// If yes, check Battery comp check, calculate output current
// If yes, start charger can
// Start Timeout after starting charger can (From cs1 = on)
return false;
};
bool state_c_2(const messages::EV102& ev_102) {
// Start Reaming Time Check task
// Check HW Charge Permission Signal
// Check 102.5.0 == 1
return false;
};
void StateC::enter() {
// ctx.log.enter_state("State C");
}
Result StateC::feed(Event ev) {
if (ev == Event::CAN_MESSAGE) {
ev_100 = m_ctx.message_100;
ev_101 = m_ctx.message_101;
ev_102 = m_ctx.message_102;
} else if (ev == Event::TIMEOUT) {
// Todo: Adding Log -> Timeout error
stop = true;
} else if (ev == Event::EVENT) {
if (const auto event = m_ctx.get_event<events::StopCharging>()) {
if (*event) {
// Todo: Adding Log -> Stop button is pressed
stop = true;
}
}
}
// Todo: Define in a separat file tasks.cpp in state
// tasks::monitoring(); -> Check ev can errors
// tasks::battery_compability_check();
// tasks::calculation_remaming_time();
// Todo: Adding Monitoring for every main state
// Todo: Start Battery Compability Check until 102.3 > 0
// Todo: Start Calculation remaming charging time untule 102.3 > 0
switch (states) {
case state_c::InternalStates::C_1:
if (state_c_1(ev_100, ev_101, ev_102)) {
states = state_c::InternalStates::C_2;
}
break;
case state_c::InternalStates::C_2:
if (state_c_2(ev_102)) {
// return sa.create_simple<StateD>(m_ctx); // Todo: Adding state D
}
break;
}
if (stop) {
// Todo(sl): How to stop in state C
m_ctx.callbacks.hw_signal({callback::ChargerSequence::CS1, callback::Status::OFF});
// Todo(sl): Stopping the session
return {};
}
return {};
}
} // namespace ieee2030::charger::v20::state

View File

@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ieee2030/common/io/logging.hpp>
#include <cstdarg>
#include <iostream>
static std::function<void(std::string)> logging_callback = [](const std::string& msg) { std::cout << msg; };
namespace ieee2030 {
void log(const std::string& msg) {
logging_callback(msg);
}
void vlogf(const char* fmt, va_list ap) {
static constexpr auto MAX_FMT_LOG_BUFSIZE = 1024;
char msg_buf[MAX_FMT_LOG_BUFSIZE];
vsnprintf(msg_buf, MAX_FMT_LOG_BUFSIZE, fmt, ap);
log(msg_buf);
}
void logf(const char* fmt, ...) {
va_list args;
va_start(args, fmt);
vlogf(fmt, args);
va_end(args);
}
namespace io {
void set_logging_callback(const std::function<void(std::string)>& callback) {
logging_callback = callback;
}
} // namespace io
} // namespace ieee2030

View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ieee2030/common/io/time.hpp>
#include <cmath>
namespace ieee2030::io {
void Timeout::start(float timeout_s) {
timeout_point =
std::chrono::steady_clock::now() + std::chrono::seconds(static_cast<int64_t>(std::round(timeout_s)));
}
void Timeout::reset() {
timeout_point.reset();
}
std::optional<bool> Timeout::timeout_reached() {
if (!timeout_point.has_value()) {
return std::nullopt;
}
if (get_current_time_point() >= timeout_point.value()) {
return true;
}
return false;
}
} // namespace ieee2030::io

View File

@@ -0,0 +1,191 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2022 - 2026 Pionix GmbH and Contributors to EVerest
#include <iostream>
#include <ieee2030/common/detail/conversions.hpp>
#include <ieee2030/common/messages/messages.hpp>
namespace ieee2030::messages {
EV100::EV100(const std::vector<uint8_t> raw) {
max_battery_voltage = static_cast<float>(from_raw<uint16_t>(raw, 4));
charged_rate = from_raw<uint8_t>(raw, 6);
}
std::ostream& operator<<(std::ostream& out, const EV100& self) {
out << "Maximum Battery Voltage: " << self.max_battery_voltage << "Charged Rate: " << self.charged_rate;
return out;
}
EV100::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0), data);
to_raw(static_cast<uint8_t>(0), data);
to_raw(static_cast<uint8_t>(0), data);
to_raw(static_cast<uint8_t>(0), data);
to_raw(static_cast<uint16_t>(max_battery_voltage), data);
to_raw(static_cast<uint8_t>(charged_rate), data);
to_raw(static_cast<uint8_t>(0), data);
return data;
}
EV101::EV101(const std::vector<uint8_t> raw) {
max_charging_time_10s = from_raw<uint8_t>(raw, 1);
max_charging_time_1min = from_raw<uint8_t>(raw, 2);
estimated_charging_time_1min = from_raw<uint8_t>(raw, 3);
float capacity = static_cast<float>(from_raw<uint16_t>(raw, 5));
if (capacity > 0) {
total_capacity = capacity;
}
}
std::ostream& operator<<(std::ostream& out, const EV101& self) {
out << "EV Msg 101: Maximum Charging Time (10s): " << self.max_charging_time_10s
<< "Maximum Charging Time (1min): " << self.max_charging_time_1min
<< "Estimated Charging Time: " << self.estimated_charging_time_1min
<< "Total Capacity: " << self.total_capacity.value_or(0);
return out;
}
EV101::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0), data);
to_raw(static_cast<uint8_t>(max_charging_time_10s), data);
to_raw(static_cast<uint8_t>(max_charging_time_1min), data);
to_raw(static_cast<uint8_t>(estimated_charging_time_1min), data);
to_raw(static_cast<uint8_t>(0), data);
to_raw(static_cast<uint16_t>(total_capacity.value_or(0)), data);
to_raw(static_cast<uint8_t>(0), data);
return data;
}
EV102::EV102(const std::vector<uint8_t> raw) {
protocol = static_cast<defs::ProtocolNumber>(from_raw<uint8_t>(raw, 0));
target_voltage = static_cast<float>(from_raw<uint16_t>(raw, 1));
target_current = static_cast<float>(from_raw<uint8_t>(raw, 3));
uint8_t fault = from_raw<uint8_t>(raw, 4);
fault_battery_over_voltage = fault & (1 << 0);
fault_battery_under_voltage = fault & (1 << 1);
fault_battery_current_deviation_error = fault & (1 << 2);
fault_high_battery_temperature = fault & (1 << 3);
fault_battery_voltage_deviation_error = fault & (1 << 4);
uint8_t status = from_raw<uint8_t>(raw, 5);
status_charging_enabled = status & (1 << 0);
status_shift_position = status & (1 << 1);
status_system_fault = status & (1 << 2);
status_vehicle_status = status & (1 << 3);
status_stop_request = status & (1 << 4);
soc = from_raw<uint8_t>(raw, 6);
}
std::ostream& operator<<(std::ostream& out, const EV102& self) {
out << "EV Msg 102: Protocol: " << static_cast<uint8_t>(self.protocol)
<< "Target Voltage [V]: " << self.target_voltage << "Target Current [A]: " << self.target_current
<< "SoC [%]: " << self.soc << "Fault battery over voltage: " << self.fault_battery_under_voltage
<< "Fault battery under voltage: " << self.fault_battery_under_voltage
<< "Fault battery current deviation error: " << self.fault_battery_current_deviation_error
<< "Fault high battery temp: " << self.fault_high_battery_temperature
<< "Fault battery voltage deviation error: " << self.fault_battery_voltage_deviation_error
<< "Status charging enabled: " << self.status_charging_enabled
<< "Status shift position: " << self.status_shift_position
<< "Status system fault: " << self.status_system_fault
<< "Status vehicle status: " << self.status_vehicle_status
<< "Status stop request: " << self.status_stop_request;
return out;
}
EV102::operator std::vector<uint8_t, std::allocator<uint8_t>>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(protocol), data);
to_raw(static_cast<uint16_t>(target_voltage), data);
to_raw(static_cast<uint8_t>(target_current), data);
uint8_t fault = fault_battery_voltage_deviation_error << 4 | fault_high_battery_temperature << 3 |
fault_battery_current_deviation_error << 2 | fault_battery_under_voltage << 1 |
fault_battery_over_voltage;
to_raw(static_cast<uint8_t>(fault), data);
uint8_t status = status_stop_request << 4 | status_vehicle_status << 3 | status_system_fault << 2 |
status_shift_position << 1 | status_charging_enabled;
to_raw(static_cast<uint8_t>(status), data);
to_raw(static_cast<uint8_t>(soc), data);
to_raw(static_cast<uint8_t>(0), data);
return data;
}
Charger108::Charger108(const std::vector<uint8_t> raw) {
identifier_welding_detection = from_raw<uint8_t>(raw, 0);
available_voltage = static_cast<float>(from_raw<uint16_t>(raw, 1));
available_current = static_cast<float>(from_raw<uint8_t>(raw, 3));
threshold_voltage = static_cast<float>(from_raw<uint16_t>(raw, 4));
}
std::ostream& operator<<(std::ostream& out, const Charger108& self) {
out << "Charger Msg 108: Identifier welding detection: " << self.identifier_welding_detection
<< "Available Voltage [V]: " << self.available_voltage << "Available Current [A]: " << self.available_current
<< "Threshold voltage [V]: " << self.threshold_voltage;
return out;
}
Charger108::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(identifier_welding_detection), data);
to_raw(static_cast<uint16_t>(available_voltage), data);
to_raw(static_cast<uint8_t>(available_current), data);
to_raw(static_cast<uint16_t>(threshold_voltage), data);
to_raw(static_cast<uint16_t>(0), data);
return data;
}
Charger109::Charger109(const std::vector<uint8_t> raw) {
protocol = static_cast<defs::ProtocolNumber>(from_raw<uint8_t>(raw, 0));
present_voltage = static_cast<float>(from_raw<uint16_t>(raw, 1));
present_current = static_cast<float>(from_raw<uint8_t>(raw, 3));
uint8_t status = from_raw<uint8_t>(raw, 5);
charger_status = status & (1 << 0);
charger_malfunction = status & (1 << 1);
connector_lock = status & (1 << 2);
battery_incompatibility = status & (1 << 3);
system_malfunction = status & (1 << 4);
stop_control = status & (1 << 5);
reamining_time_10s = from_raw<uint8_t>(raw, 6);
reamining_time_1min = from_raw<uint8_t>(raw, 7);
}
std::ostream& operator<<(std::ostream& out, const Charger109& self) {
out << "Charger Msg 109: Protocol: " << static_cast<uint8_t>(self.protocol)
<< "Present Voltage [V]: " << self.present_voltage << "Present Current [A]: " << self.present_current
<< "Reamining time [10s]: " << self.reamining_time_10s << "Reamining time [1min]: " << self.reamining_time_1min
<< "Charger status: " << self.charger_status << "Charger malfunction: " << self.charger_malfunction
<< "Connector lock: " << self.connector_lock << "Battery incompatibility: " << self.battery_incompatibility
<< "System malfunction: " << self.system_malfunction << "Stop control: " << self.stop_control;
return out;
}
Charger109::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(protocol), data);
to_raw(static_cast<uint16_t>(present_voltage), data);
to_raw(static_cast<uint8_t>(present_current), data);
to_raw(static_cast<uint8_t>(0), data);
uint8_t status = stop_control << 5 | system_malfunction << 4 | battery_incompatibility << 3 | connector_lock << 2 |
charger_malfunction << 1 | charger_status;
to_raw(static_cast<uint8_t>(status), data);
to_raw(static_cast<uint8_t>(reamining_time_10s), data);
to_raw(static_cast<uint8_t>(reamining_time_1min), data);
return data;
}
} // namespace ieee2030::messages

View File

@@ -0,0 +1,184 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include <ieee2030/common/io/time.hpp>
#include <ieee2030/ev/io/can_broker_ev.hpp>
#include <cstring>
#include <linux/can.h>
#include <net/if.h>
#include <poll.h>
#include <stdexcept>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
// Todo(sl): should be in a helper file
static void throw_with_error(const std::string& msg) {
throw std::runtime_error(msg + ": (" + std::string(strerror(errno)) + ")");
}
namespace ieee2030::ev::io {
CanBrokerEv::CanBrokerEv(const std::string& interface_name) : tx_active(false), rx_active{false} {
can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
if (can_fd == -1) {
throw_with_error("Failed to open socket");
}
// retrieve interface index from interface name
struct ifreq ifr;
if (interface_name.size() >= sizeof(ifr.ifr_name)) {
throw_with_error("Interface name too long: " + interface_name);
} else {
strcpy(ifr.ifr_name, interface_name.c_str());
}
if (ioctl(can_fd, SIOCGIFINDEX, &ifr) == -1) {
throw_with_error("Failed with ioctl/SIOCGIFINDEX on interface " + interface_name);
}
// bind to the interface
struct sockaddr_can addr;
memset(&addr, 0, sizeof(addr));
addr.can_family = AF_CAN;
addr.can_ifindex = ifr.ifr_ifindex;
if (bind(can_fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1) {
throw_with_error("Failed with bind");
}
rx_loop_thread = std::thread(&CanBrokerEv::rx_loop, this);
tx_loop_thread = std::thread(&CanBrokerEv::tx_loop, this);
}
CanBrokerEv::~CanBrokerEv() {
tx_active = false;
rx_active = false;
exit_rx_loop = true;
exit_tx_loop = true;
rx_loop_thread.join();
tx_loop_thread.join();
close(can_fd);
}
void CanBrokerEv::set_event_callback(const CanEventCallback& callback) {
event_callback = callback;
}
void CanBrokerEv::rx_loop() {
struct pollfd pfds = {can_fd, POLLIN, 0};
while (!exit_rx_loop) {
const auto poll_result = poll(&pfds, 1, 1);
if (poll_result == 0) {
continue;
}
if (pfds.revents & POLLIN) {
struct can_frame frame;
read(can_fd, &frame, sizeof(frame));
std::vector<uint8_t> payload;
payload.assign(frame.data, frame.data + frame.can_dlc);
handle_can_input(frame.can_id, payload);
}
}
}
void CanBrokerEv::handle_can_input(uint32_t can_id, const std::vector<uint8_t>& payload) {
if (!(can_id == messages::CHARGER_ID_108 || can_id == messages::CHARGER_ID_109)) {
// Todo(sl): Check standard if a error is provided
// Todo(sl): What is with the extended can ids (v1.2, 2.0, 3,1 and V2H)?
return;
}
// Todo(sl): send can charger is active event -> Active means the first 108, 109 are received?
// Todo(sl): How to detect charger can is shutdown? -> Receiving nothing over a certain time?
// Todo(sl): Check if correct sequence 108 -> 109 -> 108 ...
// Todo(sl): Check Timer for message 108, 109
if (can_id == messages::CHARGER_ID_108) {
message_108 = messages::Charger108(payload);
} else if (can_id == messages::CHARGER_ID_109) {
message_109 = messages::Charger109(payload);
}
publish_event(CanEvent::NEW_DATA);
}
void CanBrokerEv::tx_loop() {
static constexpr auto SEND_CAN_TIMEOUT_MS = 100;
auto actual_time_100 = ieee2030::io::get_current_time_point();
auto actual_time_101 = ieee2030::io::get_current_time_point();
auto actual_time_102 = ieee2030::io::get_current_time_point();
while (!exit_tx_loop) {
if (tx_active) {
switch (tx_state) {
case SendState::ID_100:
if (ieee2030::io::get_current_time_point() >=
ieee2030::io::offset_time_point_by_ms(actual_time_100, SEND_CAN_TIMEOUT_MS)) {
send(messages::EV_ID_100, message_100);
actual_time_100 = ieee2030::io::get_current_time_point();
tx_state = SendState::ID_101;
}
break;
case SendState::ID_101:
if (ieee2030::io::get_current_time_point() >=
ieee2030::io::offset_time_point_by_ms(actual_time_101, SEND_CAN_TIMEOUT_MS)) {
send(messages::EV_ID_101, message_101);
actual_time_101 = ieee2030::io::get_current_time_point();
tx_state = SendState::ID_101;
}
break;
case SendState::ID_102:
if (ieee2030::io::get_current_time_point() >=
ieee2030::io::offset_time_point_by_ms(actual_time_102, SEND_CAN_TIMEOUT_MS)) {
send(messages::EV_ID_100, message_102);
actual_time_102 = ieee2030::io::get_current_time_point();
tx_state = SendState::ID_102;
}
break;
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
void CanBrokerEv::send(uint32_t id, const std::vector<uint8_t>& payload) {
struct can_frame frame;
// Check payload length
if (payload.size() > sizeof(frame.data)) {
throw_with_error("Payload data length is too large");
}
frame.can_id = id;
frame.can_dlc = payload.size();
memcpy(frame.data, payload.data(), payload.size());
const auto wrote_bytes = write(can_fd, &frame, sizeof(frame));
if (wrote_bytes != sizeof(frame)) {
throw_with_error("Failed to send can packet!");
}
}
} // namespace ieee2030::ev::io