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,61 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright chargebyte GmbH and Contributors to EVerest
|
||||
#ifndef JSON_RPC_UTILS_HPP
|
||||
#define JSON_RPC_UTILS_HPP
|
||||
|
||||
#include "../../helpers/LimitDecimalPlaces.hpp"
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <string_view>
|
||||
|
||||
constexpr std::string_view JSON_RPC_SPEC_VERSION{"2.0"};
|
||||
|
||||
namespace json_rpc_utils {
|
||||
|
||||
inline nlohmann::json create_json_rpc_request(const std::string& method, const nlohmann::json& params, int id) {
|
||||
nlohmann::json request;
|
||||
request["jsonrpc"] = JSON_RPC_SPEC_VERSION;
|
||||
request["method"] = method;
|
||||
request["params"] = params;
|
||||
request["id"] = id;
|
||||
return request;
|
||||
}
|
||||
|
||||
inline nlohmann::json create_json_rpc_response(const nlohmann::json& result, int id) {
|
||||
auto tmp = result;
|
||||
nlohmann::json response;
|
||||
response["jsonrpc"] = JSON_RPC_SPEC_VERSION;
|
||||
helpers::round_floats_in_json(tmp);
|
||||
response["result"] = tmp;
|
||||
response["id"] = id;
|
||||
return response;
|
||||
}
|
||||
|
||||
inline nlohmann::json create_json_rpc_error_response(int code, const std::string& message, int id) {
|
||||
nlohmann::json error_response;
|
||||
error_response["jsonrpc"] = JSON_RPC_SPEC_VERSION;
|
||||
error_response["error"]["code"] = code;
|
||||
error_response["error"]["message"] = message;
|
||||
error_response["id"] = id;
|
||||
return error_response;
|
||||
}
|
||||
|
||||
// To check if single key-value pair is part of a JSON object. Key-value pair must be stored in a JSON object.
|
||||
inline bool is_key_value_in_json_rpc_result(const nlohmann::json& json_obj, const nlohmann::json& json_key_value) {
|
||||
if (not json_key_value.is_object()) {
|
||||
throw std::invalid_argument("json_key_value must be a JSON object");
|
||||
}
|
||||
|
||||
// Check if the JSON object contains the key-value pair in the result object of the JSON-RPC response
|
||||
if (json_obj.contains("result") && json_obj["result"].is_object()) {
|
||||
const auto& result_obj = json_obj["result"];
|
||||
if (result_obj.contains(json_key_value.begin().key()) &&
|
||||
result_obj[json_key_value.begin().key()] == json_key_value.begin().value()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace json_rpc_utils
|
||||
|
||||
#endif // JSON_RPC_UTILS_HPP
|
||||
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright chargebyte GmbH and Contributors to EVerest
|
||||
|
||||
#include "RequestHandlerDummy.hpp"
|
||||
|
||||
using namespace types::json_rpc_api;
|
||||
|
||||
RequestHandlerDummy::RequestHandlerDummy(data::DataStoreCharger& dataobj) : data_store(dataobj) {
|
||||
}
|
||||
|
||||
types::json_rpc_api::ErrorResObj RequestHandlerDummy::set_charging_allowed(const int32_t evse_index,
|
||||
bool charging_allowed) {
|
||||
ErrorResObj res{};
|
||||
|
||||
auto evse_store = data_store.get_evse_store(evse_index);
|
||||
evse_store->evsestatus.set_charging_allowed(charging_allowed);
|
||||
return res;
|
||||
}
|
||||
types::json_rpc_api::ErrorResObj RequestHandlerDummy::set_ac_charging(const int32_t evse_index, bool charging_allowed,
|
||||
bool max_current,
|
||||
std::optional<int> phase_count) {
|
||||
types::json_rpc_api::ErrorResObj res{types::json_rpc_api::ResponseErrorEnum::NoError};
|
||||
return res;
|
||||
}
|
||||
types::json_rpc_api::ErrorResObj RequestHandlerDummy::set_ac_charging_current(const int32_t evse_index,
|
||||
float max_current) {
|
||||
ErrorResObj res{};
|
||||
|
||||
auto evse_store = data_store.get_evse_store(evse_index);
|
||||
auto evse_state = evse_store->evsestatus.get_state();
|
||||
|
||||
// Skipping applying limits if charging is not allowed.
|
||||
// In this case, the zero limit is already applied to prevent charging. This value should not be overridden.
|
||||
if (evse_store->evsestatus.get_data()->charging_allowed == false) {
|
||||
res.error = ResponseErrorEnum::NoError;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Wait until the limits are applied or timeout occurs
|
||||
if (evse_store->evsestatus.wait_until_current_limit_applied(max_current, std::chrono::milliseconds(100))) {
|
||||
res.error = ResponseErrorEnum::NoError;
|
||||
} else {
|
||||
res.error = ResponseErrorEnum::ErrorValuesNotApplied;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
types::json_rpc_api::ErrorResObj RequestHandlerDummy::set_ac_charging_phase_count(const int32_t evse_index,
|
||||
int phase_count) {
|
||||
types::json_rpc_api::ErrorResObj res{types::json_rpc_api::ResponseErrorEnum::NoError};
|
||||
return res;
|
||||
}
|
||||
types::json_rpc_api::ErrorResObj RequestHandlerDummy::set_dc_charging(const int32_t evse_index, bool charging_allowed,
|
||||
float max_power) {
|
||||
types::json_rpc_api::ErrorResObj res{types::json_rpc_api::ResponseErrorEnum::NoError};
|
||||
return res;
|
||||
}
|
||||
types::json_rpc_api::ErrorResObj RequestHandlerDummy::set_dc_charging_power(const int32_t evse_index, float max_power) {
|
||||
types::json_rpc_api::ErrorResObj res{types::json_rpc_api::ResponseErrorEnum::NoError};
|
||||
return res;
|
||||
}
|
||||
types::json_rpc_api::ErrorResObj RequestHandlerDummy::enable_connector(const int32_t evse_index, int connector_id,
|
||||
bool enable, int priority) {
|
||||
types::json_rpc_api::ErrorResObj res{types::json_rpc_api::ResponseErrorEnum::NoError};
|
||||
return res;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright chargebyte GmbH and Contributors to EVerest
|
||||
#ifndef REQUESTHANDLERDUMMY_HPP
|
||||
#define REQUESTHANDLERDUMMY_HPP
|
||||
|
||||
#include "../data/DataStore.hpp"
|
||||
|
||||
#include <../rpc/RequestHandlerInterface.hpp>
|
||||
#include <types/json_rpc_api/json_rpc_api.hpp>
|
||||
|
||||
class RequestHandlerDummy : public request_interface::RequestHandlerInterface {
|
||||
public:
|
||||
RequestHandlerDummy() = delete;
|
||||
explicit RequestHandlerDummy(data::DataStoreCharger& data_store);
|
||||
~RequestHandlerDummy() override = default;
|
||||
|
||||
types::json_rpc_api::ErrorResObj set_charging_allowed(const int32_t evse_index, bool charging_allowed) override;
|
||||
types::json_rpc_api::ErrorResObj set_ac_charging(const int32_t evse_index, bool charging_allowed, bool max_current,
|
||||
std::optional<int> phase_count) override;
|
||||
types::json_rpc_api::ErrorResObj set_ac_charging_current(const int32_t evse_index, float max_current) override;
|
||||
types::json_rpc_api::ErrorResObj set_ac_charging_phase_count(const int32_t evse_index, int phase_count) override;
|
||||
types::json_rpc_api::ErrorResObj set_dc_charging(const int32_t evse_index, bool charging_allowed,
|
||||
float max_power) override;
|
||||
types::json_rpc_api::ErrorResObj set_dc_charging_power(const int32_t evse_index, float max_power) override;
|
||||
types::json_rpc_api::ErrorResObj enable_connector(const int32_t evse_index, int connector_id, bool enable,
|
||||
int priority) override;
|
||||
|
||||
private:
|
||||
data::DataStoreCharger& data_store;
|
||||
};
|
||||
|
||||
#endif // REQUESTHANDLERDUMMY_HPP
|
||||
@@ -0,0 +1,180 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright chargebyte GmbH and Contributors to EVerest
|
||||
|
||||
#include "WebSocketTestClient.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "JsonRpcUtils.hpp"
|
||||
|
||||
using namespace json_rpc_utils;
|
||||
|
||||
WebSocketTestClient::WebSocketTestClient(const std::string& address, int port) :
|
||||
m_address(address), m_port(port), m_context(nullptr), m_wsi(nullptr), m_connected(false) {
|
||||
|
||||
struct lws_protocols protocols[] = {{"EVerestRpcApi", callback, 0, 0, 0, NULL, 0}, LWS_PROTOCOL_LIST_TERM};
|
||||
|
||||
struct lws_context_creation_info info;
|
||||
memset(&info, 0, sizeof(info));
|
||||
info.port = CONTEXT_PORT_NO_LISTEN; /* client */
|
||||
info.protocols = protocols;
|
||||
info.gid = -1;
|
||||
info.uid = -1;
|
||||
info.user = this;
|
||||
|
||||
m_context = lws_create_context(&info);
|
||||
if (!m_context) {
|
||||
throw std::runtime_error("Failed to create WebSocket m_context");
|
||||
}
|
||||
|
||||
m_ccinfo.context = m_context;
|
||||
m_ccinfo.address = m_address.c_str();
|
||||
m_ccinfo.port = m_port;
|
||||
m_ccinfo.path = "/";
|
||||
m_ccinfo.host = m_ccinfo.address;
|
||||
m_ccinfo.origin = m_ccinfo.address;
|
||||
m_ccinfo.protocol = "EVerestRpcApi";
|
||||
}
|
||||
|
||||
WebSocketTestClient::~WebSocketTestClient() {
|
||||
close();
|
||||
}
|
||||
|
||||
int WebSocketTestClient::callback(struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in, size_t len) {
|
||||
WebSocketTestClient* client = static_cast<WebSocketTestClient*>(lws_context_user(lws_get_context(wsi)));
|
||||
|
||||
if (client == nullptr) {
|
||||
std::cerr << "Error: WebSocketTestClient instance not found!";
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (reason) {
|
||||
case LWS_CALLBACK_CLIENT_ESTABLISHED:
|
||||
client->m_connected = true;
|
||||
client->m_cv.notify_all();
|
||||
break;
|
||||
case LWS_CALLBACK_CLIENT_RECEIVE: {
|
||||
std::lock_guard<std::mutex> lock(client->m_cv_mutex);
|
||||
try {
|
||||
client->m_received_data.assign(static_cast<char*>(in), len);
|
||||
client->m_cv.notify_all();
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Exception occurred while handling data available: " << e.what();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case LWS_CALLBACK_CLIENT_CLOSED:
|
||||
case LWS_CALLBACK_CLOSED_CLIENT_HTTP: {
|
||||
client->m_connected = false;
|
||||
EVLOG_info << "Client closed connection: " << (in ? static_cast<const char*>(in) : "(null)")
|
||||
<< " reason: " << reason;
|
||||
break;
|
||||
}
|
||||
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
||||
EVLOG_error << "Client connection error: " << (in ? static_cast<const char*>(in) : "(null)");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool WebSocketTestClient::connect() {
|
||||
if (m_context == nullptr) {
|
||||
EVLOG_error << "Error: WebSocket m_context not found!";
|
||||
return false;
|
||||
}
|
||||
stop_lws_service_thread(); // Stop any existing service thread, otherwise the connect will fail
|
||||
|
||||
m_wsi = lws_client_connect_via_info(&m_ccinfo);
|
||||
|
||||
if (m_wsi == nullptr) {
|
||||
EVLOG_error << "Error while connecting to WebSocket server";
|
||||
} else {
|
||||
EVLOG_info << "Connecting to WebSocket server...";
|
||||
start_lws_service_thread();
|
||||
}
|
||||
return m_wsi != nullptr;
|
||||
}
|
||||
|
||||
void WebSocketTestClient::start_lws_service_thread() {
|
||||
if (m_lws_service_running) {
|
||||
return;
|
||||
}
|
||||
m_lws_service_thread = std::thread([this]() {
|
||||
m_lws_service_running = true;
|
||||
while (m_lws_service_running) {
|
||||
lws_service(m_context, 0);
|
||||
}
|
||||
});
|
||||
|
||||
// Wait for the service thread to start
|
||||
while (!m_lws_service_running) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(10)); // Wait for service thread to start
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketTestClient::stop_lws_service_thread() {
|
||||
if (!m_lws_service_running) {
|
||||
return;
|
||||
}
|
||||
m_lws_service_running = false;
|
||||
lws_cancel_service(m_context);
|
||||
if (m_lws_service_thread.joinable()) {
|
||||
m_lws_service_thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
bool WebSocketTestClient::is_connected() {
|
||||
return m_connected;
|
||||
}
|
||||
|
||||
void WebSocketTestClient::send(const std::string& message) {
|
||||
if (!m_connected)
|
||||
return;
|
||||
|
||||
try {
|
||||
std::vector<unsigned char> buf(LWS_PRE + message.size());
|
||||
memcpy(buf.data() + LWS_PRE, message.c_str(), message.size());
|
||||
lws_write(m_wsi, buf.data() + LWS_PRE, message.size(), LWS_WRITE_TEXT);
|
||||
} catch (const std::exception& e) {
|
||||
EVLOG_error << "Error while sending message: " << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& WebSocketTestClient::receive() const {
|
||||
return m_received_data;
|
||||
}
|
||||
|
||||
void WebSocketTestClient::close() {
|
||||
if (m_wsi) {
|
||||
if (m_connected == true) {
|
||||
lws_close_reason(m_wsi, LWS_CLOSE_STATUS_NORMAL, nullptr, 0);
|
||||
}
|
||||
|
||||
if (m_context == nullptr) {
|
||||
EVLOG_error << "Error: WebSocket m_context not found!";
|
||||
return;
|
||||
}
|
||||
|
||||
stop_lws_service_thread();
|
||||
m_wsi = nullptr;
|
||||
|
||||
if (m_lws_service_thread.joinable()) {
|
||||
m_lws_service_thread.join(); // Wait for client thread to finish
|
||||
}
|
||||
}
|
||||
if (m_context) {
|
||||
lws_context_destroy(m_context);
|
||||
m_context = nullptr;
|
||||
}
|
||||
m_connected = false;
|
||||
|
||||
EVLOG_info << "WebSocket client closed";
|
||||
}
|
||||
|
||||
void WebSocketTestClient::send_api_hello_req() {
|
||||
nlohmann::json apiHelloReq = create_json_rpc_request("API.Hello", {}, 1);
|
||||
|
||||
send(apiHelloReq.dump());
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright chargebyte GmbH and Contributors to EVerest
|
||||
#ifndef WEBSOCKETTESTCLIENT_HPP
|
||||
#define WEBSOCKETTESTCLIENT_HPP
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstring>
|
||||
#include <everest/logging.hpp>
|
||||
#include <iostream>
|
||||
#include <libwebsockets.h>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
class WebSocketTestClient {
|
||||
public:
|
||||
WebSocketTestClient(const std::string& address, int port);
|
||||
~WebSocketTestClient();
|
||||
|
||||
bool connect();
|
||||
bool is_connected();
|
||||
void send(const std::string& message);
|
||||
void send_api_hello_req();
|
||||
const std::string& receive() const;
|
||||
void close();
|
||||
std::string get_received_data() {
|
||||
std::string data;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_cv_mutex);
|
||||
data = m_received_data;
|
||||
m_received_data.clear(); // Clear the received data after getting it
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
std::string wait_for_data(std::chrono::milliseconds timeout, bool is_result = true) {
|
||||
std::unique_lock<std::mutex> lock(m_cv_mutex);
|
||||
|
||||
bool received = m_cv.wait_for(lock, timeout, [this, is_result]() {
|
||||
if (m_received_data.empty())
|
||||
return false;
|
||||
if (is_result) {
|
||||
// Check string for "result" and "id" keys if is_result is true
|
||||
bool has_result = m_received_data.find("\"result\"") != std::string::npos;
|
||||
bool has_id = m_received_data.find("\"id\"") != std::string::npos;
|
||||
return has_result && has_id;
|
||||
}
|
||||
return true; // For regular responses, we just check if we have data
|
||||
});
|
||||
|
||||
if (!received) {
|
||||
return ""; // Timeout
|
||||
}
|
||||
|
||||
std::string data = m_received_data;
|
||||
m_received_data.clear(); // Clear the received data after getting it
|
||||
return data;
|
||||
}
|
||||
|
||||
bool wait_for_response(std::chrono::milliseconds timeout) {
|
||||
std::unique_lock<std::mutex> lock(m_cv_mutex);
|
||||
return m_cv.wait_for(lock, timeout, [this] { return !m_received_data.empty(); });
|
||||
}
|
||||
|
||||
bool wait_until_connected(std::chrono::milliseconds timeout) {
|
||||
std::unique_lock<std::mutex> lock(m_cv_mutex);
|
||||
return m_cv.wait_for(lock, timeout, [this] { return m_connected.load(); });
|
||||
}
|
||||
|
||||
void start_lws_service_thread();
|
||||
void stop_lws_service_thread();
|
||||
|
||||
private:
|
||||
static int callback(struct lws* wsi, enum lws_callback_reasons reason, void* user, void* in, size_t len);
|
||||
|
||||
std::string m_address;
|
||||
int m_port;
|
||||
struct lws_context* m_context;
|
||||
struct lws_client_connect_info m_ccinfo {};
|
||||
struct lws* m_wsi;
|
||||
std::atomic<bool> m_connected{false};
|
||||
std::atomic<bool> m_lws_service_running{false};
|
||||
std::thread m_lws_service_thread;
|
||||
std::string m_received_data;
|
||||
|
||||
public:
|
||||
// Condition variable to wait for response
|
||||
std::condition_variable m_cv;
|
||||
std::mutex m_cv_mutex;
|
||||
};
|
||||
|
||||
#endif // WEBSOCKETTESTCLIENT_HPP
|
||||
Reference in New Issue
Block a user