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,521 @@
/*
* Licensor: Pionix GmbH, 2025
* License: BaseCamp - License Version 1.0
*
* Licensed under the terms and conditions of the BaseCamp License contained in the "LICENSE" file, also available
* under: https://pionix.com/pionix-license-terms
* You may not use this file/code except in compliance with said License.
*/
#include <everest/io/mqtt/mosquitto_cpp.hpp>
// #include <companion/utilities/String.hpp>
// #include <utils/logging.hpp>
#include <cstdio>
#include <iostream>
#include <mosquitto.h>
#include <stdexcept>
/**
* \file MQTT v5 client
*/
namespace {
using namespace everest::lib::io::mqtt;
constexpr int loop_timeout_ms = -1; // use default
int password_callback(char* buf, int size, [[maybe_unused]] int rwflag, [[maybe_unused]] void* userdata) {
if ((buf != nullptr) && (size > 0)) {
*buf = '\0';
}
return 0;
}
constexpr mosquitto_cpp::QoS convert_to_qos(int qos) {
switch (qos) {
case static_cast<int>(mosquitto_cpp::QoS::at_least_once):
case static_cast<int>(mosquitto_cpp::QoS::at_most_once):
case static_cast<int>(mosquitto_cpp::QoS::exactly_once):
return static_cast<mosquitto_cpp::QoS>(qos);
}
throw std::out_of_range("QoS from int: " + std::to_string(qos));
}
// clang-format off
#define FOR_ALL_ERROR_CODES(apply) \
apply(SUCCESS, Success) \
apply(INVAL, InvalidArgument) \
apply(NOMEM, NoMemory) \
apply(ERRNO, Errno) \
apply(NO_CONN, NoConnection) \
apply(CONN_LOST, ConnectionLost) \
apply(CONN_REFUSED, ConnectionRefused) \
apply(PROTOCOL, Protocol) \
apply(PAYLOAD_SIZE, PayloadSize) \
apply(MALFORMED_UTF8, MalformedUTF8) \
apply(DUPLICATE_PROPERTY, DuplicateProperty) \
apply(QOS_NOT_SUPPORTED, QoSNotSupported) \
apply(OVERSIZE_PACKET, OversizePacket) \
apply(NOT_SUPPORTED, NotSupported) \
apply(EAI, HostnameLookup) \
apply(TLS, Tls) \
apply(TLS_HANDSHAKE, TlsHandshake)
// clang-format off
#define VALUES(a, b) \
case MOSQ_ERR_##a: return ErrorCode::b;
// clang-format on
constexpr ErrorCode convertEC(int rc) {
switch (rc) {
FOR_ALL_ERROR_CODES(VALUES)
default:
// LogError << "Unrecognised mosquitto error: " << rc;
return ErrorCode::Unknown;
}
}
#undef VALUES
} // namespace
namespace everest::lib::io::mqtt {
PropertiesBase::~PropertiesBase() {
if (!is_const) {
mosquitto_property_free_all(&props);
}
}
void PropertiesBase::free_property(mqtt5__property** ptr) {
mosquitto_property_free_all(ptr);
}
const mqtt5__property* PropertiesBase::get_property(property_t prop) const {
const mosquitto_property* result{nullptr};
for (const auto* p = props; p != nullptr; p = mosquitto_property_next(p)) {
if (mosquitto_property_identifier(p) == static_cast<int>(prop)) {
result = p;
break;
}
}
return result;
}
std::string PropertiesBase::get_response_topic() const {
std::string result;
char* topic{nullptr};
if (mosquitto_property_read_string(props, static_cast<int>(property_t::ResponseTopic), &topic, false) != nullptr) {
result = topic;
}
::free(topic);
// LogDebug << "response_topic (" << props << "): " << result;
return result;
}
std::string PropertiesBase::get_correlation_data() const {
std::string result;
void* data{nullptr};
std::uint16_t len{0};
if (mosquitto_property_read_binary(props, static_cast<int>(property_t::CorrelationData), &data, &len, false) !=
nullptr) {
result = std::string{static_cast<char*>(data), len};
}
::free(data);
// LogDebug << "correlation_data (" << props << "): " << result;
return result;
}
ErrorCode Properties::set_response_topic(const std::string& topic) {
const auto result =
convertEC(mosquitto_property_add_string(&props, static_cast<int>(property_t::ResponseTopic), topic.data()));
if (result != ErrorCode::Success) {
// LogError << "set_response_topic: " << to_string(result);
}
return result;
}
ErrorCode Properties::set_correlation_data(const std::string& data) {
const auto result = convertEC(
mosquitto_property_add_binary(&props, static_cast<int>(property_t::CorrelationData), data.data(), data.size()));
if (result != ErrorCode::Success) {
// LogError << "set_correlation_data: " << to_string(result);
}
return result;
}
// clang-format off
#define VALUES(a, b) \
case ErrorCode::b: return {#b};
// clang-format on
std::string_view to_string(ErrorCode ec) {
switch (ec) {
FOR_ALL_ERROR_CODES(VALUES)
default:
return {"<unknown>"};
}
}
#undef VALUES
void mosquitto_cpp::cb_connect([[maybe_unused]] mosquitto* mosq, void* obj, int rc, int flags,
const mqtt5__property* props) {
if (obj != nullptr) {
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
const PropertiesAccess p(props);
const auto r = static_cast<ResponseCode>(rc);
client.connect_ccb(r, flags, p);
}
}
void mosquitto_cpp::cb_disconnect([[maybe_unused]] mosquitto* mosq, void* obj, int rc, const mqtt5__property* props) {
if (obj != nullptr) {
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
const PropertiesAccess p(props);
const auto ec = convertEC(rc);
client.disconnect_ccb(ec, p);
client.callbacks.clear();
}
}
void mosquitto_cpp::cb_log([[maybe_unused]] mosquitto* mosq, void* obj, int level, const char* str) {
if (obj != nullptr) {
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
LogLevel l;
switch (level) {
case MOSQ_LOG_INFO:
l = LogLevel::info;
break;
case MOSQ_LOG_NOTICE:
l = LogLevel::notice;
break;
case MOSQ_LOG_WARNING:
l = LogLevel::warning;
break;
case MOSQ_LOG_ERR:
l = LogLevel::error;
break;
case MOSQ_LOG_DEBUG:
default:
l = LogLevel::debug;
break;
}
client.log(l, str);
}
}
void mosquitto_cpp::cb_message([[maybe_unused]] mosquitto* mosq, void* obj, const mosquitto_message* msg,
const mqtt5__property* props) {
if ((obj != nullptr) && (msg != nullptr)) {
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
const PropertiesAccess p(props);
const std::string_view topic(msg->topic);
const std::string_view payload(static_cast<const char*>(msg->payload), msg->payloadlen);
const QoS qos = convert_to_qos(msg->qos);
client.message_ccb(topic, payload, qos, p);
}
}
void mosquitto_cpp::cb_publish([[maybe_unused]] mosquitto* mosq, void* obj, int mid, int rc,
const mqtt5__property* props) {
if (obj != nullptr) {
auto& client = *reinterpret_cast<mosquitto_cpp*>(obj);
const PropertiesAccess p(props);
const auto r = static_cast<ResponseCode>(rc);
client.publish_ccb(mid, r, p);
}
}
void mosquitto_cpp::connect_ccb(ResponseCode rc, int flags, const PropertiesAccess& props) {
if (connect_cb) {
connect_cb(*this, rc, flags, props);
}
}
void mosquitto_cpp::disconnect_ccb(ErrorCode ec, const PropertiesAccess& props) {
if (disconnect_cb) {
disconnect_cb(*this, ec, props);
}
}
void mosquitto_cpp::message_ccb(const std::string_view& topic, const std::string_view& payload, QoS qos,
const PropertiesAccess& props) {
// LogDebug << "MQTT msg " << topic << ' ' << bc_utils::hex_string(payload);
for (const auto& [sub_topic, callback] : callbacks) {
bool matches{false};
if ((mosquitto_topic_matches_sub2(sub_topic.data(), sub_topic.size(), topic.data(), topic.size(), &matches) ==
MOSQ_ERR_SUCCESS) &&
matches) {
callback(*this, topic, payload, qos, props);
}
}
}
void mosquitto_cpp::publish_ccb(int mid, ResponseCode rc, const PropertiesAccess& props) {
if (publish_cb) {
publish_cb(*this, mid, rc, props);
}
}
mosquitto_cpp::mosquitto_cpp() : mosquitto_cpp(nullptr) {
}
mosquitto_cpp::mosquitto_cpp(const char* id) : client(nullptr, nullptr) {
mosquitto* ptr = mosquitto_new(id, true, this);
std::unique_ptr<mosquitto, decltype(&mosquitto_destroy)> tmp(ptr, &mosquitto_destroy);
client.swap(tmp);
// use v5
mosquitto_int_option(client.get(), MOSQ_OPT_PROTOCOL_VERSION, MQTT_PROTOCOL_V5);
// TODO(james-ctc): select sensible defaults
// mosquitto_int_option(client.get(), MOSQ_OPT_RECEIVE_MAXIMUM, 20);
// mosquitto_int_option(client.get(), MOSQ_OPT_SEND_MAXIMUM, 20);
// Enable TCP no delay
// mosquitto_int_option(client.get(), MOSQ_OPT_TCP_NODELAY, 1);
// multiple threads are likely
mosquitto_threaded_set(client.get(), true);
mosquitto_connect_v5_callback_set(client.get(), cb_connect);
mosquitto_disconnect_v5_callback_set(client.get(), cb_disconnect);
mosquitto_log_callback_set(client.get(), cb_log);
mosquitto_message_v5_callback_set(client.get(), cb_message);
mosquitto_publish_v5_callback_set(client.get(), cb_publish);
// mosquitto_subscribe_v5_callback_set(client.get(), cb_subscribe);
}
mosquitto_cpp::~mosquitto_cpp() {
disconnect();
}
void mosquitto_cpp::log(LogLevel level, [[maybe_unused]] const char* str) {
switch (level) {
case LogLevel::info:
case LogLevel::notice:
// LogInfo << str;
break;
case LogLevel::warning:
// LogWarning << str;
break;
case LogLevel::error:
// LogError << str;
break;
case LogLevel::debug:
// LogDebug << str;
break;
default:
// should not occur
// LogCritical << str;
break;
}
}
void mosquitto_cpp::set_callback_connect_impl(connect_callback cb) {
connect_cb = std::move(cb);
}
void mosquitto_cpp::set_callback_disconnect_impl(disconnect_callback cb) {
disconnect_cb = std::move(cb);
}
void mosquitto_cpp::set_callback_publish_impl(publish_callback cb) {
publish_cb = std::move(cb);
}
void mosquitto_cpp::set_callback_connect(connect_callback cb) {
set_callback_connect_impl(std::move(cb));
}
void mosquitto_cpp::set_callback_disconnect(disconnect_callback cb) {
set_callback_disconnect_impl(std::move(cb));
}
void mosquitto_cpp::set_callback_publish(publish_callback cb) {
set_callback_publish_impl(std::move(cb));
}
bool mosquitto_cpp::is_connect_callback_set() {
return static_cast<bool>(connect_cb);
}
bool mosquitto_cpp::is_disconnect_callback_set() {
return static_cast<bool>(disconnect_cb);
}
ErrorCode mosquitto_cpp::tls(const char* ca_file, const char* ca_path, const char* cert_file, const char* key_file) {
return convertEC(mosquitto_tls_set(client.get(), ca_file, ca_path, cert_file, key_file, password_callback));
}
ErrorCode mosquitto_cpp::tls(const ::std::string& ca_file, const ::std::string& ca_path, const ::std::string& cert_file,
const ::std::string& key_file) {
const char* ca_file_ptr{nullptr};
const char* ca_path_ptr{nullptr};
if (!ca_file.empty()) {
ca_file_ptr = ca_file.c_str();
}
if (!ca_path.empty()) {
ca_path_ptr = ca_path.c_str();
}
return tls(ca_file_ptr, ca_path_ptr, cert_file.c_str(), key_file.c_str());
}
ErrorCode mosquitto_cpp::connect(const std::string_view& host, std::uint16_t port, std::uint16_t keepalive_seconds) {
return connect_impl("", host, port, keepalive_seconds);
}
ErrorCode mosquitto_cpp::connect(const std::string_view& bind_address, const std::string_view& host, std::uint16_t port,
std::uint16_t keepalive_seconds) {
return connect_impl(bind_address, host, port, keepalive_seconds);
}
ErrorCode mosquitto_cpp::connect(const std::string_view& unix_domain_socket, std::uint16_t keepalive_seconds) {
return connect_impl("", unix_domain_socket, 0, keepalive_seconds);
}
ErrorCode mosquitto_cpp::connect_impl(const std::string_view& bind_address, const std::string_view& host,
std::uint16_t port, std::uint16_t keepalive_seconds) {
const char* bind_to = bind_address.empty() ? nullptr : bind_address.data();
return convertEC(mosquitto_connect_bind_async(client.get(), host.data(), port, keepalive_seconds, bind_to));
}
ErrorCode mosquitto_cpp::reconnect() {
return convertEC(mosquitto_reconnect_async(client.get()));
}
ErrorCode mosquitto_cpp::disconnect() {
return convertEC(mosquitto_disconnect(client.get()));
}
ErrorCode mosquitto_cpp::loop_forever() {
return convertEC(mosquitto_loop_forever(client.get(), loop_timeout_ms, 1));
}
ErrorCode mosquitto_cpp::loop_read() {
return convertEC(mosquitto_loop_read(client.get(), 1));
}
ErrorCode mosquitto_cpp::loop_write() {
return convertEC(mosquitto_loop_write(client.get(), 1));
}
ErrorCode mosquitto_cpp::loop_misc() {
return convertEC(mosquitto_loop_misc(client.get()));
}
bool mosquitto_cpp::want_write() {
return mosquitto_want_write(client.get());
}
int mosquitto_cpp::socket() const {
return mosquitto_socket(client.get());
}
void mosquitto_cpp::set_option_threaded(bool val) {
(void)mosquitto_threaded_set(client.get(), val);
}
void mosquitto_cpp::set_option_tcpnodelay(bool val) {
(void)mosquitto_int_option(client.get(), MOSQ_OPT_TCP_NODELAY, val);
}
ErrorCode mosquitto_cpp::set_will(const std::string_view& topic, const std::string_view& payload, QoS qos, bool retain,
PropertiesBase&& props) {
return set_will_impl(topic, payload, qos, retain, std::move(props));
}
ErrorCode mosquitto_cpp::set_will_impl(const std::string_view& topic, const std::string_view& payload, QoS qos,
bool retain, PropertiesBase&& props) {
// must be called before connect()
// On success only, the property list becomes the property of libmosquitto
// once this function is called and will be freed by the library.
// The property list must be freed by the application on error.
ErrorCode result;
auto* prop_p = props.release();
result = convertEC(mosquitto_will_set_v5(client.get(), topic.data(), payload.size(), payload.data(),
static_cast<int>(qos), retain, prop_p));
if (result != ErrorCode::Success) {
PropertiesBase::free_property(&prop_p);
}
return result;
}
ErrorCode mosquitto_cpp::publish(int* mid, const std::string_view& topic, const std::string_view& payload, QoS qos,
bool retain, const PropertiesBase& props) {
return publish_impl(mid, topic, payload, qos, retain, props);
}
ErrorCode mosquitto_cpp::publish(const std::string_view& topic, const std::string_view& payload, QoS qos, bool retain,
const PropertiesBase& props) {
return publish(nullptr, topic, payload, qos, retain, props);
}
ErrorCode mosquitto_cpp::publish(const std::string_view& topic, const std::string_view& payload) {
return publish(nullptr, topic, payload, default_QoS, false, {});
}
ErrorCode mosquitto_cpp::publish(message const& data) {
return publish(data.topic, data.payload, data.qos, false, {});
}
ErrorCode mosquitto_cpp::publish_impl(int* mid, const std::string_view& topic, const std::string_view& payload, QoS qos,
bool retain, const PropertiesBase& props) {
// LogDebug << "MQTT pub " << topic << ' ' << bc_utils::hex_string(payload);
return convertEC(mosquitto_publish_v5(client.get(), mid, topic.data(), payload.size(), payload.data(),
static_cast<int>(qos), retain, props));
}
ErrorCode mosquitto_cpp::subscribe(const std::string_view& topic, QoS qos, int options, const PropertiesBase& props,
subscribe_callback cb) {
return subscribe_impl(topic, qos, options, props, std::move(cb));
}
ErrorCode mosquitto_cpp::subscribe(const std::string_view& topic, subscribe_callback cb) {
return subscribe(topic, default_QoS, 0, {}, std::move(cb));
}
ErrorCode mosquitto_cpp::subscribe(std::string_view const& topic, subscribe_message_callback const& cb, QoS qos) {
return subscribe(topic, qos, 0, {},
[cb = std::move(cb)](auto& cb_client, auto const& cb_topic, auto const& cb_payload, QoS cb_qos,
[[maybe_unused]] auto const& props) {
message data;
data.topic = cb_topic;
data.payload = cb_payload;
data.qos = cb_qos;
cb(cb_client, data);
});
}
ErrorCode mosquitto_cpp::subscribe_impl(const std::string_view& topic, QoS qos, int options,
const PropertiesBase& props, subscribe_callback cb) {
// LogDebug << "MQTT sub " << topic;
auto result =
convertEC(mosquitto_subscribe_v5(client.get(), nullptr, topic.data(), static_cast<int>(qos), options, props));
if (result == ErrorCode::Success) {
const auto [it, success] = callbacks.insert({std::string{topic}, std::move(cb)});
if (!success) {
result = ErrorCode::MapInsert;
}
}
return result;
}
ErrorCode mosquitto_cpp::unsubscribe(const std::string_view& topic, const PropertiesBase& props) {
return unsubscribe_impl(topic, props);
}
ErrorCode mosquitto_cpp::unsubscribe_impl(const std::string_view& topic, const PropertiesBase& props) {
auto result = convertEC(mosquitto_unsubscribe_v5(client.get(), nullptr, topic.data(), props));
if (result == ErrorCode::Success) {
callbacks.erase(std::string{topic});
}
return result;
}
bool mosquitto_cpp::library_init() {
return mosquitto_lib_init() == MOSQ_ERR_SUCCESS;
}
void mosquitto_cpp::library_cleanup() {
(void)mosquitto_lib_cleanup();
}
} // namespace everest::lib::io::mqtt