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:
@@ -0,0 +1 @@
|
||||
add_subdirectory(ieee2030)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user