Skip to content

Commit

Permalink
- added reduced state machine for connector 0 for 1.6
Browse files Browse the repository at this point in the history
- chargepoint can now be initialized with initial chargepoint states ; if no states are provided, last states from persistant storage will be used to initialize the state machine
- reset of state machine is now public

Signed-off-by: pietfried <pietgoempel@gmail.com>
  • Loading branch information
Pietfried committed Sep 7, 2023
1 parent 3360cd6 commit b7d16fa
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 51 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.14)

project(ocpp
VERSION 0.8.7
VERSION 0.8.8
DESCRIPTION "A C++ implementation of the Open Charge Point Protocol"
LANGUAGES CXX
)
Expand Down
11 changes: 10 additions & 1 deletion include/ocpp/v16/charge_point.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,21 @@ class ChargePoint {

/// \brief Starts the ChargePoint, initializes and connects to the Websocket endpoint and initializes a
/// BootNotification.req
bool start();
/// \param connector_status_map initial state of connectors including connector 0 with reduced set of states
/// (Available, Unavailable, Faulted). connector_status_map is empty, last availability states from the persistant
/// storage will be used
/// \return
bool start(std::map<int, ChargePointStatus> connector_status_map = {});

/// \brief Restarts the ChargePoint if it has been stopped before. The ChargePoint is reinitialized, connects to the
/// websocket and starts to communicate OCPP messages again
bool restart();

// \brief Resets the internal state machine for the connectors using the given \p connector_status_map
/// \param connector_status_map state of connectors including connector 0 with reduced set of states (Available,
/// Unavailable, Faulted)
void reset_state_machine(const std::map<int, ChargePointStatus> connector_status_map);

Check warning on line 81 in include/ocpp/v16/charge_point.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/v16/charge_point.hpp#L81

Function parameter 'connector_status_map' should be passed by const reference.

/// \brief Stops the ChargePoint, stops timers, transactions and the message queue and disconnects from the
/// websocket
bool stop();
Expand Down
11 changes: 10 additions & 1 deletion include/ocpp/v16/charge_point_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ class ChargePointImpl : ocpp::ChargingStationBase {
/// \brief This function is called after a successful connection to the Websocket
void connected_callback();
void init_websocket();
void init_state_machine(const std::map<int, ChargePointStatus> connector_status_map);

Check warning on line 177 in include/ocpp/v16/charge_point_impl.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/v16/charge_point_impl.hpp#L177

Function parameter 'connector_status_map' should be passed by const reference.
WebsocketConnectionOptions get_ws_connection_options();
void message_callback(const std::string& message);
void handle_message(const json& json_message, MessageType message_type);
Expand Down Expand Up @@ -321,14 +322,22 @@ class ChargePointImpl : ocpp::ChargingStationBase {

~ChargePointImpl() {
}

/// \brief Starts the ChargePoint, initializes and connects to the Websocket endpoint and initializes a
/// BootNotification.req
bool start();
/// \param connector_status_map initial state of connectors including connector 0 with reduced set of states
/// (Available, Unavailable, Faulted) \return
bool start(const std::map<int, ChargePointStatus> connector_status_map);

Check warning on line 330 in include/ocpp/v16/charge_point_impl.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/v16/charge_point_impl.hpp#L330

Function parameter 'connector_status_map' should be passed by const reference.

/// \brief Restarts the ChargePoint if it has been stopped before. The ChargePoint is reinitialized, connects to the
/// websocket and starts to communicate OCPP messages again
bool restart();

/// \brief Resets the internal state machine for the connectors using the given \p connector_status_map
/// \param connector_status_map state of connectors including connector 0 with reduced set of states (Available,
/// Unavailable, Faulted)
void reset_state_machine(const std::map<int, ChargePointStatus> connector_status_map);

Check warning on line 339 in include/ocpp/v16/charge_point_impl.hpp

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

include/ocpp/v16/charge_point_impl.hpp#L339

Function parameter 'connector_status_map' should be passed by const reference.

/// \brief Stops the ChargePoint, stops timers, transactions and the message queue and disconnects from the
/// websocket
bool stop();
Expand Down
3 changes: 2 additions & 1 deletion include/ocpp/v16/charge_point_state_machine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ class ChargePointStates {
using ConnectorStatusCallback =
std::function<void(int connector_id, ChargePointErrorCode errorCode, ChargePointStatus status)>;
ChargePointStates(const ConnectorStatusCallback& connector_status_callback);
void reset(std::map<int, AvailabilityType> connector_availability);
void reset(std::map<int, ChargePointStatus> connector_status_map);

void submit_event(int connector_id, FSMEvent event);
void submit_fault(int connector_id, const ChargePointErrorCode& error_code);
Expand All @@ -77,6 +77,7 @@ class ChargePointStates {
private:
ConnectorStatusCallback connector_status_callback;

std::unique_ptr<ChargePointFSM> state_machine_connector_zero;
std::vector<ChargePointFSM> state_machines;
std::mutex state_machines_mutex;
};
Expand Down
4 changes: 2 additions & 2 deletions lib/ocpp/v16/charge_point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ ChargePoint::ChargePoint(const std::string& config, const std::filesystem::path&

ChargePoint::~ChargePoint() = default;

bool ChargePoint::start() {
return this->charge_point->start();
bool ChargePoint::start(const std::map<int, ChargePointStatus> connector_status_map) {
return this->charge_point->start(connector_status_map);
}

bool ChargePoint::restart() {
Expand Down
85 changes: 62 additions & 23 deletions lib/ocpp/v16/charge_point_impl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,33 @@ void ChargePointImpl::init_websocket() {

this->websocket->register_message_callback([this](const std::string& message) { this->message_callback(message); });
}
void ChargePointImpl::init_state_machine(const std::map<int, ChargePointStatus> connector_status_map) {
// if connector_status_map empty it retrieves the last availablity states from the database
if (connector_status_map.empty()) {
auto connector_availability = this->database_handler->get_connector_availability();
std::map<int, ChargePointStatus> _connector_status_map;
for (const auto& [connector, availability] : connector_availability) {
if (availability == AvailabilityType::Operative) {
_connector_status_map[connector] = ChargePointStatus::Available;
if (this->enable_evse_callback != nullptr) {
this->enable_evse_callback(connector);
}
} else {
_connector_status_map[connector] = ChargePointStatus::Unavailable;
if (this->disable_evse_callback != nullptr) {
this->disable_evse_callback(connector);
}
}
}
this->status->reset(_connector_status_map);
} else {
if ((size_t)this->configuration->getNumberOfConnectors() + 1 != connector_status_map.size()) {
throw std::runtime_error(
"Number of configured connectors doesn't match number of connectors in the database.");
}
this->status->reset(connector_status_map);
}
}

WebsocketConnectionOptions ChargePointImpl::get_ws_connection_options() {
WebsocketConnectionOptions connection_options{OcppProtocolVersion::v16,
Expand Down Expand Up @@ -684,16 +711,8 @@ void ChargePointImpl::send_meter_value(int32_t connector, MeterValue meter_value
this->send<MeterValuesRequest>(call);
}

bool ChargePointImpl::start() {
auto connector_availability = this->database_handler->get_connector_availability();
connector_availability[0] = AvailabilityType::Operative; // FIXME(kai): fix internal representation in charge
// point states, we need a different kind of state
// machine for connector 0 anyway (with reduced states)
if ((size_t)this->configuration->getNumberOfConnectors() + 1 != connector_availability.size()) {
throw std::runtime_error("Number of configured connectors doesn't match number of connectors in the database.");
}
this->status->reset(connector_availability);

bool ChargePointImpl::start(const std::map<int, ChargePointStatus> connector_status_map) {
this->init_state_machine(connector_status_map);
this->init_websocket();
this->websocket->connect();
this->boot_notification();
Expand All @@ -713,13 +732,24 @@ bool ChargePointImpl::restart() {
this->configuration->getTransactionMessageAttempts(),
this->configuration->getTransactionMessageRetryInterval(), this->external_notify, this->database_handler);
this->initialized = true;
return this->start();

// restart with the states that are currently active
std::map<int, ChargePointStatus> connector_status_map;
for (int i = 0; i <= (size_t)this->configuration->getNumberOfConnectors(); i++) {
connector_status_map.at(i) = this->status->get_state(i);
}

return this->start(connector_status_map);
} else {
EVLOG_warning << "Attempting to restart Chargepoint while it has not been stopped before";
return false;
}
}

void ChargePointImpl::reset_state_machine(const std::map<int, ChargePointStatus> connector_status_map) {
this->status->reset(connector_status_map);
}

void ChargePointImpl::stop_all_transactions() {
this->stop_all_transactions(Reason::Other);
}
Expand Down Expand Up @@ -1079,21 +1109,22 @@ void ChargePointImpl::handleChangeAvailabilityRequest(ocpp::Call<ChangeAvailabil
// connector. is that case this change must be scheduled and we should report an availability status
// of "Scheduled"

std::vector<int32_t> connectors;
std::map<int32_t, AvailabilityStatus> connector_availability_status;

// check if connector exists
if (call.msg.connectorId <= this->configuration->getNumberOfConnectors() && call.msg.connectorId >= 0) {
bool transaction_running = false;

if (call.msg.connectorId == 0) {
connector_availability_status[0] = AvailabilityStatus::Accepted;
int32_t number_of_connectors = this->configuration->getNumberOfConnectors();
for (int32_t connector = 1; connector <= number_of_connectors; connector++) {
if (this->transaction_handler->transaction_active(connector)) {
transaction_running = true;
std::lock_guard<std::mutex> change_availability_lock(change_availability_mutex);
this->change_availability_queue[connector] = call.msg.type;
} else {
connectors.push_back(connector);
connector_availability_status[connector] = AvailabilityStatus::Accepted;
}
}
} else {
Expand All @@ -1102,7 +1133,7 @@ void ChargePointImpl::handleChangeAvailabilityRequest(ocpp::Call<ChangeAvailabil
std::lock_guard<std::mutex> change_availability_lock(change_availability_mutex);
this->change_availability_queue[call.msg.connectorId] = call.msg.type;
} else {
connectors.push_back(call.msg.connectorId);
connector_availability_status[call.msg.connectorId] = AvailabilityStatus::Accepted;
}
}

Expand All @@ -1120,20 +1151,25 @@ void ChargePointImpl::handleChangeAvailabilityRequest(ocpp::Call<ChangeAvailabil
ocpp::CallResult<ChangeAvailabilityResponse> call_result(response, call.uniqueId);
this->send<ChangeAvailabilityResponse>(call_result);

// then execute enabled / disabled callback
if (response.status == AvailabilityStatus::Accepted) {
this->database_handler->insert_or_update_connector_availability(connectors, call.msg.type);
for (auto connector : connectors) {
// if scheduled: execute status transition for connector 0
// if accepted: execute status transition for connectr 0 and other connectors
if (response.status != AvailabilityStatus::Rejected) {
for (const auto& [connector, availability_status] : connector_availability_status) {
this->database_handler->insert_or_update_connector_availability(connector, call.msg.type);
if (call.msg.type == AvailabilityType::Operative) {
if (this->enable_evse_callback != nullptr) {
if (connector == 0) {
this->status->submit_event(0, FSMEvent::BecomeAvailable);
} else if (this->enable_evse_callback != nullptr and
availability_status == AvailabilityStatus::Accepted) {
this->enable_evse_callback(connector);
}
this->status->submit_event(connector, FSMEvent::BecomeAvailable);
} else {
if (this->disable_evse_callback != nullptr) {
if (connector == 0) {
this->status->submit_event(0, FSMEvent::ChangeAvailabilityToUnavailable);
} else if (this->disable_evse_callback != nullptr and
availability_status == AvailabilityStatus::Accepted) {
this->disable_evse_callback(connector);
}
this->status->submit_event(connector, FSMEvent::ChangeAvailabilityToUnavailable);
}
}
}
Expand Down Expand Up @@ -1390,7 +1426,8 @@ void ChargePointImpl::handleRemoteStartTransactionRequest(ocpp::Call<RemoteStart
return;
}
int32_t connector = call.msg.connectorId.value();
if (this->database_handler->get_connector_availability(connector) == AvailabilityType::Inoperative) {
if (this->status->get_state(connector) == ChargePointStatus::Unavailable or
this->status->get_state(connector) == ChargePointStatus::Faulted) {
EVLOG_warning << "Received RemoteStartTransactionRequest for inoperative connector";
response.status = RemoteStartStopStatus::Rejected;
ocpp::CallResult<RemoteStartTransactionResponse> call_result(response, call.uniqueId);
Expand Down Expand Up @@ -3277,10 +3314,12 @@ void ChargePointImpl::on_reservation_end(int32_t connector) {
}

void ChargePointImpl::on_enabled(int32_t connector) {
EVLOG_critical << "On enabled: " << connector;
this->status->submit_event(connector, FSMEvent::BecomeAvailable);
}

void ChargePointImpl::on_disabled(int32_t connector) {
EVLOG_critical << "On disabled: " << connector;
this->status->submit_event(connector, FSMEvent::ChangeAvailabilityToUnavailable);
}

Expand Down
76 changes: 55 additions & 21 deletions lib/ocpp/v16/charge_point_state_machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,23 @@ static const FSMDefinition FSM_DEF = {
}},
};

// special fsm definition for connector 0 wih reduced states
static const FSMDefinition FSM_DEF_CONNECTOR_ZERO = {
{FSMState::Available,
{
{FSMEvent::ChangeAvailabilityToUnavailable, FSMState::Unavailable},
}},
{FSMState::Unavailable,
{
{FSMEvent::BecomeAvailable, FSMState::Available},
}},
{FSMState::Faulted,
{
{FSMEvent::I1_ReturnToAvailable, FSMState::Available},
{FSMEvent::I8_ReturnToUnavailable, FSMState::Unavailable},
}},
};

ChargePointFSM::ChargePointFSM(const StatusNotificationCallback& status_notification_callback_,
FSMState initial_state) :
status_notification_callback(status_notification_callback_),
Expand All @@ -112,7 +129,7 @@ bool ChargePointFSM::handle_event(FSMEvent event) {
state = dest_state_it->second;

if (!faulted) {
status_notification_callback(state, this->error_code);
status_notification_callback(state, ChargePointErrorCode::NoError);
}

return true;
Expand Down Expand Up @@ -143,52 +160,69 @@ bool ChargePointFSM::handle_error(const ChargePointErrorCode& error_code) {
}

ChargePointStates::ChargePointStates(const ConnectorStatusCallback& callback) : connector_status_callback(callback) {
// TODO special state machine for connector 0
}

void ChargePointStates::reset(std::map<int, v16::AvailabilityType> connector_availability) {
void ChargePointStates::reset(std::map<int, ChargePointStatus> connector_status_map) {
const std::lock_guard<std::mutex> lck(state_machines_mutex);
state_machines.clear();

for (size_t connector_id = 0; connector_id < connector_availability.size(); ++connector_id) {
const auto availability = connector_availability.at(connector_id);
const auto initial_state =
(availability == AvailabilityType::Operative) ? FSMState::Available : FSMState::Unavailable;
state_machines.emplace_back(
[this, connector_id](ChargePointStatus status, ChargePointErrorCode error_code) {
this->connector_status_callback(connector_id, error_code, status);
},
initial_state);
for (size_t connector_id = 0; connector_id < connector_status_map.size(); ++connector_id) {
const auto initial_state = connector_status_map.at(connector_id);

if (connector_id == 0 and initial_state != ChargePointStatus::Available and
initial_state != ChargePointStatus::Unavailable and initial_state != ChargePointStatus::Faulted) {
throw std::runtime_error("Invalid initial status for connector 0: " +
conversions::charge_point_status_to_string(initial_state));
} else if (connector_id == 0) {
state_machine_connector_zero = std::make_unique<ChargePointFSM>(
[this](ChargePointStatus status, ChargePointErrorCode error_code) {
this->connector_status_callback(0, error_code, status);
},
initial_state);
} else {
state_machines.emplace_back(
[this, connector_id](ChargePointStatus status, ChargePointErrorCode error_code) {
this->connector_status_callback(connector_id, error_code, status);
},
initial_state);
}
}
}

void ChargePointStates::submit_event(int connector_id, FSMEvent event) {
const std::lock_guard<std::mutex> lck(state_machines_mutex);
if (connector_id > 0 && (size_t)connector_id < this->state_machines.size()) {
this->state_machines.at(connector_id).handle_event(event);

if (connector_id == 0) {
this->state_machine_connector_zero->handle_event(event);
} else if (connector_id > 0 && (size_t)connector_id <= this->state_machines.size()) {
this->state_machines.at(connector_id - 1).handle_event(event);
}
}

void ChargePointStates::submit_fault(int connector_id, const ChargePointErrorCode& error_code) {
const std::lock_guard<std::mutex> lck(state_machines_mutex);
if (connector_id > 0 && (size_t)connector_id < state_machines.size()) {
state_machines.at(connector_id).handle_fault(error_code);
if (connector_id == 0) {
this->state_machine_connector_zero->handle_fault(error_code);
} else if (connector_id > 0 && (size_t)connector_id <= state_machines.size()) {
state_machines.at(connector_id - 1).handle_fault(error_code);
}
}

void ChargePointStates::submit_error(int connector_id, const ChargePointErrorCode& error_code) {
const std::lock_guard<std::mutex> lck(state_machines_mutex);
if (connector_id > 0 && (size_t)connector_id < state_machines.size()) {
state_machines.at(connector_id).handle_error(error_code);
if (connector_id == 0) {
this->state_machine_connector_zero->handle_error(error_code);
} else if (connector_id > 0 && (size_t)connector_id <= state_machines.size()) {
state_machines.at(connector_id - 1).handle_error(error_code);
}
}

ChargePointStatus ChargePointStates::get_state(int connector_id) {
const std::lock_guard<std::mutex> lck(state_machines_mutex);
if (connector_id > 0 && (size_t)connector_id < this->state_machines.size()) {
return state_machines.at(connector_id).get_state();
if (connector_id > 0 && (size_t)connector_id <= this->state_machines.size()) {
return state_machines.at(connector_id - 1).get_state();
} else if (connector_id == 0) {
return ChargePointStatus::Available;
return state_machine_connector_zero->get_state();
}

// fall through on invalid id
Expand Down
2 changes: 1 addition & 1 deletion lib/ocpp/v16/database_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ void DatabaseHandler::run_sql_init() {
}

void DatabaseHandler::init_connector_table(int32_t number_of_connectors) {
for (int32_t connector = 1; connector <= number_of_connectors; connector++) {
for (int32_t connector = 0; connector <= number_of_connectors; connector++) {
std::string sql = "INSERT OR IGNORE INTO CONNECTORS (ID, AVAILABILITY) VALUES (@connector, @availability_type)";
SQLiteStatement stmt(this->db, sql);

Expand Down

0 comments on commit b7d16fa

Please sign in to comment.