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,50 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_CHARGING_STATION_H
#define CHARGELAB_OPEN_FIRMWARE_CHARGING_STATION_H
#include "openocpp/interface/element/websocket_raw_interface.h"
#include "openocpp/interface/message_handler.h"
#include "openocpp/interface/component/system_interface.h"
#include "openocpp/interface/element/storage_interface.h"
#include "openocpp/interface/connector_interface.h"
#include "openocpp/helpers/optional.h"
#include "openocpp/helpers/string.h"
#include "openocpp/common/settings.h"
#include "openocpp/common/logging.h"
#include <memory>
#include <utility>
namespace chargelab {
class ChargingStation {
public:
ChargingStation(std::shared_ptr<WebsocketInterface> websocket, std::shared_ptr<MessageHandlerInterface> handler)
: websocket_(std::move(websocket)), handler_(std::move(handler))
{
}
~ChargingStation() {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting ChargingStation";
}
public:
void runStep() {
auto connected = websocket_->isConnected();
if (connected) {
optional::IfPresent(websocket_->pollTextMessages(), [&](auto&& x) { handler_->onTextMessage(x); });
optional::IfPresent(websocket_->pollBinaryMessages(), [&](auto&& x) { handler_->onBinaryMessage(x); });
}
handler_->runStep();
if (connected) {
optional::IfPresent(handler_->pollTextMessages(), [&](auto&& x) { websocket_->sendTextMessage(x); });
optional::IfPresent(handler_->pollBinaryMessages(), [&](auto&& x) { websocket_->sendBinaryMessage(x); });
}
}
private:
std::shared_ptr<SystemInterface> system_;
std::shared_ptr<WebsocketInterface> websocket_;
std::shared_ptr<MessageHandlerInterface> handler_;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_CHARGING_STATION_H

View File

@@ -0,0 +1,149 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_COMPRESSED_JOURNAL_H
#define CHARGELAB_OPEN_FIRMWARE_COMPRESSED_JOURNAL_H
#include "openocpp/common/compressed_queue.h"
#include "openocpp/interface/element/flash_block_interface.h"
namespace chargelab {
template <typename T, typename Serializer>
class CompressedJournalCustom {
public:
explicit CompressedJournalCustom(std::unique_ptr<FlashBlockInterface> storage, std::string protocol_version)
: storage_(std::move(storage)),
protocol_version_(std::move(protocol_version))
{
}
bool addUpdate(std::vector<T> const& final_state, T const& delta) {
// First try to add the delta to the stream
if (addToStream(std::vector<T>{delta}))
return true;
// Otherwise erase flash storage and write the final state
CHARGELAB_LOG_MESSAGE(info) << "Available journal storage exhausted - clearing";
storage_->erase();
writeHeader();
return addToStream(final_state);
}
template <typename Visitor>
void visit(Visitor&& visitor) {
if (!checkHeader())
return;
std::vector<uint8_t> raw_data;
raw_data.resize(storage_->size() - protocol_version_.size());
storage_->read(protocol_version_.size(), raw_data.data(), raw_data.size());
std::vector<uint8_t> input_buffer;
CompressedInputStreamZLib input(input_buffer, raw_data.data(), raw_data.size(), true);
while (true) {
auto record = input.nextRecord();
if (!record.has_value())
break;
auto result = Serializer::read(record.value());
if (result.has_value()) {
visitor(record.value(), result.value());
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Failed deserializing payload: " << record.value();
}
}
}
[[nodiscard]] std::size_t totalBytesWritten() const {
return bytes_written_;
}
[[nodiscard]] std::size_t storageSize() const {
return storage_->size();
}
private:
bool checkHeader() {
std::string test;
test.resize(protocol_version_.size());
storage_->read(0, test.data(), test.size());
return test == protocol_version_;
}
bool writeHeader() {
return storage_->write(0, protocol_version_.data(), protocol_version_.size());
}
bool addToStream(std::vector<T> const& records) {
if (!checkHeader())
return false;
std::vector<uint8_t> raw_data;
raw_data.resize(storage_->size() - protocol_version_.size());
storage_->read(protocol_version_.size(), raw_data.data(), raw_data.size());
std::vector<uint8_t> input_buffer;
std::vector<uint8_t> output_buffer;
CompressedInputStreamZLib input(input_buffer, raw_data.data(), raw_data.size(), true);
CompressedOutputStreamZLib output(output_buffer, Z_SYNC_FLUSH);
while (true) {
auto record = input.nextRecord();
if (!record.has_value())
break;
output.addRecord(record.value());
}
for (auto const& x : records)
output.addRecord(Serializer::write(x));
if (!output.close(false) || output_buffer.size() > raw_data.size())
return false;
total_bytes_ = output_buffer.size();
std::size_t first_index = 0;
while (first_index < output_buffer.size()) {
auto const old_byte = raw_data[first_index];
auto const new_byte = output_buffer[first_index];
// Note: assuming write operations can only set bits to zero in between erase operations
if ((old_byte & new_byte) != new_byte)
return false;
if (old_byte != new_byte)
break;
first_index++;
}
if (first_index >= output_buffer.size())
return true;
auto last_index = first_index;
for (auto i = first_index; i < output_buffer.size(); i++) {
auto const old_byte = raw_data[i];
auto const new_byte = output_buffer[i];
// Note: assuming write operations can only set bits to zero in between erase operations
if ((old_byte & new_byte) != new_byte)
return false;
if (old_byte != new_byte)
last_index = i;
}
auto const size = last_index - first_index + 1;
bytes_written_ += size;
return storage_->write(protocol_version_.size() + first_index, &output_buffer[first_index], size);
}
private:
std::unique_ptr<FlashBlockInterface> storage_;
std::string protocol_version_;
std::optional<std::size_t> total_bytes_;
std::atomic<std::size_t> bytes_written_ = 0;
};
template <typename T>
using CompressedJournalJson = CompressedJournalCustom<T, detail::JsonSerializer<T>>;
}
#endif //CHARGELAB_OPEN_FIRMWARE_COMPRESSED_JOURNAL_H

View File

@@ -0,0 +1,606 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_COMPRESSED_QUEUE_H
#define CHARGELAB_OPEN_FIRMWARE_COMPRESSED_QUEUE_H
#include "openocpp/common/logging.h"
#include <optional>
#include <vector>
#include <cstdint>
#include <cstring>
#include <variant>
#include <string>
#include "zlib.h"
namespace chargelab {
namespace detail {
class CompressedStreamZlibConstants {
public:
// zlib constants
static constexpr int kZlibLevel = Z_BEST_COMPRESSION;
// 2KiB history buffer
static constexpr int kZlibWindowBits = 11;
static constexpr int kZlibMemLevel = 1;
// Compressed block size threshold - when the size of the compressed records in a block exceeds this
// threshold a new block is created to store subsequent records
static constexpr std::size_t kCompressedBlockThreshold = 5 * 1024;
static constexpr std::size_t kInflateBufferInitialSize = 256;
static constexpr std::size_t kDeflateBufferStepSize = 256;
};
}
class CompressedOutputStreamZLib {
public:
CompressedOutputStreamZLib(std::vector<uint8_t>& buffer, int record_flush = Z_NO_FLUSH)
: buffer_(buffer), record_flush_(record_flush)
{
resetInternal();
}
~CompressedOutputStreamZLib() {
if (initialized_)
deflateEnd(&stream_);
}
CompressedOutputStreamZLib(CompressedOutputStreamZLib const&) = delete;
CompressedOutputStreamZLib(CompressedOutputStreamZLib&&) = delete;
CompressedOutputStreamZLib& operator=(CompressedOutputStreamZLib const&) = delete;
CompressedOutputStreamZLib& operator=(CompressedOutputStreamZLib&&) = delete;
void addRecord(std::string_view const& record) {
if (!initialized_)
return;
int size_prefix = (int)record.size();
writeBlock(&size_prefix, sizeof(size_prefix), Z_NO_FLUSH);
writeBlock(record.data(), record.size(), record_flush_);
}
void reset() {
resetInternal();
}
bool close(bool finish_stream = true) {
if (!initialized_)
return false;
if (finish_stream) {
while(true) {
if (stream_.avail_out <= 0) {
auto const index = buffer_.size();
buffer_.resize(buffer_.size() + detail::CompressedStreamZlibConstants::kDeflateBufferStepSize);
stream_.next_out = buffer_.data() + index;
stream_.avail_out = buffer_.size() - index;
}
auto const ret = deflate(&stream_, Z_FINISH);
if (ret == Z_STREAM_END)
break;
if (ret != Z_OK) {
CHARGELAB_LOG_MESSAGE(warning) << "deflate failed with error code: " << ret;
return false;
}
}
}
buffer_.resize(buffer_.size() - stream_.avail_out);
deflateEnd(&stream_);
std::memset(&stream_, 0, sizeof(stream_));
initialized_ = false;
return true;
}
[[nodiscard]] std::size_t approximateTotalBytes() const {
if (!initialized_)
return 0;
return buffer_.size() - stream_.avail_out;
}
private:
void writeBlock(void const* data, std::size_t size, int flush) {
stream_.next_in = (uint8_t*)data;
stream_.avail_in = size;
do {
if (stream_.avail_out <= 0) {
auto const index = buffer_.size() - stream_.avail_out;
buffer_.resize(buffer_.size() + detail::CompressedStreamZlibConstants::kDeflateBufferStepSize);
stream_.next_out = buffer_.data() + index;
stream_.avail_out = buffer_.size() - index;
}
auto const ret = deflate(&stream_, flush);
if (ret != Z_OK) {
CHARGELAB_LOG_MESSAGE(warning) << "deflate failed with error code: " << ret;
return;
}
} while(stream_.avail_out == 0);
stream_.next_in = Z_NULL;
stream_.avail_in = 0;
}
void resetInternal() {
buffer_.resize(detail::CompressedStreamZlibConstants::kDeflateBufferStepSize);
stream_.zalloc = Z_NULL;
stream_.zfree = Z_NULL;
stream_.opaque = Z_NULL;
stream_.next_in = Z_NULL;
stream_.avail_in = 0;
stream_.next_out = buffer_.data();
stream_.avail_out = buffer_.size();
auto const ret = deflateInit2(
&stream_,
detail::CompressedStreamZlibConstants::kZlibLevel,
Z_DEFLATED,
detail::CompressedStreamZlibConstants::kZlibWindowBits,
detail::CompressedStreamZlibConstants::kZlibMemLevel,
Z_DEFAULT_STRATEGY
);
if (ret == Z_OK) {
initialized_ = true;
} else {
CHARGELAB_LOG_MESSAGE(warning) << "deflateInit failed with error code: " << ret;
}
}
private:
z_stream stream_ {};
std::vector<uint8_t>& buffer_;
int record_flush_;
bool initialized_ = false;
};
class CompressedInputStreamZLib {
public:
CompressedInputStreamZLib(
std::vector<uint8_t>& buffer,
uint8_t* data,
std::size_t size,
bool ignore_read_errors = false
)
: buffer_(buffer),
ignore_read_errors_(ignore_read_errors)
{
buffer_.resize(detail::CompressedStreamZlibConstants::kInflateBufferInitialSize);
stream_.zalloc = Z_NULL;
stream_.zfree = Z_NULL;
stream_.opaque = Z_NULL;
stream_.next_in = data;
stream_.avail_in = size;
stream_.next_out = buffer_.data();
stream_.avail_out = buffer_.size();
auto const ret = inflateInit2(&stream_, detail::CompressedStreamZlibConstants::kZlibWindowBits);
if (ret == Z_OK) {
initialized_ = true;
} else {
CHARGELAB_LOG_MESSAGE(warning) << "inflateInit failed with error code: " << ret;
}
}
~CompressedInputStreamZLib() {
if (initialized_)
inflateEnd(&stream_);
}
CompressedInputStreamZLib(CompressedInputStreamZLib const&) = delete;
CompressedInputStreamZLib(CompressedInputStreamZLib&&) = delete;
CompressedInputStreamZLib& operator=(CompressedInputStreamZLib const&) = delete;
CompressedInputStreamZLib& operator=(CompressedInputStreamZLib&&) = delete;
std::optional<std::string_view> nextRecord() {
if (!initialized_)
return std::nullopt;
int size_value {};
auto size_ptr = readBlock(sizeof(size_value));
if (size_ptr == nullptr)
return std::nullopt;
std::memcpy(&size_value, size_ptr, sizeof(size_value));
auto data_ptr = readBlock(size_value);
if (data_ptr == nullptr) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading data block of size: " << size_value;
return std::nullopt;
}
return std::string_view {(char const*)data_ptr, (std::size_t)size_value};
}
bool close() {
if (!initialized_)
return false;
inflateEnd(&stream_);
initialized_ = false;
return true;
}
private:
void const* readBlock(std::size_t size) {
auto bytes_present = buffer_.size() - stream_.avail_out - index_;
if (bytes_present >= size) {
auto result = &buffer_[index_];
index_ += size;
return result;
}
if (buffer_.size() < size)
buffer_.resize(size);
std::memmove(buffer_.data(), buffer_.data()+index_, bytes_present);
stream_.next_out = buffer_.data() + bytes_present;
stream_.avail_out = buffer_.size() - bytes_present;
index_ = 0;
while (true) {
auto const avail_begin = stream_.avail_in;
auto const ret = inflate(&stream_, Z_BLOCK);
auto const avail_end = stream_.avail_in;
if (ret != Z_OK && ret != Z_STREAM_END) {
if (!ignore_read_errors_) {
CHARGELAB_LOG_MESSAGE(warning) << "inflate failed with error code " << ret << ": "
<< (char const *) stream_.msg;
}
return nullptr;
}
bytes_present = buffer_.size() - stream_.avail_out - index_;
if (bytes_present >= size)
break;
if (avail_begin == avail_end)
return nullptr;
}
index_ += size;
return buffer_.data();
}
private:
z_stream stream_ {};
std::vector<uint8_t>& buffer_;
bool ignore_read_errors_;
bool initialized_ = false;
std::size_t index_ = 0;
};
class CompressedQueueRawZLib {
public:
CompressedQueueRawZLib() {
}
// Use poll first and pop first instead, and just remove when the attempts time-out
std::optional<std::string> pollFront() {
if (blocks_.empty())
return std::nullopt;
CompressedInputStreamZLib input {inflate_buffer_, blocks_.front().data(), blocks_.front().size()};
auto const record = input.nextRecord();
if (!record.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected state - no records present in chunk";
blocks_.erase(blocks_.begin());
return std::nullopt;
}
return std::string {record.value()};
}
std::optional<std::string> popFront() {
if (blocks_.empty())
return std::nullopt;
CompressedInputStreamZLib input {inflate_buffer_, blocks_.front().data(), blocks_.front().size()};
auto const record = input.nextRecord();
if (!record.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected state - no records present in chunk";
blocks_.erase(blocks_.begin());
return std::nullopt;
}
auto result = std::string {record.value()};
bool empty = true;
CompressedOutputStreamZLib output {deflate_buffer_};
while (true) {
auto const& x = input.nextRecord();
if (!x.has_value())
break;
output.addRecord(x.value());
empty = false;
}
output.close();
if (empty) {
blocks_.erase(blocks_.begin());
} else {
blocks_.front() = deflate_buffer_;
}
return result;
}
void updateFront(std::string const& update) {
if (blocks_.empty())
return;
CompressedInputStreamZLib input {inflate_buffer_, blocks_.front().data(), blocks_.front().size()};
auto const record = input.nextRecord();
if (!record.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected state - no records present in chunk";
blocks_.erase(blocks_.begin());
return;
}
CompressedOutputStreamZLib output {deflate_buffer_};
output.addRecord(update);
while (true) {
auto const& x = input.nextRecord();
if (!x.has_value())
break;
output.addRecord(x.value());
}
if (!output.close()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed closing output stream - offline messages were dropped";
blocks_.erase(blocks_.begin());
} else {
blocks_.front() = deflate_buffer_;
}
}
void pushBack(std::string const& value) {
if (!blocks_.empty() && blocks_.back().size() < detail::CompressedStreamZlibConstants::kCompressedBlockThreshold) {
// Try to add the record to the final block
{
CompressedOutputStreamZLib writer{deflate_buffer_};
CompressedInputStreamZLib reader{inflate_buffer_, blocks_.back().data(), blocks_.back().size()};
while (true) {
auto record = reader.nextRecord();
if (!record.has_value())
break;
writer.addRecord(record.value());
}
if (!reader.close()) {
CHARGELAB_LOG_MESSAGE(error) << "Failed reading historic data - dropping record";
clear();
return;
}
writer.addRecord(value);
if (!writer.close()) {
CHARGELAB_LOG_MESSAGE(error) << "Failed closing compressed stream - dropping record" ;
clear();
return;
}
}
// Note: freeing the reader and writer before resizing buffer
blocks_.back() = deflate_buffer_;
} else {
// Otherwise add a new block
{
CompressedOutputStreamZLib writer{deflate_buffer_};
writer.addRecord(value);
if (!writer.close()) {
CHARGELAB_LOG_MESSAGE(error) << "Failed closing compressed stream - dropping record";
clear();
return;
}
}
// Note: freeing the writer before adding new buffer
blocks_.push_back(deflate_buffer_);
}
}
template <typename Visitor>
void visit(Visitor&& visitor) {
for (auto& block : blocks_) {
CompressedInputStreamZLib reader{inflate_buffer_, block.data(), block.size()};
while (true) {
auto const& record = reader.nextRecord();
if (!record.has_value())
break;
visitor(record.value());
}
}
}
template <typename Predicate>
void removeIf(Predicate&& predicate) {
bool empty = true;
CompressedOutputStreamZLib writer{deflate_buffer_};
std::vector<std::vector<uint8_t>> original_blocks;
std::swap(blocks_, original_blocks);
for (auto& block : original_blocks) {
CompressedInputStreamZLib reader{inflate_buffer_, block.data(), block.size()};
while (true) {
auto const& record = reader.nextRecord();
if (!record.has_value())
break;
if (predicate(record.value()))
continue;
empty = false;
writer.addRecord(record.value());
if (writer.approximateTotalBytes() > detail::CompressedStreamZlibConstants::kCompressedBlockThreshold) {
if (!writer.close()) {
CHARGELAB_LOG_MESSAGE(error) << "Failed closing compressed stream - blocks were dropped";
} else {
blocks_.push_back(deflate_buffer_);
}
empty = true;
writer.reset();
}
}
std::vector<uint8_t> empty_buffer{};
std::swap(empty_buffer, block);
// This may be a long-running operation, so sleep briefly between individual blocks to yield to other
// threads
using namespace std::chrono_literals;
std::this_thread::sleep_for(1ms);
}
if (!empty) {
if (!writer.close()) {
CHARGELAB_LOG_MESSAGE(error) << "Failed closing compressed stream - blocks were dropped";
} else {
blocks_.push_back(deflate_buffer_);
}
}
}
void clear() {
blocks_.clear();
}
[[nodiscard]] std::size_t totalBytes() const {
std::size_t result = 0;
for (auto const& block : blocks_)
result += block.size();
return result;
}
[[nodiscard]] bool empty() const {
return blocks_.empty();
}
template<typename Visitor>
void write(Visitor&& visitor) {
for (auto const& block : blocks_)
visitor((void*)block.data(), block.size());
}
template<typename Supplier>
void read(Supplier&& supplier) {
blocks_.clear();
while (true) {
auto const next = supplier();
if (!next.has_value())
break;
blocks_.push_back(next.value());
}
}
private:
// Note: using a shared deflate and inflate buffer here to reduce memory fragmentation
std::vector<std::vector<uint8_t>> blocks_;
std::vector<uint8_t> deflate_buffer_;
std::vector<uint8_t> inflate_buffer_;
};
template <typename T, typename Serializer>
class CompressedQueueCustom {
public:
std::optional<T> pollFront() {
auto text = queue_.pollFront();
if (!text.has_value())
return std::nullopt;
return Serializer::read(text.value());
}
std::optional<T> popFront() {
auto text = queue_.popFront();
if (!text.has_value())
return std::nullopt;
return Serializer::read(text.value());
}
void updateFront(T const& update) {
queue_.updateFront(Serializer::write(update));
}
void pushBack(T const& value) {
queue_.pushBack(Serializer::write(value));
}
template<typename Visitor>
void visit(Visitor&& visitor) {
queue_.visit([&](auto const& text) {
auto result = Serializer::read(text);
if (result.has_value()) {
visitor(text, result.value());
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Failed deserializing payload: " << text;
}
});
}
template <typename Predicate>
void removeIf(Predicate&& predicate) {
queue_.template removeIf([&](auto const& text) {
auto result = Serializer::read(text);
if (result.has_value()) {
return predicate(text, result.value());
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Failed deserializing payload";
return true;
}
});
}
void clear() {
queue_.clear();
}
[[nodiscard]] std::size_t totalBytes() const {
return queue_.totalBytes();
}
[[nodiscard]] bool empty() const {
return queue_.empty();
}
template<typename Visitor>
void write(Visitor&& visitor) {
queue_.template write(std::forward<Visitor>(visitor));
}
template<typename Supplier>
void read(Supplier&& supplier) {
queue_.template read(std::forward<Supplier>(supplier));
}
private:
CompressedQueueRawZLib queue_;
};
namespace detail {
//payload_to_string(request)
template <typename T>
struct JsonSerializer {
static std::optional<T> read(std::string_view const& text) {
return read_json_from_string<T>(text);
}
static std::string write(T const& value) {
return write_json_to_string(value);
}
};
}
template <typename T>
using CompressedQueueJson = CompressedQueueCustom<T, detail::JsonSerializer<T>>;
}
#endif //CHARGELAB_OPEN_FIRMWARE_COMPRESSED_QUEUE_H

View File

@@ -0,0 +1,73 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_JSON_FILE_H
#define CHARGELAB_OPEN_FIRMWARE_JSON_FILE_H
#include <optional>
#include <string>
#include "openocpp/helpers/json.h"
#include "logging.h"
namespace chargelab {
class JsonFile {
static constexpr int kMaxFileDumpSize = 10*1000;
public:
template<class T>
static std::optional<T> loadFile(std::string const& file_name) {
auto file_content = readTextFile(file_name.c_str());
if (file_content) {
return read_json_from_string<T>(file_content);
}
return std::nullopt;
}
template<class T>
static bool saveProfiles(std::string const& file_name, T const & content) {
FILE* file = fopen(file_name.c_str(), "w");
if (file == nullptr) {
CHARGELAB_LOG_MESSAGE(error) << "Failed to open file for writing: " << file_name;
return false;
}
auto const json_string = write_json_to_string(content);
if (!json_string.has_value()) {
CHARGELAB_LOG_MESSAGE(error) << "Failed serializing JSON content";
return false;
}
if (fwrite(json_string->data(), 1, json_string->size(), file) < json_string.size()) {
fclose(file);
CHARGELAB_LOG_MESSAGE(error) << "Failed to write file: " << file_name;
return false;
}
fclose(file);
return true;
}
private:
static std::optional<std::string> readTextFile(const char* file_name) {
FILE* file = fopen(file_name, "r");
if (file == nullptr) {
CHARGELAB_LOG_MESSAGE(debug) << "file doesn't exist:" << file_name;
return std::nullopt;
}
fseek(file, 0, SEEK_END);
size_t file_size = ftell(file);
fseek(file, 0, SEEK_SET);
std::string text;
text.resize(file_size);
if (fread(text.data(), 1, file_size, file) != file_size) {
fclose(file);
CHARGELAB_LOG_MESSAGE(error) << "Failed reading file:" << file_name << " file size:" << file_size;
return std::nullopt;
}
fclose(file);
return text;
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_JSON_FILE_H

View File

@@ -0,0 +1,195 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_LOGGING_H
#define CHARGELAB_OPEN_FIRMWARE_LOGGING_H
#include "openocpp/model/system_types.h"
#include <atomic>
#include <variant>
#include <optional>
#include <thread>
#include <memory>
#include <functional>
#include <string_view>
#include <string>
namespace chargelab::logging {
enum class LogLevel {
trace,
debug,
info,
warning,
error,
fatal
};
struct LogMetadata {
LogLevel level;
#if defined(LOG_WITH_FILE_AND_LINE)
std::string_view file;
int line;
#endif
std::string_view function;
};
void SetLogLevel(LogLevel level);
bool IsLoggingEnabled(LogLevel level);
void PrintLogMessage(LogMetadata const& metadata, std::string_view const& message);
void PrintLogMessage(LogMetadata const& metadata, std::string const& message);
using LoggingListenerFunction = std::function<void(LogMetadata const& metadata, std::string_view const& message)>;
void RegisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback);
void UnregisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback);
template <typename T, typename U=void>
struct LogWriter;
template <>
struct LogWriter<std::string, void> {
static void write(std::string& accumulator, std::string const& value) {
accumulator += value;
}
};
template <>
struct LogWriter<std::string_view, void> {
static void write(std::string& accumulator, std::string_view const& value) {
accumulator += value;
}
};
template <>
struct LogWriter<char const*, void> {
static void write(std::string& accumulator, char const* value) {
accumulator += value;
}
};
template <int N>
struct LogWriter<char const[N], void> {
static void write(std::string& accumulator, char const* value) {
accumulator += value;
}
};
template <int N>
struct LogWriter<char[N], void> {
static void write(std::string& accumulator, char const* value) {
accumulator += value;
}
};
template <typename T>
struct LogWriter<T, typename std::enable_if<std::is_arithmetic<T>::value>::type> {
static void write(std::string& accumulator, T const& value) {
accumulator += std::to_string(value);
}
};
template <>
struct LogWriter<SystemTimeMillis, void> {
static void write(std::string& accumulator, SystemTimeMillis value) {
accumulator += std::to_string(static_cast<int64_t>(value));
}
};
template <>
struct LogWriter<SteadyPointMillis, void> {
static void write(std::string& accumulator, SteadyPointMillis value) {
accumulator += std::to_string(static_cast<int64_t>(value));
}
};
template <typename T>
struct LogWriter<T, typename std::enable_if_t<std::is_enum<T>::value>> {
static void write(std::string& accumulator, T const& value) {
using IntType = typename std::underlying_type<T>::type;
accumulator += std::to_string(static_cast<IntType>(value));
}
};
class LogAccumulator {
public:
explicit LogAccumulator(LogLevel level,
#if defined(LOG_WITH_FILE_AND_LINE)
std::string_view file, int line,
#endif
std::string_view function)
: metadata_ {level,
#if defined(LOG_WITH_FILE_AND_LINE)
file, line,
#endif
function},
enabled_(IsLoggingEnabled(level))
{
}
~LogAccumulator() {
PrintLogMessage(metadata_, accumulator_);
}
[[nodiscard]] bool getDone() const {
return !enabled_ || done_;
}
void setDone(bool done) {
done_ = done;
}
template <typename T>
friend LogAccumulator& operator<<(LogAccumulator& os, T const& value) {
LogWriter<T>::write(os.accumulator_, value);
return os;
}
private:
LogMetadata metadata_;
bool enabled_;
bool done_ = false;
std::string accumulator_;
};
namespace detail {
constexpr char const* FileName(char const* path) {
char const* it = path;
char const* result = it;
while (true) {
auto ch = *(it++);
if (ch == '\0')
break;
if (ch == '/')
result = it;
}
return result;
}
}
class NoOpAccumulator {
template <typename T>
friend NoOpAccumulator& operator<<(NoOpAccumulator& os, T const&) {
return os;
}
};
}
#ifndef CHARGELAB_DISABLE_LOGGING
#if defined(LOG_WITH_FILE_AND_LINE)
#define CHARGELAB_LOG_MESSAGE(level) \
for (::chargelab::LogAccumulator accumulator{::chargelab::LogLevel::level, ::chargelab::detail::FileName(__FILE__), __LINE__, __func__}; !accumulator.getDone(); accumulator.setDone(true)) \
accumulator
#else
#define CHARGELAB_LOG_MESSAGE(level) \
for (::chargelab::logging::LogAccumulator accumulator{::chargelab::logging::LogLevel::level, __func__}; !accumulator.getDone(); accumulator.setDone(true)) \
accumulator
#endif
#else
#define CHARGELAB_LOG_MESSAGE(level) \
for (::chargelab::NoOpAccumulator accumulator{}; false;) \
accumulator
#endif
#endif //CHARGELAB_OPEN_FIRMWARE_LOGGING_H

View File

@@ -0,0 +1,94 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_MACRO_H
#define CHARGELAB_OPEN_FIRMWARE_MACRO_H
#if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(JSON_NOEXCEPTION)
#define CHARGELAB_THROW(exception) throw exception
#define CHARGELAB_TRY try
#define CHARGELAB_CATCH catch(std::exception const& e)
#define CHARGELAB_RETHROW throw
#else
#include <cstdlib>
#define CHARGELAB_THROW(exception) std::abort()
#define CHARGELAB_TRY if(true)
#define CHARGELAB_CATCH for(std::exception e; false;)
#define CHARGELAB_RETHROW
#endif
#define CHARGELAB_STR(x) #x
#define CHARGELAB_XSTR(x) CHARGELAB_STR(x)
// echo -n "#define CHARGELAB_NUM_ARGS_IMPL(x1"; for x in `seq 2 64`; do echo -n ",x$x"; done; echo ",N,...) N"; echo -n "#define CHARGELAB_NUM_ARGS(...) CHARGELAB_NUM_ARGS_IMPL(__VA_ARGS__ __VA_OPT__(,)64"; for x in `seq 63 -1 0`; do echo -n ",$x"; done; echo ")";
#define CHARGELAB_NUM_ARGS_IMPL(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62,x63,x64,N,...) N
#define CHARGELAB_NUM_ARGS(...) CHARGELAB_NUM_ARGS_IMPL(__VA_ARGS__ __VA_OPT__(,)64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0)
// echo "#define CHARGELAB_EXPAND(x) x"; for x in `seq 0 64`; do echo -n "#define CHARGELAB_EXPAND_$x(f"; for y in `seq 1 $x`; do echo -n ",x$y"; done; echo -n ")"; for y in `seq 1 $x`; do echo -n " f(x$y)"; done; echo; done; echo -n "#define CHARGELAB_GET_MACRO(f"; for x in `seq 1 64`; do echo -n ",x$x"; done; echo -n ", TARGET, ...) TARGET"; echo; echo -n "#define CHARGELAB_PASTE(...) CHARGELAB_EXPAND(CHARGELAB_GET_MACRO(__VA_ARGS__"; for x in `seq 64 -1 0`; do echo -n ",CHARGELAB_EXPAND_$x"; done; echo "))";
#define CHARGELAB_EXPAND(x) x
#define CHARGELAB_EXPAND_0(f)
#define CHARGELAB_EXPAND_1(f,x1) f(x1)
#define CHARGELAB_EXPAND_2(f,x1,x2) f(x1) f(x2)
#define CHARGELAB_EXPAND_3(f,x1,x2,x3) f(x1) f(x2) f(x3)
#define CHARGELAB_EXPAND_4(f,x1,x2,x3,x4) f(x1) f(x2) f(x3) f(x4)
#define CHARGELAB_EXPAND_5(f,x1,x2,x3,x4,x5) f(x1) f(x2) f(x3) f(x4) f(x5)
#define CHARGELAB_EXPAND_6(f,x1,x2,x3,x4,x5,x6) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6)
#define CHARGELAB_EXPAND_7(f,x1,x2,x3,x4,x5,x6,x7) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7)
#define CHARGELAB_EXPAND_8(f,x1,x2,x3,x4,x5,x6,x7,x8) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8)
#define CHARGELAB_EXPAND_9(f,x1,x2,x3,x4,x5,x6,x7,x8,x9) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9)
#define CHARGELAB_EXPAND_10(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10)
#define CHARGELAB_EXPAND_11(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11)
#define CHARGELAB_EXPAND_12(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12)
#define CHARGELAB_EXPAND_13(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13)
#define CHARGELAB_EXPAND_14(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14)
#define CHARGELAB_EXPAND_15(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15)
#define CHARGELAB_EXPAND_16(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16)
#define CHARGELAB_EXPAND_17(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17)
#define CHARGELAB_EXPAND_18(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18)
#define CHARGELAB_EXPAND_19(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19)
#define CHARGELAB_EXPAND_20(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20)
#define CHARGELAB_EXPAND_21(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21)
#define CHARGELAB_EXPAND_22(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22)
#define CHARGELAB_EXPAND_23(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23)
#define CHARGELAB_EXPAND_24(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24)
#define CHARGELAB_EXPAND_25(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25)
#define CHARGELAB_EXPAND_26(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26)
#define CHARGELAB_EXPAND_27(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27)
#define CHARGELAB_EXPAND_28(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28)
#define CHARGELAB_EXPAND_29(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29)
#define CHARGELAB_EXPAND_30(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30)
#define CHARGELAB_EXPAND_31(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31)
#define CHARGELAB_EXPAND_32(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32)
#define CHARGELAB_EXPAND_33(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33)
#define CHARGELAB_EXPAND_34(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34)
#define CHARGELAB_EXPAND_35(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35)
#define CHARGELAB_EXPAND_36(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36)
#define CHARGELAB_EXPAND_37(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37)
#define CHARGELAB_EXPAND_38(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38)
#define CHARGELAB_EXPAND_39(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39)
#define CHARGELAB_EXPAND_40(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40)
#define CHARGELAB_EXPAND_41(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41)
#define CHARGELAB_EXPAND_42(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42)
#define CHARGELAB_EXPAND_43(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43)
#define CHARGELAB_EXPAND_44(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44)
#define CHARGELAB_EXPAND_45(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45)
#define CHARGELAB_EXPAND_46(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46)
#define CHARGELAB_EXPAND_47(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47)
#define CHARGELAB_EXPAND_48(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48)
#define CHARGELAB_EXPAND_49(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49)
#define CHARGELAB_EXPAND_50(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50)
#define CHARGELAB_EXPAND_51(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51)
#define CHARGELAB_EXPAND_52(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52)
#define CHARGELAB_EXPAND_53(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53)
#define CHARGELAB_EXPAND_54(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54)
#define CHARGELAB_EXPAND_55(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55)
#define CHARGELAB_EXPAND_56(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56)
#define CHARGELAB_EXPAND_57(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57)
#define CHARGELAB_EXPAND_58(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58)
#define CHARGELAB_EXPAND_59(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59)
#define CHARGELAB_EXPAND_60(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60)
#define CHARGELAB_EXPAND_61(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60) f(x61)
#define CHARGELAB_EXPAND_62(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60) f(x61) f(x62)
#define CHARGELAB_EXPAND_63(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62,x63) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60) f(x61) f(x62) f(x63)
#define CHARGELAB_EXPAND_64(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62,x63,x64) f(x1) f(x2) f(x3) f(x4) f(x5) f(x6) f(x7) f(x8) f(x9) f(x10) f(x11) f(x12) f(x13) f(x14) f(x15) f(x16) f(x17) f(x18) f(x19) f(x20) f(x21) f(x22) f(x23) f(x24) f(x25) f(x26) f(x27) f(x28) f(x29) f(x30) f(x31) f(x32) f(x33) f(x34) f(x35) f(x36) f(x37) f(x38) f(x39) f(x40) f(x41) f(x42) f(x43) f(x44) f(x45) f(x46) f(x47) f(x48) f(x49) f(x50) f(x51) f(x52) f(x53) f(x54) f(x55) f(x56) f(x57) f(x58) f(x59) f(x60) f(x61) f(x62) f(x63) f(x64)
#define CHARGELAB_GET_MACRO(f,x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16,x17,x18,x19,x20,x21,x22,x23,x24,x25,x26,x27,x28,x29,x30,x31,x32,x33,x34,x35,x36,x37,x38,x39,x40,x41,x42,x43,x44,x45,x46,x47,x48,x49,x50,x51,x52,x53,x54,x55,x56,x57,x58,x59,x60,x61,x62,x63,x64, TARGET, ...) TARGET
#define CHARGELAB_PASTE(...) CHARGELAB_EXPAND(CHARGELAB_GET_MACRO(__VA_ARGS__,CHARGELAB_EXPAND_64,CHARGELAB_EXPAND_63,CHARGELAB_EXPAND_62,CHARGELAB_EXPAND_61,CHARGELAB_EXPAND_60,CHARGELAB_EXPAND_59,CHARGELAB_EXPAND_58,CHARGELAB_EXPAND_57,CHARGELAB_EXPAND_56,CHARGELAB_EXPAND_55,CHARGELAB_EXPAND_54,CHARGELAB_EXPAND_53,CHARGELAB_EXPAND_52,CHARGELAB_EXPAND_51,CHARGELAB_EXPAND_50,CHARGELAB_EXPAND_49,CHARGELAB_EXPAND_48,CHARGELAB_EXPAND_47,CHARGELAB_EXPAND_46,CHARGELAB_EXPAND_45,CHARGELAB_EXPAND_44,CHARGELAB_EXPAND_43,CHARGELAB_EXPAND_42,CHARGELAB_EXPAND_41,CHARGELAB_EXPAND_40,CHARGELAB_EXPAND_39,CHARGELAB_EXPAND_38,CHARGELAB_EXPAND_37,CHARGELAB_EXPAND_36,CHARGELAB_EXPAND_35,CHARGELAB_EXPAND_34,CHARGELAB_EXPAND_33,CHARGELAB_EXPAND_32,CHARGELAB_EXPAND_31,CHARGELAB_EXPAND_30,CHARGELAB_EXPAND_29,CHARGELAB_EXPAND_28,CHARGELAB_EXPAND_27,CHARGELAB_EXPAND_26,CHARGELAB_EXPAND_25,CHARGELAB_EXPAND_24,CHARGELAB_EXPAND_23,CHARGELAB_EXPAND_22,CHARGELAB_EXPAND_21,CHARGELAB_EXPAND_20,CHARGELAB_EXPAND_19,CHARGELAB_EXPAND_18,CHARGELAB_EXPAND_17,CHARGELAB_EXPAND_16,CHARGELAB_EXPAND_15,CHARGELAB_EXPAND_14,CHARGELAB_EXPAND_13,CHARGELAB_EXPAND_12,CHARGELAB_EXPAND_11,CHARGELAB_EXPAND_10,CHARGELAB_EXPAND_9,CHARGELAB_EXPAND_8,CHARGELAB_EXPAND_7,CHARGELAB_EXPAND_6,CHARGELAB_EXPAND_5,CHARGELAB_EXPAND_4,CHARGELAB_EXPAND_3,CHARGELAB_EXPAND_2,CHARGELAB_EXPAND_1,CHARGELAB_EXPAND_0)(__VA_ARGS__))
#endif //CHARGELAB_OPEN_FIRMWARE_MACRO_H

View File

@@ -0,0 +1,129 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_OPERATION_HOLDER_H
#define CHARGELAB_OPEN_FIRMWARE_OPERATION_HOLDER_H
#include "openocpp/interface/component/system_interface.h"
#include "openocpp/common/logging.h"
#include <optional>
#include <memory>
namespace chargelab {
struct NoOperation {};
inline constexpr NoOperation kNoOperation;
template <typename T>
class OperationHolder {
public:
explicit OperationHolder(std::shared_ptr<SystemInterface> system)
: system_(std::move(system))
{
assert(system_ != nullptr);
}
OperationHolder& operator=(NoOperation const&) {
state_ = std::nullopt;
consecutive_failures_ = std::nullopt;
idle_since_ = system_->steadyClockNow();
return *this;
}
bool operator==(NoOperation const&) const {
return !operationInProgress();
}
bool operator==(T const& rhs) const {
if (!state_.has_value())
return false;
return state_->first == rhs && operationInProgress();
}
bool operator!=(NoOperation const& rhs) const {
return !operator==(rhs);
}
bool operator!=(T const& rhs) const {
return !operator==(rhs);
}
[[nodiscard]] int consecutiveFailures() const {
int consecutive_failures = 0;
if (consecutive_failures_.has_value()) {
consecutive_failures = consecutive_failures_.value();
}
if (currentOperationTimedOut()) {
consecutive_failures++;
}
return consecutive_failures;
}
[[nodiscard]] bool currentOperationTimedOut() const {
return state_.has_value() && !operationInProgress();
}
[[nodiscard]] bool operationInProgress() const {
if (!state_.has_value())
return false;
auto const delta = system_->steadyClockNow() - state_->second;
if (delta < 0) {
CHARGELAB_LOG_MESSAGE(error) << "Invalid steady clock delta: " << delta;
return false;
} else if (delta >= timeout_seconds_*1000) {
return false;
}
return true;
}
[[nodiscard]] int getIdleDurationSeconds() const {
if (!state_.has_value()) {
if (idle_since_.has_value()) {
return (system_->steadyClockNow() - idle_since_.value())/1000;
} else {
return std::numeric_limits<int>::max();
}
}
auto const delta = (system_->steadyClockNow() - state_->second)/1000 - timeout_seconds_;
if (delta < 0)
return 0;
if (delta >= std::numeric_limits<int>::max())
return std::numeric_limits<int>::max();
return delta;
}
[[nodiscard]] bool wasIdleFor(int seconds) {
return !operationInProgress() && getIdleDurationSeconds() >= seconds;
}
void setWithTimeout(int timeout_seconds, std::optional<T> const& id) {
timeout_seconds_ = timeout_seconds;
if (id.has_value()) {
state_ = std::make_pair(id.value(), system_->steadyClockNow());
if (consecutive_failures_.has_value()) {
consecutive_failures_ = consecutive_failures_.value() + 1;
} else {
consecutive_failures_ = 0;
}
} else {
state_ = std::nullopt;
consecutive_failures_ = std::nullopt;
}
}
private:
std::shared_ptr<SystemInterface> system_;
int timeout_seconds_;
std::optional<std::pair<T, SteadyPointMillis>> state_ = std::nullopt;
std::optional<int> consecutive_failures_ = std::nullopt;
std::optional<SteadyPointMillis> idle_since_ = std::nullopt;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_OPERATION_HOLDER_H

View File

@@ -0,0 +1,81 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_RING_BUFFER_H
#define CHARGELAB_OPEN_FIRMWARE_RING_BUFFER_H
#include <array>
#include <optional>
#include <cassert>
namespace chargelab {
template <typename T, int N>
class RingBuffer {
public:
void pushBack(T value) {
int index;
if (size_ < (int)buffer_.size()) {
index = (begin_index_ + size_++) % buffer_.size();
} else {
index = begin_index_;
begin_index_ = (begin_index_ + 1) % buffer_.size();
}
buffer_[index] = std::move(value);
}
std::optional<T> popFront() {
if (size_ <= 0)
return std::nullopt;
T result = removeAt(begin_index_);
begin_index_ = (begin_index_ + 1) % buffer_.size();
size_--;
return result;
}
std::optional<T> popBack() {
if (size_ <= 0)
return std::nullopt;
T result = removeAt((begin_index_ + size_ - 1) % buffer_.size());
size_--;
return result;
}
T& front() {
return buffer_[begin_index_];
}
T& back() {
return buffer_[(begin_index_ + size_ - 1) % buffer_.size()];
}
T operator[] (int i) const {
assert(i >= 0 && i < size_);
return buffer_[(begin_index_ + i) % buffer_.size()];
}
T& operator[] (int i) {
assert(i >= 0 && i < size_);
return buffer_[(begin_index_ + i) % buffer_.size()];
}
[[nodiscard]] bool empty() const {
return size_ <= 0;
}
[[nodiscard]] int size() const {
return size_;
}
private:
T removeAt(int index) {
T result {};
std::swap(result, buffer_[index]);
return result;
}
private:
std::array<T, N> buffer_;
int begin_index_ = 0;
int size_ = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_RING_BUFFER_H

View File

@@ -0,0 +1,108 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_SERIALIZATION_H
#define CHARGELAB_OPEN_FIRMWARE_SERIALIZATION_H
#include <type_traits>
#include <optional>
#include <string>
#include <cstring>
namespace chargelab {
// Note: this file provides a number of very simple binary serialization wrappers that are used in certain contexts
// where JSON was too expensive (such as maintaining a large number of historic messages as a compressed stream).
template <typename T>
typename std::enable_if<std::is_trivial<T>::value, std::optional<std::size_t>>::type
readPrimitive(std::string_view const& text, std::optional<std::size_t> index, T& out) {
if (!index.has_value())
return std::nullopt;
if (index.value() + sizeof(out) > text.size())
return std::nullopt;
std::memcpy(&out, text.data() + index.value(), sizeof(out));
return index.value() + sizeof(out);
}
template <typename T>
typename std::enable_if<std::is_trivial<typename T::Value>::value, std::optional<std::size_t>>::type
readPrimitive(std::string_view const& text, std::optional<std::size_t> index, T& out) {
using raw_type = typename T::Value;
if (!index.has_value())
return std::nullopt;
if (index.value() + sizeof(raw_type{}) > text.size())
return std::nullopt;
raw_type raw;
std::memcpy(&raw, text.data() + index.value(), sizeof(raw));
out = raw;
return index.value() + sizeof(raw);
}
inline std::optional<std::size_t> readPrimitive(std::string_view const& text, std::optional<std::size_t> index, std::string& out) {
int32_t size = 0;
index = readPrimitive(text, index, size);
if (!index.has_value() || size < 0)
return std::nullopt;
if (index.value() + size > text.size())
return std::nullopt;
out.resize(size);
std::memcpy(out.data(), text.data() + index.value(), size);
return index.value() + size;
}
template <typename T>
std::optional<std::size_t> readPrimitive(std::string_view const& text, std::optional<std::size_t> index, std::optional<T>& out) {
bool present = false;
index = readPrimitive(text, index, present);
if (!index.has_value())
return std::nullopt;
if (present) {
T value;
auto result = readPrimitive(text, index, value);
out = std::move(value);
return result;
} else {
return index;
}
}
template <typename T>
typename std::enable_if<std::is_trivial<T>::value>::type
writePrimitive(std::string& text, T const& in) {
auto index = text.size();
text.resize(index + sizeof(in));
std::memcpy(text.data() + index, &in, sizeof(in));
}
template <typename T>
typename std::enable_if<std::is_trivial<typename T::Value>::value>::type
writePrimitive(std::string& text, T const& in) {
using raw_type = typename T::Value;
raw_type raw = in;
auto index = text.size();
text.resize(index + sizeof(raw));
std::memcpy(text.data() + index, &raw, sizeof(raw));
}
inline void writePrimitive(std::string& text, std::string const& in) {
writePrimitive(text, (int32_t)in.size());
auto index = text.size();
text.resize(index + in.size());
std::memcpy(text.data() + index, in.data(), in.size());
}
template <typename T>
void writePrimitive(std::string& text, std::optional<T> const& in) {
if (in.has_value()) {
writePrimitive(text, true);
writePrimitive(text, in.value());
} else {
writePrimitive(text, false);
}
}
}
#endif //CHARGELAB_OPEN_FIRMWARE_SERIALIZATION_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_STORAGE_H
#define CHARGELAB_OPEN_FIRMWARE_STORAGE_H
#include <string>
#include "openocpp/interface/element/storage_interface.h"
namespace chargelab {
namespace detail {
class CloseFileWrapper {
public:
CloseFileWrapper(std::FILE* file)
: file_(file)
{
}
~CloseFileWrapper() {
std::fclose(file_);
}
private:
std::FILE* file_;
};
}
class StorageFile : public StorageInterface {
public:
explicit StorageFile(std::string filename) : filename_(std::move(filename))
{
}
public:
bool read(std::function<bool(FILE *)> const& function) override {
auto file = std::fopen(filename_.c_str(), "r");
if (file == nullptr)
return false;
detail::CloseFileWrapper wrapper{file};
return function(file);
}
bool write(std::function<bool(FILE *)> const& function) override {
auto file = std::fopen(filename_.c_str(), "w");
if (file == nullptr)
return false;
detail::CloseFileWrapper wrapper{file};
return function(file);
}
private:
std::string filename_;
};
class StorageNull : public StorageInterface {
public:
bool read(std::function<bool(FILE *)> const&) override {
return false;
}
bool write(std::function<bool(FILE *)> const&) override {
return false;
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_STORAGE_H

View File

@@ -0,0 +1,150 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_STREAM_H
#define CHARGELAB_OPEN_FIRMWARE_STREAM_H
#include <array>
#include <utility>
#include "openocpp/interface/element/byte_reader_interface.h"
#include "openocpp/interface/element/byte_writer_interface.h"
namespace chargelab::stream {
class BufferedByteWriter {
public:
BufferedByteWriter(ByteWriterInterface& output)
: output_(output)
{
}
void put(char ch) {
if (index_ >= buffer_.size())
flush();
buffer_[index_++] = ch;
}
void write(char const* s, std::size_t n) {
for (int i = 0; i < n; i++) {
put(s[i]);
}
}
void write(char const* s) {
write(s, std::strlen(s));
}
void write(std::string const &s) {
write(s.c_str(), s.size());
}
void flush() {
if (index_ == 0)
return;
output_.write(buffer_.data(), index_);
index_ = 0;
}
private:
ByteWriterInterface& output_;
std::array<char, 128> buffer_ {};
std::size_t index_ = 0;
};
class BufferedByteReader {
public:
BufferedByteReader(ByteReaderInterface& input)
: input_(input)
{
next();
}
int peek() const {
if (index_ >= count_)
return EOF;
return buffer_[index_];
}
int take() {
if (index_ >= count_)
return EOF;
auto const result = buffer_[index_++];
if (index_ >= count_ && count_ == buffer_.size())
next();
return result;
}
std::size_t tellg() const {
return offset_ + index_;
}
private:
void next() {
count_ = input_.read(buffer_.data(), buffer_.size());
index_ = 0;
}
private:
ByteReaderInterface& input_;
std::array<char, 128> buffer_ {};
std::size_t index_ = 0;
std::size_t count_ = 0;
std::size_t offset_ = 0;
};
class StringReader : public ByteReaderInterface {
public:
explicit StringReader(std::string text) : text_(std::move(text))
{
}
size_t read(char *s, std::size_t n) override {
if (index_ >= text_.size())
return 0;
std::size_t count = std::min(n, text_.size() - index_);
std::memcpy(s, &text_[index_], count);
index_ += count;
return count;
}
private:
std::string text_;
std::size_t index_ = 0;
};
class StringWriter : public ByteWriterInterface {
public:
void write(char const *s, std::size_t count) override {
auto const index = text_.size();
text_.resize(text_.size() + count);
std::memcpy(&text_[index], s, count);
}
std::string const& str() const {
return text_;
}
private:
std::string text_;
};
class SizeCalculator : public ByteWriterInterface {
public:
[[nodiscard]] std::size_t getTotalBytes() const {
return total_bytes_;
}
private:
void write(const char *, std::size_t count) override {
total_bytes_ += count;
}
private:
std::size_t total_bytes_ = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_STREAM_H

View File

@@ -0,0 +1,82 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_CHRONO_H
#define CHARGELAB_OPEN_FIRMWARE_CHRONO_H
#include "openocpp/model/system_types.h"
#include "openocpp/common/logging.h"
#include <optional>
#include <string>
#include <sstream>
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
namespace chargelab::chrono {
namespace detail {
inline bool isLeapYear(unsigned int year) {
return (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0));
}
// month: 1-based
inline int getDaysOfMonth(unsigned int year, int month) {
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
case 4:
case 6:
case 9:
case 11:
return 30;
case 2:
return isLeapYear(year) ? 29 : 28;
default:
return -1;
}
}
inline int totalLeapYears(int year) {
return year/4 - year/100 + year/400;
}
}
inline std::optional<std::string> ToString(SystemTimeMillis const& timestamp) {
return ocpp1_6::DateTime::timestampToText(timestamp);
}
inline std::optional<SystemTimeMillis> FromString(std::string const& text) {
return ocpp1_6::DateTime::textToTimestamp(text);
}
inline std::time_t timegm2(std::tm const *tm) {
if (tm == nullptr) return -1;
if (tm->tm_mon >= 12 || tm->tm_hour >= 24 || tm->tm_min >= 60 || tm->tm_sec >= 61) {
return -1;
}
if (detail::getDaysOfMonth(tm->tm_year + 1900, tm->tm_mon + 1) == -1) {
return -1;
}
// get the day to the tm date since 1900-01-01 00:00:00 +0000, UTC instead of the Epoch (1970-01-01 00:00:00 +0000, UTC)
std::int64_t total_days = 0; //
int year = tm->tm_year + 1900;
auto const year_since_epoch = year - 1970;
int leap_years = detail::totalLeapYears(year - 1) - detail::totalLeapYears(1970);
total_days = year_since_epoch * 365 + leap_years;
for (int i = 0; i < tm->tm_mon; ++i) {
total_days += detail::getDaysOfMonth(year, i+1);
}
total_days += tm->tm_mday - 1; // tm_mday is 1-31
std::int64_t total_hours = total_days*24 + tm->tm_hour;
std::time_t total_seconds = (total_hours*60 + tm->tm_min)*60 + tm->tm_sec;
return total_seconds /*+ kSecondsFrom1990*/;
}
}
#endif //CHARGELAB_OPEN_FIRMWARE_CHRONO_H

View File

@@ -0,0 +1,25 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_MAP_H
#define CHARGELAB_OPEN_FIRMWARE_MAP_H
#include <utility>
namespace chargelab {
namespace container {
template <typename T, typename V>
bool contains(T&& container, V const& value) {
return container.find(value) != container.end();
}
template <typename T>
bool containsAny(T&&) {
return true;
}
template <typename T, typename V, typename... Tail>
bool containsAny(T&& container, V const& head, Tail... tail) {
return contains(container, head) || containsAny(container, std::forward<Tail>(tail)...);
}
}
}
#endif //CHARGELAB_OPEN_FIRMWARE_MAP_H

View File

@@ -0,0 +1,76 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_FILE_H
#define CHARGELAB_OPEN_FIRMWARE_FILE_H
#include "openocpp/common/logging.h"
#include "openocpp/helpers/json.h"
#include <cstdio>
#include <cctype>
namespace chargelab::file {
namespace detail {
template<typename CharType=char>
class FileByteWriter : public ByteWriterInterface {
public:
FileByteWriter(FILE* file) : file_(file) {
assert(file_ != nullptr);
}
~FileByteWriter() {
}
void write(const char *s, std::size_t length) override {
std::fwrite(s, sizeof(CharType), length, file_);
}
private:
FILE* file_;
std::array<CharType, 128> buffer_ {};
std::size_t index_ = 0;
};
}
inline bool is_eof_ignore_whitespace(FILE* file) {
while (true) {
auto ch = std::fgetc(file);
if (ch == EOF) {
return true;
}
if (!std::isspace(ch)) {
std::ungetc(ch, file);
return false;
}
}
}
template <typename T>
std::optional<T> json_read_object_from_file(FILE* file) {
if (is_eof_ignore_whitespace(file))
return std::nullopt;
int ch;
std::string line;
while ((ch = std::fgetc(file)) != EOF) {
if (ch == '\r' || ch == '\n')
break;
line += (char)ch;
}
auto const result = read_json_from_string<T>(line);
if (!result.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed parsing line as JSON: " << line;
return std::nullopt;
}
return result;
}
template<typename T>
void json_write_object_to_file(FILE* file, T&& value) {
write_json(detail::FileByteWriter<char> {file}, value);
std::fputc('\n', file);
}
}
#endif //CHARGELAB_OPEN_FIRMWARE_FILE_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_OPTIONAL_H
#define CHARGELAB_OPEN_FIRMWARE_OPTIONAL_H
#include <optional>
namespace chargelab::optional {
template <typename T, typename C>
inline void IfPresent(std::optional<T> const& container, C&& consumer) {
if (container.has_value()) {
consumer(container.value());
}
}
template <typename T>
inline T GetOrDefault(std::optional<T> const& currentValue, T const& defaultValue) {
if (currentValue.has_value()) {
return currentValue.value();
} else {
return defaultValue;
}
}
}
#endif //CHARGELAB_OPEN_FIRMWARE_OPTIONAL_H

View File

@@ -0,0 +1,40 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_SET_H
#define CHARGELAB_OPEN_FIRMWARE_SET_H
#include <set>
namespace chargelab {
namespace set {
template <typename T, typename V>
bool contains(std::vector<T> const& container, V const& value) {
return std::find(container.begin(), container.end(), value) != container.end();
}
template <typename T>
bool containsAny(std::vector<T> const&) {
return false;
}
template <typename T, typename V, typename... Tail>
bool containsAny(std::vector<T> const& container, V const& head, Tail... tail) {
return contains(container, head) || containsAny(container, std::forward<Tail>(tail)...);
}
template <typename T, typename V>
bool contains(std::set<T> const& container, V const& value) {
return container.find(value) != container.end();
}
template <typename T>
bool containsAny(std::set<T> const&) {
return false;
}
template <typename T, typename V, typename... Tail>
bool containsAny(std::set<T> const& container, V const& head, Tail... tail) {
return contains(container, head) || containsAny(container, std::forward<Tail>(tail)...);
}
}
}
#endif //CHARGELAB_OPEN_FIRMWARE_SET_H

View File

@@ -0,0 +1,161 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_STRING_H
#define CHARGELAB_OPEN_FIRMWARE_STRING_H
#include <string>
#include <array>
#include <optional>
#include <limits.h>
#include <cstdint>
namespace chargelab::string {
namespace detail {
char const kHexCharacters[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D' ,'E', 'F'};
}
inline bool BeginsWithIgnoreCaseAscii(std::string const& text, std::string const& prefix) {
if (text.length() < prefix.length())
return false;
auto text_it = text.begin();
auto prefix_it = prefix.begin();
while (prefix_it != prefix.end()) {
auto text_ch = *(text_it++);
auto prefix_ch = *(prefix_it++);
if (std::tolower(text_ch) != std::tolower(prefix_ch)) {
return false;
}
}
return true;
}
inline bool EqualsIgnoreCaseAscii(std::string const& lhs, std::string const& rhs) {
return lhs.size() == rhs.size() && BeginsWithIgnoreCaseAscii(lhs, rhs);
}
inline std::optional<int> ToInteger(std::string const& text) {
if (text.empty())
return std::nullopt;
errno = 0;
char* end = nullptr;
auto const result = strtol(text.c_str(), &end, 10);
auto const parse_error = errno;
if (end != text.c_str() + text.size()) {
return std::nullopt;
}
if (parse_error != 0) {
return std::nullopt;
}
return result;
}
inline std::optional<std::int64_t> ToInteger64(std::string const& text) {
if (text.empty())
return std::nullopt;
errno = 0;
char* end = nullptr;
auto const result = strtoll(text.c_str(), &end, 10);
auto const parse_error = errno;
if (end != text.c_str() + text.size()) {
return std::nullopt;
}
if (parse_error != 0) {
return std::nullopt;
}
return result;
}
inline std::optional<double> ToDouble(std::string const& text) {
if (text.empty())
return std::nullopt;
errno = 0;
char* end = nullptr;
auto const result = strtod(text.c_str(), &end);
auto const parse_error = errno;
if (end != text.c_str() + text.size()) {
return std::nullopt;
}
if (parse_error != 0) {
return std::nullopt;
}
return result;
}
template<typename T>
typename std::enable_if<
std::is_integral<T>::value,
std::string
>::type ToHexValue(T value) {
if (value == 0)
return "0x0";
std::string result;
if (value < 0) {
value = -value;
result = "-0x";
} else {
result = "0x";
}
for (int i=0; i < (int)sizeof(T); i++) {
auto const offset = (sizeof(T) - i - 1) * CHAR_BIT;
auto const byte = (value >> offset) & 0xFF;
result += detail::kHexCharacters[byte >> 4];
result += detail::kHexCharacters[byte & 0xF];
}
return result;
}
inline std::string ToHexString(std::uint8_t const* begin, std::uint8_t const* end, char const* separator = " ") {
std::string result;
char const* ifs = "";
for (auto it = begin; it != end; it++) {
auto const byte = *it;
result += ifs;
result += detail::kHexCharacters[byte >> 4];
result += detail::kHexCharacters[byte & 0xF];
ifs = separator;
}
return result;
}
inline std::string ToHexString(std::uint8_t const* begin, std::size_t length, char const* separator = " ") {
return ToHexString(begin, begin+length, separator);
}
template <std::size_t N>
inline std::string ToHexString(std::array<std::uint8_t, N> const& array, char const* separator = " ") {
return ToHexString(array.data(), array.size(), separator);
}
template <typename F>
inline void SplitVisitor(std::string const& text, std::string const& delimiter, F&& visitor) {
std::size_t begin = 0;
while (true) {
auto it = text.find(delimiter, begin);
if (it == std::string::npos) {
visitor(text.substr(begin));
break;
}
visitor(text.substr(begin, it-begin));
begin = it + delimiter.size();
}
}
inline std::string zeroPad(std::string const& text, int length) {
std::string result;
result.resize(std::max(length - text.size(), (std::size_t)0), '0');
return result + text;
}
}
#endif //CHARGELAB_OPEN_FIRMWARE_STRING_H

View File

@@ -0,0 +1,251 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_URI_H
#define CHARGELAB_OPEN_FIRMWARE_URI_H
#include "openocpp/helpers/string.h"
#include "openocpp/helpers/json.h"
#include "openocpp/helpers/optional.h"
#include <cstdlib>
#include <regex>
namespace chargelab::uri {
namespace detail {
inline std::pair<std::string, std::optional<std::string>> SplitLeft(std::string const& text, std::string const& delimiter) {
auto it = text.find_first_of(delimiter);
if (it == std::string::npos)
return std::make_pair(text, std::nullopt);
return std::make_pair(
text.substr(0, it),
std::make_optional(text.substr(it + delimiter.size()))
);
}
inline std::pair<std::optional<std::string>, std::string> SplitRight(std::string const& text, std::string const& delimiter) {
auto result = SplitLeft(text, delimiter);
if (result.second.has_value()) {
return std::make_pair(std::make_optional(result.first), result.second.value());
} else {
return std::make_pair(std::nullopt, result.first);
}
}
int GetHexValue(char ch) {
if (ch >= '0' && ch <= '9')
return ch-'0';
if (ch >= 'a' && ch <= 'f')
return (ch-'a')+10;
if (ch >= 'A' && ch <= 'F')
return (ch-'A')+10;
return -1;
}
}
// TODO: Update to ws/wss
CHARGELAB_JSON_ENUM(WebsocketScheme,
WS,
WSS,
)
struct WebsocketParts {
WebsocketScheme scheme = WebsocketScheme::kValueNotFoundInEnum;
std::string host {};
int port = -1;
std::string path {};
CHARGELAB_JSON_INTRUSIVE(WebsocketParts, scheme, host, port, path)
};
inline std::optional<WebsocketParts> ParseWebsocketUri(std::string const& uri) {
WebsocketParts result {};
auto scheme_fragment = detail::SplitLeft(uri, "://");
if (!scheme_fragment.second.has_value())
return std::nullopt;
auto scheme = scheme_fragment.first;
if (string::EqualsIgnoreCaseAscii(scheme, "ws")) {
result.scheme = WebsocketScheme::kWS;
} else if (string::EqualsIgnoreCaseAscii(scheme, "wss")) {
result.scheme = WebsocketScheme::kWSS;
} else {
CHARGELAB_LOG_MESSAGE(error) << "Bad scheme in: " << uri;
return std::nullopt;
}
auto target_fragment = detail::SplitLeft(scheme_fragment.second.value(), "/");
auto host_port_fragment = detail::SplitLeft(target_fragment.first, ":");
if (host_port_fragment.second.has_value()) {
auto port = string::ToInteger(host_port_fragment.second.value());
if (!port.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed parsing port text in: " << uri;
return std::nullopt;
}
result.port = port.value();
} else {
switch (result.scheme) {
default:
assert(false && "Unexpected websocket protocol");
case WebsocketScheme::kWS:
result.port = 80;
break;
case WebsocketScheme::kWSS:
result.port = 443;
break;
}
}
result.host = host_port_fragment.first;
result.path = "/" + optional::GetOrDefault<std::string>(target_fragment.second, "");
CHARGELAB_LOG_MESSAGE(trace) << "Parsed websocket URI: " << result;
return result;
}
CHARGELAB_JSON_ENUM(FtpScheme,
FTP,
FTPS,
)
struct FtpParts {
FtpScheme scheme = FtpScheme::kValueNotFoundInEnum;
std::optional<std::string> username = std::nullopt;
std::optional<std::string> password = std::nullopt;
std::string host {};
int port = -1;
std::string path {};
CHARGELAB_JSON_INTRUSIVE(FtpParts, scheme, username, password, host, port, path)
};
inline std::optional<FtpParts> ParseFtpUri(std::string const& uri) {
FtpParts result {};
auto scheme_fragment = detail::SplitLeft(uri, "://");
if (!scheme_fragment.second.has_value())
return std::nullopt;
auto scheme = scheme_fragment.first;
if (string::EqualsIgnoreCaseAscii(scheme, "ftp")) {
result.scheme = FtpScheme::kFTP;
} else if (string::EqualsIgnoreCaseAscii(scheme, "ftps")) {
result.scheme = FtpScheme::kFTPS;
} else {
CHARGELAB_LOG_MESSAGE(error) << "Bad scheme in: " << uri;
return std::nullopt;
}
auto target_fragment = detail::SplitLeft(scheme_fragment.second.value(), "/");
auto credentials_fragment = detail::SplitRight(target_fragment.first, "@");
auto host_port_fragment = detail::SplitLeft(credentials_fragment.second, ":");
if (host_port_fragment.second.has_value()) {
auto port = string::ToInteger(host_port_fragment.second.value());
if (!port.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed parsing port text in: " << uri;
return std::nullopt;
}
result.port = port.value();
} else {
result.port = 21;
}
result.host = host_port_fragment.first;
result.path = "/" + optional::GetOrDefault<std::string>(target_fragment.second, "");
if (credentials_fragment.first.has_value()) {
auto username_password_fragment = detail::SplitLeft(credentials_fragment.first.value(), ":");
result.username = username_password_fragment.first;
if (username_password_fragment.second.has_value())
result.password = username_password_fragment.second.value();
}
CHARGELAB_LOG_MESSAGE(trace) << "Parsed FTP URI: " << result;
return result;
}
// TODO: Update to http/https
CHARGELAB_JSON_ENUM(HttpScheme,
Http,
Https,
)
struct HttpParts {
HttpScheme scheme = HttpScheme::kValueNotFoundInEnum;
std::string host {};
int port = -1;
std::string path {};
};
inline std::optional<HttpParts> parseHttpUri(std::string const& uri) {
HttpParts result {};
auto scheme_fragment = detail::SplitLeft(uri, "://");
if (!scheme_fragment.second.has_value())
return std::nullopt;
auto scheme = scheme_fragment.first;
if (string::EqualsIgnoreCaseAscii(scheme, "http")) {
result.scheme = HttpScheme::kHttp;
} else if (string::EqualsIgnoreCaseAscii(scheme, "https")) {
result.scheme = HttpScheme::kHttps;
} else {
CHARGELAB_LOG_MESSAGE(error) << "Bad scheme in: " << uri;
return std::nullopt;
}
auto target_fragment = detail::SplitLeft(scheme_fragment.second.value(), "/");
auto host_port_fragment = detail::SplitLeft(target_fragment.first, ":");
if (host_port_fragment.second.has_value()) {
auto port = string::ToInteger(host_port_fragment.second.value());
if (!port.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed parsing port text in: " << uri;
return std::nullopt;
}
result.port = port.value();
} else {
switch (result.scheme) {
default:
assert(false && "Unexpected websocket protocol");
case HttpScheme::kHttp:
result.port = 80;
break;
case HttpScheme::kHttps:
result.port = 443;
break;
}
}
result.host = host_port_fragment.first;
result.path = "/" + optional::GetOrDefault<std::string>(target_fragment.second, "");
return result;
}
std::string decodeUriComponent(std::string const& text) {
std::string result;
for (std::size_t i=0; i < text.size(); i++) {
// Note: allowing for deviations here; % prefixes not representing a valid octet and ignored
if (text[i] == '%' && i+2 < text.size()) {
auto const ch1 = detail::GetHexValue(text[i+1]);
auto const ch2 = detail::GetHexValue(text[i+2]);
if (ch1 >= 0 && ch2 >= 0) {
result += (char)((ch1 << 8) | ch2);
i += 2;
continue;
}
}
result += text[i];
}
return result;
}
}
#endif //CHARGELAB_OPEN_FIRMWARE_URI_H

View File

@@ -0,0 +1,171 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_HASH_METHODS_MBEDTLS_H
#define CHARGELAB_OPEN_FIRMWARE_HASH_METHODS_MBEDTLS_H
#include "openocpp/common/logging.h"
#include "openocpp/helpers/string.h"
#include <string>
#include <optional>
#include "mbedtls/ssl.h"
#include "mbedtls/base64.h"
namespace chargelab {
class HashMethodsMbedTLS;
namespace detail {
class HashCalculatorTypeMbedTLS {
private:
friend class ::chargelab::HashMethodsMbedTLS;
HashCalculatorTypeMbedTLS() {
mbedtls_md_init(&context_);
}
public:
~HashCalculatorTypeMbedTLS() {
mbedtls_md_free(&context_);
}
bool update(unsigned char const* input, std::size_t ilen) {
int err = mbedtls_md_update(&context_, input, ilen);
if (err != 0) {
CHARGELAB_LOG_MESSAGE(error) << "Update failed with error: " << err;
return false;
}
return true;
}
std::optional<std::vector<uint8_t>> finishBinary() {
auto md_info = mbedtls_md_info_from_ctx(&context_);
if (md_info == nullptr) {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - missing md_info in context";
return std::nullopt;
}
std::vector<uint8_t> result;
result.resize(mbedtls_md_get_size(md_info));
int err = mbedtls_md_finish(&context_, (unsigned char*)result.data());
if (err != 0) {
CHARGELAB_LOG_MESSAGE(error) << "Finish failed with error: " << err;
return std::nullopt;
}
err = mbedtls_md_starts(&context_);
if (err != 0) {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - Failed resetting MbedTLS MD context";
}
return result;
}
std::optional<std::string> finishHex() {
auto binary = finishBinary();
if (!binary.has_value())
return std::nullopt;
return string::ToHexString((unsigned char*)binary->data(), binary->size(), "");
}
void reset() {
finishBinary();
}
private:
mbedtls_md_context_t context_;
};
}
class HashMethodsMbedTLS {
private:
HashMethodsMbedTLS() {}
public:
using hash_enum_type = mbedtls_md_type_t;
using hash_calculator_type = detail::HashCalculatorTypeMbedTLS;
static constexpr hash_enum_type kHashTypeSHA256 = MBEDTLS_MD_SHA256;
static constexpr hash_enum_type kHashTypeSHA512 = MBEDTLS_MD_SHA512;
static std::unique_ptr<hash_calculator_type> createCalculator(hash_enum_type hash_type) {
auto md_info = mbedtls_md_info_from_type(hash_type);
if (md_info == nullptr) {
CHARGELAB_LOG_MESSAGE(error) << "Missing message digest info";
return nullptr;
}
auto result = std::unique_ptr<detail::HashCalculatorTypeMbedTLS> (new detail::HashCalculatorTypeMbedTLS());
int err = mbedtls_md_setup(&result->context_, md_info, 0);
if (err != 0) {
CHARGELAB_LOG_MESSAGE(error) << "Failed setting up context - error code: " << err;
return nullptr;
}
err = mbedtls_md_starts(&result->context_);
if (err != 0) {
CHARGELAB_LOG_MESSAGE(error) << "Failed starting context - error code: " << err;
return nullptr;
}
return result;
}
static std::optional<std::vector<uint8_t>> calculateHashBinary(
hash_enum_type hash_type,
unsigned char const* input,
std::size_t ilen
) {
auto md_info = mbedtls_md_info_from_type(hash_type);
if (md_info == nullptr) {
CHARGELAB_LOG_MESSAGE(warning) << "Missing message digest info";
return std::nullopt;
}
std::vector<uint8_t> digest_buffer;
digest_buffer.resize(mbedtls_md_get_size(md_info));
auto ret = mbedtls_md(md_info, input, ilen, (unsigned char*)digest_buffer.data());
if (ret != 0) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed calculating message digest - error: " << ret;
return std::nullopt;
}
return digest_buffer;
}
static std::optional<std::string> calculateHashHex(
hash_enum_type hash_type,
unsigned char const* input,
std::size_t ilen
) {
auto binary = calculateHashBinary(hash_type, input, ilen);
if (!binary.has_value())
return std::nullopt;
return string::ToHexString((unsigned char*)binary->data(), binary->size(), "");
}
static std::optional<std::vector<uint8_t>> decodeBase64(std::string const& base64_text) {
std::size_t olen = 0;
auto ret = mbedtls_base64_decode(nullptr, 0, &olen, (const unsigned char*)base64_text.data(), base64_text.size());
if (ret != 0 && ret != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed decoding base64 text/calculating size - error: " << ret;
return std::nullopt;
}
std::vector<uint8_t> result;
result.resize(olen);
ret = mbedtls_base64_decode(result.data(), result.size(), &olen, (const unsigned char*)base64_text.data(), base64_text.size());
if (ret != 0) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed decoding base64 text - error: " << ret;
return std::nullopt;
}
return result;
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_HASH_METHODS_MBEDTLS_H

View File

@@ -0,0 +1,111 @@
#include "openocpp/common/logging.h"
#include <atomic>
#include <mutex>
#define LOG_LOCAL_LEVEL ESP_LOG_VERBOSE
#include "esp_log.h"
namespace chargelab::logging {
namespace {
std::atomic<LogLevel> gLogLevel {LogLevel::trace};
constexpr char const* kTag = "main";
std::recursive_mutex gMutex {};
std::vector<std::shared_ptr<LoggingListenerFunction>> gListeners {};
}
void SetLogLevel(LogLevel level) {
gLogLevel = level;
switch (level) {
case LogLevel::trace: esp_log_level_set(kTag, ESP_LOG_VERBOSE); break;
case LogLevel::debug: esp_log_level_set(kTag, ESP_LOG_DEBUG); break;
default:
case LogLevel::info: esp_log_level_set(kTag, ESP_LOG_INFO); break;
case LogLevel::warning: esp_log_level_set(kTag, ESP_LOG_WARN); break;
case LogLevel::error: esp_log_level_set(kTag, ESP_LOG_ERROR); break;
case LogLevel::fatal: esp_log_level_set(kTag, ESP_LOG_ERROR); break;
}
}
bool IsLoggingEnabled(LogLevel level) {
LogLevel const limit = gLogLevel;
return static_cast<int>(level) >= static_cast<int>(limit);
}
#if defined(LOG_WITH_FILE_AND_LINE)
#define CHARGELAB_ESP_LOGGING_TEMPLATE(esp_macro) esp_macro(kTag, "[%s:%d(%s)] %s", metadata.file.data(), metadata.line, metadata.function.data(), message)
#else
#define CHARGELAB_ESP_LOGGING_TEMPLATE(esp_macro) esp_macro(kTag, "[%s] %s", metadata.function.data(), message)
#endif
namespace detail {
void PrintLogMessage(LogMetadata const& metadata, char const* message) {
switch (metadata.level) {
case LogLevel::trace: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGV); break;
case LogLevel::debug: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGD); break;
case LogLevel::info: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGI); break;
case LogLevel::warning: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGW); break;
case LogLevel::error: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGE); break;
default:
case LogLevel::fatal: CHARGELAB_ESP_LOGGING_TEMPLATE(ESP_LOGE); break;
}
}
}
#undef CHARGELAB_ESP_LOGGING_TEMPLATE
void PrintLogMessage(LogMetadata const& metadata, std::string const& message) {
detail::PrintLogMessage(metadata, message.data());
std::lock_guard lock {gMutex};
if (!gListeners.empty()) {
for (auto const& x : gListeners) {
if (x != nullptr) {
(*x)(metadata, message);
}
}
}
}
void PrintLogMessage(LogMetadata const& metadata, std::string_view const& message) {
detail::PrintLogMessage(metadata, message.data());
std::lock_guard lock {gMutex};
if (!gListeners.empty()) {
for (auto const& x : gListeners) {
if (x != nullptr) {
(*x)(metadata, message);
}
}
}
}
void RegisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback) {
std::lock_guard lock {gMutex};
for (auto const& x : gListeners) {
if (x == callback) {
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
return;
}
}
gListeners.push_back(callback);
}
void UnregisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback) {
std::lock_guard lock {gMutex};
auto initial_size = gListeners.size();
gListeners.erase(
std::remove_if(gListeners.begin(), gListeners.end(), [&](auto& x) {return x == callback;}),
gListeners.end()
);
if (initial_size - gListeners.size() > 1)
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
}
}

View File

@@ -0,0 +1,138 @@
#include "openocpp/common/logging.h"
#include "openocpp/helpers/chrono.h"
#include "openocpp/helpers/optional.h"
#include <iostream>
#include <atomic>
#include <chrono>
#include <mutex>
namespace chargelab::logging {
namespace {
std::atomic<LogLevel> gLogLevel {LogLevel::trace};
std::atomic<int> gRecursiveCounter {};
std::recursive_mutex gMutex {};
std::vector<std::shared_ptr<LoggingListenerFunction>> gListeners {};
class RaiiCounter {
public:
explicit RaiiCounter(std::atomic<int>& counter) : counter_{counter} {
counter_++;
}
~RaiiCounter() {
counter_--;
}
private:
std::atomic<int>& counter_;
};
}
std::string_view ToString(LogLevel const& level) {
switch (level) {
case LogLevel::trace: return std::string_view {"trace"};
case LogLevel::debug: return std::string_view {"debug"};
case LogLevel::info: return std::string_view {"info"};
case LogLevel::warning: return std::string_view {"warning"};
case LogLevel::error: return std::string_view {"error"};
default:
case LogLevel::fatal: return std::string_view {"fatal"};
}
}
void SetLogLevel(LogLevel level) {
gLogLevel = level;
}
bool IsLoggingEnabled(LogLevel level) {
LogLevel const limit = gLogLevel;
return static_cast<int>(level) >= static_cast<int>(limit);
}
std::ostream& LogPrefix(LogMetadata const& metadata) {
auto const now = std::chrono::system_clock::now();
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()).count();
auto const ts = chrono::ToString(static_cast<chargelab::SystemTimeMillis>(millis));
std::ios_base::fmtflags original_flags(std::cout.flags());
std::cout << "[" << optional::GetOrDefault<std::string>(ts, "<bad timestamp>")
<< "] [" << std::hex << std::this_thread::get_id()
<< "] [" << ToString(metadata.level)
#if defined(LOG_WITH_FILE_AND_LINE)
<< "] [" << metadata.file << ":" << metadata.line << "(" << metadata.function << ")] ";
#else
<< "] [" << "(" << metadata.function << ")] ";
#endif
std::cout.flags(original_flags);
return std::cout;
}
void PrintLogMessage(LogMetadata const& metadata, std::string const& message) {
LogPrefix(metadata) << message << std::endl;
std::lock_guard lock {gMutex};
RaiiCounter counter {gRecursiveCounter};
if (gRecursiveCounter > 2) {
LogPrefix(metadata) << "Recursive PrintLogMessage call - suppressing messages" << std::endl;
return;
}
if (!gListeners.empty()) {
for (auto const& x : gListeners) {
if (x != nullptr) {
(*x)(metadata, message);
}
}
}
}
void PrintLogMessage(LogMetadata const& metadata, std::string_view const& message) {
LogPrefix(metadata) << message << std::endl;
std::lock_guard lock {gMutex};
RaiiCounter counter {gRecursiveCounter};
if (gRecursiveCounter > 2) {
LogPrefix(metadata) << "Recursive PrintLogMessage call - suppressing messages" << std::endl;
return;
}
if (!gListeners.empty()) {
for (auto const& x : gListeners) {
if (x != nullptr) {
(*x)(metadata, message);
}
}
}
}
void RegisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback) {
std::lock_guard lock {gMutex};
for (auto const& x : gListeners) {
if (x == callback) {
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
return;
}
}
gListeners.push_back(callback);
}
void UnregisterLoggingListener(std::shared_ptr<LoggingListenerFunction> const& callback) {
std::lock_guard lock {gMutex};
auto initial_size = gListeners.size();
gListeners.erase(
std::remove_if(gListeners.begin(), gListeners.end(), [&](auto& x) {return x == callback;}),
gListeners.end()
);
if (initial_size - gListeners.size() > 1)
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,200 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_STANDARD_CHARGER_H
#define CHARGELAB_OPEN_FIRMWARE_STANDARD_CHARGER_H
#include "openocpp/interface/platform_interface.h"
#include "openocpp/interface/station_interface.h"
#include "openocpp/common/settings.h"
#include "openocpp/module/pending_messages_module.h"
#include "openocpp/module/connector_status_module.h"
#include "openocpp/module/get_logs_module.h"
#include "openocpp/module/boot_notification_module.h"
#include "openocpp/module/heartbeat_module.h"
#include "openocpp/module/configuration_module.h"
#include "openocpp/module/fallback_module.h"
#include "openocpp/module/reset_module.h"
#include "openocpp/module/firmware_update_module.h"
#include "openocpp/module/power_management_module1_6.h"
#include "openocpp/module/transaction_module1_6.h"
#include "openocpp/protocol/ocpp1_6/handlers/ocpp_message_handler.h"
#include "openocpp/module/power_management_module2_0.h"
#include "openocpp/module/transaction_module2_0.h"
#include "openocpp/protocol/ocpp2_0/handlers/ocpp_message_handler.h"
#include "openocpp/implementation/hash_methods_mbedtls.h"
namespace chargelab {
class StandardCharger {
public:
std::shared_ptr<PlatformInterface> platform;
std::shared_ptr<StationInterface> station;
std::shared_ptr<Settings> settings;
std::shared_ptr<PendingMessagesModule> pending_messages_module;
std::shared_ptr<ConnectorStatusModule> connector_status_module;
std::shared_ptr<GetLogsModule> get_logs_module;
std::shared_ptr<BootNotificationModule> boot_notification_module;
std::shared_ptr<HeartbeatModule> heartbeat_module;
std::shared_ptr<ConfigurationModule> configuration_module;
std::shared_ptr<FallbackModule> fallback_module;
std::shared_ptr<ResetModule> reset_module;
std::shared_ptr<FirmwareUpdateModule<HashMethodsMbedTLS>> firmware_update_module;
std::shared_ptr<PowerManagementModule1_6> power_management_module1_6;
std::shared_ptr<TransactionModule1_6> transaction_module1_6;
std::shared_ptr<ocpp1_6::OcppMessageHandler> message_handler1_6;
std::shared_ptr<PowerManagementModule2_0> power_management_module2_0;
std::shared_ptr<TransactionModule2_0> transaction_module2_0;
std::shared_ptr<ocpp2_0::OcppMessageHandler> message_handler2_0;
public:
template <typename T>
T notNull(T value) {
assert(value != nullptr);
return value;
}
StandardCharger(
std::shared_ptr<PlatformInterface> platform_interface,
std::shared_ptr<StationInterface> station_interface
)
: platform(std::move(platform_interface)),
station(std::move(station_interface))
{
settings = platform->getSettings();
CHARGELAB_LOG_MESSAGE(info) << "Initializing common modules...";
pending_messages_module = std::make_shared<PendingMessagesModule>(
notNull(settings),
notNull(platform),
platform->getStorage("transactions.dat")
);
connector_status_module = std::make_shared<ConnectorStatusModule>(
notNull(settings),
notNull(platform),
notNull(station)
);
get_logs_module = std::make_shared<GetLogsModule>(notNull(platform), notNull(pending_messages_module));
boot_notification_module = std::make_shared<BootNotificationModule>(notNull(settings), notNull(platform));
heartbeat_module = std::make_shared<HeartbeatModule>(notNull(settings), notNull(platform));
configuration_module = std::make_shared<ConfigurationModule>(notNull(settings), notNull(platform));
fallback_module = std::make_shared<FallbackModule>(notNull(platform));
reset_module = std::make_shared<ResetModule>(notNull(settings), notNull(platform), notNull(connector_status_module));
// TODO: Maybe hash methods should move into platform?
firmware_update_module = std::make_shared<FirmwareUpdateModule<HashMethodsMbedTLS>>(
notNull(platform),
notNull(reset_module),
notNull(pending_messages_module),
notNull(connector_status_module),
notNull(station)
);
CHARGELAB_LOG_MESSAGE(info) << "Initializing OCPP 1.6 modules...";
power_management_module1_6 = std::make_shared<PowerManagementModule1_6>(
notNull(settings),
notNull(platform),
notNull(station),
platform->getPartition("pmjournal")
);
transaction_module1_6 = std::make_shared<TransactionModule1_6>(
notNull(platform),
notNull(boot_notification_module),
notNull(power_management_module1_6),
notNull(pending_messages_module),
notNull(connector_status_module),
notNull(station)
);
message_handler1_6 = std::make_shared<ocpp1_6::OcppMessageHandler>(
notNull(settings),
notNull(platform),
std::vector<std::shared_ptr<AbstractModuleInterface>> {
notNull(platform),
notNull(boot_notification_module),
notNull(heartbeat_module),
notNull(configuration_module),
notNull(reset_module),
notNull(connector_status_module),
notNull(pending_messages_module),
notNull(firmware_update_module),
notNull(get_logs_module),
notNull(power_management_module1_6),
notNull(transaction_module1_6),
notNull(fallback_module)
},
// TODO: Get rid of this lambda; more expensive than a pointer? Also less clear what's being invoked at the call-site.
[this]() { return boot_notification_module->registrationComplete(); }
);
CHARGELAB_LOG_MESSAGE(info) << "Initializing OCPP 2.0 modules...";
power_management_module2_0 = std::make_shared<PowerManagementModule2_0>(
notNull(settings),
notNull(platform),
notNull(station),
platform->getPartition("pmjournal")
);
transaction_module2_0 = std::make_shared<TransactionModule2_0>(
notNull(platform),
notNull(boot_notification_module),
notNull(power_management_module2_0),
notNull(pending_messages_module),
notNull(connector_status_module),
notNull(station)
);
message_handler2_0 = std::make_shared<ocpp2_0::OcppMessageHandler>(
notNull(settings),
notNull(platform),
std::vector<std::shared_ptr<AbstractModuleInterface>> {
platform,
boot_notification_module,
heartbeat_module,
configuration_module,
reset_module,
connector_status_module,
pending_messages_module,
firmware_update_module,
get_logs_module,
power_management_module2_0,
transaction_module2_0,
fallback_module
},
// TODO: As above; remove this?
[this]() { return boot_notification_module->registrationComplete(); }
);
}
void runStep() {
message_handler1_6->runStep(platform->ocppConnection());
message_handler2_0->runStep(platform->ocppConnection());
}
void runLoop() {
while (true) {
message_handler1_6->runStep(platform->ocppConnection());
message_handler2_0->runStep(platform->ocppConnection());
using namespace std::chrono_literals;
std::this_thread::sleep_for(10ms);
}
}
template <typename T, typename... Args>
void addAfter(std::shared_ptr<T> module, Args&&... args) {
message_handler1_6->addAfter(module, std::forward<Args>(args)...);
message_handler2_0->addAfter(module, std::forward<Args>(args)...);
}
template <typename T, typename... Args>
void addBefore(std::shared_ptr<T> module, Args&&... args) {
message_handler1_6->addBefore(module, std::forward<Args>(args)...);
message_handler2_0->addBefore(module, std::forward<Args>(args)...);
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_STANDARD_CHARGER_H

View File

@@ -0,0 +1,109 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_STATION_ESP32_H
#define CHARGELAB_OPEN_FIRMWARE_STATION_ESP32_H
#include "openocpp/interface/station_interface.h"
#include "openocpp/common/logging.h"
#include "openocpp/helpers/string.h"
#include "esp_ota_ops.h"
namespace chargelab {
class StationEsp32 : public StationInterface {
public:
StationEsp32()
: running_partition_(esp_ota_get_running_partition()),
update_partition_(esp_ota_get_next_update_partition(nullptr))
{
assert(running_partition_ != nullptr);
assert(update_partition_ != nullptr);
ESP_ERROR_CHECK(esp_ota_mark_app_valid_cancel_rollback());
CHARGELAB_LOG_MESSAGE(debug) << "Running partition: " << running_partition_->label;
CHARGELAB_LOG_MESSAGE(debug) << "Update partition: " << update_partition_->label;
}
std::string getActiveSlotId() override {
std::array<uint8_t, sizeof(uint32_t)> buffer {};
std::memcpy(buffer.data(), &running_partition_->address, buffer.size());
return string::ToHexString(buffer, "");
}
std::string getUpdateSlotId() override {
std::array<uint8_t, sizeof(uint32_t)> buffer {};
std::memcpy(buffer.data(), &update_partition_->address, buffer.size());
return string::ToHexString(buffer, "");
}
Result startUpdateProcess(std::size_t update_size) override {
if (update_handle_ != 0) {
CHARGELAB_LOG_MESSAGE(error) << "Another firmware update operation was in progress";
return Result::kFailed;
}
if (update_size > update_partition_->size) {
CHARGELAB_LOG_MESSAGE(warning) << "Update size exceeded partition size: " << update_size << " > " << update_partition_->size;
return Result::kFailed;
}
if (esp_err_t esp_status = esp_ota_begin(update_partition_, OTA_SIZE_UNKNOWN, &update_handle_) != ESP_OK) {
CHARGELAB_LOG_MESSAGE(error) << "Failed to start firmware update - error " << esp_status;
return Result::kFailed;
}
CHARGELAB_LOG_MESSAGE(info) << "Starting firmware update for partition: " << update_partition_->label;
update_size_ = update_size;
update_offset_ = 0;
return Result::kSucceeded;
}
Result processFirmwareChunk(const std::uint8_t *block, std::size_t size) override {
if (update_handle_ == 0) {
CHARGELAB_LOG_MESSAGE(error) << "Process chunk called outside of a firmware update operation";
return Result::kFailed;
}
if (update_offset_ + size > update_size_) {
CHARGELAB_LOG_MESSAGE(error) << "Firmware update chunks exceeded update size";
return Result::kFailed;
}
if (esp_err_t esp_status = esp_ota_write(update_handle_, (const void*)block, size) != ESP_OK) {
CHARGELAB_LOG_MESSAGE(error) << "Firmware update write failed - error " << esp_status;
esp_ota_abort(update_handle_);
update_handle_ = 0;
return Result::kFailed;
}
update_size_ += size;
return Result::kSucceeded;
}
Result finishUpdateProcess(bool succeeded) override {
if (update_handle_ == 0) {
CHARGELAB_LOG_MESSAGE(error) << "Finish update called outside of a firmware update operation";
return Result::kFailed;
}
if (succeeded) {
esp_ota_end(update_handle_);
esp_ota_set_boot_partition(update_partition_);
CHARGELAB_LOG_MESSAGE(info) << "Update applied - the next boot will attempt to boot into the updated partition";
} else {
esp_ota_abort(update_handle_);
}
update_handle_ = 0;
return Result::kSucceeded;
}
private:
esp_partition_t const* running_partition_;
esp_partition_t const* update_partition_;
esp_ota_handle_t update_handle_ = 0;
std::size_t update_size_ = 0;
std::size_t update_offset_ = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_STATION_ESP32_H

View File

@@ -0,0 +1,452 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_STATION_TEST_ESP32_H
#define CHARGELAB_OPEN_FIRMWARE_STATION_TEST_ESP32_H
#include "openocpp/implementation/station_esp32.h"
#include "openocpp/interface/platform_interface.h"
#include "openocpp/helpers/string.h"
#include "openocpp/version.h"
#include <random>
namespace chargelab {
namespace detail {
struct SimulatorConnectorState {
bool connector_available {true};
bool vehicle_connected {false};
bool suspended_by_vehicle {false};
bool suspended_by_charger {false};
std::optional<SteadyPointMillis> last_update = std::nullopt;
double amps {48.0};
double volts {220.0};
double watt_hours {0.0};
// Note: not saved
std::optional<double> limit = std::nullopt;
bool charging_enabled {false};
CHARGELAB_JSON_INTRUSIVE(
SimulatorConnectorState,
connector_available,
vehicle_connected,
suspended_by_vehicle,
suspended_by_charger,
last_update,
amps,
volts,
watt_hours
)
};
struct SimulatorState {
charger::StationMetadata station_metadata {};
std::map<ocpp2_0::EVSEType, charger::EvseMetadata> evse_metadata {};
std::map<ocpp2_0::EVSEType, charger::ConnectorMetadata> connector_metadata {};
std::map<ocpp2_0::EVSEType, SimulatorConnectorState> connector_state {};
// Note: connector state is not saved intentionally
CHARGELAB_JSON_INTRUSIVE(SimulatorState, station_metadata, evse_metadata, connector_metadata, connector_state)
};
}
class StationTestEsp32 : public StationEsp32 {
public:
StationTestEsp32(std::shared_ptr<PlatformInterface> platform)
: platform_(std::move(platform)),
random_engine_ {std::random_device{}()}
{
assert(platform_ != nullptr);
settings_ = platform_->getSettings();
assert(settings_ != nullptr);
// TODO: Move these into SimulatorState so that they're persisted and can be configured?
settings_->ChargerVendor.setValue("ChargeLab");
settings_->ChargerModel.setValue("Simulator");
settings_->ChargerSerialNumber.setValue("0000");
settings_->ChargerFirmwareVersion.setValue(OPENOCPP_VERSION_TEXT);
settings_->ChargerAccessPointSSID.setValue("Charger Simulator");
settings_->registerCustomSetting(saved_state_ = std::make_shared<SettingString>(
[]() {
detail::SimulatorState default_state;
default_state.station_metadata = charger::StationMetadata {
1
};
default_state.evse_metadata[ocpp2_0::EVSEType {1}] = charger::EvseMetadata {
1,
10560.0
};
default_state.connector_metadata[ocpp2_0::EVSEType {1, 1}] = charger::ConnectorMetadata {
1,
"cType1",
1,
10560.0,
48
};
return SettingMetadata {
"SimulatorState",
SettingConfig::rwPolicy(),
std::nullopt,
std::nullopt,
write_json_to_string(default_state)
};
},
[](auto const&) {return true;}
));
}
public:
void simulateTap(ocpp1_6::IdToken token1_6, ocpp2_0::IdTokenType token2_0, int timeout_millis) {
std::lock_guard lock {mutex_};
rfid_action_ = std::make_pair(
static_cast<SteadyPointMillis>(platform_->steadyClockNow() + timeout_millis),
std::make_pair(token1_6, token2_0)
);
}
void updateMeasurements() {
std::lock_guard lock {mutex_};
loadIfModified();
std::uniform_real_distribution<double> distribution(0.95, 1.05);
for (auto& x : state_.connector_state) {
if (x.first.id == 0 || (x.first.connectorId && x.first.connectorId.value() == 0))
continue;
auto amps = 48.0;
if (x.second.limit.has_value())
amps = std::min(amps, x.second.limit.value());
if (station_limit_.has_value())
amps = std::min(amps, station_limit_.value());
x.second.amps = std::round((amps*distribution(random_engine_))*100.0)/100.0;
x.second.volts = std::round((220.0*distribution(random_engine_))*100.0)/100.0;
auto const now = platform_->steadyClockNow();
if (x.second.last_update.has_value()) {
auto const delta = now - x.second.last_update.value();
auto const delta_hours = delta/(1000.0 * 3600.0);
x.second.watt_hours += x.second.amps*x.second.volts*delta_hours;
}
x.second.last_update = now;
}
}
template<typename Visitor>
void updateState(Visitor&& visitor) {
std::lock_guard lock {mutex_};
loadIfModified();
visitor(state_);
saveIfModified();
}
public:
charger::StationMetadata getStationMetadata() override {
std::lock_guard lock {mutex_};
loadIfModified();
return state_.station_metadata;
}
[[nodiscard]] std::map<ocpp2_0::EVSEType, charger::EvseMetadata> getEvseMetadata() override {
std::lock_guard lock {mutex_};
loadIfModified();
return state_.evse_metadata;
}
[[nodiscard]] std::map<ocpp2_0::EVSEType, charger::ConnectorMetadata> getConnectorMetadata() override {
std::lock_guard lock {mutex_};
loadIfModified();
return state_.connector_metadata;
}
std::optional<charger::ConnectorStatus> pollConnectorStatus(const ocpp2_0::EVSEType &evse) override {
std::lock_guard lock {mutex_};
loadIfModified();
if (state_.connector_metadata.find(evse) == state_.connector_metadata.end())
return std::nullopt;
auto const& state = state_.connector_state[evse];
return charger::ConnectorStatus {
state.connector_available,
state.vehicle_connected,
state.charging_enabled,
state.suspended_by_vehicle,
state.suspended_by_charger,
state.watt_hours,
std::nullopt
};
}
std::vector<ocpp1_6::SampledValue> pollMeterValues1_6(const std::optional<ocpp2_0::EVSEType> &evse) override {
std::lock_guard lock {mutex_};
loadIfModified();
// Note: not supporting station level meter values here
if (!evse.has_value())
return {};
if (state_.connector_metadata.find(evse.value()) == state_.connector_metadata.end())
return {};
auto const& state = state_.connector_state[evse.value()];
return {
ocpp1_6::SampledValue {
std::to_string(roundTo(state.watt_hours, 1)),
std::nullopt,
std::nullopt,
ocpp1_6::Measurand::kEnergyActiveImportRegister,
std::nullopt,
std::nullopt,
ocpp1_6::UnitOfMeasure::kWattHours
},
ocpp1_6::SampledValue {
std::to_string(roundTo(state.amps, 1)),
std::nullopt,
std::nullopt,
ocpp1_6::Measurand::kCurrentImport,
std::nullopt,
std::nullopt,
ocpp1_6::UnitOfMeasure::kAmps
},
ocpp1_6::SampledValue {
std::to_string(roundTo(state.volts, 1)),
std::nullopt,
std::nullopt,
ocpp1_6::Measurand::kVoltage,
std::nullopt,
std::nullopt,
ocpp1_6::UnitOfMeasure::kVolts
}
};
}
std::vector<ocpp2_0::SampledValueType>
pollMeterValues2_0(const std::optional<ocpp2_0::EVSEType> &evse) override {
std::lock_guard lock {mutex_};
loadIfModified();
// Note: not supporting station level meter values here
if (!evse.has_value())
return {};
if (state_.connector_metadata.find(evse.value()) == state_.connector_metadata.end())
return {};
auto const& state = state_.connector_state[evse.value()];
return {
ocpp2_0::SampledValueType {
roundTo(state.watt_hours, 1),
std::nullopt,
ocpp2_0::MeasurandEnumType::kEnergy_Active_Import_Register,
std::nullopt,
std::nullopt,
std::nullopt,
ocpp2_0::UnitOfMeasureType {"Wh"}
},
ocpp2_0::SampledValueType {
roundTo(state.amps, 1),
std::nullopt,
ocpp2_0::MeasurandEnumType::kCurrent_Import,
std::nullopt,
std::nullopt,
std::nullopt,
ocpp2_0::UnitOfMeasureType {"A"}
},
ocpp2_0::SampledValueType {
roundTo(state.volts, 1),
std::nullopt,
ocpp2_0::MeasurandEnumType::kVoltage,
std::nullopt,
std::nullopt,
std::nullopt,
ocpp2_0::UnitOfMeasureType {"V"}
}
};
}
void setChargingEnabled(const ocpp2_0::EVSEType &evse, bool value) override {
std::lock_guard lock {mutex_};
loadIfModified();
if (state_.connector_metadata.find(evse) == state_.connector_metadata.end())
return;
state_.connector_state[evse].charging_enabled = value;
}
void setActiveChargePointMaxProfiles(std::vector<schedule_type1_6> const& active_schedules) override {
std::lock_guard lock {mutex_};
loadIfModified();
// Note: ignoring phases
std::optional<double> limit;
for (auto const& x : active_schedules) {
// Note: this should be handled here or the station should only accept a specific rate type
if (x.first.csChargingProfiles.chargingSchedule.chargingRateUnit != ocpp1_6::ChargingRateUnitType::kA)
continue;
if (!limit.has_value()) {
limit = x.second.limit;
} else {
limit = std::min(limit.value(), x.second.limit);
}
}
station_limit_ = limit;
}
void setActiveChargePointMaxProfiles(std::vector<schedule_type2_0> const& active_schedules) override {
std::lock_guard lock {mutex_};
loadIfModified();
// Note: ignoring phases
std::optional<double> limit;
for (auto const& x : active_schedules) {
// Note: profiles with multiple schedules are for ISO 15118 and aren't supported at the moment
if (x.first.chargingProfile.chargingSchedule.size() != 1)
continue;
auto const& schedule = x.first.chargingProfile.chargingSchedule.front();
if (schedule.chargingRateUnit != ocpp2_0::ChargingRateUnitEnumType::kA)
continue;
if (!limit.has_value()) {
limit = x.second.limit;
} else {
limit = std::min(limit.value(), x.second.limit);
}
}
station_limit_ = limit;
}
void setActiveEvseProfiles(int evse_id, std::vector<schedule_type1_6> const& active_schedules) override {
std::lock_guard lock {mutex_};
loadIfModified();
// Note: ignoring phases
std::optional<double> limit;
for (auto const& x : active_schedules) {
// Note: this should be handled here or the station should only accept a specific rate type
if (x.first.csChargingProfiles.chargingSchedule.chargingRateUnit != ocpp1_6::ChargingRateUnitType::kA)
continue;
if (!limit.has_value()) {
limit = x.second.limit;
} else {
limit = std::min(limit.value(), x.second.limit);
}
}
for (auto& x : state_.connector_state) {
if (x.first.id != evse_id)
continue;
x.second.limit = limit;
}
}
void setActiveEvseProfiles(int evse_id, std::vector<schedule_type2_0> const& active_schedules) override {
std::lock_guard lock {mutex_};
loadIfModified();
// Note: ignoring phases
std::optional<double> limit;
for (auto const& x : active_schedules) {
// Note: profiles with multiple schedules are for ISO 15118 and aren't supported at the moment
if (x.first.chargingProfile.chargingSchedule.size() != 1)
continue;
auto const& schedule = x.first.chargingProfile.chargingSchedule.front();
if (schedule.chargingRateUnit != ocpp2_0::ChargingRateUnitEnumType::kA)
continue;
if (!limit.has_value()) {
limit = x.second.limit;
} else {
limit = std::min(limit.value(), x.second.limit);
}
}
for (auto& x : state_.connector_state) {
if (x.first.id != evse_id)
continue;
x.second.limit = limit;
}
}
[[nodiscard]] std::optional<ocpp1_6::IdToken> readToken1_6() override {
std::lock_guard lock {mutex_};
if (!rfid_action_.has_value())
return std::nullopt;
if (platform_->steadyClockNow() - rfid_action_->first >= 0) {
rfid_action_ = std::nullopt;
return std::nullopt;
}
return rfid_action_->second.first;
}
[[nodiscard]] std::optional<ocpp2_0::IdTokenType> readToken2_0() override {
std::lock_guard lock {mutex_};
if (!rfid_action_.has_value())
return std::nullopt;
if (platform_->steadyClockNow() - rfid_action_->first >= 0) {
rfid_action_ = std::nullopt;
return std::nullopt;
}
return rfid_action_->second.second;
}
private:
void loadIfModified() {
auto const next_saved_state = saved_state_->getValue();
if (last_saved_state_ == next_saved_state)
return;
auto const data = read_json_from_string<detail::SimulatorState>(next_saved_state);
if (!data.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading saved state: " << next_saved_state;
} else {
state_ = data.value();
}
last_saved_state_ = next_saved_state;
}
void saveIfModified() {
auto const next_saved_state = write_json_to_string(state_);
if (last_saved_state_ == next_saved_state)
return;
saved_state_->setValue(next_saved_state);
last_saved_state_ = next_saved_state;
}
static double roundTo(double value, int decimal_places) {
auto const factor = (double)std::pow(10.0, decimal_places);
return std::round(value/factor) * factor;
}
private:
std::shared_ptr<PlatformInterface> platform_;
std::shared_ptr<Settings> settings_;
std::default_random_engine random_engine_;
std::mutex mutex_ {};
std::optional<double> station_limit_ = std::nullopt;
std::optional<std::pair<SteadyPointMillis, std::pair<ocpp1_6::IdToken, ocpp2_0::IdTokenType>>> rfid_action_;
detail::SimulatorState state_;
std::shared_ptr<SettingString> saved_state_;
std::string last_saved_state_;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_STATION_TEST_ESP32_H

View File

@@ -0,0 +1,53 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_CHARGING_PROFILE_PERSISTENCE_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_CHARGING_PROFILE_PERSISTENCE_INTERFACE_H
#include <string>
#include <vector>
#include <optional>
#include "openocpp/protocol/ocpp1_6/types/charging_profile.h"
#include "openocpp/helpers/json.h"
namespace chargelab {
struct ActiveProfileIdsPersistenceInfo {
std::unordered_set<int> activeRestartProtectionProfileIds;
std::optional<SystemTimeMillis> lastUpdated;
};
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(
ActiveProfileIdsPersistenceInfo,
activeRestartProtectionProfileIds,
lastUpdated
)
struct ChargingProfilePersistenceInfo {
std::vector<pair<int, ocpp1_6::ChargingProfile>> profiles;
int criticalWriteCredits;
};
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(
ChargingProfilePersistenceInfo,
profiles,
criticalWriteCredits
)
class ChargingProfilePersistenceInterface {
public:
virtual ~ChargingProfilePersistenceInterface() = default;
virtual std::optional<ChargingProfilePersistenceInfo> loadProfilePersistenceInfo() = 0;
virtual bool saveProfilePersistenceInfo(ChargingProfilePersistenceInfo const & profiles) = 0;
virtual std::optional<ActiveProfileIdsPersistenceInfo> loadActiveProfileIdsInfo() = 0;
virtual bool saveActiveProfileIdsInfo(ActiveProfileIdsPersistenceInfo const & persistence_info) = 0;
virtual bool saveWriteCredits(int write_credits) = 0;
virtual int getCreditIncreaseIntervalSeconds() = 0; // this is for making unit test easier
virtual int getActiveIdsUpdateIntervalSeconds() = 0; // for making unit test easier
virtual int getCheckProfileExpiryIntervalSeconds() = 0; // for making unit test easier
virtual int getFlashCreditInterval() = 0; // how many difference between the memory value and disk value, for making unit test easier
virtual int getFlashCreditMaximumLimit() = 0; // the biggest credit value allowed to write to disk, for making unit test easier
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_CHARGING_PROFILE_PERSISTENCE_INTERFACE_H

View File

@@ -0,0 +1,24 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_FETCH_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_FETCH_INTERFACE_H
#include <string>
#include <vector>
#include <cstdint>
#include <optional>
#include <functional>
namespace chargelab {
class FetchInterface {
public:
enum class Result {
kSucceeded,
kFailed
};
public:
virtual ~FetchInterface() = default;
virtual Result fetch(std::string const& uri, std::function<Result(std::uint8_t const*, std::size_t)> const& process_chunk) = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_FETCH_INTERFACE_H

View File

@@ -0,0 +1,30 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_SYSTEM_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_SYSTEM_INTERFACE_H
#include "openocpp/model/system_types.h"
#include "openocpp/interface/element/storage_interface.h"
#include "openocpp/interface/element/flash_block_interface.h"
#include <cstdint>
#include <memory>
namespace chargelab {
class SystemInterface {
public:
virtual ~SystemInterface() = default;
virtual SystemTimeMillis systemClockNow() = 0;
virtual SteadyPointMillis steadyClockNow() = 0;
virtual void setSystemClock(SystemTimeMillis now) = 0;
virtual void resetSoft() = 0;
virtual void resetHard() = 0;
virtual bool isClockOutOfSync() = 0;
virtual std::unique_ptr<chargelab::StorageInterface> getStorage(std::string const& file_name) = 0;
virtual std::unique_ptr<chargelab::FlashBlockInterface> getPartition(std::string const& label) = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_SYSTEM_INTERFACE_H

View File

@@ -0,0 +1,22 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_UPLOAD_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_UPLOAD_INTERFACE_H
#include <string>
#include <functional>
namespace chargelab {
class UploadInterface {
public:
enum class Result {
kSucceeded,
kFailed
};
public:
virtual ~UploadInterface() = default;
virtual Result upload(std::string const& uri, std::vector<uint8_t> const& content, bool append, std::function<void(std::size_t)> const& report_progress) = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_UPLOAD_INTERFACE_H

View File

@@ -0,0 +1,14 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_BYTE_READER_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_BYTE_READER_INTERFACE_H
#include <string>
#include <cstring>
namespace chargelab {
class ByteReaderInterface {
public:
virtual std::size_t read(char* s, std::size_t n) = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_BYTE_READER_INTERFACE_H

View File

@@ -0,0 +1,14 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_BYTE_WRITER_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_BYTE_WRITER_INTERFACE_H
#include <string>
#include <cstring>
namespace chargelab {
class ByteWriterInterface {
public:
virtual void write(char const *s, std::size_t count) = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_BYTE_WRITER_INTERFACE_H

View File

@@ -0,0 +1,18 @@
#ifndef ESP32_FIRMWARE_TEST_FLASH_BLOCK_INTERFACE_H
#define ESP32_FIRMWARE_TEST_FLASH_BLOCK_INTERFACE_H
#include <functional>
namespace chargelab {
class FlashBlockInterface {
public:
virtual ~FlashBlockInterface() = default;
virtual bool read(std::size_t src_offset, void *dst, std::size_t size) const = 0;
virtual bool write(std::size_t dst_offset, void *src, std::size_t size) = 0;
virtual bool erase() = 0;
[[nodiscard]] virtual std::size_t size() const = 0;
};
}
#endif //ESP32_FIRMWARE_TEST_FLASH_BLOCK_INTERFACE_H

View File

@@ -0,0 +1,47 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_REST_CONNECTION_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_REST_CONNECTION_INTERFACE_H
#include <optional>
namespace chargelab {
class RestConnectionInterface {
public:
virtual ~RestConnectionInterface() = default;
virtual int getStatusCode() = 0;
virtual std::size_t getContentLength() = 0;
/**
* Sets a custom header. Must be called before open().
* @param key
* @param value
*/
virtual void setHeader(std::string const& key, std::string const& value) = 0;
/**
* Opens the connection to the server. Must be called after setting any headers for this request.
* @param content_length length of data that will be written to the server
* @return true on success, otherwise false
*/
virtual bool open(std::size_t content_length) = 0;
/**
* Completes the REST request and reads the status code/content length from the server. This must be called
* after any POST payload and headers are written to the connection.
* @return true on success, otherwise false
*/
virtual bool send() = 0;
/**
* @return -1 on error, otherwise number bytes read
*/
virtual int read(char* buffer, int len) = 0;
/**
* @return a negative status code on error, otherwise number bytes read
*/
virtual int write(char const* buffer, int len) = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_REST_CONNECTION_INTERFACE_H

View File

@@ -0,0 +1,17 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_STORAGE_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_STORAGE_INTERFACE_H
#include <cstdio>
#include <functional>
namespace chargelab {
class StorageInterface {
public:
virtual ~StorageInterface() = default;
virtual bool read(std::function<bool(FILE*)> const& function) = 0;
virtual bool write(std::function<bool(FILE*)> const& function) = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_STORAGE_INTERFACE_H

View File

@@ -0,0 +1,33 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_WEBSOCKET_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_WEBSOCKET_INTERFACE_H
#include <string>
#include <vector>
#include <cstdint>
#include <optional>
#include "openocpp/helpers/json.h"
#include "openocpp/interface/element/byte_writer_interface.h"
namespace chargelab {
class WebsocketInterface {
public:
virtual ~WebsocketInterface() = default;
virtual bool isConnected() = 0;
virtual std::optional<std::string> getSubprotocol() = 0;
virtual std::size_t pendingMessages() = 0;
virtual std::optional<std::string> pollMessages() = 0;
virtual void sendCustom(std::function<void(ByteWriterInterface&)> payload) = 0;
template <typename T>
void sendJson(T const& value) {
sendCustom([&](ByteWriterInterface& stream) {
write_json(stream, value);
});
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_WEBSOCKET_INTERFACE_H

View File

@@ -0,0 +1,58 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_PLATFORM_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_PLATFORM_INTERFACE_H
#include "openocpp/model/system_types.h"
#include "openocpp/interface/element/rest_connection_interface.h"
#include "openocpp/interface/component/system_interface.h"
#include "openocpp/interface/component/upload_interface.h"
#include "openocpp/interface/element/websocket_interface.h"
#include "openocpp/common/settings.h"
#include "openocpp/module/common_templates.h"
#include <cstdint>
#include <memory>
namespace chargelab {
enum class RestMethod {
kGet,
kPost,
kPut,
kPatch,
kDelete,
kHead,
kOptions
};
struct SignatureAndHash {
const unsigned char *hash = nullptr;
std::size_t hash_len = 0;
const unsigned char *sig = nullptr;
std::size_t sig_len = 0;
};
class PlatformInterface :
public SystemInterface,
public ServiceStatefulGeneral
{
public:
virtual std::shared_ptr<Settings> getSettings() = 0;
virtual std::shared_ptr<WebsocketInterface> ocppConnection() = 0;
virtual std::shared_ptr<RestConnectionInterface> restRequest(RestMethod method, std::string const& uri) = 0;
virtual bool verifyManufacturerCertificate(
std::string const& pem,
std::optional<SignatureAndHash> const& check_sha256
) = 0;
virtual std::shared_ptr<RestConnectionInterface> getRequest(std::string const& uri) {
return restRequest(RestMethod::kGet, uri);
}
virtual std::shared_ptr<RestConnectionInterface> putRequest(std::string const& uri) {
return restRequest(RestMethod::kPut, uri);
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_PLATFORM_INTERFACE_H

View File

@@ -0,0 +1,274 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_STATION_INTERFACE_H
#define CHARGELAB_OPEN_FIRMWARE_STATION_INTERFACE_H
#include "openocpp/protocol/ocpp1_6/types/charge_point_error_code.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/protocol/ocpp1_6/types/sampled_value.h"
#include "openocpp/protocol/ocpp1_6/types/charging_schedule_period.h"
#include "openocpp/protocol/ocpp1_6/messages/set_charging_profile.h"
#include "openocpp/protocol/ocpp2_0/types/sampled_value_type.h"
#include "openocpp/protocol/ocpp2_0/types/evse_type.h"
#include "openocpp/protocol/ocpp2_0/messages/set_charging_profile.h"
#include "openocpp/interface/component/system_interface.h"
#include "openocpp/helpers/json.h"
#include <string>
#include <vector>
#include <optional>
namespace chargelab {
namespace charger {
// TODO: Move these into nested classes under StationInterface?
struct FaultedStatus1_6 {
ocpp1_6::ChargePointErrorCode errorCode = ocpp1_6::ChargePointErrorCode::kValueNotFoundInEnum;
std::optional<ocpp1_6::CiString50Type> info;
std::optional<ocpp1_6::CiString255Type> vendorId;
std::optional<ocpp1_6::CiString50Type> vendorErrorCode;
CHARGELAB_JSON_INTRUSIVE(FaultedStatus1_6, errorCode, info, vendorId, vendorErrorCode)
};
struct FaultedStatus2_0 {
// TODO
CHARGELAB_JSON_INTRUSIVE_EMPTY(FaultedStatus2_0)
};
struct FaultedStatus {
FaultedStatus1_6 status1_6;
FaultedStatus2_0 status2_0;
CHARGELAB_JSON_INTRUSIVE(FaultedStatus, status1_6, status2_0)
};
// TODO: Need to add type information and flags to enable an implementation to enable/disable connectors (if,
// for example, multiple physical connectors are present but only one can be used at a time).
struct ConnectorStatus {
bool connector_available;
bool vehicle_connected;
bool charging_enabled;
bool suspended_by_vehicle;
bool suspended_by_charger;
double meter_watt_hours;
std::optional<FaultedStatus> faulted_status = std::nullopt;
CHARGELAB_JSON_INTRUSIVE(ConnectorStatus, connector_available, vehicle_connected, charging_enabled, suspended_by_vehicle,
suspended_by_charger, faulted_status)
};
struct ConnectorMetadata {
/**
* OCPP 1.6 connector ID
*/
int connector_id1_6;
std::string connector_type;
int supply_phases;
double power_max_watts;
double power_max_amps;
CHARGELAB_JSON_INTRUSIVE(ConnectorMetadata, connector_id1_6, connector_type, supply_phases, power_max_watts, power_max_amps)
};
struct EvseMetadata {
int supply_phases;
double power_max_watts;
CHARGELAB_JSON_INTRUSIVE(EvseMetadata, supply_phases, power_max_watts)
};
struct StationMetadata {
int supply_phases;
CHARGELAB_JSON_INTRUSIVE(StationMetadata, supply_phases)
};
}
class StationInterface {
public:
using schedule_type1_6 = std::pair<ocpp1_6::SetChargingProfileReq, ocpp1_6::ChargingSchedulePeriod>;
using schedule_type2_0 = std::pair<ocpp2_0::SetChargingProfileRequest, ocpp2_0::ChargingSchedulePeriodType>;
enum class Result {
kSucceeded,
kFailed
};
public:
virtual ~StationInterface() = default;
/**
* Returns the station metadata required by OCPP 2.0.1.
*
* @return station metadata
*/
virtual charger::StationMetadata getStationMetadata() = 0;
/**
* Returns the EVSE metadata required by OCPP 2.0.1. The connectorId field should not be set for all entries in
* the returned map.
*
* @return EVSE metadata
*/
virtual std::map<ocpp2_0::EVSEType, charger::EvseMetadata> getEvseMetadata() = 0;
/**
* Returns the connector metadata containing the OCPP 1.6 connectorId mappings and the information required by
* OCPP 2.0.1. The connectorId field should be set for all entries in the returned map.
*
* @return connector metadata
*/
virtual std::map<ocpp2_0::EVSEType, charger::ConnectorMetadata> getConnectorMetadata() = 0;
/**
* Polls the status of a particular connector. Note that this call is expected to be provided with a connector
* ID - when absent this call should always return an empty optional.
*
* @param evse
* @return a non-empty optional with the connector status if the connector exists, otherwise an empty optional
*/
virtual std::optional<charger::ConnectorStatus> pollConnectorStatus(ocpp2_0::EVSEType const& evse) = 0;
/**
* Polls the meter values for the station, EVSE, or connector if they're available.
*
* @param evse an empty optional for station meter values, an EVSE with no connector for EVSE meter values, and
* an EVSE/connector for connector meter values.
* @return the meter values data if available, otherwise an empty vector
*/
virtual std::vector<ocpp1_6::SampledValue> pollMeterValues1_6(std::optional<ocpp2_0::EVSEType> const& evse) = 0;
/**
* Polls the meter values for the station, EVSE, or connector if they're available.
*
* @param evse an empty optional for station meter values, an EVSE with no connector for EVSE meter values, and
* an EVSE/connector for connector meter values.
* @return the meter values data if available, otherwise an empty vector
*/
virtual std::vector<ocpp2_0::SampledValueType> pollMeterValues2_0(std::optional<ocpp2_0::EVSEType> const& evse) = 0;
/**
* Enables/disables charging on a particular connector.
*
* @param evse
* @param value true to enable charging, otherwise false
*/
virtual void setChargingEnabled(ocpp2_0::EVSEType const& evse, bool value) = 0;
// Note: providing the charger with the active profile and period explicitly for the following reasons:
// - If a TxProfile limits based on Amps and a ChargePointMaxProfile limits based on Watts the interpretation
// will need to be left to the vendor. Do they apply the limit based on the maximum voltage or a live reading?
// If the reading fluctuates does the implementation switch from one limit to another?
// - A vendor *may* want to communicate more details to a user. For example, they may want to display the
// current profile limits (or other associated metadata) on screen.
// This method will be called whenever the active schedule changes.
/**
* Sets the active OCPP 1.6 charging schedules that were assigned to the charger.
*
* @param active_schedules the active schedule periods and their associated charging profiles
*/
virtual void setActiveChargePointMaxProfiles(std::vector<schedule_type1_6> const& active_schedules) = 0;
/**
* Sets the active OCPP 2.0 charging schedules that were assigned to the charger.
*
* @param active_schedules the active schedule periods and their associated charging profiles
*/
virtual void setActiveChargePointMaxProfiles(std::vector<schedule_type2_0> const& active_schedules) = 0;
/**
* Sets the active OCPP 1.6 charging schedules that were assigned to a specific EVSE ID.
*
* @param active_schedules the active schedule periods and their associated charging profiles
*/
virtual void setActiveEvseProfiles(int evse_id, std::vector<schedule_type1_6> const& active_schedules) = 0;
/**
* Sets the active OCPP 2.0 charging schedules that were assigned to a specific EVSE ID.
*
* @param active_schedules the active schedule periods and their associated charging profiles
*/
virtual void setActiveEvseProfiles(int evse_id, std::vector<schedule_type2_0> const& active_schedules) = 0;
/**
* Reads an OCPP 1.6 ID token that was presented to the station. This method may either return the token once
* every interaction, or it may return the IdToken for as long as the RFID tag is detected by the RFID reader.
* If the latter approach is used the running transaction module is expected to only respond to changes in
* output.
*
* @return the IdToken value associated with an RFID (or other local) interaction
*/
[[nodiscard]] virtual std::optional<ocpp1_6::IdToken> readToken1_6() = 0;
/**
* Returns an OCPP 2.0 ID token that was presented to the station. This method may either return the token once
* every interaction, or it may return the IdToken for as long as the RFID tag is detected by the RFID reader.
* If the latter approach is used the running transaction module is expected to only respond to changes in
* output.
*
* @return
*/
[[nodiscard]] virtual std::optional<ocpp2_0::IdTokenType> readToken2_0() = 0;
/**
* Gets the active firmware slot (generally some kind of partition ID). This is compared to the update slot to
* determine whether or not the firmware update succeeded.
*
* @return active slot ID
*/
virtual std::string getActiveSlotId() = 0;
/**
* Starts a firmware update operation. Must be paired with a terminating finishUpdateProcess call on success or
* failure.
*
* @param update_size
* @return kSucceeded on success, otherwise kFailed
*/
virtual Result startUpdateProcess(std::size_t update_size) = 0;
/**
* Process an incoming block of data as part of a firmware update, generally writing that block to the update
* partition. May only be called during a firmware update operation (after a call to startUpdateProcess but
* before the associated call to finishUpdateProcess).
*
* @param block
* @param size
* @return kSucceeded on success, otherwise kFailed
*/
virtual Result processFirmwareChunk(std::uint8_t const* block, std::size_t size) = 0;
/**
* Gets the update slot ID (generally some kind of partition ID). This is compared against the active slot ID
* after an update completes to determine whether or not the firmware update succeeded. May only be called
* during a firmware update operation (after a call to startUpdateProcess but before the associated call to
* finishUpdateProcess).
*
* @return update slot ID
*/
virtual std::string getUpdateSlotId() = 0;
/**
* Finishes a firmware update. If succeeded == true and this call returns kSucceeded then the next restart is
* expected to attempt to boot into the updated firmware. If succeeded == false or this call returns kFailed
* then the next restart is expected to boot into the active firmware slot. A return value of kFailed when
* provided with a succeeded == false flag is treated as an error state and the next restart is expected to boot
* into the active firmware slot;
*
* @param succeeded
* @return kSucceeded on success, otherwise kFailed
*/
virtual Result finishUpdateProcess(bool succeeded) = 0;
public:
/**
* Helper method to lookup an OCPP 1.6 connector ID based on the provided metadata.
*
* @param id an OCPP 1.6 connector ID
* @return the associated EVSE if the connector was found, otherwise std::nullopt
*/
virtual std::optional<ocpp2_0::EVSEType> lookupConnectorId1_6(int id) {
for (auto const& entry : getConnectorMetadata()) {
if (entry.second.connector_id1_6 == id)
return entry.first;
}
return std::nullopt;
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_STATION_INTERFACE_H

View File

@@ -0,0 +1,11 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_SYSTEM_TYPES_H
#define CHARGELAB_OPEN_FIRMWARE_SYSTEM_TYPES_H
#include <cstdint>
namespace chargelab {
enum SystemTimeMillis : std::int64_t {};
enum SteadyPointMillis : std::int64_t {};
}
#endif //CHARGELAB_OPEN_FIRMWARE_SYSTEM_TYPES_H

View File

@@ -0,0 +1,79 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_ABSTRACT_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_ABSTRACT_MODULE_H
#include "openocpp/protocol/ocpp1_6/handlers/abstract_request_handler.h"
#include "openocpp/protocol/ocpp1_6/handlers/abstract_response_handler.h"
#include "openocpp/protocol/ocpp1_6/handlers/abstract_service.h"
#include "openocpp/protocol/ocpp2_0/handlers/abstract_request_handler.h"
#include "openocpp/protocol/ocpp2_0/handlers/abstract_response_handler.h"
#include "openocpp/protocol/ocpp2_0/handlers/abstract_service.h"
namespace chargelab {
namespace detail {
class PureServiceInterface {
public:
virtual void runUnconditionally() {
};
};
struct ConcreteImplementationsOCPP1_6 {
ocpp1_6::AbstractService* service;
ocpp1_6::AbstractRequestHandler* request_handler;
ocpp1_6::AbstractResponseHandler* response_handler;
};
struct ConcreteImplementationsOCPP2_0 {
ocpp2_0::AbstractService* service;
ocpp2_0::AbstractRequestHandler* request_handler;
ocpp2_0::AbstractResponseHandler* response_handler;
};
struct ConcreteImplementations {
ConcreteImplementationsOCPP1_6 ocpp1_6;
ConcreteImplementationsOCPP2_0 ocpp2_0;
PureServiceInterface* pure_service;
};
}
class AbstractModuleInterface {
public:
virtual ~AbstractModuleInterface() = default;
virtual detail::ConcreteImplementations getImplementations() = 0;
};
template <typename T>
class AbstractModuleBase : public AbstractModuleInterface {
public:
detail::ConcreteImplementations getImplementations() override {
auto parent_this = static_cast<T*>(this);
detail::ConcreteImplementations result {};
PopulateImplementation(result.ocpp1_6.service, parent_this);
PopulateImplementation(result.ocpp1_6.request_handler, parent_this);
PopulateImplementation(result.ocpp1_6.response_handler, parent_this);
PopulateImplementation(result.ocpp2_0.service, parent_this);
PopulateImplementation(result.ocpp2_0.request_handler, parent_this);
PopulateImplementation(result.ocpp2_0.response_handler, parent_this);
PopulateImplementation(result.pure_service, parent_this);
return result;
}
private:
template <typename U>
static typename std::enable_if<std::is_base_of<U, T>::value, U*>::type ConvertToBase(T* ptr) {
return static_cast<U*>(ptr);
}
template <typename U>
static typename std::enable_if<!std::is_base_of<U, T>::value, U*>::type ConvertToBase(T*) {
return nullptr;
}
template <typename U>
static void PopulateImplementation(U*& implementation, T* ptr) {
implementation = ConvertToBase<U>(ptr);
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_ABSTRACT_MODULE_H

View File

@@ -0,0 +1,699 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_BOOT_NOTIFICATION_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_BOOT_NOTIFICATION_MODULE_H
#include <utility>
#include "openocpp/module/common_templates.h"
#include "openocpp/common/operation_holder.h"
#include "openocpp/common/settings.h"
#include "openocpp/common/logging.h"
namespace chargelab {
class BootNotificationModule : public ServiceStatefulGeneral
{
/* OCPP 1.6 Section 4.2 on Pending registration statuses:
*
* The Central System MAY also return a Pending registration status to indicate that it wants to retrieve or set
* certain information on the Charge Point before the Central System will accept the Charge Point. If the Central
* System returns the Pending status, the communication channel SHOULD NOT be closed by either the Charge
* Point or the Central System. The Central System MAY send request messages to retrieve information from the
* Charge Point or change its configuration. The Charge Point SHOULD respond to these messages. The Charge
* Point SHALL NOT send request messages to the Central System unless it has been instructed by the Central
* System to do so with a TriggerMessage.req request.
*/
private:
static constexpr const int kSecurityProfileMigrationResetGracePeriodMillis = 5*1000;
public:
BootNotificationModule(
std::shared_ptr<Settings> settings,
std::shared_ptr<SystemInterface> const& system_interface
)
: settings_(std::move(settings)),
system_interface_(system_interface),
pending_boot_notification_req_ {system_interface}
{
assert(system_interface_ != nullptr);
if (settings_->hasPendingTransition(SettingTransitionType::kConnection)) {
auto timeout = system_interface_->steadyClockNow() + settings_->ConnectionTransitionTimeout.getValue()*1000;
if (settings_->startTransition(SettingTransitionType::kConnection)) {
CHARGELAB_LOG_MESSAGE(info) << "Starting connection transition";
connection_transition_timeout_ = static_cast<SteadyPointMillis>(timeout);
}
}
auto const reason_text = settings_->CustomBootReason.getValue();
if (!reason_text.empty())
CHARGELAB_LOG_MESSAGE(info) << "Custom boot reason was: " << settings_->CustomBootReason.getValue();
}
~BootNotificationModule() override {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting BootNotificationModule";
}
public:
bool registrationComplete() {
return registration_complete_;
}
private:
// OCPP 1.6 implementation
void runStep(ocpp1_6::OcppRemote &remote) override {
// Clear this flag if present
settings_->CustomBootReason.setValue("");
runStepImpl([&]() {
// TODO: Improved validation/length safety (truncate?)
ocpp1_6::BootNotificationReq req {};
req.chargePointVendor = ocpp1_6::CiString20Type(settings_->ChargerVendor.getValue());
req.chargePointModel = ocpp1_6::CiString20Type(settings_->ChargerModel.getValue());
req.chargePointSerialNumber = ocpp1_6::CiString25Type(settings_->ChargerSerialNumber.getValue());
req.firmwareVersion = ocpp1_6::CiString50Type(settings_->ChargerFirmwareVersion.getValue());
if (auto iccid = settings_->ChargerICCID.getValue(); !iccid.empty())
req.iccid = ocpp1_6::CiString20Type(iccid);
if (auto imsi = settings_->ChargerIMSI.getValue(); !imsi.empty())
req.imsi = ocpp1_6::CiString20Type(imsi);
if (auto meter_serial_number = settings_->ChargerMeterSerialNumber.getValue(); !meter_serial_number.empty())
req.meterSerialNumber = ocpp1_6::CiString25Type(meter_serial_number);
if (auto meter_type = settings_->ChargerMeterType.getValue(); !meter_type.empty())
req.meterType = ocpp1_6::CiString25Type(meter_type);
return remote.sendBootNotificationReq(req);
});
}
void onBootNotificationRsp(
const std::string &unique_id,
const ocpp1_6::ResponseMessage<ocpp1_6::BootNotificationRsp> &rsp
) override {
if (pending_boot_notification_req_ == unique_id) {
pending_boot_notification_req_ = kNoOperation;
if (std::holds_alternative<ocpp1_6::BootNotificationRsp>(rsp)) {
auto const& value = std::get<ocpp1_6::BootNotificationRsp>(rsp);
auto const& ts = value.currentTime.getTimestamp();
if (ts.has_value()) {
system_interface_->setSystemClock(ts.value());
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Invalid BootNotification response timestamp: "
<< value.currentTime;
}
CHARGELAB_LOG_MESSAGE(info) << "BootNotification response was: " << value;
switch (value.status) {
case ocpp1_6::RegistrationStatus::kAccepted:
requested_next_boot_notification_req_ = std::nullopt;
settings_->HeartbeatInterval.setValue(value.interval);
break;
case ocpp1_6::RegistrationStatus::kValueNotFoundInEnum:
CHARGELAB_LOG_MESSAGE(error) << "Invalid BootNotification response status for request ID: " << unique_id;
[[fallthrough]];
case ocpp1_6::RegistrationStatus::kPending:
case ocpp1_6::RegistrationStatus::kRejected:
requested_next_boot_notification_req_ = static_cast<SteadyPointMillis> (
system_interface_->steadyClockNow() + value.interval*1000
);
break;
}
switch (value.status) {
case ocpp1_6::RegistrationStatus::kRejected:
case ocpp1_6::RegistrationStatus::kValueNotFoundInEnum:
registration_complete_ = false;
allow_ocpp_calls_ = false;
break;
case ocpp1_6::RegistrationStatus::kPending:
registration_complete_ = false;
allow_ocpp_calls_ = true;
break;
case ocpp1_6::RegistrationStatus::kAccepted:
registration_complete_ = true;
allow_ocpp_calls_ = true;
break;
}
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to BootNotification request: " << std::get<ocpp1_6::CallError> (rsp);
}
}
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
switch (req.requestedMessage) {
case ocpp1_6::MessageTrigger::kBootNotification:
// Note: deviating from the 2.0.1 specification and allowing boot notification trigger messages
// after registration completed in 1.6 here.
force_boot_notification_req_ = true;
return ocpp1_6::TriggerMessageRsp{ocpp1_6::TriggerMessageStatus::kAccepted};
default:
case ocpp1_6::MessageTrigger::kValueNotFoundInEnum:
case ocpp1_6::MessageTrigger::kDiagnosticsStatusNotification:
case ocpp1_6::MessageTrigger::kFirmwareStatusNotification:
case ocpp1_6::MessageTrigger::kHeartbeat:
case ocpp1_6::MessageTrigger::kMeterValues:
case ocpp1_6::MessageTrigger::kStatusNotification:
if (!registrationComplete()) {
return unauthorizedError1_6();
} else {
return std::nullopt;
}
}
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::RemoteStartTransactionRsp>>
onRemoteStartTransactionReq(const ocpp1_6::RemoteStartTransactionReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::RemoteStopTransactionRsp>>
onRemoteStopTransactionReq(const ocpp1_6::RemoteStopTransactionReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::CancelReservationRsp>>
onCancelReservationReq(const ocpp1_6::CancelReservationReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeAvailabilityRsp>>
onChangeAvailabilityReq(const ocpp1_6::ChangeAvailabilityReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeConfigurationRsp>>
onChangeConfigurationReq(const ocpp1_6::ChangeConfigurationReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ClearCacheRsp>>
onClearCacheReq(const ocpp1_6::ClearCacheReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ClearChargingProfileRsp>>
onClearChargingProfileReq(const ocpp1_6::ClearChargingProfileReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::DataTransferRsp>>
onDataTransferReq(const ocpp1_6::DataTransferReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetCompositeScheduleRsp>>
onGetCompositeScheduleReq(const ocpp1_6::GetCompositeScheduleReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetConfigurationRsp>>
onGetConfigurationReq(const ocpp1_6::GetConfigurationReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetDiagnosticsRsp>>
onGetDiagnosticsReq(const ocpp1_6::GetDiagnosticsReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetLocalListVersionRsp>>
onGetLocalListVersionReq(const ocpp1_6::GetLocalListVersionReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ReserveNowRsp>>
onReserveNowReq(const ocpp1_6::ReserveNowReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ResetRsp>>
onResetReq(const ocpp1_6::ResetReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::SendLocalListRsp>>
onSendLocalListReq(const ocpp1_6::SendLocalListReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::SetChargingProfileRsp>>
onSetChargingProfileReq(const ocpp1_6::SetChargingProfileReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::UnlockConnectorRsp>>
onUnlockConnectorReq(const ocpp1_6::UnlockConnectorReq&) override {
return unauthorizedCallHandler1_6();
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::UpdateFirmwareRsp>>
onUpdateFirmwareReq(const ocpp1_6::UpdateFirmwareReq&) override {
return unauthorizedCallHandler1_6();
}
// OCPP 2.0.1 implementation
void runStep(ocpp2_0::OcppRemote &remote) override {
auto reason = ocpp2_0::BootReasonEnumType::kPowerUp;
auto const reason_text = settings_->CustomBootReason.getValue();
if (!reason_text.empty()) {
reason = ocpp2_0::BootReasonEnumType::from_string(reason_text);
} else if (force_boot_notification_req_) {
reason = ocpp2_0::BootReasonEnumType::kTriggered;
}
runStepImpl([&]() {
ocpp2_0::BootNotificationRequest req {};
req.reason = reason;
req.chargingStation.serialNumber = settings_->ChargerSerialNumber.getValue();
req.chargingStation.model = settings_->ChargerModel.getValue();
req.chargingStation.vendorName = settings_->ChargerVendor.getValue();
req.chargingStation.firmwareVersion = settings_->ChargerFirmwareVersion.getValue();
auto modem = ocpp2_0::ModemType {};
if (auto iccid = settings_->ChargerICCID.getValue(); !iccid.empty())
modem.iccid = iccid;
if (auto imsi = settings_->ChargerIMSI.getValue(); !imsi.empty())
modem.imsi = imsi;
if (modem.iccid.has_value() || modem.imsi.has_value())
req.chargingStation.modem = std::move(modem);
return remote.sendBootNotificationReq(req);
});
}
void onBootNotificationRsp(
const std::string &unique_id,
const ocpp2_0::ResponseMessage<ocpp2_0::BootNotificationResponse> &rsp
) override {
if (pending_boot_notification_req_ == unique_id) {
pending_boot_notification_req_ = kNoOperation;
settings_->CustomBootReason.setValue("");
if (std::holds_alternative<ocpp2_0::BootNotificationResponse>(rsp)) {
auto const& value = std::get<ocpp2_0::BootNotificationResponse>(rsp);
auto const& ts = value.currentTime.getTimestamp();
if (ts.has_value()) {
system_interface_->setSystemClock(ts.value());
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Invalid BootNotification response timestamp: "
<< value.currentTime;
}
CHARGELAB_LOG_MESSAGE(info) << "BootNotification response was: " << value;
switch (value.status) {
case ocpp2_0::RegistrationStatusEnumType::kAccepted:
requested_next_boot_notification_req_ = std::nullopt;
settings_->HeartbeatInterval.setValue(value.interval);
break;
case ocpp2_0::RegistrationStatusEnumType::kValueNotFoundInEnum:
CHARGELAB_LOG_MESSAGE(error) << "Invalid BootNotification response status for request ID: " << unique_id;
[[fallthrough]];
case ocpp2_0::RegistrationStatusEnumType::kPending:
case ocpp2_0::RegistrationStatusEnumType::kRejected:
requested_next_boot_notification_req_ = static_cast<SteadyPointMillis> (
system_interface_->steadyClockNow() + value.interval*1000
);
break;
}
switch (value.status) {
case ocpp2_0::RegistrationStatusEnumType::kRejected:
case ocpp2_0::RegistrationStatusEnumType::kValueNotFoundInEnum:
registration_complete_ = false;
allow_ocpp_calls_ = false;
break;
case ocpp2_0::RegistrationStatusEnumType::kPending:
registration_complete_ = false;
allow_ocpp_calls_ = true;
break;
case ocpp2_0::RegistrationStatusEnumType::kAccepted:
registration_complete_ = true;
allow_ocpp_calls_ = true;
break;
}
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to BootNotification request: " << std::get<ocpp2_0::CallError> (rsp);
}
}
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest& req) override {
switch (req.requestedMessage) {
case ocpp2_0::MessageTriggerEnumType::kBootNotification:
if (!registration_complete_) {
force_boot_notification_req_ = true;
return ocpp2_0::TriggerMessageResponse{ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
} else {
// F06.FR.17
return ocpp2_0::TriggerMessageResponse{ocpp2_0::TriggerMessageStatusEnumType::kRejected};
}
default:
return unauthorizedCallHandler2_0();
}
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CancelReservationResponse>>
onCancelReservationReq(const ocpp2_0::CancelReservationRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CertificateSignedResponse>>
onCertificateSignedReq(const ocpp2_0::CertificateSignedRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ChangeAvailabilityResponse>>
onChangeAvailabilityReq(const ocpp2_0::ChangeAvailabilityRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearCacheResponse>>
onClearCacheReq(const ocpp2_0::ClearCacheRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearChargingProfileResponse>>
onClearChargingProfileReq(const ocpp2_0::ClearChargingProfileRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearDisplayMessageResponse>>
onClearDisplayMessageReq(const ocpp2_0::ClearDisplayMessageRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearVariableMonitoringResponse>>
onClearVariableMonitoringReq(const ocpp2_0::ClearVariableMonitoringRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CostUpdatedResponse>>
onCostUpdatedReq(const ocpp2_0::CostUpdatedRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CustomerInformationResponse>>
onCustomerInformationReq(const ocpp2_0::CustomerInformationRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::DataTransferResponse>>
onDataTransferReq(const ocpp2_0::DataTransferRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::DeleteCertificateResponse>>
onDeleteCertificateReq(const ocpp2_0::DeleteCertificateRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetBaseReportResponse>>
onGetBaseReportReq(const ocpp2_0::GetBaseReportRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetChargingProfilesResponse>>
onGetChargingProfilesReq(const ocpp2_0::GetChargingProfilesRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetCompositeScheduleResponse>>
onGetCompositeScheduleReq(const ocpp2_0::GetCompositeScheduleRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetDisplayMessagesResponse>>
onGetDisplayMessagesReq(const ocpp2_0::GetDisplayMessagesRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetInstalledCertificateIdsResponse>>
onGetInstalledCertificateIdsReq(const ocpp2_0::GetInstalledCertificateIdsRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetLocalListVersionResponse>>
onGetLocalListVersionReq(const ocpp2_0::GetLocalListVersionRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetLogResponse>>
onGetLogReq(const ocpp2_0::GetLogRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetMonitoringReportResponse>>
onGetMonitoringReportReq(const ocpp2_0::GetMonitoringReportRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetReportResponse>>
onGetReportReq(const ocpp2_0::GetReportRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetTransactionStatusResponse>>
onGetTransactionStatusReq(const ocpp2_0::GetTransactionStatusRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetVariablesResponse>>
onGetVariablesReq(const ocpp2_0::GetVariablesRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::InstallCertificateResponse>>
onInstallCertificateReq(const ocpp2_0::InstallCertificateRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::PublishFirmwareResponse>>
onPublishFirmwareReq(const ocpp2_0::PublishFirmwareRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::RequestStartTransactionResponse>>
onRequestStartTransactionReq(const ocpp2_0::RequestStartTransactionRequest&) override {
// B02.FR.05
if (!registration_complete_) {
return ocpp2_0::RequestStartTransactionResponse {ocpp2_0::RequestStartStopStatusEnumType::kRejected};
}
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::RequestStopTransactionResponse>>
onRequestStopTransactionReq(const ocpp2_0::RequestStopTransactionRequest&) override {
// B02.FR.05
if (!registration_complete_) {
return ocpp2_0::RequestStopTransactionResponse {ocpp2_0::RequestStartStopStatusEnumType::kRejected};
}
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ReserveNowResponse>>
onReserveNowReq(const ocpp2_0::ReserveNowRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ResetResponse>>
onResetReq(const ocpp2_0::ResetRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SendLocalListResponse>>
onSendLocalListReq(const ocpp2_0::SendLocalListRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetChargingProfileResponse>>
onSetChargingProfileReq(const ocpp2_0::SetChargingProfileRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetDisplayMessageResponse>>
onSetDisplayMessageReq(const ocpp2_0::SetDisplayMessageRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetMonitoringBaseResponse>>
onSetMonitoringBaseReq(const ocpp2_0::SetMonitoringBaseRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetMonitoringLevelResponse>>
onSetMonitoringLevelReq(const ocpp2_0::SetMonitoringLevelRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetNetworkProfileResponse>>
onSetNetworkProfileReq(const ocpp2_0::SetNetworkProfileRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetVariableMonitoringResponse>>
onSetVariableMonitoringReq(const ocpp2_0::SetVariableMonitoringRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetVariablesResponse>>
onSetVariablesReq(const ocpp2_0::SetVariablesRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UnlockConnectorResponse>>
onUnlockConnectorReq(const ocpp2_0::UnlockConnectorRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UnpublishFirmwareResponse>>
onUnpublishFirmwareReq(const ocpp2_0::UnpublishFirmwareRequest&) override {
return unauthorizedCallHandler2_0();
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UpdateFirmwareResponse>>
onUpdateFirmwareReq(const ocpp2_0::UpdateFirmwareRequest&) override {
return unauthorizedCallHandler2_0();
}
private:
template <typename T>
void runStepImpl(T&& sendBootNotification) {
auto const now = system_interface_->steadyClockNow();
if (connection_transition_timeout_.has_value()) {
if (allow_ocpp_calls_) {
CHARGELAB_LOG_MESSAGE(debug) << "Connection transition completed - committing";
connection_transition_timeout_ = std::nullopt;
settings_->commit(SettingTransitionType::kConnection);
} else if (now >= connection_transition_timeout_.value()) {
CHARGELAB_LOG_MESSAGE(debug) << "Connection transition failed - rolling back to previous settings";
settings_->rollback(SettingTransitionType::kConnection);
settings_->saveIfModified();
system_interface_->resetHard();
}
}
// Note: only updating the security profile when first connecting to the back-end
if (!security_profile_checked_ && allow_ocpp_calls_ && !settings_->hasRunningTransition(SettingTransitionType::kConnection)) {
auto const activeNetworkProfileSlot = settings_->ActiveNetworkProfile.getValue();
auto const activeNetworkProfile = settings_->NetworkConnectionProfiles.transitionCurrentValue(activeNetworkProfileSlot);
if (activeNetworkProfile.has_value() &&
activeNetworkProfile->securityProfile > settings_->SecurityProfile.getValue()) {
// A05.FR.06
for (std::size_t i = 0; i < settings_->NetworkConnectionProfiles.transitionSize(); i++) {
auto const value = settings_->NetworkConnectionProfiles.transitionCurrentValue(i);
if (!value.has_value())
continue;
if (value->securityProfile >= activeNetworkProfile->securityProfile)
continue;
CHARGELAB_LOG_MESSAGE(info) << "Removing profile " << i << ": " << value;
settings_->NetworkConnectionProfiles.forceCurrentValue(i, std::nullopt);
char const* ifs = "";
std::string filtered_priorities;
string::SplitVisitor(settings_->NetworkConfigurationPriority.transitionCurrentValue(), ",", [&](std::string const& value) {
if (string::ToInteger(value) != std::make_optional(i)) {
filtered_priorities += ifs + value;
ifs = ",";
}
});
CHARGELAB_LOG_MESSAGE(info) << "Priorities before: " << settings_->NetworkConfigurationPriority.transitionCurrentValue();
settings_->NetworkConfigurationPriority.forceValue(filtered_priorities);
CHARGELAB_LOG_MESSAGE(info) << "Priorities after: " << settings_->NetworkConfigurationPriority.transitionCurrentValue();
}
settings_->SecurityProfile.setValue(activeNetworkProfile->securityProfile);
CHARGELAB_LOG_MESSAGE(info) << "Security profile updated to: " << activeNetworkProfile->securityProfile;
}
security_profile_checked_ = true;
}
if (force_boot_notification_req_) {
force_boot_notification_req_ = false;
pending_boot_notification_req_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
sendBootNotification()
);
return;
}
if (registrationComplete())
return;
if (requested_next_boot_notification_req_.has_value()) {
if (now - requested_next_boot_notification_req_.value() < 0)
return;
}
if (pending_boot_notification_req_.wasIdleFor(settings_->HeartbeatInterval.getValue())) {
pending_boot_notification_req_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
sendBootNotification()
);
}
}
std::optional<ocpp1_6::CallError> unauthorizedCallHandler1_6() {
if (allow_ocpp_calls_) {
return std::nullopt;
} else {
return unauthorizedError1_6();
}
}
std::optional<ocpp2_0::CallError> unauthorizedCallHandler2_0() {
if (allow_ocpp_calls_) {
return std::nullopt;
} else {
return unauthorizedError2_0();
}
}
static ocpp1_6::CallError unauthorizedError1_6() {
return ocpp1_6::CallError {
ocpp1_6::ErrorCode::kSecurityError,
"Waiting for CSMS to accept boot notification",
common::RawJson::empty_object()
};
}
static ocpp2_0::CallError unauthorizedError2_0() {
return ocpp2_0::CallError {
ocpp2_0::ErrorCode::kSecurityError,
"Waiting for CSMS to accept boot notification",
common::RawJson::empty_object()
};
}
private:
std::shared_ptr<Settings> settings_;
std::shared_ptr<SystemInterface> system_interface_;
OperationHolder<std::string> pending_boot_notification_req_;
std::optional<SteadyPointMillis> connection_transition_timeout_ = std::nullopt;
std::optional<SteadyPointMillis> requested_next_boot_notification_req_ = std::nullopt;
std::atomic<bool> registration_complete_ = false;
std::atomic<bool> allow_ocpp_calls_ = false;
std::atomic<bool> force_boot_notification_req_ = false;
std::atomic<bool> security_profile_checked_ = false;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_BOOT_NOTIFICATION_MODULE_H

View File

@@ -0,0 +1,79 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_COMMON_TEMPLATES_H
#define CHARGELAB_OPEN_FIRMWARE_COMMON_TEMPLATES_H
#include "openocpp/protocol/ocpp1_6/handlers/abstract_request_handler.h"
#include "openocpp/protocol/ocpp1_6/handlers/abstract_response_handler.h"
#include "openocpp/protocol/ocpp1_6/handlers/abstract_service.h"
#include "openocpp/protocol/ocpp2_0/handlers/abstract_request_handler.h"
#include "openocpp/protocol/ocpp2_0/handlers/abstract_response_handler.h"
#include "openocpp/protocol/ocpp2_0/handlers/abstract_service.h"
#include "openocpp/module/abstract_module.h"
namespace chargelab {
class ServiceStateless1_6 :
public AbstractModuleBase<ServiceStateless1_6>,
private detail::PureServiceInterface,
private ocpp1_6::AbstractRequestHandler
{
friend class AbstractModuleBase<ServiceStateless1_6>;
};
class ServiceStateful1_6 :
public AbstractModuleBase<ServiceStateful1_6>,
private detail::PureServiceInterface,
private ocpp1_6::AbstractService,
private ocpp1_6::AbstractRequestHandler,
private ocpp1_6::AbstractResponseHandler
{
friend class AbstractModuleBase<ServiceStateful1_6>;
};
class ServiceStateless2_0 :
public AbstractModuleBase<ServiceStateless2_0>,
private detail::PureServiceInterface,
private ocpp2_0::AbstractRequestHandler
{
friend class AbstractModuleBase<ServiceStateless2_0>;
};
class ServiceStateful2_0 :
public AbstractModuleBase<ServiceStateful2_0>,
private detail::PureServiceInterface,
private ocpp2_0::AbstractService,
private ocpp2_0::AbstractRequestHandler,
private ocpp2_0::AbstractResponseHandler
{
friend class AbstractModuleBase<ServiceStateful2_0>;
};
class ServiceStatelessGeneral :
public AbstractModuleBase<ServiceStatelessGeneral>,
private detail::PureServiceInterface,
private ocpp1_6::AbstractRequestHandler,
private ocpp2_0::AbstractRequestHandler
{
friend class AbstractModuleBase<ServiceStatelessGeneral>;
};
class ServiceStatefulGeneral :
public AbstractModuleBase<ServiceStatefulGeneral>,
private detail::PureServiceInterface,
private ocpp1_6::AbstractService,
private ocpp1_6::AbstractRequestHandler,
private ocpp1_6::AbstractResponseHandler,
private ocpp2_0::AbstractService,
private ocpp2_0::AbstractRequestHandler,
private ocpp2_0::AbstractResponseHandler
{
friend class AbstractModuleBase<ServiceStatefulGeneral>;
};
class PureService :
public AbstractModuleBase<PureService>,
private detail::PureServiceInterface
{
friend class AbstractModuleBase<PureService>;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_COMMON_TEMPLATES_H

View File

@@ -0,0 +1,521 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_CONFIGURATION_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_CONFIGURATION_MODULE_H
#include "openocpp/protocol/common/protocol_constants.h"
#include "openocpp/module/common_templates.h"
#include "openocpp/common/settings.h"
#include "openocpp/helpers/string.h"
#include "openocpp/interface/component/system_interface.h"
#include <utility>
#include <functional>
namespace chargelab {
class ConfigurationModule : public ServiceStatefulGeneral {
private:
static constexpr char const* kMaskedValue = "****";
static constexpr int kOcpp20NotifyReportRequestOverheadBytes = 100;
public:
explicit ConfigurationModule(std::shared_ptr<Settings> settings, std::shared_ptr<SystemInterface> system)
: settings_(std::move(settings)),
system_(std::move(system))
{
assert(settings_ != nullptr);
assert(system_ != nullptr);
}
~ConfigurationModule() override {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting ConfigurationModule";
settings_->saveIfModified();
}
public:
void runUnconditionally() override {
settings_->saveIfModified();
}
void runStep(ocpp1_6::OcppRemote&) override {
}
void runStep(ocpp2_0::OcppRemote &remote) override {
if (ocpp2_0_pending_base_report_.has_value()) {
auto request = ocpp2_0_pending_base_report_.value();
auto settings = settings_;
ocpp2_0::NotifyReportRequest response {
request.requestId,
{system_->systemClockNow()},
false,
0,
{[&](std::function<void(ocpp2_0::ReportDataType const &)> const &visitor) {
settings->visitSettings([&](SettingBase& setting) {
auto const metadata = setting.getMetadata();
bool include_characteristics;
switch (request.reportBase) {
default:
CHARGELAB_LOG_MESSAGE(error) << "Unexpected report type in generator: " << request.reportBase;
return;
case ocpp2_0::ReportBaseEnumType::kConfigurationInventory:
include_characteristics = true;
if (!metadata.config.isAllowOcppWrite())
return;
break;
case ocpp2_0::ReportBaseEnumType::kFullInventory:
include_characteristics = true;
break;
}
if (!metadata.model2_0.has_value())
return;
auto element = ocpp2_0::ReportDataType {
metadata.model2_0->component_type,
metadata.model2_0->variable_type,
setting.getAttributes2_0()
};
if (include_characteristics) {
element.variableCharacteristics = metadata.model2_0->variable_characteristics;
}
visitor(element);
});
}}
};
if (remote.sendNotifyReportReq(response).has_value()) {
ocpp2_0_pending_base_report_ = std::nullopt;
}
}
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetVariablesResponse>>
onGetVariablesReq(const ocpp2_0::GetVariablesRequest &request) override {
if ((int)request.getVariableData.size() > settings_->ItemsPerMessageGetVariables.getValue()) {
return ocpp2_0::CallError {
ocpp2_0::ErrorCode::kOccurrenceConstraintViolation,
{"Exceeded ItemsPerMessageGetVariables limit"},
common::RawJson::empty_object()
};
}
auto settings = settings_;
return ocpp2_0::GetVariablesResponse {
{[=](std::function<void(ocpp2_0::GetVariableResultType const &)> const &visitor) {
for (auto const& get_variable : request.getVariableData) {
bool componentFound = false;
bool variableFound = false;
auto result = ocpp2_0::GetVariableResultType {
ocpp2_0::GetVariableStatusEnumType::kValueNotFoundInEnum,
get_variable.attributeType,
std::nullopt,
get_variable.component,
get_variable.variable
};
settings->visitSettings([&](SettingBase& setting) {
auto const metadata = setting.getMetadata();
if (variableFound)
return;
if (!metadata.model2_0.has_value())
return;
if (metadata.model2_0->component_type != get_variable.component) {
return;
} else {
componentFound = true;
}
if (metadata.model2_0->variable_type != get_variable.variable) {
return;
} else {
variableFound = true;
}
if (get_variable.attributeType.has_value()) {
// TODO - right now everything defined is "actual"
if (get_variable.attributeType.value() != ocpp2_0::AttributeEnumType::kActual) {
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kNotSupportedAttributeType;
return;
}
}
if (!metadata.config.isAllowOcppRead()) {
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kRejected;
return;
}
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kAccepted;
result.attributeValue = setting.getValueAsString();
});
if (!componentFound) {
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kUnknownComponent;
} else if (!variableFound) {
result.attributeStatus = ocpp2_0::GetVariableStatusEnumType::kUnknownVariable;
}
visitor(result);
}
}}
};
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetVariablesResponse>>
onSetVariablesReq(const ocpp2_0::SetVariablesRequest &request) override {
std::vector<ocpp2_0::SetVariableResultType> results;
for (auto const& set_variable : request.setVariableData) {
bool componentFound = false;
bool variableFound = false;
auto result = ocpp2_0::SetVariableResultType {
set_variable.attributeType,
ocpp2_0::SetVariableStatusEnumType::kValueNotFoundInEnum,
set_variable.component,
set_variable.variable
};
auto const network_configuration_priority_id = settings_->NetworkConfigurationPriority.getId();
settings_->visitSettings([&](SettingBase& setting) {
auto const metadata = setting.getMetadata();
if (variableFound)
return;
if (!metadata.model2_0.has_value())
return;
if (metadata.model2_0->component_type != set_variable.component) {
return;
} else {
componentFound = true;
}
if (metadata.model2_0->variable_type != set_variable.variable) {
return;
} else {
variableFound = true;
}
if (set_variable.attributeType.has_value()) {
// TODO - right now everything defined is "actual"
if (set_variable.attributeType.value() != ocpp2_0::AttributeEnumType::kActual) {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kNotSupportedAttributeType;
return;
}
}
if (!metadata.config.isAllowOcppWrite()) {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
return;
}
// A05.FR.02
if (metadata.id == network_configuration_priority_id) {
bool missing_profile = false;
int target_security_profile = 0;
string::SplitVisitor(set_variable.attributeValue.value(), ",", [&](std::string const& text) {
auto slot = string::ToInteger(text);
if (!slot.has_value())
return;
auto const& profile = settings_->NetworkConnectionProfiles.getValue(slot.value());
if (!profile.has_value()) {
missing_profile = true;
return;
}
target_security_profile = std::max(target_security_profile, profile->securityProfile);
});
// TODO: Is there a specific requirement for this?
if (target_security_profile < settings_->SecurityProfile.getValue()) {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
return;
}
if (target_security_profile > settings_->SecurityProfile.getValue()) {
// A05.FR.02
if (target_security_profile >= 2 && settings_->InstalledCSMSRootCertificateCount.getValue() <= 0) {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
return;
}
// A05.FR.03
// TODO: Not relevant until we have something that can store a charging station certificate
if (target_security_profile == 3) {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
return;
}
}
}
// TODO: move to update attribute?
if (!settings_->setSettingValue(metadata.id, set_variable.attributeValue.value())) {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRejected;
return;
}
if (metadata.config.isRebootRequired()) {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kRebootRequired;
} else {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kAccepted;
}
});
if (!componentFound) {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kUnknownComponent;
} else if (!variableFound) {
result.attributeStatus = ocpp2_0::SetVariableStatusEnumType::kUnknownVariable;
}
results.push_back(std::move(result));
}
return ocpp2_0::SetVariablesResponse {std::move(results)};
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetBaseReportResponse>>
onGetBaseReportReq(const ocpp2_0::GetBaseReportRequest &request) override {
if (ocpp2_0_pending_base_report_.has_value()) {
return ocpp2_0::GetBaseReportResponse {
ocpp2_0::GenericDeviceModelStatusEnumType::kRejected
};
}
switch (request.reportBase) {
case ocpp2_0::ReportBaseEnumType::kValueNotFoundInEnum:
case ocpp2_0::ReportBaseEnumType::kSummaryInventory:
return ocpp2_0::GetBaseReportResponse {
ocpp2_0::GenericDeviceModelStatusEnumType::kNotSupported
};
case ocpp2_0::ReportBaseEnumType::kFullInventory:
case ocpp2_0::ReportBaseEnumType::kConfigurationInventory:
break;
}
ocpp2_0_pending_base_report_ = request;
return ocpp2_0::GetBaseReportResponse {
ocpp2_0::GenericDeviceModelStatusEnumType::kAccepted
};
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeConfigurationRsp>>
onChangeConfigurationReq(const ocpp1_6::ChangeConfigurationReq& req) override {
return changeConfiguration(req, false);
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeConfigurationRsp>>
changeConfiguration(const ocpp1_6::ChangeConfigurationReq& req, bool force_change) {
CHARGELAB_LOG_MESSAGE(info) << "changing configuration, key:" << req.key.value() << ", value:" << req.value.value();
auto const state = settings_->getSettingState(req.key.value());
if (!force_change) {
if (!state.has_value() || (!state->config.isAllowOcppWrite() && !state->config.isAllowOcppRead()))
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kNotSupported};
if (!state->config.isAllowOcppWrite())
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kRejected};
}
// TODO: Move to 1_6 device model rather than id
if (!settings_->setSettingValue(req.key.value(), req.value.value()))
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kRejected};
if (force_change) {
settings_->saveIfModified();
}
if (state->config.isRebootRequired()) {
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kRebootRequired};
} else {
return ocpp1_6::ChangeConfigurationRsp{ocpp1_6::ConfigurationStatus::kAccepted};
}
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetConfigurationRsp>>
onGetConfigurationReq(const ocpp1_6::GetConfigurationReq &req) override {
return getConfiguration(req);
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetConfigurationRsp>>
getConfiguration(const ocpp1_6::GetConfigurationReq &req) {
std::vector<std::string> include_keys;
std::vector<ocpp1_6::CiString50Type> unknown_keys = req.key.value();
// TODO: Move to 1_6 device model rather than id
bool include_all_keys = req.key.value().empty();
if (!unknown_keys.empty()) {
if ((int)req.key.value().size() > settings_->GetConfigurationMaxKeys.getValue()) {
return ocpp1_6::CallError {
ocpp1_6::ErrorCode::kPropertyConstraintViolation,
"ConnectorId is not same as the local connector Id - must be same",
common::RawJson::empty_object()
};
}
unknown_keys = req.key.value();
settings_->visitSettings([&](SettingBase const& setting) {
auto const metadata = setting.getMetadata();
if (!metadata.config.isAllowOcppRead() && !metadata.config.isAllowOcppWrite())
return;
auto it = std::remove_if(unknown_keys.begin(), unknown_keys.end(), [&] (auto const& x) {
return string::EqualsIgnoreCaseAscii(x.value(), metadata.id);
});
if (it != unknown_keys.end()) {
include_keys.push_back(metadata.id);
unknown_keys.erase(it, unknown_keys.end());
}
});
}
auto settings = settings_;
return ocpp1_6::GetConfigurationRsp {
{[=](std::function<void(ocpp1_6::KeyValue const &)> const &visitor) {
auto const& keys = include_keys;
settings->visitSettings([&](SettingBase const& setting) {
auto const metadata = setting.getMetadata();
if (!metadata.config.isAllowOcppRead() && !metadata.config.isAllowOcppWrite())
return;
std::string value;
if (metadata.config.isAllowOcppRead()) {
value = setting.getValueAsString();
} else {
value = kMaskedValue;
}
if (include_all_keys || std::find(keys.begin(), keys.end(), setting.getId()) != keys.end()) {
visitor(ocpp1_6::KeyValue{
{metadata.id},
!metadata.config.isAllowOcppWrite(),
std::move(value)
});
}
});
}},
std::move(unknown_keys)
};
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::SetNetworkProfileResponse>>
onSetNetworkProfileReq(const ocpp2_0::SetNetworkProfileRequest &request) override {
if (request.configurationSlot < 0 || request.configurationSlot >= detail::SettingsConstants::kMaxProfileSlots) {
// B09.FR.02
return ocpp2_0::SetNetworkProfileResponse {
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {{"BadConfigurationSlot"}}
};
}
// Note: SIM/VPN not supported
auto const& data = request.connectionData;
if (data.apn.has_value()) {
// B09.FR.02
return ocpp2_0::SetNetworkProfileResponse {
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {{"ApnNotSupported"}}
};
}
if (data.vpn.has_value()) {
// B09.FR.02
return ocpp2_0::SetNetworkProfileResponse {
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {{"VpnNotSupported"}}
};
}
// Note: only JSON is supported
if (data.ocppTransport != ocpp2_0::OCPPTransportEnumType::kJSON) {
// B09.FR.02
return ocpp2_0::SetNetworkProfileResponse {
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {{"BadOcppTransport"}}
};
}
std::string protocol;
switch (data.ocppVersion) {
default:
case ocpp2_0::OCPPVersionEnumType::kOCPP12:
case ocpp2_0::OCPPVersionEnumType::kOCPP15:
// B09.FR.02
return ocpp2_0::SetNetworkProfileResponse {
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {{"BadOcppVersion"}}
};
case ocpp2_0::OCPPVersionEnumType::kOCPP16:
protocol = ProtocolConstants::kProtocolOcpp1_6;
break;
case ocpp2_0::OCPPVersionEnumType::kOCPP20:
protocol = ProtocolConstants::kProtocolOcpp2_0_1;
break;
}
// Note: an assumed "wifi" interface is always used here
if (data.ocppInterface != ocpp2_0::OCPPInterfaceEnumType::kWireless0) {
// B09.FR.02
return ocpp2_0::SetNetworkProfileResponse {
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {{"BadOcppInterface"}}
};
}
auto parsed = uri::ParseWebsocketUri(data.ocppCsmsUrl.value());
if (!parsed.has_value()) {
// B09.FR.02
return ocpp2_0::SetNetworkProfileResponse {
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {{"BadOcppCsmsUrl"}}
};
}
if (request.connectionData.securityProfile < settings_->SecurityProfile.getValue()) {
// B09.FR.04
return ocpp2_0::SetNetworkProfileResponse {
ocpp2_0::SetNetworkProfileStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {{"BadSecurityProfile"}}
};
}
if (settings_->NetworkConnectionProfiles.setValue(request.configurationSlot, request.connectionData)) {
// B09.FR.01
return ocpp2_0::SetNetworkProfileResponse {ocpp2_0::SetNetworkProfileStatusEnumType::kAccepted};
} else {
// B09.FR.03
return ocpp2_0::SetNetworkProfileResponse {ocpp2_0::SetNetworkProfileStatusEnumType::kFailed};
}
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetMonitoringReportResponse>>
onGetMonitoringReportReq(const ocpp2_0::GetMonitoringReportRequest&) override {
return ocpp2_0::GetMonitoringReportResponse {ocpp2_0::GenericDeviceModelStatusEnumType::kEmptyResultSet};
}
private:
template<int N>
static bool ciEquals(ocpp2_0::IdentifierStringPrimitive<N> const& lhs, ocpp2_0::IdentifierStringPrimitive<N> const& rhs) {
return string::EqualsIgnoreCaseAscii(lhs.value(), rhs.value());
}
template<int N>
static bool ciEquals(std::optional<ocpp2_0::IdentifierStringPrimitive<N>> const& lhs, std::optional<ocpp2_0::IdentifierStringPrimitive<N>> const& rhs) {
if (lhs.has_value() != rhs.has_value())
return false;
if (!lhs.has_value())
return true;
return ciEquals(lhs.value(), rhs.value());
}
private:
std::shared_ptr<Settings> settings_;
std::shared_ptr<SystemInterface> system_;
std::optional<ocpp2_0::GetBaseReportRequest> ocpp2_0_pending_base_report_ = std::nullopt;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_CONFIGURATION_MODULE_H

View File

@@ -0,0 +1,849 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_CONNECTOR_STATUS_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_CONNECTOR_STATUS_MODULE_H
#include "openocpp/module/common_templates.h"
#include "openocpp/interface/platform_interface.h"
#include "openocpp/interface/station_interface.h"
#include "openocpp/common/settings.h"
#include "openocpp/common/logging.h"
#include "openocpp/common/operation_holder.h"
#include <utility>
#include <set>
namespace chargelab {
namespace detail {
struct ReportedConnectorStatus {
std::optional<SteadyPointMillis> last_status_sent = std::nullopt;
std::optional<ocpp2_0::StatusNotificationRequest> last_reported_status2_0 = std::nullopt;
std::optional<ocpp1_6::StatusNotificationReq> last_reported_status1_6 = std::nullopt;
bool current_plug_was_charging = false;
bool force_update = false;
};
}
class ConnectorStatusModule : public ServiceStatefulGeneral {
private:
static constexpr int kSettingUpdateIntervalMillis = 500;
public:
ConnectorStatusModule(
std::shared_ptr<Settings> settings,
std::shared_ptr<PlatformInterface> const& platform,
std::shared_ptr<StationInterface> station
)
: settings_(std::move(settings)),
platform_(platform),
station_(std::move(station)),
pending_connector_status_req_ {platform}
{
assert(platform_ != nullptr);
loadInoperativeConnectors();
}
~ConnectorStatusModule() override {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting ConnectorStatusModule";
}
public:
bool isChargingEnabled() {
for (auto const& entry : station_->getConnectorMetadata()) {
auto const current_state = station_->pollConnectorStatus(entry.first);
if (current_state.has_value() && current_state->charging_enabled)
return true;
}
return false;
}
void setPendingReset(bool value, bool hard_reset) {
pending_reset_ = value;
reset_reason_hard_ = hard_reset;
}
bool getPendingReset() const {
return pending_reset_;
}
bool getResetReasonHard() const {
return reset_reason_hard_;
}
void setPendingStartRequests(std::vector<std::optional<ocpp2_0::EVSEType>> const& pending) {
pending_start_.assign(pending.begin(), pending.end());
}
std::unordered_map<int, ocpp1_6::ChargePointStatus> getChargePointStatus1_6() {
std::lock_guard<std::mutex> lock(last_charge_point_status_map1_6_mutex_);
return last_charge_point_status_map1_6_;
}
bool setConnector0Inoperative(bool inoperative) {
if (inoperative_connectors_[std::nullopt] != inoperative) {
inoperative_connectors_[std::nullopt] = inoperative;
return true;
}
return false;
}
private:
void runUnconditionally() override {
addAndUpdateStateSettings();
auto const connection = platform_->ocppConnection();
if (connection != nullptr && connection->isConnected()) {
last_online_ = platform_->steadyClockNow();
} else if (last_online_.has_value()) {
// B04.FR.01
auto const elapsed = platform_->steadyClockNow() - last_online_.value();
if (elapsed/1000 >= settings_->OfflineThreshold.getValue()) {
bool forced_update = false;
for (auto& x : reported_status_) {
if (!x.second.force_update) {
x.second.force_update = true;
forced_update = true;
}
}
if (forced_update) {
CHARGELAB_LOG_MESSAGE(info) << "OfflineThreshold exceeded - forcing status notification updates on next connection";
}
}
}
}
void runStep(ocpp2_0::OcppRemote &remote) override {
for (auto& entry : station_->getConnectorMetadata()) {
auto const current_state = station_->pollConnectorStatus(entry.first);
if (!current_state.has_value())
continue;
auto& reported_status = reported_status_[entry.first];
advanceConnectorState(reported_status, current_state.value());
auto current_status = getStatus2_0(entry.first, current_state.value());
if (pending_connector_status_req_.operationInProgress())
continue;
if (!reported_status.force_update && reported_status.last_reported_status2_0.has_value()) {
if (current_status == reported_status.last_reported_status2_0->connectorStatus)
continue;
// Note: rate limiting charging status doesn't appear to be part of the 2.0.1 spec
}
reported_status.last_status_sent = platform_->steadyClockNow();
pending_connector_status_update2_0_ = std::make_pair(entry.first, ocpp2_0::StatusNotificationRequest {
ocpp2_0::DateTime{platform_->systemClockNow()},
current_status,
entry.first.id,
entry.first.connectorId.value()
});
pending_connector_status_req_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
remote.sendStatusNotificationReq(pending_connector_status_update2_0_->second)
);
}
}
void onStatusNotificationRsp(
const std::string &uniqueId,
const ocpp2_0::ResponseMessage<ocpp2_0::StatusNotificationResponse> &rsp
) override {
if (pending_connector_status_req_ == uniqueId) {
// TODO: Allowing failed operations to timeout instead of retrying immediately. This might need more
// thought.
//pending_connector_status_req_ = kNoOperation;
if (std::holds_alternative<ocpp2_0::StatusNotificationResponse>(rsp)) {
if (pending_connector_status_update2_0_.has_value()) {
auto const& evse = pending_connector_status_update2_0_->first;
auto const& request = pending_connector_status_update2_0_->second;
reported_status_[evse].last_reported_status2_0 = request;
reported_status_[evse].force_update = false;
}
pending_connector_status_req_ = kNoOperation;
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to StatusNotification request: " << std::get<ocpp2_0::CallError> (rsp);
}
pending_connector_status_update2_0_ = std::nullopt;
}
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest &req) override {
if (req.requestedMessage != ocpp2_0::MessageTriggerEnumType::kStatusNotification)
return std::nullopt;
// Note: deviating from F06.FR.12 and treating req.evse as a filter instead. If evse isn't specified all
// connector statuses are reported, if connector ID isn't specified all statuses for that EVSE ID are
// reported.
bool found = false;
for (auto const& entry : station_->getConnectorMetadata()) {
if (req.evse.has_value()) {
if (req.evse->id != entry.first.id)
continue;
if (req.evse->connectorId.has_value() && req.evse->connectorId != entry.first.connectorId)
continue;
}
CHARGELAB_LOG_MESSAGE(debug) << "Got trigger message for EVSE: " << entry.first;
reported_status_[entry.first].force_update = true;
found = true;
}
if (!found) {
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kRejected};
}
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
}
void runStep(ocpp1_6::OcppRemote &remote) override {
for (auto& entry : station_->getConnectorMetadata()) {
auto const current_state = station_->pollConnectorStatus(entry.first);
if (!current_state.has_value())
continue;
auto& reported_status = reported_status_[entry.first];
advanceConnectorState(reported_status, current_state.value());
auto current_status = getStatus1_6(entry.first, current_state.value(), reported_status.current_plug_was_charging);
if (pending_connector_status_req_.operationInProgress())
continue;
{
std::lock_guard<std::mutex> lock(last_charge_point_status_map1_6_mutex_);
last_charge_point_status_map1_6_[entry.second.connector_id1_6] = current_status;
}
ocpp1_6::StatusNotificationReq request {
entry.second.connector_id1_6,
ocpp1_6::ChargePointErrorCode::kNoError,
std::nullopt,
current_status,
ocpp1_6::DateTime{platform_->systemClockNow()},
std::nullopt,
std::nullopt
};
if (current_state->faulted_status.has_value()) {
auto const& faulted = current_state->faulted_status->status1_6;
request.errorCode = faulted.errorCode;
request.info = faulted.info;
request.vendorId = faulted.vendorId;
request.vendorErrorCode = faulted.vendorErrorCode;
}
if (!reported_status.force_update && reported_status.last_reported_status1_6.has_value()) {
// Note: ignoring info parameter for updates; could potentially contain noisy information
auto const& last = reported_status.last_reported_status1_6.value();
if (request.status == last.status && request.errorCode == last.errorCode && request.vendorId == last.vendorId && request.vendorErrorCode == last.vendorErrorCode)
continue;
// TODO: rate limiting?
}
reported_status.last_status_sent = platform_->steadyClockNow();
pending_connector_status_update1_6_ = std::make_pair(entry.first, request);
pending_connector_status_req_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
remote.sendStatusNotificationReq(request)
);
}
}
void onStatusNotificationRsp(
const std::string &uniqueId,
const ocpp1_6::ResponseMessage<ocpp1_6::StatusNotificationRsp> &rsp
) override {
if (pending_connector_status_req_ == uniqueId) {
// TODO: Allowing failed operations to timeout instead of retrying immediately. This might need more
// thought.
//pending_connector_status_req_ = kNoOperation;
if (std::holds_alternative<ocpp1_6::StatusNotificationRsp>(rsp)) {
if (pending_connector_status_update1_6_.has_value()) {
auto const& evse = pending_connector_status_update1_6_->first;
auto const& request = pending_connector_status_update1_6_->second;
reported_status_[evse].last_reported_status1_6 = request;
reported_status_[evse].force_update = false;
}
pending_connector_status_req_ = kNoOperation;
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to StatusNotification request: " << std::get<ocpp1_6::CallError> (rsp);
}
pending_connector_status_update1_6_ = std::nullopt;
}
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
if (req.requestedMessage != ocpp1_6::MessageTrigger::kStatusNotification)
return std::nullopt;
bool found = false;
for (auto const& entry : station_->getConnectorMetadata()) {
if (req.connectorId.has_value()) {
if (entry.second.connector_id1_6 != req.connectorId.value())
continue;
}
CHARGELAB_LOG_MESSAGE(debug) << "Got trigger message for EVSE: " << entry.first;
reported_status_[entry.first].force_update = true;
found = true;
}
if (!found) {
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kRejected};
}
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kAccepted};
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ChangeAvailabilityRsp>>
onChangeAvailabilityReq(const ocpp1_6::ChangeAvailabilityReq &req) override {
std::vector<bool*> inoperative_connectors;
for (auto const& entry : station_->getConnectorMetadata()) {
if (req.connectorId == 0 || entry.second.connector_id1_6 == req.connectorId) {
inoperative_connectors.emplace_back(&inoperative_connectors_[entry.first]);
}
}
if (inoperative_connectors.empty())
return ocpp1_6::ChangeAvailabilityRsp {ocpp1_6::AvailabilityStatus::kRejected};
switch (req.type) {
default:
return ocpp1_6::CallError {
ocpp1_6::ErrorCode::kPropertyConstraintViolation,
"Bad ChangeAvailabilityReq type enum",
common::RawJson::empty_object()
};
case ocpp1_6::AvailabilityType::kOperative:
for (auto &connector : inoperative_connectors) {
*connector = false;
}
break;
case ocpp1_6::AvailabilityType::kInoperative:
for (auto &connector : inoperative_connectors) {
*connector = true;
}
break;
}
bool charging = false;
for (auto const& entry : station_->getConnectorMetadata()) {
if (req.connectorId != 0) {
if (req.connectorId != entry.second.connector_id1_6)
continue;
}
auto const status = station_->pollConnectorStatus(entry.first);
if (status.has_value() && status->charging_enabled) {
charging = true;
break;
}
}
saveInoperativeConnectors();
if (charging) {
return ocpp1_6::ChangeAvailabilityRsp {ocpp1_6::AvailabilityStatus::kScheduled};
} else {
return ocpp1_6::ChangeAvailabilityRsp {ocpp1_6::AvailabilityStatus::kAccepted};
}
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ChangeAvailabilityResponse>>
onChangeAvailabilityReq(const ocpp2_0::ChangeAvailabilityRequest &req) override {
std::map<ocpp2_0::EVSEType, charger::ConnectorMetadata> metadata;
for (auto const& entry : station_->getConnectorMetadata()) {
if (req.evse.has_value()) {
if (req.evse->id != entry.first.id)
continue;
if (req.evse->connectorId.has_value() && req.evse->connectorId != entry.first.connectorId)
continue;
}
metadata[entry.first] = entry.second;
}
if (metadata.size() == 0)
return ocpp2_0::ChangeAvailabilityResponse {ocpp2_0::ChangeAvailabilityStatusEnumType::kRejected};
switch (req.operationalStatus) {
default:
return ocpp2_0::CallError {
ocpp2_0::ErrorCode::kPropertyConstraintViolation,
"Bad ChangeAvailabilityRequest operationalStatus enum",
common::RawJson::empty_object()
};
case ocpp2_0::OperationalStatusEnumType::kOperative:
inoperative_connectors_[req.evse] = false;
break;
case ocpp2_0::OperationalStatusEnumType::kInoperative:
inoperative_connectors_[req.evse] = true;
break;
}
bool charging = false;
for (auto const& entry : metadata) {
auto const status = station_->pollConnectorStatus(entry.first);
if (status.has_value() && status->charging_enabled) {
charging = true;
break;
}
}
saveInoperativeConnectors();
if (charging) {
return ocpp2_0::ChangeAvailabilityResponse {ocpp2_0::ChangeAvailabilityStatusEnumType::kScheduled};
} else {
return ocpp2_0::ChangeAvailabilityResponse {ocpp2_0::ChangeAvailabilityStatusEnumType::kAccepted};
}
}
private:
void advanceConnectorState(detail::ReportedConnectorStatus& reported, charger::ConnectorStatus const& current) {
if (current.vehicle_connected && current.charging_enabled) {
reported.current_plug_was_charging = true;
} else if (!current.vehicle_connected) {
reported.current_plug_was_charging = false;
}
}
void saveInoperativeConnectors() {
std::string result;
char const* ifs = "";
for (auto const& x : inoperative_connectors_) {
if (!x.second)
continue;
result += ifs;
if (!x.first.has_value()) {
result += "0";
} else if (!x.first->connectorId.has_value()) {
result += std::to_string(x.first->id);
} else {
result += std::to_string(x.first->id) + "." + std::to_string(x.first->connectorId.value());
}
ifs = ",";
}
settings_->InoperativeConnectors.setValue(result);
CHARGELAB_LOG_MESSAGE(debug) << "Saved inoperative connectors: " << result;
}
void loadInoperativeConnectors() {
auto const value = settings_->InoperativeConnectors.getValue();
CHARGELAB_LOG_MESSAGE(debug) << "Loading inoperative connectors: " << value;
inoperative_connectors_.clear();
string::SplitVisitor(value, ",", [&](std::string const& text) {
if (text.empty())
return;
auto const it = text.find('.');
if (it != std::string::npos) {
auto const id = string::ToInteger(text.substr(0, it));
auto const connectorId = string::ToInteger(text.substr(it+1));
if (id && connectorId) {
inoperative_connectors_[ocpp2_0::EVSEType{id.value(), connectorId}] = true;
}
} else {
auto const id = string::ToInteger(text);
if (id) {
if (id.value() == 0) {
inoperative_connectors_[std::nullopt] = true;
} else {
inoperative_connectors_[ocpp2_0::EVSEType{id.value()}] = true;
}
}
}
});
CHARGELAB_LOG_MESSAGE(debug) << "Inoperative connectors: " << inoperative_connectors_;
}
ocpp2_0::ConnectorStatusEnumType getStatus2_0(ocpp2_0::EVSEType const& evse, charger::ConnectorStatus const& status) {
if (status.faulted_status.has_value())
return ocpp2_0::ConnectorStatusEnumType::kFaulted;
if (status.vehicle_connected && status.charging_enabled)
return ocpp2_0::ConnectorStatusEnumType::kOccupied;
// TODO: Need to explore this further. This is failing TC_B_22_CS in OCTT; is that because the transaction
// stop message wasn't received first or because the station must not send out an Unavailable status under
// these conditions?
// if (pending_reset_)
// return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
if (!status.connector_available)
return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
if (status.vehicle_connected)
return ocpp2_0::ConnectorStatusEnumType::kOccupied;
// TODO: What is the precedence of "Unavailable" in 2.0.1?
// Note: this fails test case TC_G_11_CS if this takes precedence over the "Occupied" state above.
if (inoperative_connectors_[std::nullopt])
return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
if (inoperative_connectors_[ocpp2_0::EVSEType {evse.id}])
return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
if (inoperative_connectors_[evse])
return ocpp2_0::ConnectorStatusEnumType::kUnavailable;
return ocpp2_0::ConnectorStatusEnumType::kAvailable;
}
ocpp1_6::ChargePointStatus getStatus1_6(ocpp2_0::EVSEType const& evse, charger::ConnectorStatus const& current, bool was_charging) {
if (current.faulted_status.has_value())
return ocpp1_6::ChargePointStatus::kFaulted;
if (current.vehicle_connected && current.charging_enabled) {
if (current.suspended_by_charger) {
return ocpp1_6::ChargePointStatus::kSuspendedEVSE;
} else if (current.suspended_by_vehicle) {
return ocpp1_6::ChargePointStatus::kSuspendedEV;
} else {
return ocpp1_6::ChargePointStatus::kCharging;
}
}
if (inoperative_connectors_[std::nullopt])
return ocpp1_6::ChargePointStatus::kUnavailable;
if (inoperative_connectors_[evse])
return ocpp1_6::ChargePointStatus::kUnavailable;
// if (pending_reset_)
// return ocpp1_6::ChargePointStatus::kUnavailable;
if (!current.connector_available)
return ocpp1_6::ChargePointStatus::kUnavailable;
if (current.vehicle_connected) {
if (was_charging) {
return ocpp1_6::ChargePointStatus::kFinishing;
} else {
return ocpp1_6::ChargePointStatus::kPreparing;
}
}
// For RFID, we have a pending start entry but with a nullopt value
auto it = std::find_if(pending_start_.begin(), pending_start_.end(), [=] (auto entry) {
return !entry.has_value() || entry->id == evse.id; } );
// Connector 0 can't be preparing
if (it != pending_start_.end() && evse.id != 0) {
return ocpp1_6::ChargePointStatus::kPreparing;
}
return ocpp1_6::ChargePointStatus::kAvailable;
}
template <typename Map, typename Key, typename Generator>
auto& getOrCreateSetting(Map& map, Key&& key, Generator&& generator) {
auto it = map.find(key);
if (it != map.end() && it->second != nullptr)
return *it->second;
auto setting = generator();
map[key] = setting;
settings_->registerCustomSetting(setting);
return *setting;
}
void addAndUpdateStateSettings() {
auto const now = platform_->steadyClockNow();
if (last_settings_update_.has_value()) {
auto const delta = now - last_settings_update_.value();
if (delta < kSettingUpdateIntervalMillis)
return;
}
last_settings_update_ = now;
// Update station level settings
{
auto const& metadata = station_->getStationMetadata();
settings_->ChargingStationAvailable.setValue(true);
settings_->ChargingStationAvailabilityState.setValue(
inoperative_connectors_[std::nullopt] ? "Unavailable" : "Available");
settings_->ChargingStationSupplyPhases.setValue(metadata.supply_phases);
}
// Update connector level settings
std::set<int> evse_ids {};
for (auto const& entry : station_->getConnectorMetadata()) {
if (!entry.first.connectorId.has_value()) {
CHARGELAB_LOG_MESSAGE(error) << "Bad connector metadata - must specify a connector ID";
continue;
}
auto const& evse_id = entry.first.id;
auto const& connector_id = entry.first.connectorId.value();
auto const& connector_status = station_->pollConnectorStatus(entry.first);
evse_ids.insert(evse_id);
// 2.13.1
auto& available = getOrCreateSetting(settings_available_, entry.first, [&]() {
return std::make_shared<SettingBool>(
std::make_unique<SettingMetadata>(SettingMetadata {
"EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "Available",
SettingConfig::roNotSavedPolicy(),
std::nullopt,
DeviceModel2_0{
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
ocpp2_0::VariableType{"Available"},
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kboolean}
},
SettingBool::kTextTrue
}),
[](auto const&) {return true;}
);
});
available.setValue(connector_status.has_value());
// 2.13.2
auto& availability_state = getOrCreateSetting(settings_availability_state_, entry.first, [&]() {
return std::make_shared<SettingString>(
std::make_unique<SettingMetadata>(SettingMetadata {
"EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "AvailabilityState",
SettingConfig::roNotSavedPolicy(),
std::nullopt,
DeviceModel2_0{
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
ocpp2_0::VariableType{"AvailabilityState"},
ocpp2_0::VariableCharacteristicsType{
std::nullopt,
ocpp2_0::DataEnumType::kOptionList,
std::nullopt,
std::nullopt,
"Available,Occupied,Reserved,Unavailable,Faulted"
}
},
"Unavailable"
}),
[](auto const&) {return true;}
);
});
if (connector_status.has_value()) {
auto const& status = getStatus2_0(entry.first, connector_status.value());
availability_state.setValue(status.to_string());
}
// 2.13.4
auto& connector_type = getOrCreateSetting(settings_connector_type_, entry.first, [&]() {
auto const& name = "EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "ConnectorType";
return std::make_shared<SettingString>(
std::make_unique<SettingMetadata>(SettingMetadata {
name,
SettingConfig::roNotSavedPolicy(),
DeviceModel1_6{name},
DeviceModel2_0{
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
ocpp2_0::VariableType{"ConnectorType"},
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kstring}
},
""
}),
[](auto const&) {return true;}
);
});
connector_type.setValue(entry.second.connector_type);
// 2.13.6
auto& supply_phases = getOrCreateSetting(settings_supply_phases_, entry.first, [&]() {
auto const& name = "EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "SupplyPhases";
return std::make_shared<SettingInt>(
std::make_unique<SettingMetadata>(SettingMetadata {
name,
SettingConfig::roNotSavedPolicy(),
DeviceModel1_6{name},
DeviceModel2_0{
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
ocpp2_0::VariableType{"SupplyPhases"},
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kinteger}
},
std::to_string(1)
}),
[](auto const&) {return true;}
);
});
supply_phases.setValue(entry.second.supply_phases);
// 2.13.7
// Note: max power won't be updated dynamically if the station information changes; this could possibly
// be improved in the future, but that may not be something that's ever needed in practice.
auto& power = getOrCreateSetting(settings_power_, entry.first, [&]() {
auto const& name = "EVSE" + std::to_string(evse_id) + "Connector" + std::to_string(connector_id) + "Power";
return std::make_shared<SettingDouble>(
std::make_unique<SettingMetadata>(SettingMetadata {
name,
SettingConfig::roNotSavedPolicy(),
DeviceModel1_6{name},
DeviceModel2_0{
ocpp2_0::ComponentType{"Connector", std::nullopt, ocpp2_0::EVSEType{evse_id, connector_id}},
ocpp2_0::VariableType{"Power"},
ocpp2_0::VariableCharacteristicsType{
"W",
ocpp2_0::DataEnumType::kdecimal,
std::nullopt,
entry.second.power_max_watts
}
},
std::to_string(0)
}),
[](auto const&) {return true;}
);
});
// TODO: Take this from the supplied meter values for the connector? Note that a wattage reading isn't
// mandatory, in which case we'd need to support not reporting the actual characteristic.
power.setValue(0);
}
// Update EVSE level settings
for (auto const& entry : station_->getEvseMetadata()) {
if (entry.first.connectorId.has_value()) {
CHARGELAB_LOG_MESSAGE(error) << "Bad EVSE metadata - should not specify a connector ID";
continue;
}
// 2.13.1
getOrCreateSetting(settings_available_, ocpp2_0::EVSEType{entry.first.id}, [&]() {
return std::make_shared<SettingBool>(
std::make_unique<SettingMetadata>(SettingMetadata {
"EVSE" + std::to_string(entry.first.id) + "Available",
SettingConfig::roNotSavedPolicy(),
std::nullopt,
DeviceModel2_0{
ocpp2_0::ComponentType{"EVSE", std::nullopt, ocpp2_0::EVSEType{entry.first.id}},
ocpp2_0::VariableType{"Available"},
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kboolean}
},
SettingBool::kTextTrue
}),
[](auto const&) {return true;}
);
});
// 2.13.2
auto& availability_state = getOrCreateSetting(settings_availability_state_, ocpp2_0::EVSEType{entry.first.id}, [&]() {
return std::make_shared<SettingString>(
std::make_unique<SettingMetadata>(SettingMetadata {
"EVSE" + std::to_string(entry.first.id) + "AvailabilityState",
SettingConfig::roNotSavedPolicy(),
std::nullopt,
DeviceModel2_0{
ocpp2_0::ComponentType{"EVSE", std::nullopt, ocpp2_0::EVSEType{entry.first.id}},
ocpp2_0::VariableType{"AvailabilityState"},
ocpp2_0::VariableCharacteristicsType{
std::nullopt,
ocpp2_0::DataEnumType::kOptionList,
std::nullopt,
std::nullopt,
"Available,Occupied,Reserved,Unavailable,Faulted"
}
},
"Unavailable"
}),
[](auto const&) {return true;}
);
});
// TODO: How should this be interpreted when there's more than one connector under an EVSE? Is it
// reasonable to only report "Available"/"Unavailable" here based on what was configured via
// ChangeAvailability?
availability_state.setValue(inoperative_connectors_[entry.first] ? "Unavailable" : "Available");
// 2.13.6
auto& supply_phases = getOrCreateSetting(settings_supply_phases_, entry.first, [&]() {
auto const& name = "EVSE" + std::to_string(entry.first.id) + "SupplyPhases";
return std::make_shared<SettingInt>(
std::make_unique<SettingMetadata>(SettingMetadata {
name,
SettingConfig::roNotSavedPolicy(),
DeviceModel1_6{name},
DeviceModel2_0{
ocpp2_0::ComponentType{"EVSE", std::nullopt, ocpp2_0::EVSEType{entry.first.id}},
ocpp2_0::VariableType{"SupplyPhases"},
ocpp2_0::VariableCharacteristicsType{std::nullopt, ocpp2_0::DataEnumType::kinteger}
},
std::to_string(1)
}),
[](auto const&) {return true;}
);
});
supply_phases.setValue(entry.second.supply_phases);
// 2.13.7
// Note: max power won't be updated dynamically if the station information changes; this could possibly
// be improved in the future, but that may not be something that's ever needed in practice.
auto& power = getOrCreateSetting(settings_power_, entry.first, [&]() {
auto const& name = "EVSE" + std::to_string(entry.first.id) + "Power";
return std::make_shared<SettingDouble>(
std::make_unique<SettingMetadata>(SettingMetadata {
name,
SettingConfig::roNotSavedPolicy(),
DeviceModel1_6{name},
DeviceModel2_0{
ocpp2_0::ComponentType{"EVSE", std::nullopt, ocpp2_0::EVSEType{entry.first.id}},
ocpp2_0::VariableType{"Power"},
ocpp2_0::VariableCharacteristicsType{
"W",
ocpp2_0::DataEnumType::kdecimal,
std::nullopt,
entry.second.power_max_watts
}
},
std::to_string(0)
}),
[](auto const&) {return true;}
);
});
// TODO: Take this from the supplied meter values for the connector? Note that a wattage reading isn't
// mandatory, in which case we'd need to support not reporting the actual characteristic.
power.setValue(0);
}
}
private:
std::shared_ptr<Settings> settings_;
std::shared_ptr<PlatformInterface> platform_;
std::shared_ptr<StationInterface> station_;
OperationHolder<std::string> pending_connector_status_req_;
std::optional<std::pair<chargelab::ocpp2_0::EVSEType, ocpp2_0::StatusNotificationRequest>> pending_connector_status_update2_0_ = std::nullopt;
std::optional<std::pair<chargelab::ocpp2_0::EVSEType, ocpp1_6::StatusNotificationReq>> pending_connector_status_update1_6_ = std::nullopt;
std::map<chargelab::ocpp2_0::EVSEType, detail::ReportedConnectorStatus> reported_status_;
std::map<std::optional<chargelab::ocpp2_0::EVSEType>, bool> inoperative_connectors_;
std::atomic<bool> pending_reset_ = false;
std::atomic<bool> reset_reason_hard_ = false; // hard reset or soft reset
std::optional<SteadyPointMillis> last_online_ = std::nullopt;
std::vector<std::optional<ocpp2_0::EVSEType>> pending_start_;
std::optional<SteadyPointMillis> last_settings_update_ = std::nullopt;
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingBool>> settings_available_ {};
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingString>> settings_availability_state_ {};
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingString>> settings_connector_type_ {};
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingInt>> settings_supply_phases_ {};
std::map<chargelab::ocpp2_0::EVSEType, std::shared_ptr<SettingDouble>> settings_power_ {};
// For portal module
std::unordered_map<int, ocpp1_6::ChargePointStatus> last_charge_point_status_map1_6_ {};
std::mutex last_charge_point_status_map1_6_mutex_;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_CONNECTOR_STATUS_MODULE_H

View File

@@ -0,0 +1,102 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_FALLBACK_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_FALLBACK_MODULE_H
#include "openocpp/module/common_templates.h"
namespace chargelab {
class FallbackModule : public ServiceStatefulGeneral {
public:
FallbackModule(std::shared_ptr<SystemInterface> system)
: system_(std::move(system))
{
}
private:
void runStep(ocpp1_6::OcppRemote&) override {
}
void runStep(ocpp2_0::OcppRemote &remote) override {
if (pending_customer_report_.has_value()) {
auto id = remote.sendNotifyCustomerInformationReq(ocpp2_0::NotifyCustomerInformationRequest {
"",
false,
0,
system_->systemClockNow(),
pending_customer_report_->requestId
});
if (id.has_value()) {
pending_customer_report_ = std::nullopt;
}
}
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq&) override {
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kNotImplemented};
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest&) override {
// F06.FR.08
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kNotImplemented};
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::DataTransferRsp>>
onDataTransferReq(const ocpp1_6::DataTransferReq&) override {
return ocpp1_6::DataTransferRsp {ocpp1_6::DataTransferStatus::kUnknownVendorId};
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::DataTransferResponse>>
onDataTransferReq(const ocpp2_0::DataTransferRequest&) override {
// P01.FR.05
return ocpp2_0::DataTransferResponse {ocpp2_0::DataTransferStatusEnumType::kUnknownVendorId};
}
// TODO: This operation doesn't seem relevant if we're not caching customer identifiers like authorization
// tokens in any sort of local list or authorization cache. Leaving this here for now.
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::CustomerInformationResponse>>
onCustomerInformationReq(const ocpp2_0::CustomerInformationRequest& request) override {
// N10.FR.05
if (pending_customer_report_.has_value())
return ocpp2_0::CustomerInformationResponse {ocpp2_0::CustomerInformationStatusEnumType::kRejected};
// N10.FR.07
if (!request.clear && !request.report)
return ocpp2_0::CustomerInformationResponse {ocpp2_0::CustomerInformationStatusEnumType::kRejected};
// N10.FR.01
pending_customer_report_ = request;
return ocpp2_0::CustomerInformationResponse {ocpp2_0::CustomerInformationStatusEnumType::kAccepted};
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::UnlockConnectorRsp>>
onUnlockConnectorReq(const ocpp1_6::UnlockConnectorReq&) override {
return ocpp1_6::UnlockConnectorRsp {ocpp1_6::UnlockStatus::kNotSupported};
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UnlockConnectorResponse>>
onUnlockConnectorReq(const ocpp2_0::UnlockConnectorRequest&) override {
// Note - responding here to satisfy TC_E_16_CS, however a NotImplemented default response seems more
// appropriate.
return ocpp2_0::UnlockConnectorResponse {ocpp2_0::UnlockStatusEnumType::kUnlockFailed};
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::ClearCacheRsp>>
onClearCacheReq(const ocpp1_6::ClearCacheReq&) override {
return ocpp1_6::ClearCacheRsp {ocpp1_6::ClearCacheStatus::kAccepted};
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::ClearCacheResponse>>
onClearCacheReq(const ocpp2_0::ClearCacheRequest&) override {
return ocpp2_0::ClearCacheResponse {ocpp2_0::ClearCacheStatusEnumType::kAccepted};
}
private:
std::shared_ptr<SystemInterface> system_;
std::optional<ocpp2_0::CustomerInformationRequest> pending_customer_report_ = std::nullopt;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_FALLBACK_MODULE_H

View File

@@ -0,0 +1,788 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_FIRMWARE_UPDATE_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_FIRMWARE_UPDATE_MODULE_H
#include "openocpp/interface/component/system_interface.h"
#include "openocpp/interface/component/fetch_interface.h"
#include "openocpp/interface/platform_interface.h"
#include "openocpp/common/operation_holder.h"
#include "openocpp/module/common_templates.h"
#include "openocpp/module/pending_messages_module.h"
#include "openocpp/module/connector_status_module.h"
#include "openocpp/module/reset_module.h"
#include "openocpp/common/settings.h"
#include "openocpp/common/logging.h"
#include "openocpp/common/macro.h"
#include <thread>
#include <condition_variable>
#include <charconv>
namespace chargelab {
namespace detail {
template <typename HM>
struct FirmwareUpdateOperation {
ocpp2_0::UpdateFirmwareRequest request;
// for ocpp1.6 only
std::optional<ocpp1_6::FirmwareStatus> last_status1_6 = std::nullopt;
// L01.FR.04
std::unique_ptr<typename HM::hash_calculator_type> signature_hash = HM::createCalculator(HM::kHashTypeSHA256);
std::shared_ptr<RestConnectionInterface> connection = nullptr;
std::vector<uint8_t> buffer {};
std::size_t content_length = 0;
std::size_t total_bytes_read = 0;
std::size_t total_failures = 0;
std::vector<std::vector<uint8_t>> block_hashes {};
std::optional<std::vector<uint8_t>> expected_signature_hash = std::nullopt;
std::optional<ocpp2_0::FirmwareStatusEnumType> last_status2_0 = std::nullopt;
bool finished_signature_check = false;
bool finished_flashing_firmware = false;
bool running_firmware_update = false;
};
}
template <typename HM>
class FirmwareUpdateModule : public ServiceStatefulGeneral {
private:
static constexpr int kChunkSize = 20*1024;
static constexpr int kPriorityFirmwareStatusNotification = 100;
// Note: arbitrary random assigned ID
static constexpr std::uint64_t kOperationGroupId = 0x78106033AC8E780Aull;
public:
explicit FirmwareUpdateModule(
std::shared_ptr<PlatformInterface> platform,
std::shared_ptr<ResetModule> reset,
std::shared_ptr<PendingMessagesModule> pending_messages,
std::shared_ptr<ConnectorStatusModule> connector_status,
std::shared_ptr<StationInterface> station
)
: platform_(std::move(platform)),
reset_(std::move(reset)),
pending_messages_(std::move(pending_messages)),
connector_status_(std::move(connector_status)),
station_(std::move(station))
{
assert(platform_ != nullptr);
assert(station_ != nullptr);
settings_ = platform_->getSettings();
assert(settings_ != nullptr);
}
~FirmwareUpdateModule() override {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting FirmwareUpdateModule";
}
private:
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::UpdateFirmwareRsp>>
onUpdateFirmwareReq(const ocpp1_6::UpdateFirmwareReq &req) override {
if (operation_.has_value()) {
return ocpp1_6::CallError {
ocpp1_6::ErrorCode::kGenericError,
"InProgress",
common::RawJson::empty_object()
};
}
// Check if there is transaction in progress
bool found_charging_connector = false;
for (auto const& entry: station_->getConnectorMetadata()) {
if (entry.first.id == 0) continue;
auto const connector_status = station_->pollConnectorStatus(entry.first);
if (!connector_status.has_value())
continue;
if (connector_status->charging_enabled) {
found_charging_connector = true;
break;
}
}
if (found_charging_connector) {
return ocpp1_6::CallError {
ocpp1_6::ErrorCode::kGenericError,
"ActiveChargeSession",
common::RawJson::empty_object()
};
}
// Set all connectors to Unavailable
need_to_restore_connector_0_operative_ = connector_status_->setConnector0Inoperative(true);
operation_ = detail::FirmwareUpdateOperation<HM> {
ocpp2_0::UpdateFirmwareRequest {
req.retries,
req.retryInterval,
0,
ocpp2_0::FirmwareType {
req.location.value(),
optional::GetOrDefault(req.retrieveDate.getTimestamp(), platform_->systemClockNow())
}
}
};
return ocpp1_6::UpdateFirmwareRsp {};
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::UpdateFirmwareResponse>>
onUpdateFirmwareReq(const ocpp2_0::UpdateFirmwareRequest &request) override {
// Note: there doesn't appear to be any specific requirement here in the 2.0.1 specification and this is
// incompatible with a station simultaneously supporting L02...but this is required by TC_L_18_CS which is
// mandatory for core certification.
if (!request.firmware.signature.has_value()) {
return ocpp2_0::UpdateFirmwareResponse{
ocpp2_0::UpdateFirmwareStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {"MissingSignature"}
};
}
if (!request.firmware.signingCertificate.has_value()) {
return ocpp2_0::UpdateFirmwareResponse{
ocpp2_0::UpdateFirmwareStatusEnumType::kRejected,
ocpp2_0::StatusInfoType {"MissingSigningCert"}
};
}
// L01.FR.21
// L01.FR.22
if (request.firmware.signingCertificate.has_value()) {
if (!platform_->verifyManufacturerCertificate(request.firmware.signingCertificate->value(), std::nullopt)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad manufacturer certificate";
// L01.FR.02
pending_messages_->sendRequest2_0(
ocpp2_0::SecurityEventNotificationRequest {
"InvalidFirmwareSigningCertificate",
platform_->systemClockNow()
},
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
);
return ocpp2_0::UpdateFirmwareResponse{ocpp2_0::UpdateFirmwareStatusEnumType::kInvalidCertificate};
}
}
// Note: this isn't strictly necessary, however passing a free-form text string to the platform URI parsing
// method seems potentially dangerous.
if (!uri::parseHttpUri(request.firmware.location.value()).has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad firmware location: " << request.firmware.location.value();
return ocpp2_0::UpdateFirmwareResponse{ocpp2_0::UpdateFirmwareStatusEnumType::kRejected};
}
auto status = ocpp2_0::UpdateFirmwareStatusEnumType::kAccepted;
if (operation_.has_value()) {
// L01.FR.24
operation_ = std::nullopt;
status = ocpp2_0::UpdateFirmwareStatusEnumType::kAcceptedCanceled;
}
operation_ = detail::FirmwareUpdateOperation<HM>{request};
return ocpp2_0::UpdateFirmwareResponse{status};
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq&) override {
return std::nullopt;
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest &req) override {
if (req.requestedMessage != ocpp2_0::MessageTriggerEnumType::kFirmwareStatusNotification)
return std::nullopt;
if (req.evse.has_value()) {
force_update_ = false;
} else {
force_update_ = true;
}
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
}
void checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType status) {
if (operation_->last_status2_0 == std::make_optional(status))
return;
pending_messages_->sendRequest2_0(
ocpp2_0::FirmwareStatusNotificationRequest {
status,
operation_->request.requestId
},
buildPendingMessagePolicy(PendingMessageType::kNotificationEvent)
);
operation_->last_status2_0 = status;
}
void checkAndUpdateStatus(ocpp1_6::FirmwareStatus status) {
if (operation_->last_status1_6 == std::make_optional(status))
return;
pending_messages_->sendRequest1_6(
ocpp1_6::FirmwareStatusNotificationReq {
status
},
buildPendingMessagePolicy(PendingMessageType::kNotificationEvent)
);
operation_->last_status1_6 = status;
}
static bool checkOrRetryConnection(
std::shared_ptr<PlatformInterface> const& platform,
detail::FirmwareUpdateOperation<HM>& operation
) {
if (operation.connection != nullptr)
return true;
auto const uri = operation.request.firmware.location.value();
CHARGELAB_LOG_MESSAGE(info) << "Downloading firmware from: " << uri;
operation.connection = platform->getRequest(uri);
if (operation.connection == nullptr) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed establishing connection to: " << uri;
operation.total_failures++;
return false;
}
if (!operation.connection->open(0)) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed opening connection to server";
operation.total_failures++;
operation.connection = nullptr;
return false;
}
if (!operation.connection->send()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed sending request";
operation.total_failures++;
operation.connection = nullptr;
return false;
}
auto const status = operation.connection->getStatusCode();
if (!(status >= 200 && status < 300)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad status code - expecting 2xx response: " << status;
operation.total_failures++;
operation.connection = nullptr;
return false;
}
operation.content_length = operation.connection->getContentLength();
operation.total_bytes_read = 0;
return true;
}
void runStep(ocpp1_6::OcppRemote &) override {
performBootChecks1_6();
performFirmwareUpdate1_6();
}
void runStep(ocpp2_0::OcppRemote& remote) override {
performBootChecks2_0();
performFirmwareUpdate2_0();
if (force_update_.has_value()) {
if (!force_update_.value()) {
// Note - doesn't seem to be defined by 2.0.1 requirements, following TC_F_17_CS
auto const result = remote.sendCall(ocpp2_0::FirmwareStatusNotificationRequest {
ocpp2_0::FirmwareStatusEnumType::kIdle
});
if (result.has_value())
force_update_ = std::nullopt;
} else {
if (operation_.has_value() && operation_->last_status2_0.has_value()) {
auto const result = remote.sendCall(ocpp2_0::FirmwareStatusNotificationRequest {
operation_->last_status2_0.value(),
operation_->request.requestId
});
if (result.has_value())
force_update_ = std::nullopt;
} else {
// F06.FR.16
auto const result = remote.sendCall(ocpp2_0::FirmwareStatusNotificationRequest {
ocpp2_0::FirmwareStatusEnumType::kIdle
});
if (result.has_value())
force_update_ = std::nullopt;
}
}
}
}
void performBootChecks2_0() {
if (performed_boot_checks_)
return;
auto const active = station_->getActiveSlotId();
settings_->ActiveFirmwareSlotId.setValue(active);
auto const update = settings_->ExpectedUpdateFirmwareSlotId.getValue();
if (!update.empty()) {
int request_id;
std::string slot_id;
parseExpectedUpdateFirmwareSlotId(update, request_id, slot_id);
pending_messages_->sendRequest2_0(
ocpp2_0::FirmwareStatusNotificationRequest {
slot_id == active ?
ocpp2_0::FirmwareStatusEnumType::kInstalled :
ocpp2_0::FirmwareStatusEnumType::kInstallationFailed,
request_id
},
buildPendingMessagePolicy(PendingMessageType::kNotificationEvent)
);
pending_messages_->sendRequest2_0(
ocpp2_0::SecurityEventNotificationRequest{
"FirmwareUpdated",
platform_->systemClockNow()
},
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
);
settings_->ExpectedUpdateFirmwareSlotId.setValue("");
}
performed_boot_checks_ = true;
}
void performFirmwareUpdate2_0() {
if (!operation_.has_value())
return;
auto const max_retries = optional::GetOrDefault(
operation_->request.retries,
settings_->FirmwareUpdateDefaultRetries.getValue()
);
if (operation_->request.firmware.retrieveDateTime.isAfter(platform_->systemClockNow(), false)) {
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kDownloadScheduled);
return;
}
if (!operation_->finished_signature_check) {
if ((int)operation_->total_failures > max_retries) {
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kDownloadFailed);
operation_ = std::nullopt;
return;
}
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kDownloading);
if (!checkOrRetryConnection(platform_, operation_.value()))
return;
auto const remaining = (int)operation_->content_length - (int)operation_->total_bytes_read;
if (remaining > 0) {
if (operation_->total_bytes_read == 0)
operation_->signature_hash->reset();
operation_->buffer.resize(kChunkSize);
auto const bytes_read = operation_->connection->read((char*)operation_->buffer.data(), (int)operation_->buffer.size());
// Note: treating zero bytes read as a failure condition here to prevent an infinite loop under
// those conditions.
if (bytes_read <= 0) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading data - retrying operation";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
// Block hash
auto hash = HM::calculateHashBinary(
HM::kHashTypeSHA256,
(unsigned char const*)operation_->buffer.data(),
bytes_read
);
if (!hash.has_value()) {
CHARGELAB_LOG_MESSAGE(error) << "Failed hashing block";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
operation_->block_hashes.push_back(std::move(hash.value()));
operation_->signature_hash->update((unsigned char const*)operation_->buffer.data(), bytes_read);
operation_->total_bytes_read += bytes_read;
CHARGELAB_LOG_MESSAGE(debug) << "Hashing progress: " << operation_->total_bytes_read << " / " << operation_->content_length;
}
if (operation_->total_bytes_read >= operation_->content_length) {
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kDownloaded);
auto signature_hash = operation_->signature_hash->finishBinary();
if (!signature_hash.has_value()) {
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInvalidSignature);
// L01.FR.03
pending_messages_->sendRequest2_0(
ocpp2_0::SecurityEventNotificationRequest {
"InvalidFirmwareSignature",
platform_->systemClockNow()
},
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
);
operation_ = std::nullopt;
return;
}
auto const& firmware = operation_->request.firmware;
if (firmware.signingCertificate.has_value() && firmware.signature.has_value()) {
auto signature_binary = HM::decodeBase64(firmware.signature->value());
if (!signature_binary.has_value()) {
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInvalidSignature);
// L01.FR.03
pending_messages_->sendRequest2_0(
ocpp2_0::SecurityEventNotificationRequest {
"InvalidFirmwareSignature",
platform_->systemClockNow()
},
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
);
operation_ = std::nullopt;
return;
}
SignatureAndHash signature_and_hash {};
signature_and_hash.sig = signature_binary->data();
signature_and_hash.sig_len = signature_binary->size();
signature_and_hash.hash = signature_hash->data();
signature_and_hash.hash_len = signature_hash->size();
auto const valid = platform_->verifyManufacturerCertificate(
firmware.signingCertificate->value(),
signature_and_hash
);
if (!valid) {
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInvalidSignature);
// L01.FR.03
pending_messages_->sendRequest2_0(
ocpp2_0::SecurityEventNotificationRequest {
"InvalidFirmwareSignature",
platform_->systemClockNow()
},
buildPendingMessagePolicy(PendingMessageType::kSecurityEvent)
);
operation_ = std::nullopt;
return;
}
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kSignatureVerified);
}
std::size_t total_block_hash_size = 0;
for (auto const& x : operation_->block_hashes)
total_block_hash_size += x.size();
CHARGELAB_LOG_MESSAGE(debug) << "Total block hash size (" << operation_->block_hashes.size() << " blocks): " << total_block_hash_size;
operation_->expected_signature_hash = std::move(signature_hash);
operation_->finished_signature_check = true;
operation_->connection = nullptr;
}
return;
}
if (!operation_->finished_flashing_firmware) {
// L01.FR.16
if (operation_->request.firmware.installDateTime.has_value()) {
if (operation_->request.firmware.installDateTime->isAfter(platform_->systemClockNow(), false)) {
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInstallScheduled);
return;
}
}
if ((int)operation_->total_failures > max_retries) {
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInstallationFailed);
operation_ = std::nullopt;
return;
}
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInstalling);
if (!checkOrRetryConnection(platform_, operation_.value()))
return;
auto const remaining = (int)operation_->content_length - (int)operation_->total_bytes_read;
if (remaining > 0) {
operation_->buffer.resize(kChunkSize);
auto const bytes_read = operation_->connection->read((char*)operation_->buffer.data(), (int)operation_->buffer.size());
// Note: treating zero bytes read as a failure condition here to prevent an infinite loop under
// those conditions.
if (bytes_read <= 0) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading data - retrying operation";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
if (operation_->total_bytes_read == 0) {
if (operation_->running_firmware_update) {
station_->finishUpdateProcess(false);
operation_->running_firmware_update = false;
}
if (station_->startUpdateProcess(operation_->content_length) != StationInterface::Result::kSucceeded) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed starting firmware update process";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
operation_->running_firmware_update = true;
} else {
if (!operation_->running_firmware_update) {
CHARGELAB_LOG_MESSAGE(error)
<< "Unexpected state - expected running firmware update operation";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
}
if (station_->processFirmwareChunk(operation_->buffer.data(), bytes_read) != StationInterface::Result::kSucceeded) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed processing firmware chunk";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
operation_->signature_hash->update((unsigned char const*)operation_->buffer.data(), bytes_read);
operation_->total_bytes_read += bytes_read;
CHARGELAB_LOG_MESSAGE(debug) << "Flashing progress: " << operation_->total_bytes_read << " / " << operation_->content_length;
}
if (operation_->total_bytes_read >= operation_->content_length) {
// TODO: Check signature
auto const slot_id = station_->getUpdateSlotId();
if (station_->finishUpdateProcess(true) != StationInterface::Result::kSucceeded) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed finishing update process";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
operation_->finished_flashing_firmware = true;
operation_->connection = nullptr;
// settings_->ExpectedUpdateFirmwareSlotId.setValueFromString(std::to_string(operation_->request.requestId) + ":" + slot_id);
settings_->ExpectedUpdateFirmwareSlotId.setValueFromString(buildExpectedUpdateFirmwareSlotId(
operation_->request.requestId, slot_id));
checkAndUpdateStatus(ocpp2_0::FirmwareStatusEnumType::kInstallRebooting);
reset_->resetOnIdle(ocpp2_0::BootReasonEnumType::kFirmwareUpdate);
}
return;
}
}
void performBootChecks1_6() {
if (performed_boot_checks_)
return;
auto const active = station_->getActiveSlotId();
settings_->ActiveFirmwareSlotId.setValue(active);
auto const update = settings_->ExpectedUpdateFirmwareSlotId.getValue();
if (!update.empty()) {
int request_id;
std::string slot_id;
parseExpectedUpdateFirmwareSlotId(update, request_id, slot_id);
pending_messages_->sendRequest1_6(
ocpp1_6::FirmwareStatusNotificationReq {
slot_id == active ?
ocpp1_6::FirmwareStatus::kInstalled :
ocpp1_6::FirmwareStatus::kInstallationFailed
},
buildPendingMessagePolicy(PendingMessageType::kNotificationEvent)
);
settings_->ExpectedUpdateFirmwareSlotId.setValue("");
}
performed_boot_checks_ = true;
}
void performFirmwareUpdate1_6() {
if (!operation_.has_value())
return;
auto const max_retries = optional::GetOrDefault(
operation_->request.retries,
settings_->FirmwareUpdateDefaultRetries.getValue()
);
if ((int)operation_->total_failures > max_retries) {
if (operation_->content_length == 0 || operation_->content_length > operation_->total_bytes_read) {
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kDownloadFailed);
} else {
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kInstallationFailed);
}
operation_ = std::nullopt;
restoreConnector0OperativeIfNeeded();
return;
}
if (!operation_->last_status1_6.has_value()) {
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kDownloading);
} else {
if (operation_->last_status1_6 == ocpp1_6::FirmwareStatus::kDownloaded) {
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kInstalling);
}
}
if (!checkOrRetryConnection(platform_, operation_.value()))
return;
auto const remaining = (int)operation_->content_length - (int)operation_->total_bytes_read;
if (remaining > 0) {
operation_->buffer.resize(kChunkSize);
auto const bytes_read = operation_->connection->read((char*)operation_->buffer.data(), (int)operation_->buffer.size());
// Note: treating zero bytes read as a failure condition here to prevent an infinite loop under
// those conditions.
if (bytes_read <= 0) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading data - retrying operation";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
if (operation_->total_bytes_read == 0) {
if (operation_->running_firmware_update) {
station_->finishUpdateProcess(false);
operation_->running_firmware_update = false;
}
if (station_->startUpdateProcess(operation_->content_length) != StationInterface::Result::kSucceeded) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed starting firmware update process";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
operation_->running_firmware_update = true;
} else {
if (!operation_->running_firmware_update) {
CHARGELAB_LOG_MESSAGE(error)
<< "Unexpected state - expected running firmware update operation";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
}
if (station_->processFirmwareChunk(operation_->buffer.data(), bytes_read) != StationInterface::Result::kSucceeded) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed processing firmware chunk";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
// TODO: For ocpp1.6, the existing approach is to pre-set the hash value to the setting parameter and
// read the firmware hash value from the firmware itself. Then compare them to decide if the downloaded firmware is the right one.
// What approach is expected here?
operation_->signature_hash->update((unsigned char const*)operation_->buffer.data(), bytes_read);
operation_->total_bytes_read += bytes_read;
CHARGELAB_LOG_MESSAGE(debug) << "Flashing progress: " << operation_->total_bytes_read << " / " << operation_->content_length;
}
if (operation_->total_bytes_read >= operation_->content_length) {
// TODO: Check signature
if (operation_->last_status1_6 == ocpp1_6::FirmwareStatus::kDownloading) {
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kDownloaded);
return;
} else if (operation_->last_status1_6 == ocpp1_6::FirmwareStatus::kDownloaded) {
checkAndUpdateStatus(ocpp1_6::FirmwareStatus::kInstalling);
return;
}
auto const slot_id = station_->getUpdateSlotId();
if (station_->finishUpdateProcess(true) != StationInterface::Result::kSucceeded) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed finishing update process";
operation_->connection = nullptr;
operation_->total_failures++;
return;
}
//settings_->ExpectedUpdateFirmwareSlotId.setValueFromString(std::to_string(operation_->request.requestId) + ":" + slot_id);
settings_->ExpectedUpdateFirmwareSlotId.setValueFromString(buildExpectedUpdateFirmwareSlotId(
operation_->request.requestId, slot_id));
operation_ = std::nullopt;
restoreConnector0OperativeIfNeeded();
reset_->resetOnIdle(ocpp2_0::BootReasonEnumType::kFirmwareUpdate);
}
}
void restoreConnector0OperativeIfNeeded() {
if (need_to_restore_connector_0_operative_) {
connector_status_->setConnector0Inoperative(false);
need_to_restore_connector_0_operative_ = false;
}
}
static std::string buildExpectedUpdateFirmwareSlotId(int request_id, std::string const& slot_id) {
return std::to_string(request_id) + ":" + slot_id;
}
static void parseExpectedUpdateFirmwareSlotId(std::string const& expectedUpdateFirmwareSlotId,
int& request_id, std::string& slot_id) {
auto index = expectedUpdateFirmwareSlotId.find(':');
if (index != std::string::npos) {
request_id = optional::GetOrDefault(string::ToInteger(expectedUpdateFirmwareSlotId.substr(0, index)), 0);
slot_id = expectedUpdateFirmwareSlotId.substr(index+1);
} else {
request_id = 0;
slot_id = expectedUpdateFirmwareSlotId;
}
}
PendingMessagePolicy buildPendingMessagePolicy(PendingMessageType message_type) {
return PendingMessagePolicy {
message_type,
kOperationGroupId,
settings_->NotificationMessageDefaultRetries.getValue(),
settings_->NotificationMessageDefaultRetryInterval.getValue(),
kPriorityFirmwareStatusNotification,
false,
false
};
}
private:
std::shared_ptr<PlatformInterface> platform_;
std::shared_ptr<ResetModule> reset_;
std::shared_ptr<PendingMessagesModule> pending_messages_;
std::shared_ptr<ConnectorStatusModule> connector_status_; // TODO: Remove dependency on ConnectorStatusModule
std::shared_ptr<StationInterface> station_;
std::shared_ptr<Settings> settings_;
/**
* Set to true to force an OCPP 2.0.1 or 1.6 firmware status update and false to force an "idle" firmware status
* update (only applies when an associated trigger message is received with an EVSE filter).
*/
std::optional<bool> force_update_ = std::nullopt;
std::optional<detail::FirmwareUpdateOperation<HM>> operation_ = std::nullopt;
bool performed_boot_checks_ = false;
bool need_to_restore_connector_0_operative_ { false };
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_FIRMWARE_UPDATE_MODULE_H

View File

@@ -0,0 +1,288 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_GET_DIAGNOSTICS_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_GET_DIAGNOSTICS_MODULE_H
#include "openocpp/module/common_templates.h"
#include "openocpp/interface/component/system_interface.h"
#include "log_streaming_module.h"
#include "openocpp/common/ring_buffer.h"
#include "openocpp/common/operation_holder.h"
#include "openocpp/interface/component/upload_interface.h"
#include "openocpp/helpers/chrono.h"
#include "openocpp/common/macro.h"
#include <queue>
#include <thread>
#include <sstream>
#include <ctime>
#include <utility>
namespace chargelab {
namespace detail {
struct DiagnosticsUploadState {
explicit DiagnosticsUploadState(std::shared_ptr<SystemInterface> const& system)
: pending_notification {system} {
}
OperationHolder<std::string> pending_notification;
std::string location {};
int failed_attempts = 0;
int total_retries = 0;
int retry_interval_seconds = 0;
std::atomic<DiagnosticsStatus> operation_state = DiagnosticsStatus::kPending;
std::atomic<DiagnosticsStatus> messaging_state = DiagnosticsStatus::kPending;
std::optional<std::thread> operation = std::nullopt;
std::optional<std::vector<uint8_t>> current_upload = std::nullopt;
std::queue<detail::DiagnosticsLine> pending_upload;
};
}
class GetDiagnosticsModule : public ServiceStatefulGeneral {
private:
static constexpr int kBufferLimitBytes = 3*1024; // 10*1024;
static constexpr int kRingBufferMaxSize = 25; // 50;
// for diagnostic uploading
static constexpr int kDefaultUploadRetries = 1;
static constexpr int kDefaultUploadRetryIntervalSeconds = 10;
static constexpr int kMaxBytesPerUpload = 3*1024; // 5*1024;
public:
explicit GetDiagnosticsModule(
Settings& settings,
std::shared_ptr<SystemInterface> system_interface,
std::shared_ptr<UploadInterface> upload
) :
system_interface_(std::move(system_interface)),
upload_(std::move(upload)),
charge_point_id_ {settings.getSetting(settings::CommonKey::kChargePointId)}
{
listener_ = std::make_shared<LoggingListenerFunction>([&](LogMetadata const& metadata, std::string_view const& message) {
if (metadata.level != LogLevel::warning && metadata.level != LogLevel::error && metadata.level != LogLevel::fatal)
return;
detail::DiagnosticsLine line {};
line.level = metadata.level;
#if defined(LOG_WITH_FILE_AND_LINE)
line.file = metadata.file;
line.line = metadata.line;
#endif
line.function = metadata.function;
line.message = message;
line.timestamp = system_interface_->systemClockNow();
std::lock_guard lock {mutex_};
int total_size = history_byte_size_ + line.size();
while (total_size > kBufferLimitBytes && !history_.empty()) {
total_size -= history_.front().size();
history_.popFront();
}
if (total_size < kBufferLimitBytes) {
history_byte_size_ = total_size;
history_.pushBack(std::move(line));
} else {
history_byte_size_ = 0;
}
});
RegisterLoggingListener(listener_); // register this callback to the global listener
}
~GetDiagnosticsModule() override {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting GetDiagnosticsModule";
UnregisterLoggingListener(listener_);
}
private:
void runStep(ocpp1_6::ChargePointRemoteInterface &remote) override {
if (!in_progress_.has_value())
return;
auto &state = in_progress_.value();
detail::DiagnosticsStatus const operation_state = state.operation_state;
detail::DiagnosticsStatus const messaging_state = state.messaging_state;
if (operation_state == messaging_state)
return;
ocpp1_6::DiagnosticsStatus status;
switch (operation_state) {
default:
assert(false && "Unexpected diagnostics status");
case detail::DiagnosticsStatus::kPending:
case detail::DiagnosticsStatus::kUploading:
status = ocpp1_6::DiagnosticsStatus::kUploading;
break;
case detail::DiagnosticsStatus::kUploaded:
status = ocpp1_6::DiagnosticsStatus::kUploaded;
state.operation->join();
in_progress_ = std::nullopt;
break;
case detail::DiagnosticsStatus::kUploadFailed:
status = ocpp1_6::DiagnosticsStatus::kUploadFailed;
state.operation->join();
in_progress_ = std::nullopt;
break;
}
state.messaging_state = operation_state;
state.pending_notification.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
remote.sendDiagnosticsStatusNotificationReq({status})
);
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::GetDiagnosticsRsp>>
onGetDiagnosticsReq(
const ocpp1_6::GetDiagnosticsReq& req
) override {
if (in_progress_.has_value()) {
return ocpp1_6::CallError {
ocpp1_6::ErrorCode::kGenericError,
"InProgress",
"An uploading diagnostics operation is already in progress"
};
}
ocpp1_6::GetDiagnosticsRsp rsp;
std::queue<detail::DiagnosticsLine> pending_upload;
{
std::lock_guard lock{mutex_};
int size = history_.size();
for (int i = 0; i < size; ++i) {
if (req.startTime.has_value() && history_[i].timestamp < req.startTime->getTimestamp())
continue;
if (req.stopTime.has_value() && history_[i].timestamp > req.stopTime->getTimestamp())
continue;
pending_upload.push(history_[i]);
}
}
if (pending_upload.empty()) {
rsp.fileName = std::nullopt;
return rsp;
}
in_progress_.emplace(system_interface_);
in_progress_->pending_upload = std::move(pending_upload);
rsp.fileName = buildDiagnosticsFileName();
auto &state = in_progress_.value();
// Trim leading and trailing whitespaces
std::string location = req.location.value();
auto trim = [](const std::string &str, const std::string &whitespaces = " \t") -> std::string {
auto begin = str.find_first_not_of(whitespaces);
if (begin == std::string::npos) return "";
auto end = str.find_last_not_of(whitespaces);
return str.substr(begin, end - begin + 1);
};
location = trim(location);
if (!location.empty() && location.back() != '/')
state.location = location + "/" + rsp.fileName.value();
else
state.location = location + rsp.fileName.value();
state.total_retries = optional::GetOrDefault(req.retries, kDefaultUploadRetries);
state.retry_interval_seconds = optional::GetOrDefault(req.retryInterval,
kDefaultUploadRetryIntervalSeconds);
state.operation = std::thread([&]() {
using namespace std::chrono_literals;
CHARGELAB_TRY {
CHARGELAB_LOG_MESSAGE(info) << "Starting diagnostics upload: " << state.location;
state.operation_state = detail::DiagnosticsStatus::kUploading;
bool append = false;
while (!state.pending_upload.empty() || state.current_upload.has_value()) {
if (!state.current_upload.has_value()) {
std::string content;
size_t content_len = 0;
while (!state.pending_upload.empty()) {
std::string s = state.pending_upload.front().to_string();
if (content_len + s.size() > kMaxBytesPerUpload)
break;
content.append(s);
content_len += s.size();
state.pending_upload.pop();
}
state.current_upload.emplace(std::vector<uint8_t>(content.begin(), content.end()));
}
// uploading
std::vector<uint8_t> const &content = state.current_upload.value();
auto result = upload_->write(state.location, content, append, [&](std::size_t) {});
if (result == UploadInterface::Result::kFailed) {
if (++state.failed_attempts > state.total_retries) {
CHARGELAB_LOG_MESSAGE(warning) << "Diagnostics upload failed writing";
state.operation_state = detail::DiagnosticsStatus::kUploadFailed;
return;
}
std::this_thread::sleep_for(std::chrono::seconds(state.retry_interval_seconds));
} else {
state.current_upload = std::nullopt;
append = true; // appending for next block
}
}
CHARGELAB_LOG_MESSAGE(info) << "Diagnostics upload succeeded";
state.operation_state = detail::DiagnosticsStatus::kUploaded;
} CHARGELAB_CATCH {
CHARGELAB_LOG_MESSAGE(error) << "Diagnostics upload failed unexpectedly: " << e.what();
state.operation_state = detail::DiagnosticsStatus::kUploadFailed;
}
});
return rsp;
}
void onDiagnosticsStatusNotificationRsp(
std::string const& unique_id,
ocpp1_6::ResponseMessage<::chargelab::ocpp1_6::DiagnosticsStatusNotificationRsp> const& rsp
) override {
if (in_progress_.has_value()) {
auto& state = in_progress_.value();
if (state.pending_notification == unique_id) {
state.pending_notification = kNoOperation;
if (std::holds_alternative<ocpp1_6::CallError>(rsp)) {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to FirmwareStatusNotification request: " << std::get<ocpp1_6::CallError> (rsp);
}
}
}
}
private:
std::string buildDiagnosticsFileName() {
std::time_t t = system_interface_->systemClockNow()/1000;
std::tm *tm = gmtime(&t);
char buf[100];
sprintf(buf, "%d-%02d-%02dT%02d-%02d-%02dZ", tm->tm_year+1900, tm->tm_mon+1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
std::string file_name = "diagn_" + charge_point_id_.getValue() + "_" + std::string(buf) + ".txt";
return file_name;
}
private:
std::shared_ptr<SystemInterface> system_interface_;
std::shared_ptr<LoggingListenerFunction> listener_ = nullptr;
std::mutex mutex_;
RingBuffer<detail::DiagnosticsLine, kRingBufferMaxSize> history_; // keep the original diagnostics lines
int history_byte_size_ = 0;
std::shared_ptr<UploadInterface> upload_;
std::optional<detail::DiagnosticsUploadState> in_progress_ = std::nullopt;
BasicTextSetting charge_point_id_;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_GET_DIAGNOSTICS_MODULE_H

View File

@@ -0,0 +1,492 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_GET_LOGS_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_GET_LOGS_MODULE_H
#include "openocpp/module/common_templates.h"
#include "openocpp/module/pending_messages_module.h"
#include "openocpp/protocol/common/small_string.h"
#include "openocpp/interface/element/rest_connection_interface.h"
#include "openocpp/interface/platform_interface.h"
#include "openocpp/common/ring_buffer.h"
#include "openocpp/common/serialization.h"
namespace chargelab {
namespace detail {
struct UploadState {
ocpp2_0::GetLogRequest request;
std::string filename;
std::shared_ptr<RestConnectionInterface> connection = nullptr;
int total_failures = 0;
std::size_t bytes_written = 0;
std::size_t content_length = 0;
int min_index = 0;
int max_index = 0;
int last_index = 0;
std::optional<ocpp2_0::UploadLogStatusEnumType> last_status2_0 = std::nullopt;
std::optional<SteadyPointMillis> first_attempt = std::nullopt;
};
struct LogLine {
int message_index;
SystemTimeMillis timestamp;
logging::LogLevel level;
std::string message;
};
class LogLineSerializer {
public:
static std::optional<LogLine> read(std::string_view const& text) {
LogLine result;
std::optional<int> index = 0;
index = readPrimitive(text, index, result.message_index);
index = readPrimitive(text, index, result.timestamp);
index = readPrimitive(text, index, result.level);
index = readPrimitive(text, index, result.message);
if (!index.has_value())
return std::nullopt;
return result;
}
static std::string write(LogLine const& wrapper) {
std::string result;
writePrimitive(result, wrapper.message_index);
writePrimitive(result, wrapper.timestamp);
writePrimitive(result, wrapper.level);
writePrimitive(result, wrapper.message);
return result;
}
};
}
class GetLogsModule : public ServiceStatefulGeneral {
private:
// Note: this is the decompressed size of the dropped records
static constexpr int kTargetDroppedLogMessageBytes = 1024;
static constexpr int kMaxRetainedLogMessagesBytes = 2*1024;
static constexpr int kUploadStepMaxTimeMillis = 100;
static constexpr int kPriorityLogStatusNotification = 100;
static constexpr int kQueueReportFrequencySeconds = 10;
static constexpr int kMaxRingBufferSize = 5;
// Note: arbitrary random assigned ID
static constexpr std::uint64_t kOperationGroupId = 0xDB0A571F9D6E2A10ull;
public:
explicit GetLogsModule(
std::shared_ptr<PlatformInterface> platform,
std::shared_ptr<PendingMessagesModule> pending_messages
)
: platform_(std::move(platform)),
pending_messages_(std::move(pending_messages))
{
assert(platform_ != nullptr);
settings_ = platform_->getSettings();
assert(settings_ != nullptr);
listener_ = std::make_shared<logging::LoggingListenerFunction>([&](logging::LogMetadata const& metadata, std::string_view const& message) {
std::string merged_message;
#if defined(LOG_WITH_FILE_AND_LINE)
merged_message += "[";
merged_message += metadata.file;
merged_message += ":";
merged_message += std::to_string(metadata.line);
merged_message += "]";
#endif
merged_message += message;
log_buffer_.pushBack(detail::LogLine{
index_++,
platform_->systemClockNow(),
metadata.level,
std::move(merged_message)
});
});
logging::RegisterLoggingListener(listener_); // register this callback to the global listener
CHARGELAB_LOG_MESSAGE(info) << "Size of platform pointer: " << sizeof(platform_);
CHARGELAB_LOG_MESSAGE(info) << "Size of operation: " << sizeof(operation_);
CHARGELAB_LOG_MESSAGE(info) << "Size of listener: " << sizeof(*listener_);
CHARGELAB_LOG_MESSAGE(info) << "Size of queue: " << sizeof(log_queue_);
CHARGELAB_LOG_MESSAGE(info) << "Size of buffer: " << sizeof(log_buffer_);
}
private:
void runUnconditionally() override {
reportQueueSize();
uploadLogs();
flushLogMessages();
}
void flushLogMessages() {
for (int i=0; i < 10; i++) {
auto next = log_buffer_.popFront();
if (!next.has_value())
break;
log_queue_.pushBack(std::move(next.value()));
}
while (log_queue_.totalBytes() > kMaxRetainedLogMessagesBytes) {
std::size_t total_removed = 0;
log_queue_.removeIf([&] (std::string_view const& text, detail::LogLine const&) {
if (total_removed > kTargetDroppedLogMessageBytes)
return false;
total_removed += text.size();
return true;
});
}
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::GetLogResponse>>
onGetLogReq(const ocpp2_0::GetLogRequest& request) override {
if (!uri::parseHttpUri(request.log.remoteLocation.value()).has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad remoteLocation: " << request.log.remoteLocation.value();
return ocpp2_0::GetLogResponse {ocpp2_0::LogStatusEnumType::kRejected};
}
std::string filename = settings_->ChargerSerialNumber.getValue();
switch (request.logType) {
case ocpp2_0::LogEnumType::kValueNotFoundInEnum:
CHARGELAB_LOG_MESSAGE(warning) << "Bad logType in request";
return ocpp2_0::GetLogResponse {ocpp2_0::LogStatusEnumType::kRejected};
case ocpp2_0::LogEnumType::kDiagnosticsLog:
filename += "-diagnostics-";
break;
case ocpp2_0::LogEnumType::kSecurityLog:
filename += "-security-";
break;
}
filename += std::to_string(platform_->systemClockNow()/1000) + ".txt";
ocpp2_0::LogStatusEnumType status;
if (!operation_.has_value()) {
// N01.FR.01
status = ocpp2_0::LogStatusEnumType::kAccepted;
} else {
// N01.FR.12
status = ocpp2_0::LogStatusEnumType::kAcceptedCanceled;
// N01.FR.20
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kAcceptedCanceled);
}
operation_ = detail::UploadState {request, filename};
return ocpp2_0::GetLogResponse {status, filename};
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
(void)req;
return std::nullopt;
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest &req) override {
if (req.requestedMessage != ocpp2_0::MessageTriggerEnumType::kLogStatusNotification)
return std::nullopt;
if (req.evse.has_value() || !operation_.has_value()) {
pending_messages_->sendRequest2_0(
ocpp2_0::LogStatusNotificationRequest {
ocpp2_0::UploadLogStatusEnumType::kIdle
},
PendingMessagePolicy {
PendingMessageType::kNotificationEvent,
kOperationGroupId,
settings_->NotificationMessageDefaultRetries.getValue(),
settings_->NotificationMessageDefaultRetryInterval.getValue(),
kPriorityLogStatusNotification,
false,
false
}
);
} else {
operation_->last_status2_0 = std::nullopt;
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploading);
}
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
}
private:
void reportQueueSize() {
auto const now = platform_->steadyClockNow();
if (last_queue_size_report_.has_value()) {
auto const delta = now - last_queue_size_report_.value();
if (delta < kQueueReportFrequencySeconds*1000)
return;
}
last_queue_size_report_ = now;
CHARGELAB_LOG_MESSAGE(info) << "Total log queue size: " << log_queue_.totalBytes();
}
void uploadLogs() {
if (!operation_.has_value())
return;
if ((int)operation_->total_failures > optional::GetOrDefault(operation_->request.retries, 0)) {
// N01.FR.10
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
operation_ = std::nullopt;
return;
}
if (!checkOrRetryConnection())
return;
if (!operation_->first_attempt.has_value())
operation_->first_attempt = platform_->steadyClockNow();
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploading);
bool wrote_first = false;
bool ran_out_of_time = false;
auto const start_ts = platform_->steadyClockNow();
{
bool only_security_logs = operation_->request.logType == ocpp2_0::LogEnumType::kSecurityLog;
log_queue_.visit([&] (std::string_view const&, detail::LogLine const& line) {
if (ran_out_of_time)
return;
if (operation_->connection == nullptr)
return;
if (operation_->bytes_written >= operation_->content_length)
return;
if (wrote_first) {
if (platform_->steadyClockNow() - start_ts > kUploadStepMaxTimeMillis) {
ran_out_of_time = true;
return;
}
}
auto const& log = operation_->request.log;
if (log.oldestTimestamp.has_value() && log.oldestTimestamp->isAfter(line.timestamp, false))
return;
if (log.latestTimestamp.has_value() && log.latestTimestamp->isBefore(line.timestamp, false))
return;
if (only_security_logs && line.message.find("[security]") == std::string::npos)
return;
// Note: allowing for integer overflow; checking if message_index < min_index
if (line.message_index - operation_->min_index < 0)
return;
// Note: allowing for integer overflow; checking if message_index > max_index
if (line.message_index - operation_->max_index > 0)
return;
// Note: allowing for integer overflow; checking if message_index <= last_index
if (line.message_index - operation_->last_index <= 0)
return;
// Truncate the message to fit in the original content_length
auto message = renderLine(line);
if (operation_->bytes_written + message.size() > operation_->content_length) {
auto const truncated = operation_->bytes_written + message.size() - operation_->content_length;
message.resize(std::min(std::max((std::size_t)0, truncated), message.size()));
}
if (!operation_->connection->write(message.data(), message.size())) {
CHARGELAB_LOG_MESSAGE(info) << "Failed writing payload at " << operation_->bytes_written << " bytes";
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
operation_->total_failures++;
operation_->connection = nullptr;
return;
}
operation_->last_index = line.message_index;
operation_->bytes_written += message.size();
wrote_first = true;
});
}
if (ran_out_of_time)
return;
if (operation_->connection == nullptr)
return;
std::string buffer;
while (operation_->bytes_written < operation_->content_length) {
// Note: this can happen if log lines are removed while an upload operation is in progress
buffer.resize(std::min((std::size_t)1024, operation_->content_length - operation_->bytes_written), ' ');
if (!operation_->connection->write(buffer.data(), buffer.size())) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed writing padding to server";
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
operation_->total_failures++;
operation_->connection = nullptr;
return;
}
CHARGELAB_LOG_MESSAGE(warning) << "Added padding: " << buffer.size() << " bytes";
operation_->bytes_written += buffer.size();
}
if (!operation_->connection->send()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed finishing HTTP request";
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
operation_->total_failures++;
operation_->connection = nullptr;
return;
}
auto const status = operation_->connection->getStatusCode();
if (status == 403) {
CHARGELAB_LOG_MESSAGE(warning) << "Unauthorized response from server: " << status;
// N01.FR.10
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kPermissionDenied);
operation_->total_failures++;
operation_->connection = nullptr;
return;
}
if (status < 200 || status >= 300) {
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected status code: " << status;
// N01.FR.10
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kBadMessage);
operation_->total_failures++;
operation_->connection = nullptr;
return;
}
CHARGELAB_LOG_MESSAGE(warning) << "Upload succeeded - response was: " << status;
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploaded);
operation_ = std::nullopt;
}
static std::string renderLine(detail::LogLine const& line) {
std::string result;
result += "[" + write_json_to_string(ocpp2_0::DateTime{line.timestamp}) + "] ";
result += "[" + write_json_to_string(line.level) + "] ";
result += line.message;
result += "\n";
return result;
}
bool checkOrRetryConnection() {
if (!operation_.has_value())
return false;
if (operation_->connection != nullptr)
return true;
if (operation_->first_attempt.has_value()) {
auto const delta_seconds = (platform_->steadyClockNow() - operation_->first_attempt.value())/1000;
auto const interval = optional::GetOrDefault(operation_->request.retryInterval, 0);
if (delta_seconds < interval*operation_->total_failures)
return false;
}
auto const uri = operation_->request.log.remoteLocation.value();
CHARGELAB_LOG_MESSAGE(info) << "Uploading logs to: " << uri;
operation_->connection = platform_->putRequest(uri);
if (operation_->connection == nullptr) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed creating post request for: " << uri;
// N01.FR.10
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
operation_->total_failures++;
return false;
}
// Compute the size and index limits
std::optional<int> min_index = std::nullopt;
std::optional<int> max_index = std::nullopt;
std::size_t content_length = 0;
{
log_queue_.visit([&] (std::string_view const&, detail::LogLine const& line) {
auto const& log = operation_->request.log;
if (log.oldestTimestamp.has_value() && log.oldestTimestamp->isAfter(line.timestamp, false))
return;
if (log.latestTimestamp.has_value() && log.latestTimestamp->isBefore(line.timestamp, false))
return;
if (!min_index.has_value()) {
min_index = line.message_index;
} else {
// Note: allowing for integer overflow; checking if message_index < min_index
if (line.message_index - min_index.value() < 0)
min_index = line.message_index;
}
if (!max_index.has_value()) {
max_index = line.message_index;
} else {
// Note: allowing for integer overflow; checking if message_index > max_index
if (line.message_index - max_index.value() > 0)
max_index = line.message_index;
}
content_length += renderLine(line).size();
});
}
operation_->bytes_written = 0;
operation_->content_length = content_length;
operation_->min_index = optional::GetOrDefault(min_index, 0);
operation_->max_index = optional::GetOrDefault(max_index, 0);
operation_->last_index = optional::GetOrDefault(min_index, 0) - 1;
// N01.FR.19
operation_->connection->setHeader("Content-Type", "text/plain");
operation_->connection->setHeader("Content-Disposition", operation_->filename);
if (!operation_->connection->open(content_length)) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed opening connection to server";
checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType::kUploadFailure);
operation_->total_failures++;
operation_->connection = nullptr;
} else {
CHARGELAB_LOG_MESSAGE(info) << "Connection opened to server - uploading " << content_length << " bytes...";
}
return true;
}
void checkAndUpdateStatus(ocpp2_0::UploadLogStatusEnumType status) {
if (operation_->last_status2_0 == std::make_optional(status))
return;
pending_messages_->sendRequest2_0(
ocpp2_0::LogStatusNotificationRequest {
status,
operation_->request.requestId
},
PendingMessagePolicy {
PendingMessageType::kNotificationEvent,
kOperationGroupId,
settings_->NotificationMessageDefaultRetries.getValue(),
settings_->NotificationMessageDefaultRetryInterval.getValue(),
kPriorityLogStatusNotification,
false,
false
}
);
operation_->last_status2_0 = status;
}
private:
std::shared_ptr<PlatformInterface> platform_;
std::shared_ptr<PendingMessagesModule> pending_messages_;
std::shared_ptr<Settings> settings_;
std::shared_ptr<logging::LoggingListenerFunction> listener_;
std::optional<detail::UploadState> operation_ = std::nullopt;
std::optional<SteadyPointMillis> last_queue_size_report_ = std::nullopt;
RingBuffer<detail::LogLine, kMaxRingBufferSize> log_buffer_;
std::atomic<int> index_ = 0;
CompressedQueueCustom<detail::LogLine, detail::LogLineSerializer> log_queue_;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_GET_LOGS_MODULE_H

View File

@@ -0,0 +1,129 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_HEARTBEAT_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_HEARTBEAT_MODULE_H
#include "openocpp/module/common_templates.h"
#include "openocpp/interface/component/system_interface.h"
#include "openocpp/common/operation_holder.h"
#include "openocpp/common/settings.h"
#include <utility>
namespace chargelab {
class HeartbeatModule : public ServiceStatefulGeneral {
public:
HeartbeatModule(
std::shared_ptr<Settings> settings,
std::shared_ptr<SystemInterface> const& system_interface
)
: settings_(std::move(settings)),
system_interface_(system_interface),
pending_heartbeat_req_ {system_interface}
{
}
~HeartbeatModule() override {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting HeartbeatModule";
}
public:
void runStep(ocpp1_6::OcppRemote &remote) override {
if (!pending_heartbeat_req_.wasIdleFor(settings_->HeartbeatInterval.getValue())) {
if (!force_heartbeat_ || pending_heartbeat_req_.operationInProgress())
return;
}
pending_heartbeat_req_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
remote.sendHeartbeatReq({})
);
force_heartbeat_ = false;
}
void onHeartbeatRsp(
const std::string &unique_id,
const std::variant<ocpp1_6::HeartbeatRsp, ocpp1_6::CallError> &rsp
) override {
if (pending_heartbeat_req_ == unique_id) {
pending_heartbeat_req_ = kNoOperation;
if (std::holds_alternative<ocpp1_6::HeartbeatRsp>(rsp)) {
auto const& value = std::get<ocpp1_6::HeartbeatRsp>(rsp);
auto const& ts = value.currentTime.getTimestamp();
if (ts.has_value()) {
system_interface_->setSystemClock(ts.value());
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Invalid Heartbeat response timestamp: "
<< value.currentTime;
}
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to Hearbeat request: " << std::get<ocpp1_6::CallError> (rsp);
}
}
}
void runStep(ocpp2_0::OcppRemote &remote) override {
if (!pending_heartbeat_req_.wasIdleFor(settings_->HeartbeatInterval.getValue())) {
if (!force_heartbeat_ || pending_heartbeat_req_.operationInProgress())
return;
}
pending_heartbeat_req_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
remote.sendHeartbeatReq({})
);
force_heartbeat_ = false;
}
void onHeartbeatRsp(
const std::string &unique_id,
const std::variant<ocpp2_0::HeartbeatResponse, ocpp2_0::CallError> &rsp
) override {
if (pending_heartbeat_req_ == unique_id) {
pending_heartbeat_req_ = kNoOperation;
if (std::holds_alternative<ocpp2_0::HeartbeatResponse>(rsp)) {
auto const& value = std::get<ocpp2_0::HeartbeatResponse>(rsp);
auto const& ts = value.currentTime.getTimestamp();
if (ts.has_value()) {
system_interface_->setSystemClock(ts.value());
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Invalid Heartbeat response timestamp: "
<< value.currentTime;
}
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to Hearbeat request: " << std::get<ocpp2_0::CallError> (rsp);
}
}
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::TriggerMessageRsp>>
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
if (req.requestedMessage == ocpp1_6::MessageTrigger::kHeartbeat) {
force_heartbeat_ = true;
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kAccepted};
}
return std::nullopt;
}
std::optional<ocpp2_0::ResponseToRequest<ocpp2_0::TriggerMessageResponse>>
onTriggerMessageReq(const ocpp2_0::TriggerMessageRequest &request) override {
if (request.requestedMessage == ocpp2_0::MessageTriggerEnumType::kHeartbeat) {
force_heartbeat_ = true;
return ocpp2_0::TriggerMessageResponse {ocpp2_0::TriggerMessageStatusEnumType::kAccepted};
}
return std::nullopt;
}
private:
std::shared_ptr<Settings> settings_;
std::shared_ptr<SystemInterface> system_interface_;
OperationHolder<std::string> pending_heartbeat_req_;
bool force_heartbeat_ = false;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_HEARTBEAT_MODULE_H

View File

@@ -0,0 +1,218 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_LOCAL_LIMITS_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_LOCAL_LIMITS_MODULE_H
#include "openocpp/interface/component/system_interface.h"
#include <vector>
#include <string>
#include <optional>
#include <iostream>
namespace chargelab {
struct LeaseAmps {
int created_index;
double allocated_amps;
SteadyPointMillis expiry;
std::optional<int> completed_index = std::nullopt;
};
namespace detail {
struct LocalLimitState {
std::string charge_point_id_;
std::vector<LeaseAmps> active_leases_;
std::optional<double> requested_amps_;
std::optional<SteadyPointMillis> requested_amps_ts_;
// Informational
std::optional<double> last_reported_amps_;
std::optional<double> last_reported_watts_;
};
}
class LocalLimitModule {
private:
static constexpr int kRequestExpirySeconds = 3600;
static constexpr int kLeaseDurationSeconds = 3600;
static constexpr int kLeaseRefreshThresholdSeconds = 15*60;
public:
explicit LocalLimitModule(
std::shared_ptr<chargelab::SystemInterface> system,
double max_amps
)
: system_(std::move(system)),
max_amps_(max_amps)
{
}
public:
int getUniqueId(std::string const& charge_point_id) {
std::lock_guard lock {mutex_};
for (auto const& x : state_) {
if (x.second.charge_point_id_ == charge_point_id)
return x.first;
}
auto result = last_id_++;
state_[result].charge_point_id_ = charge_point_id;
return result;
}
void setRequestedAmps(int id, double amps) {
std::lock_guard lock {mutex_};
state_[id].requested_amps_ = amps;
state_[id].requested_amps_ts_ = system_->steadyClockNow();
}
void setCurrentAllocatedAmps(int id, double amps) {
std::lock_guard lock {mutex_};
state_[id].last_reported_amps_ = amps;
}
void setCurrentWatts(int id, double watts) {
std::lock_guard lock {mutex_};
state_[id].last_reported_watts_ = watts;
}
std::optional<LeaseAmps> leaseAmps(int id, std::optional<LeaseAmps> const& current) {
std::lock_guard lock {mutex_};
auto result = leaseAmpsImpl(id);
bool refresh = false;
if (result.allocated_amps != current->allocated_amps)
refresh = true;
if ((current->expiry - system_->steadyClockNow())/1000 < kLeaseRefreshThresholdSeconds)
refresh = true;
if (!refresh)
return std::nullopt;
state_[id].active_leases_.push_back(result);
return result;
}
void activateLease(int id, LeaseAmps& current) {
std::lock_guard lock {mutex_};
int completed_index = lease_index_++;
current.completed_index = completed_index;
auto& leases = state_[id].active_leases_;
for (auto& x : leases) {
if (x.created_index == current.created_index) {
x.completed_index = completed_index;
break;
}
}
leases.erase(
std::remove_if(
leases.begin(),
leases.end(),
[=](LeaseAmps const& x){return x.completed_index && current.created_index - x.completed_index.value() > 0;}
),
leases.end()
);
}
void printCurrentAmps() {
std::lock_guard lock {mutex_};
system("clear");
auto now = system_->steadyClockNow();
for (auto& x : state_) {
auto lease = removeDeadEntriesAndGetMaxLease(x.second.active_leases_);
if (!lease)
continue;
std::cout << "ID " << x.first << " {" << x.second.charge_point_id_ << "}: ";
std::cout << std::setw(4) << std::round(lease->allocated_amps*10)/10.0 << "A (" << std::setw(2) << static_cast<int>((lease->expiry - now)/1000) << "s)";
if (x.second.last_reported_amps_ && x.second.requested_amps_)
std::cout << " " << std::setw(4) << std::round(x.second.last_reported_amps_.value()*10)/10.0 << "A/" << std::setw(4) << std::round(x.second.requested_amps_.value()*10)/10.0 << "A";
if (x.second.last_reported_watts_)
std::cout << " " << std::setw(4) << std::round(x.second.last_reported_watts_.value()) << "W";
std::cout << std::endl;
}
}
private:
LeaseAmps leaseAmpsImpl(int id) {
auto now = system_->steadyClockNow();
auto expiry = static_cast<SteadyPointMillis>(now + kLeaseDurationSeconds*1000);
int index = lease_index_++;
auto const& state = state_[id];
if (!state.requested_amps_)
return {index, 0.0, expiry};
if (!state.requested_amps_ts_ || (now - state.requested_amps_ts_.value())/1000.0 >= kRequestExpirySeconds)
return {index, 0.0, expiry};
double total_requested = 0;
for (auto const& x : state_) {
if (!x.second.requested_amps_ || !x.second.requested_amps_ts_)
continue;
if ((now - x.second.requested_amps_ts_.value())/1000.0 >= kRequestExpirySeconds)
continue;
total_requested += x.second.requested_amps_.value();
}
double total_leased = 0;
for (auto& x : state_) {
if (x.first == id)
continue;
auto lease = removeDeadEntriesAndGetMaxLease(x.second.active_leases_);
if (lease)
total_leased += lease->allocated_amps;
}
auto requested = state.requested_amps_.value();
auto result = std::min(requested, requested*max_amps_/total_requested);
result = std::min(max_amps_ - total_leased, result);
result = std::max(0.0, result);
return {index, result, expiry};
}
std::optional<LeaseAmps> removeDeadEntriesAndGetMaxLease(std::vector<LeaseAmps>& leases) {
auto now = system_->steadyClockNow();
leases.erase(
std::remove_if(
leases.begin(),
leases.end(),
[&](auto const& x) {return now - x.expiry > 0;}
),
leases.end()
);
std::optional<LeaseAmps> max_lease;
for (auto const& x : leases) {
if (max_lease) {
if (x.allocated_amps < max_lease->allocated_amps)
continue;
if (x.allocated_amps == max_lease->allocated_amps && x.expiry - max_lease->expiry < 0)
continue;
}
max_lease = x;
}
return max_lease;
}
private:
std::shared_ptr<chargelab::SystemInterface> system_;
double max_amps_;
int last_id_ = 0;
int lease_index_ = 0;
std::mutex mutex_;
std::map<int, detail::LocalLimitState> state_;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_LOCAL_LIMITS_MODULE_H

View File

@@ -0,0 +1,231 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_LOG_STREAMING_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_LOG_STREAMING_MODULE_H
#include "openocpp/module/common_templates.h"
#include "openocpp/interface/component/system_interface.h"
#include "openocpp/common/settings.h"
#include <utility>
#include <sstream>
namespace chargelab {
namespace detail {
struct LogLine {
LogLevel level = LogLevel::kValueNotFoundInEnum;
#if defined(LOG_WITH_FILE_AND_LINE)
std::string file {};
int line = -1;
#endif
std::string function {};
std::string message {};
int size() {
return sizeof(LogLine) +
#if defined(LOG_WITH_FILE_AND_LINE)
file.size() +
#endif
function.size() + message.size();
}
};
#if defined(LOG_WITH_FILE_AND_LINE)
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(LogLine, level, file, line, function, message)
#else
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(LogLine, level, function, message)
#endif
struct LogMessage {
std::vector<LogLine> messages;
};
CHARGELAB_OPTIONAL_NULL_DEFINE_TYPE_NON_INTRUSIVE(LogMessage, messages)
}
class LogStreamingModule : public ChargePointServiceStateful {
private:
static const int kBufferLimitBytes = 3*1024; // 10*1024;
static const int kRingBufferMaxSize = 25; // 50;
static const int kMaxLinesPerMessage = 15; // 20;
static const int kBufferLimitMillis = 30*1000; // 30*1000;
public:
LogStreamingModule(
Settings& settings,
std::shared_ptr<SystemInterface> const& system_interface
) : system_interface_(system_interface),
pending_dump_request_ {system_interface},
log_streaming_enabled_ {chargelab::settings::CreateBasicBooleanSetting<false>(
settings,
settings::CommonKey::kLogStreamingEnabled,
"LogStreamingEnabled"
)}
{
listener_ = std::make_shared<LoggingListenerFunction>([&](LogMetadata const& metadata, std::string_view const& message) {
// TODO: Find a better way to filter out specific messages related to the dump itself
if (message.find("Writing text message") != std::string::npos && message.find("StreamingLogMessage") != std::string::npos) {
return;
}
if (!log_streaming_enabled_.getValue())
return;
detail::LogLine line {};
line.level = metadata.level;
#if defined(LOG_WITH_FILE_AND_LINE)
line.file = metadata.file;
line.line = metadata.line;
line.file.shrink_to_fit();
#endif
line.function = metadata.function;
line.message = message;
line.function.shrink_to_fit();
line.message.shrink_to_fit();
std::lock_guard lock{mutex_};
history_.pushBack(std::move(line));
});
RegisterLoggingListener(listener_);
}
~LogStreamingModule() override {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting LogStreamingModule";
UnregisterLoggingListener(listener_);
}
private:
void runStep(ocpp1_6::ChargePointRemoteInterface &remote) override {
{
std::lock_guard lock{mutex_};
if (history_.empty())
return;
auto const now = system_interface_->steadyClockNow();
if (history_.size() < kMaxLinesPerMessage && !getHistoryLimitIndex().has_value()) {
if (!last_dump_.has_value())
last_dump_ = now;
auto const delta = now - last_dump_.value();
if (delta < kBufferLimitMillis) {
return;
}
}
}
if (!pending_dump_request_.operationInProgress()) {
CHARGELAB_LOG_MESSAGE(debug) << "Flushing log messages - total size was: " << getHistorySize() << " bytes" << ", total memory size: " << getHistoryMemorySize()
<< ",ring buffer size: " << history_.size();
detail::LogMessage payload {};
{
std::lock_guard lock{mutex_};
int total_size = 0;
for (int i=0; i < kMaxLinesPerMessage && !history_.empty(); i++) {
if (total_size + history_.front().size() > kBufferLimitBytes) break;
total_size += history_.front().size();
detail::LogLine value = std::move(history_.front());
history_.popFront();
payload.messages.push_back(std::move(value));
}
}
pending_dump_request_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
remote.sendDataTransferReq(ocpp1_6::DataTransferReq {
ocpp1_6::CiString255Type("ChargeLab"),
ocpp1_6::CiString50Type("StreamingLogMessage"),
write_json_to_string(payload)
})
);
}
std::lock_guard lock{mutex_};
auto limit = getHistoryLimitIndex();
if (limit.has_value()) {
for (int i=0; i <= limit.value(); i++) {
history_.front() = detail::LogLine {};
history_.popFront();
}
}
}
void onDataTransferRsp(
const std::string &unique_id,
const ocpp1_6::ResponseMessage <chargelab::ocpp1_6::DataTransferRsp> &rsp
) override {
if (pending_dump_request_ == unique_id) {
pending_dump_request_ = kNoOperation;
last_dump_ = system_interface_->steadyClockNow();
if (std::holds_alternative<ocpp1_6::CallError>(rsp)) {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to DataTransfer request: " << std::get<ocpp1_6::CallError> (rsp);
}
}
}
private:
std::optional<int> getHistoryLimitIndex() {
int index;
std::size_t total_bytes = 0;
for (index = history_.size()-1; index >= 0; index--) {
auto const& value = history_[index];
total_bytes += sizeof(value.level);
#if defined(LOG_WITH_FILE_AND_LINE)
total_bytes += sizeof(std::string) + value.file.size();
total_bytes += sizeof(value.line);
#endif
total_bytes += sizeof(std::string) + value.function.size();
total_bytes += sizeof(std::string) + value.message.size();
if (total_bytes >= kBufferLimitBytes)
return index;
}
return std::nullopt;
}
std::size_t getHistorySize() {
std::size_t total_bytes = 0;
for (int index=0; index < history_.size(); index++) {
auto const& value = history_[index];
total_bytes += sizeof(value.level);
#if defined(LOG_WITH_FILE_AND_LINE)
total_bytes += sizeof(std::string) + value.file.size();
total_bytes += sizeof(value.line);
#endif
total_bytes += sizeof(std::string) + value.function.size();
total_bytes += sizeof(std::string) + value.message.size();
}
return total_bytes;
}
std::size_t getHistoryMemorySize() {
std::size_t total_bytes = 0;
for (int index=0; index < history_.size(); index++) {
auto const& value = history_[index];
total_bytes += sizeof(value.level);
#if defined(LOG_WITH_FILE_AND_LINE)
total_bytes += sizeof(std::string) + value.file.capacity();
total_bytes += sizeof(value.line);
#endif
total_bytes += sizeof(std::string) + value.function.capacity();
total_bytes += sizeof(std::string) + value.message.capacity();
}
return total_bytes;
}
private:
std::shared_ptr<SystemInterface> system_interface_;
OperationHolder<std::string> pending_dump_request_;
BasicBooleanSetting<false> log_streaming_enabled_;
std::optional<SteadyPointMillis> last_dump_ = std::nullopt;
std::shared_ptr<LoggingListenerFunction> listener_ = nullptr;
std::mutex mutex_;
RingBuffer<detail::LogLine, kRingBufferMaxSize> history_;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_LOG_STREAMING_MODULE_H

View File

@@ -0,0 +1,967 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_PENDING_MESSAGES_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_PENDING_MESSAGES_MODULE_H
#include "openocpp/interface/platform_interface.h"
#include "openocpp/module/common_templates.h"
#include "openocpp/common/operation_holder.h"
#include "openocpp/common/compressed_queue.h"
#include "openocpp/common/logging.h"
#include "openocpp/common/serialization.h"
#include <string>
#include <optional>
#include <unordered_set>
namespace chargelab {
CHARGELAB_JSON_ENUM(PendingMessageType,
TransactionEvent,
SecurityEvent,
NotificationEvent,
Other
);
struct PendingMessagePolicy {
PendingMessageType message_type;
std::optional<uint64_t> group_id = std::nullopt;
int message_attempts = 1;
int retry_interval_seconds = 10;
// Determines which records are dropped first when available storage is exceeded - higher priority takes precedence
int priority = 0;
bool add_remote_transaction_id = false;
bool add_message_sequence_number = false;
bool must_flush_to_disk = false;
CHARGELAB_JSON_INTRUSIVE(
PendingMessagePolicy,
message_type,
group_id,
message_attempts,
retry_interval_seconds,
priority,
add_remote_transaction_id,
add_message_sequence_number,
must_flush_to_disk
)
};
namespace detail {
struct PendingMessageWrapper {
int64_t unique_id;
std::string payload;
PendingMessagePolicy policy;
std::optional<ocpp1_6::ActionId> action_id1_6;
std::optional<ocpp2_0::ActionId> action_id2_0;
int attempts = 0;
CHARGELAB_JSON_INTRUSIVE(PendingMessageWrapper, unique_id, payload, policy, action_id1_6, action_id2_0, attempts)
};
class PendingMessageSerializer {
public:
static std::optional<PendingMessageWrapper> read(std::string_view const& text) {
PendingMessageWrapper result;
std::optional<int> index = 0;
index = readPrimitive(text, index, result.unique_id);
index = readPrimitive(text, index, result.payload);
index = readPrimitive(text, index, result.policy.message_type);
index = readPrimitive(text, index, result.policy.group_id);
index = readPrimitive(text, index, result.policy.retry_interval_seconds);
index = readPrimitive(text, index, result.policy.message_attempts);
index = readPrimitive(text, index, result.policy.priority);
index = readPrimitive(text, index, result.policy.add_remote_transaction_id);
index = readPrimitive(text, index, result.policy.add_message_sequence_number);
index = readPrimitive(text, index, result.policy.must_flush_to_disk);
index = readPrimitive(text, index, result.action_id1_6);
index = readPrimitive(text, index, result.action_id2_0);
index = readPrimitive(text, index, result.attempts);
if (!index.has_value())
return std::nullopt;
return result;
}
static std::string write(PendingMessageWrapper const& wrapper) {
std::string result;
writePrimitive(result, wrapper.unique_id);
writePrimitive(result, wrapper.payload);
writePrimitive(result, wrapper.policy.message_type);
writePrimitive(result, wrapper.policy.group_id);
writePrimitive(result, wrapper.policy.retry_interval_seconds);
writePrimitive(result, wrapper.policy.message_attempts);
writePrimitive(result, wrapper.policy.priority);
writePrimitive(result, wrapper.policy.add_remote_transaction_id);
writePrimitive(result, wrapper.policy.add_message_sequence_number);
writePrimitive(result, wrapper.policy.must_flush_to_disk);
writePrimitive(result, wrapper.action_id1_6);
writePrimitive(result, wrapper.action_id2_0);
writePrimitive(result, wrapper.attempts);
return result;
}
};
}
class PendingMessagesModule : public ServiceStatefulGeneral {
private:
static constexpr int kUpdateCacheAndStatsFrequencySeconds = 15;
static constexpr int kMaxLiveMessages = 5;
static constexpr int kOfflineSizeLimitBytes = 10*1024;
static constexpr int kTargetDeleteRecordCount = 10;
static constexpr int kMaxStorageBlockSize = 50*1024;
static constexpr int kFlushToDiskFrequencyMillis = 30*60*1000; // half hour
public:
using saved_message_supplier = std::function<void(std::function<void(detail::PendingMessageWrapper const&)> const&)>;
public:
PendingMessagesModule(
std::shared_ptr<Settings> settings,
std::shared_ptr<SystemInterface> const& system,
std::shared_ptr<StorageInterface> storage
)
: settings_ {std::move(settings)},
system_ {system},
storage_ {std::move(storage)},
random_engine_ {std::random_device{}()},
// TODO: Review this; timeout will not change until reboot
request_operation_ {system}
{
std::uniform_int_distribution<int64_t> distribution(0, std::numeric_limits<int64_t>::max());
request_id_ = distribution(random_engine_);
loadFromStorage();
// Clear transaction_ids_ and sequence_ids_ if offline_queue_ and live_queue_ are both empty
if (offline_queue_.empty() && live_queue_.empty()) {
transaction_ids_.clear();
sequence_ids_.clear();
}
settings_->MessageFlushCounter.setValue(pending_messages_write_count_);
}
void registerOnSaveMessageSupplier(std::shared_ptr<saved_message_supplier> const& supplier) {
for (auto const& x : saved_message_suppliers_) {
if (x == supplier) {
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
return;
}
}
saved_message_suppliers_.push_back(supplier);
}
void unregisterOnSaveMessageSupplier(std::shared_ptr<saved_message_supplier> const& supplier) {
auto initial_size = saved_message_suppliers_.size();
saved_message_suppliers_.erase(
std::remove_if(saved_message_suppliers_.begin(), saved_message_suppliers_.end(), [&](auto& x) {return x == supplier;}),
saved_message_suppliers_.end()
);
if (initial_size - saved_message_suppliers_.size() > 1)
CHARGELAB_LOG_MESSAGE(warning) << "Callback registered multiple times";
}
public:
template<typename T>
detail::PendingMessageWrapper generateRequest1_6(T const& request, PendingMessagePolicy policy = PendingMessagePolicy{}) {
return detail::PendingMessageWrapper {
request_id_++,
write_json_to_string(request),
policy,
T::kActionId,
std::nullopt
};
}
template<typename T>
detail::PendingMessageWrapper generateRequest2_0(T const& request, PendingMessagePolicy const& policy) {
return detail::PendingMessageWrapper {
request_id_++,
write_json_to_string(request),
policy,
std::nullopt,
T::kActionId
};
}
template<typename T>
std::string sendRequest1_6(T const& request, PendingMessagePolicy policy = PendingMessagePolicy{}) {
detail::PendingMessageWrapper wrapper = generateRequest1_6(request, policy);
if (activeGroupsContains(policy.group_id)) {
offline_queue_.pushBack(wrapper);
pending_messages_changed_ = true;
} else {
live_queue_.push_back(wrapper);
}
if (policy.must_flush_to_disk)
must_flush_to_disk_ = true;
return std::to_string(wrapper.unique_id);
}
template<typename T>
std::string sendRequest2_0(T const& request, PendingMessagePolicy const& policy) {
detail::PendingMessageWrapper wrapper = generateRequest2_0(request, policy);
if (activeGroupsContains(policy.group_id)) {
offline_queue_.pushBack(wrapper);
pending_messages_changed_ = true;
} else {
live_queue_.push_back(wrapper);
}
if (policy.must_flush_to_disk)
must_flush_to_disk_ = true;
return std::to_string(wrapper.unique_id);
}
template <typename Visitor>
void visitPending(Visitor&& visitor) {
for (auto const& x : live_queue_)
visitor(x.policy, x.payload);
offline_queue_.visit([&] (std::string_view const&, detail::PendingMessageWrapper const& wrapper) {
visitor(wrapper.policy, wrapper.payload);
});
}
public:
void runUnconditionally() override {
updateCacheAndStats();
limitOfflineQueueSize();
flushToDisk();
// If an operation is not in progress, flush messages from the live queue to the offline queue as necessary
if (!request_operation_.operationInProgress() && !live_queue_.empty()) {
while (live_queue_.size() > kMaxLiveMessages) {
auto it = live_queue_.begin();
if (it->policy.group_id.has_value())
active_group_ids_.insert(it->policy.group_id.value());
offline_queue_.pushBack(live_queue_.front());
live_queue_.erase(live_queue_.begin());
pending_messages_changed_ = true;
}
// Make sure live messages with the same group IDs also get moved to offline messages so that they're
// not sent out of order.
live_queue_.erase(
std::remove_if(
live_queue_.begin(),
live_queue_.end(),
[&](detail::PendingMessageWrapper const& wrapper) {
if (activeGroupsContains(wrapper.policy.group_id)) {
offline_queue_.pushBack(wrapper);
pending_messages_changed_ = true;
return true;
} else {
return false;
}
}
),
live_queue_.end()
);
}
}
void runStep(ocpp1_6::OcppRemote& remote) override {
// Send out a live message, if one is available
if (!request_operation_.operationInProgress() && !live_queue_.empty()) {
auto it = live_queue_.begin();
bool delete_message = false;
bool send_message = true;
if (blacklistContains(it->policy.group_id))
delete_message = true;
if (!it->action_id1_6.has_value())
delete_message = true;
if (it->attempts > 0 && it->attempts >= it->policy.message_attempts)
delete_message = true;
if (it->attempts > 0) {
auto const threshold = std::max(it->attempts, 1) * it->policy.retry_interval_seconds;
if (request_operation_.getIdleDurationSeconds() < threshold)
send_message = false;
}
if (delete_message) {
live_queue_.erase(it);
} else if (send_message) {
if (sendWithTransactionId(remote, *it)) {
it->attempts++;
request_operation_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
std::to_string(it->unique_id)
);
request_object_ = *it;
}
}
}
// Next send out an offline message, if one is available
if (!request_operation_.operationInProgress() && !offline_queue_.empty()) {
auto record = offline_queue_.pollFront();
if (record.has_value()) {
bool delete_message = false;
bool send_message = true;
if (blacklistContains(record->policy.group_id))
delete_message = true;
if (!record->action_id1_6.has_value())
delete_message = true;
if (record->attempts > 0 && record->attempts >= record->policy.message_attempts)
delete_message = true;
if (record->attempts > 0) {
auto const threshold = std::max(record->attempts, 1) * record->policy.retry_interval_seconds;
if (request_operation_.getIdleDurationSeconds() < threshold)
send_message = false;
}
if (delete_message) {
CHARGELAB_LOG_MESSAGE(info) << "popFront: delete_message";
offline_queue_.popFront();
pending_messages_changed_ = true;
} else if (send_message) {
if (sendWithTransactionId(remote, record.value())) {
record->attempts++;
offline_queue_.updateFront(record.value());
pending_messages_changed_ = true;
request_operation_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
std::to_string(record->unique_id)
);
request_object_ = record.value();
}
}
}
}
}
void runStep(ocpp2_0::OcppRemote &remote) override {
updateCacheAndStats();
limitOfflineQueueSize();
// If an operation is not in progress, flush messages from the live queue to the offline queue as necessary
if (!request_operation_.operationInProgress() && !live_queue_.empty()) {
while (live_queue_.size() > kMaxLiveMessages) {
auto it = live_queue_.begin();
if (it->policy.group_id.has_value())
active_group_ids_.insert(it->policy.group_id.value());
offline_queue_.pushBack(live_queue_.front());
live_queue_.erase(live_queue_.begin());
pending_messages_changed_ = true;
}
// Make sure live messages with the same group IDs also get moved to offline messages so that they're
// not sent out of order.
live_queue_.erase(
std::remove_if(
live_queue_.begin(),
live_queue_.end(),
[&](detail::PendingMessageWrapper const& wrapper) {
if (activeGroupsContains(wrapper.policy.group_id)) {
offline_queue_.pushBack(wrapper);
pending_messages_changed_ = true;
return true;
} else {
return false;
}
}
),
live_queue_.end()
);
}
// First send out a live message, if one is available
if (!request_operation_.operationInProgress() && !live_queue_.empty()) {
auto it = live_queue_.begin();
bool delete_message = false;
bool send_message = true;
if (blacklistContains(it->policy.group_id))
delete_message = true;
if (!it->action_id2_0.has_value())
delete_message = true;
if (it->attempts > 0 && it->attempts >= it->policy.message_attempts)
delete_message = true;
if (it->attempts > 0) {
auto const threshold = std::max(it->attempts, 1) * it->policy.retry_interval_seconds;
if (request_operation_.getIdleDurationSeconds() < threshold)
send_message = false;
}
if (delete_message) {
if (it->policy.group_id.has_value()) {
auto sequence = sequence_ids_.find(it->policy.group_id.value());
if (sequence != sequence_ids_.end()) {
if (shouldRemoveSequenceId(*it)) {
sequence_ids_.erase(sequence);
pending_messages_changed_ = true;
} else {
sequence->second++;
}
}
}
live_queue_.erase(it);
} else if (send_message) {
if (sendWithSequenceNumber(remote, *it)) {
it->attempts++;
request_operation_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
std::to_string(it->unique_id)
);
request_object_ = *it;
}
}
}
// Next send out an offline message, if one is available
if (!request_operation_.operationInProgress() && !offline_queue_.empty()) {
auto record = offline_queue_.pollFront();
if (record.has_value()) {
bool delete_message = false;
bool send_message = true;
if (blacklistContains(record->policy.group_id))
delete_message = true;
if (!record->action_id2_0.has_value())
delete_message = true;
if (record->attempts > 0 && record->attempts >= record->policy.message_attempts)
delete_message = true;
if (record->attempts > 0) {
auto const threshold = std::max(record->attempts, 1) * record->policy.retry_interval_seconds;
if (request_operation_.getIdleDurationSeconds() < threshold)
send_message = false;
}
if (delete_message) {
CHARGELAB_LOG_MESSAGE(info) << "popFront: delete_message";
if (record->policy.group_id.has_value()) {
auto sequence = sequence_ids_.find(record->policy.group_id.value());
if (sequence != sequence_ids_.end()) {
if (shouldRemoveSequenceId(record.value())) {
sequence_ids_.erase(sequence);
} else {
sequence->second++;
}
}
}
offline_queue_.popFront();
pending_messages_changed_ = true;
} else if (send_message) {
if (sendWithSequenceNumber(remote, record.value())) {
record->attempts++;
offline_queue_.updateFront(record.value());
pending_messages_changed_ = true;
request_operation_.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
std::to_string(record->unique_id)
);
request_object_ = record.value();
}
}
}
}
}
void onStartTransactionRsp(
const std::string &unique_id,
const ocpp1_6::ResponseMessage<ocpp1_6::StartTransactionRsp> &rsp
) override {
if (request_operation_ == unique_id && std::holds_alternative<ocpp1_6::StartTransactionRsp>(rsp)) {
auto const& message = std::get<ocpp1_6::StartTransactionRsp>(rsp);
if (!request_object_.has_value())
return;
if (!request_object_->policy.group_id.has_value())
return;
transaction_ids_.insert(std::make_pair(request_object_->policy.group_id.value(), message.transactionId));
// Flush the pending message to remove StartTransaction from the live_queue_ and
// update the StopTransaction with transaction Id is the StartTransaction is accepted
must_flush_to_disk_ = true;
}
}
private:
[[nodiscard]] bool activeGroupsContains(std::optional<int64_t> const& group_id) const {
if (!group_id.has_value())
return false;
return active_group_ids_.find(group_id.value()) != active_group_ids_.end();
}
[[nodiscard]] bool blacklistContains(std::optional<int64_t> const& group_id) const {
if (!group_id.has_value())
return false;
return group_blacklist_.find(group_id.value()) != group_blacklist_.end();
}
bool sendWithTransactionId(ocpp1_6::OcppRemote& remote, detail::PendingMessageWrapper const& wrapper) {
if (!wrapper.action_id1_6.has_value())
return false;
std::string payload = wrapper.payload;
if (wrapper.policy.group_id.has_value()) {
auto it = transaction_ids_.find(wrapper.policy.group_id.value());
if (it != transaction_ids_.end())
payload = insert_into_object(payload, "transactionId", it->second);
}
return remote.sendCall(
std::to_string(wrapper.unique_id),
wrapper.action_id1_6.value(),
payload
);
}
bool sendWithSequenceNumber(ocpp2_0::OcppRemote& remote, detail::PendingMessageWrapper const& wrapper) {
if (!wrapper.action_id2_0.has_value())
return false;
std::string payload = wrapper.payload;
if (wrapper.policy.add_message_sequence_number && wrapper.policy.group_id.has_value()) {
auto it = sequence_ids_.find(wrapper.policy.group_id.value());
if (it == sequence_ids_.end()) {
auto first = read_field_from_object<int>(payload, "seqNo");
sequence_ids_[wrapper.policy.group_id.value()] = first.value_or(0);
pending_messages_changed_ = true;
}
payload = insert_into_object(payload, "seqNo", sequence_ids_[wrapper.policy.group_id.value()]);
}
return remote.sendCall(
std::to_string(wrapper.unique_id),
wrapper.action_id2_0.value(),
payload
);
}
void limitOfflineQueueSize() {
auto const initial_total_bytes = offline_queue_.totalBytes();
if (initial_total_bytes < kOfflineSizeLimitBytes)
return;
CHARGELAB_LOG_MESSAGE(info) << "Dropping messages to limit offline queue size: "
<< initial_total_bytes << " > " << kOfflineSizeLimitBytes;
auto const start_ts = system_->steadyClockNow();
std::map<std::pair<int, std::optional<int>>, std::pair<int, int>> stats;
offline_queue_.visit([&] (std::string_view const& text, detail::PendingMessageWrapper const& wrapper) {
auto& value = stats[std::make_pair(wrapper.policy.priority, wrapper.policy.group_id)];
value.first += 1;
value.second += (int)text.size();
});
auto const middle_ts1 = system_->steadyClockNow();
int max_priority = std::numeric_limits<int>::min();
for (auto const& x : stats)
max_priority = std::max(x.first.first, max_priority);
bool delete_safely = false;
for (auto const& x : stats) {
if (!x.first.second.has_value()) {
delete_safely = true;
break;
}
if (x.first.first == max_priority && x.second.first > 2) {
delete_safely = true;
break;
}
}
int delete_count = 0;
for (auto const& x : stats) {
if (!x.first.second.has_value()) {
delete_count++;
} else if (delete_safely) {
delete_count += std::max(x.second.first - 2, 0);
} else {
delete_count += x.second.first;
}
}
std::size_t deleted_records = 0;
std::size_t deleted_decompressed_bytes = 0;
std::uniform_int_distribution<int64_t> distribution(0, delete_count-1);
std::map<int64_t, int> counter;
auto const middle_ts2 = system_->steadyClockNow();
offline_queue_.removeIf([&] (std::string_view const& text, detail::PendingMessageWrapper const& wrapper) {
if (wrapper.policy.priority != max_priority)
return false;
if (wrapper.policy.group_id.has_value() && delete_safely) {
auto index = counter[wrapper.policy.group_id.value()]++;
auto const& stat = stats[std::make_pair(wrapper.policy.priority, wrapper.policy.group_id)];
if (index == 0 || index == stat.first-1)
return false;
}
if (distribution(random_engine_) < kTargetDeleteRecordCount) {
deleted_records++;
deleted_decompressed_bytes += text.size();
CHARGELAB_LOG_MESSAGE(info) << "Dropping offline message: " << wrapper.payload;
pending_messages_changed_ = true;
return true;
} else {
return false;
}
});
auto const end_ts = system_->steadyClockNow();
CHARGELAB_LOG_MESSAGE(info) << "Delete stats:"
<< " processing_ms=" << (end_ts - start_ts)
<< " deleted_records=" << deleted_records
<< " deleted_decompressed_bytes=" << deleted_decompressed_bytes
<< " new_compressed_size=" << offline_queue_.totalBytes();
CHARGELAB_LOG_MESSAGE(info) << "Times: "
<< " block1=" << (middle_ts1 - start_ts)
<< " block2=" << (middle_ts2 - middle_ts1)
<< " block3=" << (end_ts - middle_ts2);
}
void onCallRsp(const std::string &unique_id, const ocpp1_6::ResponseMessage<common::RawJson>& payload) override {
if (request_operation_ == unique_id) {
request_operation_ = kNoOperation;
if (std::holds_alternative<common::RawJson>(payload)) {
if (!request_object_.has_value())
return;
auto onCallRsp = [this](detail::PendingMessageWrapper const &wrapper) {
if (wrapper.action_id1_6 && wrapper.action_id1_6.value() == ocpp1_6::ActionId::kStopTransaction) {
auto const request = read_json_from_string<ocpp1_6::StopTransactionReq>(wrapper.payload);
if (!request.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed read StopTransaction request from: " << wrapper.payload;
return;
}
// remove transaction id from transaction_ids_ if there is one
for (auto it = transaction_ids_.begin(); it != transaction_ids_.end(); ) {
if (it->second == request->transactionId) {
it = transaction_ids_.erase(it);
pending_messages_changed_ = true;
} else {
++it;
}
}
if (request->reason == ocpp1_6::Reason::kHardReset || request->reason == ocpp1_6::Reason::kSoftReset) {
must_flush_to_disk_ = true;
flushToDisk();
}
}
};
if (!live_queue_.empty() && live_queue_.front().unique_id == request_object_->unique_id) {
auto wrapper = live_queue_.front();
live_queue_.erase(live_queue_.begin());
onCallRsp(wrapper);
return;
}
if (offline_queue_.pollFront().has_value() && offline_queue_.pollFront()->unique_id == request_object_->unique_id) {
CHARGELAB_LOG_MESSAGE(info) << "popFront: onCallRsp";
auto record = offline_queue_.pollFront();
offline_queue_.popFront();
onCallRsp(record.value());
pending_messages_changed_ = true;
return;
}
}
}
}
void onCallRsp(const std::string &unique_id, const ocpp2_0::ResponseMessage<common::RawJson>& payload) override {
if (request_operation_ == unique_id) {
request_operation_ = kNoOperation;
if (std::holds_alternative<common::RawJson>(payload)) {
if (!request_object_.has_value())
return;
if (!live_queue_.empty() && live_queue_.front().unique_id == request_object_->unique_id) {
auto it = sequence_ids_.find(live_queue_.front().policy.group_id.value());
if (it != sequence_ids_.end()) {
if (shouldRemoveSequenceId(live_queue_.front())) {
sequence_ids_.erase(it);
pending_messages_changed_ = true;
} else {
it->second++;
}
}
live_queue_.erase(live_queue_.begin());
return;
}
auto const& record = offline_queue_.pollFront();
if (record.has_value() && record->unique_id == request_object_->unique_id) {
CHARGELAB_LOG_MESSAGE(info) << "popFront: onCallRsp";
auto it = sequence_ids_.find(record->policy.group_id.value());
if (it != sequence_ids_.end()) {
if (shouldRemoveSequenceId(record.value())) {
sequence_ids_.erase(it);
} else {
it->second++;
}
}
offline_queue_.popFront();
pending_messages_changed_ = true;
return;
}
}
}
}
private:
void updateCacheAndStats() {
auto const now = system_->steadyClockNow();
if (last_cache_update_.has_value()) {
auto const delta = now - last_cache_update_.value();
if (delta/1000 < kUpdateCacheAndStatsFrequencySeconds)
return;
}
last_cache_update_ = now;
std::size_t total_records = 0;
std::size_t total_decompressed_size = 0;
active_group_ids_.clear();
offline_queue_.visit([&](std::string_view const& text, detail::PendingMessageWrapper const& wrapper) {
if (wrapper.policy.group_id.has_value())
active_group_ids_.insert(wrapper.policy.group_id.value());
total_records++;
total_decompressed_size += text.size();
});
auto const total_compressed_size = offline_queue_.totalBytes();
double compression_ratio = 0;
if (total_decompressed_size > 0)
compression_ratio = (double)total_compressed_size/(double)total_decompressed_size;
CHARGELAB_LOG_MESSAGE(info) << "Offline message stats:"
<< " records=" << total_records
<< " compressed_bytes=" << total_compressed_size
<< " decompressed_bytes=" << total_decompressed_size
<< " compression_ratio=" << compression_ratio;
}
void flushToDisk() {
if (!must_flush_to_disk_ && !pending_messages_changed_) {
return; // No need to flush anything
}
auto const now = system_->steadyClockNow();
auto total_writing_bytes = calculateAllPendingSavedInfoSize();
total_writing_bytes = ((total_writing_bytes + 255)/256)*256;
int flush_interval = ((double)total_writing_bytes / kMaxStorageBlockSize) * kFlushToDiskFrequencyMillis;
CHARGELAB_LOG_MESSAGE(trace) << "flush interval: " << flush_interval << ", offline_queue_ bytes: " << offline_queue_.totalBytes()
<< ", must_flush_to_disk_: " << must_flush_to_disk_ << ", pending_messages_changed_: " << pending_messages_changed_;
if (!must_flush_to_disk_ && last_flush_to_disk_.has_value()) {
// Note: flushing to disk explicitly after the historic backlog has cleared after an offline period or
// after restarting.
auto const delta = now - last_flush_to_disk_.value();
if (delta < flush_interval)
return;
}
last_flush_to_disk_ = now;
must_flush_to_disk_ = false;
// Note: must flush to disk here even if online, otherwise the Ended messages aren't added to the stream and
// a dangling transaction is created if the station looses power. On the other hand, at one extra
// write per hour here we'll end up exhausting one block of flash memory on an ESP32 every ~11 years,
// which may not be worth optimizing out (particularly with a wear-leveling filesystem).
pending_messages_write_count_ ++;
saveToStorage();
settings_->MessageFlushCounter.setValue(pending_messages_write_count_);
pending_messages_changed_ = false;
}
void saveToStorage() {
// TODO: This could likely be optimized to avoid writing an empty file repeatedly while there's nothing in
// the buffer (and nothing added via the registered suppliers).
CHARGELAB_LOG_MESSAGE(info) << "Saving pending messages to storage, write count: " << pending_messages_write_count_;
storage_->write([&](auto file) {
offline_queue_.write([&](void* data, std::size_t size) {
auto length = (int)size;
std::fwrite(&length, sizeof(length), 1, file);
std::fwrite(data, size, 1, file);
});
int terminator = -1;
std::fwrite(&terminator, sizeof(terminator), 1, file);
file::json_write_object_to_file(file, active_group_ids_);
file::json_write_object_to_file(file, group_blacklist_);
file::json_write_object_to_file(file, transaction_ids_);
file::json_write_object_to_file(file, sequence_ids_);
file::json_write_object_to_file(file, pending_messages_write_count_);
for (auto const& x : live_queue_) {
file::json_write_object_to_file(file, x);
}
for (auto const& supplier : saved_message_suppliers_) {
if (supplier == nullptr)
continue;
(*supplier)([&](auto const& record) {
file::json_write_object_to_file(file, record);
});
}
return true;
});
}
template <typename T>
static void readFromFile(FILE* file, T& value) {
auto parsed = file::json_read_object_from_file<T>(file);
if (!parsed.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed reading value from file";
return;
}
value = parsed.value();
}
void loadFromStorage() {
CHARGELAB_LOG_MESSAGE(info) << "Reading pending messages from storage";
storage_->read([&](auto file) {
offline_queue_.read([&]() {
std::optional<std::vector<uint8_t>> result = std::nullopt;
int length;
if (std::fread(&length, sizeof(length), 1, file) != 1)
return result;
if (length == -1)
return result;
if (length < 0 || length > kMaxStorageBlockSize) {
CHARGELAB_LOG_MESSAGE(error) << "Bad block length encountered reading historic data: " << length;
return result;
}
std::vector<uint8_t> buffer;
buffer.resize(length);
if (std::fread(buffer.data(), sizeof(uint8_t), buffer.size(), file) != buffer.size()) {
CHARGELAB_LOG_MESSAGE(error) << "Failed reading block data";
return result;
}
result = std::move(buffer);
return result;
});
{
readFromFile(file, active_group_ids_);
readFromFile(file, group_blacklist_);
readFromFile(file, transaction_ids_);
readFromFile(file, sequence_ids_);
readFromFile(file, pending_messages_write_count_);
}
// Loading previous live queue into offline queue
while (true) {
auto record = file::json_read_object_from_file<detail::PendingMessageWrapper>(file);
if (!record.has_value())
break;
offline_queue_.pushBack(record.value());
}
return true;
});
}
int calculateAllPendingSavedInfoSize() {
int total_bytes = 0;
total_bytes += offline_queue_.totalBytes();
total_bytes += sizeof(int); // integer terminator size
total_bytes += calculate_size(active_group_ids_) + 1; // Count the newline '\n' character
total_bytes += calculate_size(group_blacklist_) + 1; // Count the newline '\n' character
total_bytes += calculate_size(transaction_ids_) + 1; // Count the newline '\n' character
total_bytes += calculate_size(sequence_ids_) + 1; // Count the newline '\n' character
total_bytes += calculate_size(pending_messages_write_count_) + 1; // CCount the newline '\n' character
for (auto const& x : live_queue_) {
if (x.action_id1_6.has_value() && x.action_id1_6.value() == ocpp1_6::ActionId::kStartTransaction)
continue;
total_bytes += calculate_size(x) + 1; // Count the newline '\n' character
}
for (auto const& supplier : saved_message_suppliers_) {
if (supplier == nullptr)
continue;
(*supplier)([&](auto const& record) {
total_bytes += calculate_size(record) + 1; // Count the newline '\n' character
});
}
return total_bytes;
}
bool shouldRemoveSequenceId(detail::PendingMessageWrapper const& wrapper) {
if (!wrapper.action_id2_0 || wrapper.action_id2_0.value() != chargelab::ocpp2_0::ActionId::kTransactionEvent)
return false;
auto const request = read_json_from_string<ocpp2_0::TransactionEventRequest>(wrapper.payload);
if (!request.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Failed read TransactionEvent request from: " << wrapper.payload;
return false;
}
return request->eventType == chargelab::ocpp2_0::TransactionEventEnumType::kEnded;
}
private:
std::shared_ptr<Settings> settings_;
std::shared_ptr<SystemInterface> system_;
std::shared_ptr<StorageInterface> storage_;
std::default_random_engine random_engine_;
std::vector<detail::PendingMessageWrapper> live_queue_;
CompressedQueueCustom<detail::PendingMessageWrapper, detail::PendingMessageSerializer> offline_queue_;
OperationHolder<std::string> request_operation_;
std::optional<detail::PendingMessageWrapper> request_object_;
std::vector<std::shared_ptr<saved_message_supplier>> saved_message_suppliers_ {};
bool must_flush_to_disk_ = false;
int64_t request_id_;
std::unordered_set<int64_t> active_group_ids_;
std::unordered_set<int64_t> group_blacklist_;
std::unordered_map<int64_t, int> transaction_ids_;
std::unordered_map<int64_t, int> sequence_ids_;
std::optional<SteadyPointMillis> last_cache_update_ = std::nullopt;
std::optional<SteadyPointMillis> last_flush_to_disk_ = std::nullopt;
bool pending_messages_changed_ = false;
int32_t pending_messages_write_count_ = 0;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_PENDING_MESSAGES_MODULE_H

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,207 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_RESET_MODULE_H
#define CHARGELAB_OPEN_FIRMWARE_RESET_MODULE_H
#include "openocpp/module/common_templates.h"
#include "openocpp/module/connector_status_module.h"
#include "openocpp/interface/component/system_interface.h"
#include "openocpp/common/settings.h"
namespace chargelab {
class ResetModule : public ServiceStatefulGeneral {
private:
static constexpr int kMinimumResetDelayMillis = 1*1000; // 3 seconds
public:
ResetModule(
std::shared_ptr<Settings> settings,
std::shared_ptr<SystemInterface> system,
std::shared_ptr<ConnectorStatusModule> connector_status_module
)
: settings_(std::move(settings)),
system_(std::move(system)),
connector_status_module_(std::move(connector_status_module))
{
}
~ResetModule() override {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting ResetModule";
}
public:
[[nodiscard]] bool pendingReset() const {
return hard_reset_threshold_.has_value();
}
/**
* Resets the charger when it's no longer in use with the provided reason code. This method is thread safe.
*
* @param reason
*/
void resetOnIdle(ocpp2_0::BootReasonEnumType reason) {
if (hard_reset_requested_) {
CHARGELAB_LOG_MESSAGE(info) << "Ignoring soft reset request - hard reset already scheduled";
return;
}
reset_reason_ = reason;
soft_reset_requested_ = true;
}
/**
* Resets the charger immediately with the provided reason code. This method is thread safe.
*
* @param reason
*/
void resetImmediately(ocpp2_0::BootReasonEnumType reason) {
reset_reason_ = reason;
hard_reset_requested_ = true;
}
private:
void runStep(ocpp1_6::OcppRemote&) override {
runStepCommon();
}
void runStep(ocpp2_0::OcppRemote&) override {
runStepCommon();
}
std::optional<ocpp2_0::ResponseToRequest <ocpp2_0::ResetResponse>>
onResetReq(const ocpp2_0::ResetRequest &req) override {
// B11.FR.09
if (req.evseId.has_value()) {
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kRejected};
}
switch (req.type) {
default:
CHARGELAB_LOG_MESSAGE(warning) << "Invalid reset request type - treating as OnIdle";
[[fallthrough]];
case ocpp2_0::ResetEnumType::kOnIdle:
if (hard_reset_requested_) {
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - hard reset already scheduled";
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kRejected};
}
if (soft_reset_requested_) {
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - soft reset already scheduled";
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kRejected};
}
soft_reset_requested_ = true;
// B12.FR.01
if (connector_status_module_->isChargingEnabled()) {
reset_reason_ = ocpp2_0::BootReasonEnumType::kScheduledReset;
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kScheduled};
}
break;
case ocpp2_0::ResetEnumType::kImmediate:
if (hard_reset_requested_) {
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - hard reset already scheduled";
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kRejected};
}
hard_reset_requested_ = true;
break;
}
reset_reason_ = ocpp2_0::BootReasonEnumType::kRemoteReset;
return ocpp2_0::ResetResponse {ocpp2_0::ResetStatusEnumType::kAccepted};
}
std::optional<ocpp1_6::ResponseToRequest <ocpp1_6::ResetRsp>>
onResetReq(const ocpp1_6::ResetReq &req) override {
switch (req.type) {
case ocpp1_6::ResetType::kValueNotFoundInEnum:
CHARGELAB_LOG_MESSAGE(warning) << "Invalid reset request type - treating as Soft";
[[fallthrough]];
case ocpp1_6::ResetType::kSoft:
if (hard_reset_requested_) {
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - hard reset already scheduled";
return ocpp1_6::ResetRsp{ocpp1_6::ResetStatus::kRejected};
}
if (soft_reset_requested_) {
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - soft reset already scheduled";
return ocpp1_6::ResetRsp{ocpp1_6::ResetStatus::kRejected};
}
soft_reset_requested_ = true;
break;
case ocpp1_6::ResetType::kHard:
if (hard_reset_requested_) {
CHARGELAB_LOG_MESSAGE(warning) << "Rejecting soft reset request - hard reset already scheduled";
return ocpp1_6::ResetRsp{ocpp1_6::ResetStatus::kRejected};
}
hard_reset_requested_ = true;
break;
}
return ocpp1_6::ResetRsp {ocpp1_6::ResetStatus::kAccepted};
}
private:
void runStepCommon() {
ocpp2_0::BootReasonEnumType const reason = reset_reason_;
auto const now = system_->steadyClockNow();
bool running_transactions = connector_status_module_->isChargingEnabled();
if (hard_reset_requested_) {
if (!hard_reset_threshold_.has_value()) {
hard_reset_threshold_ = static_cast<SteadyPointMillis> (now + kMinimumResetDelayMillis);
connector_status_module_->setPendingReset(true, true);
CHARGELAB_LOG_MESSAGE(info) << "Hard reset requested - restarting in: " << kMinimumResetDelayMillis << " millis";
}
auto const delta = now - hard_reset_threshold_.value();
if (delta >= 0) {
CHARGELAB_LOG_MESSAGE(info) << "Resetting system";
hard_reset_threshold_ = std::nullopt;
settings_->CustomBootReason.setValue(reason.to_string());
settings_->saveIfModified();
system_->resetHard();
}
} else if (soft_reset_requested_) {
if (!running_transactions) {
if (!soft_reset_threshold_.has_value()) {
soft_reset_threshold_ = static_cast<SteadyPointMillis> (now + kMinimumResetDelayMillis);
connector_status_module_->setPendingReset(true, false);
CHARGELAB_LOG_MESSAGE(info) << "Soft reset requested - restarting in: " << kMinimumResetDelayMillis << " millis";
}
auto const delta = now - soft_reset_threshold_.value();
if (delta >= 0) {
CHARGELAB_LOG_MESSAGE(info) << "Resetting system";
soft_reset_threshold_ = std::nullopt;
settings_->CustomBootReason.setValue(reason.to_string());
settings_->saveIfModified();
system_->resetSoft();
}
} else {
soft_reset_threshold_ = std::nullopt;
// Tell the transaction module to stop transactions indirectly
connector_status_module_->setPendingReset(true, false);
CHARGELAB_LOG_MESSAGE(trace) << "Reset pending - waiting for running transactions to complete";
}
}
}
private:
std::shared_ptr<Settings> settings_;
std::shared_ptr<SystemInterface> system_;
std::shared_ptr<ConnectorStatusModule> connector_status_module_;
std::optional<SteadyPointMillis> hard_reset_threshold_ = std::nullopt;
std::optional<SteadyPointMillis> soft_reset_threshold_ = std::nullopt;
std::atomic<ocpp2_0::BootReasonEnumType> reset_reason_ = {ocpp2_0::BootReasonEnumType::kUnknown};
std::atomic<bool> soft_reset_requested_ = false;
std::atomic<bool> hard_reset_requested_ = false;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_RESET_MODULE_H

View File

@@ -0,0 +1,766 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_TRANSACTION_MODULE1_6_H
#define CHARGELAB_OPEN_FIRMWARE_TRANSACTION_MODULE1_6_H
#include "openocpp/interface/platform_interface.h"
#include "openocpp/interface/station_interface.h"
#include "openocpp/module/common_templates.h"
#include "openocpp/module/pending_messages_module.h"
#include "openocpp/module/boot_notification_module.h"
#include "openocpp/module/power_management_module1_6.h"
#include "openocpp/module/connector_status_module.h"
#include "openocpp/common/operation_holder.h"
#include "openocpp/common/logging.h"
#include <utility>
#include <random>
#include <unordered_set>
#include <deque>
#include <vector>
#include <memory>
#include <string>
#include <optional>
namespace chargelab {
namespace transaction_module1_6 {
struct TransactionContainer {
int connector_id;
uint64_t group_id;
std::string id_tag;
std::string start_transaction_request_id;
std::optional<int> transaction_id = std::nullopt;
std::optional<SteadyPointMillis> last_reading_timestamp = std::nullopt;
std::optional<SteadyPointMillis> last_clock_aligned_reading_timestamp = std::nullopt;
bool connector_unplugged_after_transaction_ended = false;
};
struct PendingStartRequest {
PendingStartRequest(std::shared_ptr<PlatformInterface> const& platform, int connector_id, std::string tag_id)
: created_timestamp {platform->steadyClockNow()},
pending_authorize_req {platform},
connector_id {connector_id},
tag_id {tag_id},
charging_profile {},
authorize_finished {false}
{
}
PendingStartRequest(std::shared_ptr<PlatformInterface> const& platform, ocpp1_6::RemoteStartTransactionReq const& req)
: created_timestamp {platform->steadyClockNow()},
pending_authorize_req {platform},
connector_id {req.connectorId.value_or(0)},
tag_id {req.idTag.value()},
charging_profile {req.chargingProfile},
authorize_finished {!platform->getSettings()->AuthorizeRemoteTxRequests.getValue()}
{
}
SteadyPointMillis created_timestamp;
OperationHolder<std::string> pending_authorize_req;
int connector_id;
std::string tag_id;
std::optional<ocpp1_6::ChargingProfile> charging_profile;
bool authorize_finished;
};
}
class TransactionModule1_6 : public ServiceStateful1_6 {
private:
static constexpr int kPriorityStartTransaction = 0;
static constexpr int kPriorityStopTransaction = 0;
static constexpr int kPriorityMeterValue = 5;
static const int kMaxReadingsBytes = 10*1024;
static const int kGeneralOperationMaxFailures = 4;
static constexpr int kOfflineTransactions = 3;
static constexpr char const* kDefaultTagId = "missing-tag";
static constexpr int kTransactionMessageRetryIntervalSeconds = 10;
static const int kSecondsPerDay = 86400; // 24*60*60
public:
TransactionModule1_6(
std::shared_ptr<PlatformInterface> const& platform,
std::shared_ptr<BootNotificationModule> boot_notification_module,
std::shared_ptr<PowerManagementModule1_6> power_management_module,
std::shared_ptr<PendingMessagesModule> pending_messages_module,
std::shared_ptr<ConnectorStatusModule> connector_status_module,
std::shared_ptr<StationInterface> station
) : platform_(platform),
boot_notification_module_(std::move(boot_notification_module)),
power_management_module_(std::move(power_management_module)),
pending_messages_module_(std::move(pending_messages_module)),
connector_status_module_(std::move(connector_status_module)),
station_(std::move(station))
{
settings_ = platform_->getSettings();
std::default_random_engine random_engine {std::random_device{}()};
std::uniform_int_distribution<int64_t> distribution(
std::numeric_limits<int64_t>::min(),
std::numeric_limits<int64_t>::max()
);
unique_index_ = distribution(random_engine);
stop_transaction_supplier_ = std::make_shared<PendingMessagesModule::saved_message_supplier> (
[&](std::function<void(detail::PendingMessageWrapper const&)> const& processor) {
for (auto& entry : active_transactions_) {
if (!entry.second.has_value())
continue;
auto const request = generateStopRequest(entry.first, ocpp1_6::Reason::kPowerLoss);
if (request.has_value())
processor(request.value());
}
}
);
pending_messages_module_->registerOnSaveMessageSupplier(stop_transaction_supplier_);
}
~TransactionModule1_6() override {
CHARGELAB_LOG_MESSAGE(debug) << "Deleting TransactionModule1_6";
}
private:
void runStep(ocpp1_6::OcppRemote& remote) override {
// If a reset was requested stop all active transactions
if (connector_status_module_->getPendingReset()) {
for (auto& x : active_transactions_) {
if (!x.second.has_value())
continue;
CHARGELAB_LOG_MESSAGE(info) << "Stopping transaction (pending reset) - transaction ID: " << x.second->transaction_id;
stopTransaction(x.first, connector_status_module_->getResetReasonHard() ? ocpp1_6::Reason::kHardReset : ocpp1_6::Reason::kSoftReset);
}
return; // We should not handle any other events when reset is pending.
}
// Process an RFID tap if there was a state change
auto id_token = station_->readToken1_6();
if (id_token != last_rfid_tag_id_) {
last_rfid_tag_id_ = id_token;
if (id_token.has_value())
processRfidTap(id_token->value());
}
// Process plugged in state changes (PlugAndCharge and stopping transaction on disconnect)
for (auto const& entry: station_->getConnectorMetadata()) {
// Only take action if the connector state has changed. If, for example, a pending RemoteStarTransaction
// request timed out do not attempt an autostart transaction until the cable is unplugged and plugged
// back in.
auto const connector_status = station_->pollConnectorStatus(entry.first);
if (!connector_status.has_value())
continue;
auto const id = entry.second.connector_id1_6;
if (connector_status->vehicle_connected == last_plugged_in_state_[id])
continue;
last_plugged_in_state_[id] = connector_status->vehicle_connected;
processVehicleConnectedStateChanged(id, connector_status->vehicle_connected);
}
// First process any pending start requests for a specific connector, then process any for the station
for (auto& entry : pending_start_req_) {
if (entry.first > 0)
processPendingStartRequest(remote, entry.second);
}
for (auto& entry : pending_start_req_) {
if (entry.first <= 0)
processPendingStartRequest(remote, entry.second);
}
std::vector<std::optional<ocpp2_0::EVSEType>> pending;
for (auto& entry : pending_start_req_) {
if (entry.second.has_value()) {
if (entry.first > 0) {
pending.emplace_back(ocpp2_0::EVSEType {entry.first, 1});
} else if (entry.first == 0 && entry.second != std::nullopt) {
pending.emplace_back(std::nullopt);
}
}
}
connector_status_module_->setPendingStartRequests(pending);
// Process transactions
for (auto& entry : active_transactions_) {
if (!entry.second.has_value())
continue;
auto const evse = station_->lookupConnectorId1_6(entry.first);
if (!evse.has_value()) {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state: connector did not exist for running transaction - dropping: " << entry.second->transaction_id;
entry.second = std::nullopt;
continue;
}
bool first_reading = !entry.second->last_reading_timestamp.has_value();
if (shouldTakeReading(entry.second->last_reading_timestamp, settings_->MeterValueSampleInterval.getValue()))
addReading(entry.second.value(), first_reading ? ocpp1_6::ReadingContext::kTransactionBegin : ocpp1_6::ReadingContext::kSamplePeriodic);
first_reading = !entry.second->last_clock_aligned_reading_timestamp.has_value();
if (shouldTakeReading(entry.second->last_clock_aligned_reading_timestamp, settings_->ClockAlignedDataInterval.getValue()))
addReading(entry.second.value(), first_reading ? ocpp1_6::ReadingContext::kTransactionBegin : ocpp1_6::ReadingContext::kSampleClock);
}
#if 0 // For OCTT _012
for (auto it = pending_stop_transaction_times_.begin(); it != pending_stop_transaction_times_.end(); ) {
if (platform_->steadyClockNow() - it->second > 5000 ) {
stopTransaction(it->first, ocpp1_6::Reason::kRemote);
it = pending_stop_transaction_times_.erase(it); // erase returns the next iterator
} else {
++it;
}
}
#endif
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::RemoteStartTransactionRsp>> onRemoteStartTransactionReq(
const ocpp1_6::RemoteStartTransactionReq &req
) override {
int connector_id = 0;
if (req.connectorId.has_value()) {
connector_id = req.connectorId.value();
bool found = false;
for (auto const& entry : station_->getConnectorMetadata()) {
if (entry.second.connector_id1_6 == connector_id) {
found = true;
break;
}
}
if (!found) {
return ocpp1_6::RemoteStartTransactionRsp{ocpp1_6::RemoteStartStopStatus::kRejected};
}
}
if (req.chargingProfile.has_value()) {
// Note: as per 5.16.2. the profile purpose must be TxProfile
if (req.chargingProfile->chargingProfilePurpose != ocpp1_6::ChargingProfilePurposeType::kTxProfile)
return ocpp1_6::RemoteStartTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
// Note: as per 5.16.2. the transaction ID must not be set
if (req.chargingProfile->transactionId.has_value())
return ocpp1_6::RemoteStartTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
}
bool available_connector = true;
// check if there is an active transaction already
for (auto& x : active_transactions_) {
if (req.connectorId && req.connectorId == x.first && x.second) {
available_connector = false;
break;
}
}
if (!available_connector) {
return ocpp1_6::RemoteStartTransactionRsp{ocpp1_6::RemoteStartStopStatus::kRejected};
}
// Note: adopting the convention that a new RemoteStartTransaction request will replace an existing pending
// one, rather than be blocked by it.
pending_start_req_[connector_id] = transaction_module1_6::PendingStartRequest {platform_, req};
return ocpp1_6::RemoteStartTransactionRsp {ocpp1_6::RemoteStartStopStatus::kAccepted};
}
void onAuthorizeRsp(
const std::string &unique_id,
const std::variant<ocpp1_6::AuthorizeRsp, ocpp1_6::CallError> &rsp
) override {
for (auto& entry : pending_start_req_) {
if (!entry.second.has_value())
continue;
if (entry.second->pending_authorize_req != unique_id)
continue;
if (std::holds_alternative<ocpp1_6::AuthorizeRsp>(rsp)) {
// Note: only clearing the operation if a valid response was received, otherwise treating as absent
// and retrying after configured timeout.
entry.second->pending_authorize_req = kNoOperation;
auto const& value = std::get<ocpp1_6::AuthorizeRsp>(rsp);
CHARGELAB_LOG_MESSAGE(info) << "Received authorize response: " << value;
switch (value.idTagInfo.status) {
case ocpp1_6::AuthorizationStatus::kAccepted:
entry.second->authorize_finished = true;
CHARGELAB_LOG_MESSAGE(info) << "Set authorize_finished to true: " << entry.second->authorize_finished;
break;
case ocpp1_6::AuthorizationStatus::kValueNotFoundInEnum:
case ocpp1_6::AuthorizationStatus::kBlocked:
case ocpp1_6::AuthorizationStatus::kExpired:
case ocpp1_6::AuthorizationStatus::kInvalid:
case ocpp1_6::AuthorizationStatus::kConcurrentTx:
entry.second = std::nullopt;
break;
}
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to Authorize request: " << std::get<ocpp1_6::CallError> (rsp);
}
}
}
void onStartTransactionRsp(
const std::string &unique_id,
const std::variant<ocpp1_6::StartTransactionRsp, ocpp1_6::CallError> &rsp
) override {
// Note: this is processed here in addition to pending_messages to allow this module to take action if the
// back-end does not accept the provided idTag.
for (auto& entry : active_transactions_) {
if (!entry.second.has_value())
continue;
if (unique_id != entry.second->start_transaction_request_id)
continue;
if (std::holds_alternative<ocpp1_6::StartTransactionRsp>(rsp)) {
auto const &value = std::get<ocpp1_6::StartTransactionRsp>(rsp);
entry.second->transaction_id = value.transactionId;
switch (value.idTagInfo.status) {
case ocpp1_6::AuthorizationStatus::kAccepted:
power_management_module_->onActiveTransactionIdAssigned(entry.first, value.transactionId);
break;
case ocpp1_6::AuthorizationStatus::kValueNotFoundInEnum:
case ocpp1_6::AuthorizationStatus::kBlocked:
case ocpp1_6::AuthorizationStatus::kExpired:
case ocpp1_6::AuthorizationStatus::kInvalid:
case ocpp1_6::AuthorizationStatus::kConcurrentTx:
// TODO: Support MaxEnergyOnInvalidId?
stopTransaction(entry.first, ocpp1_6::Reason::kDeAuthorized);
break;
}
} else {
CHARGELAB_LOG_MESSAGE(warning) << "Error response to StartTransaction request: " << std::get<ocpp1_6::CallError>(rsp);
}
}
}
std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::RemoteStopTransactionRsp>> onRemoteStopTransactionReq(
const ocpp1_6::RemoteStopTransactionReq &req
) override {
for (auto& entry : active_transactions_) {
if (!entry.second.has_value())
continue;
if (!entry.second->transaction_id.has_value())
continue;
if (entry.second->transaction_id.value() != req.transactionId)
continue;
#if 0 // For OCTT _012
pending_stop_transaction_times_[entry.first] = platform_->steadyClockNow();
#else
stopTransaction(entry.first, ocpp1_6::Reason::kRemote);
#endif
return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kAccepted};
}
// TODO: unrecognized transaction module behaves badly with this one. Maybe fold that implementation into
// this one?
//return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
if (!settings_->StopTransactionWithDifferentId.getValue())
return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
if (!force_stop_transaction_.has_value()) {
force_stop_transaction_ = req.transactionId;
return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kAccepted};
} else {
return ocpp1_6::RemoteStopTransactionRsp {ocpp1_6::RemoteStartStopStatus::kRejected};
}
return std::nullopt;
}
std::optional<ocpp1_6::ResponseToRequest <ocpp1_6::TriggerMessageRsp>>
onTriggerMessageReq(const ocpp1_6::TriggerMessageReq &req) override {
if (req.requestedMessage != ocpp1_6::MessageTrigger::kMeterValues)
return std::nullopt;
for (auto& entry : active_transactions_) {
if (!entry.second.has_value())
continue;
if (req.connectorId.has_value() && req.connectorId.value() != entry.first)
continue;
addReading(entry.second.value(), ocpp1_6::ReadingContext::kTrigger);
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kAccepted};
}
return ocpp1_6::TriggerMessageRsp {ocpp1_6::TriggerMessageStatus::kRejected};
}
private:
void startTransaction(
int connector_id,
std::string const& id_tag,
std::optional<ocpp1_6::ChargingProfile> const& charging_profile = std::nullopt
) {
auto const group_id = unique_index_++;
auto const evse = station_->lookupConnectorId1_6(connector_id);
if (!evse.has_value()) {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - connector ID does not exist: " << connector_id;
return;
}
auto const status = station_->pollConnectorStatus(evse.value());
if (!status.has_value()) {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed fetching connector status for EVSE: " << evse;
return;
}
auto start_transaction_request_id = pending_messages_module_->sendRequest1_6(
ocpp1_6::StartTransactionReq {
connector_id,
id_tag,
(int)status->meter_watt_hours,
std::nullopt,
platform_->systemClockNow()
},
PendingMessagePolicy {
PendingMessageType::kTransactionEvent,
group_id,
settings_->TransactionMessageAttempts.getValue(),
settings_->TransactionMessageRetryInterval.getValue(),
kPriorityStartTransaction,
false,
false,
true
}
);
// Setting enabling charging earlier for OCTT test case _010
station_->setChargingEnabled(evse.value(), true);
active_transactions_[connector_id] = transaction_module1_6::TransactionContainer {
connector_id,
group_id,
id_tag,
start_transaction_request_id
};
power_management_module_->onActiveTransactionStarted(
connector_id,
charging_profile
);
}
std::optional<detail::PendingMessageWrapper> generateStopRequest(int connector_id, ocpp1_6::Reason const& reason) {
auto const& transaction = active_transactions_[connector_id];
if (!transaction.has_value())
return std::nullopt;
auto const evse = station_->lookupConnectorId1_6(connector_id);
if (!evse.has_value()) {
// Note: the transaction is not stopped here intentionally; we'd have to provide a bad watt hour reading
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - connector ID does not exist: " << connector_id;
return std::nullopt;
}
auto const status = station_->pollConnectorStatus(evse.value());
if (!status.has_value()) {
// Note: the transaction is not stopped here intentionally; we'd have to provide a bad watt hour reading
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed fetching connector status for EVSE: " << evse;
return std::nullopt;
}
return pending_messages_module_->generateRequest1_6(
ocpp1_6::StopTransactionReq {
transaction->id_tag,
(int)status->meter_watt_hours,
platform_->systemClockNow(),
transaction->transaction_id.value_or(0),
reason,
{} // TODO: Stop transaction data
},
PendingMessagePolicy {
PendingMessageType::kTransactionEvent,
transaction->group_id,
settings_->TransactionMessageAttempts.getValue(),
settings_->TransactionMessageRetryInterval.getValue(),
kPriorityStopTransaction,
false,
false
}
);
power_management_module_->onActiveTransactionFinished(connector_id);
}
void stopTransaction(int connector_id, ocpp1_6::Reason const& reason) {
std::optional<transaction_module1_6::TransactionContainer> transaction = std::nullopt;
std::swap(transaction, active_transactions_[connector_id]);
if (!transaction.has_value())
return;
auto const evse = station_->lookupConnectorId1_6(connector_id);
if (!evse.has_value()) {
// Note: the transaction is not stopped here intentionally; we'd have to provide a bad watt hour reading
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - connector ID does not exist: " << connector_id;
return;
}
auto const status = station_->pollConnectorStatus(evse.value());
station_->setChargingEnabled(evse.value(), false);
if (!status.has_value()) {
// Note: the transaction is not stopped here intentionally; we'd have to provide a bad watt hour reading
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed fetching connector status for EVSE: " << evse;
return;
}
pending_messages_module_->sendRequest1_6(
ocpp1_6::StopTransactionReq {
transaction->id_tag,
(int)status->meter_watt_hours,
platform_->systemClockNow(),
transaction->transaction_id.value_or(0),
reason,
{} // TODO: Stop transaction data
},
PendingMessagePolicy {
PendingMessageType::kTransactionEvent,
transaction->group_id,
settings_->TransactionMessageAttempts.getValue(),
settings_->TransactionMessageRetryInterval.getValue(),
kPriorityStopTransaction,
false,
false,
true
}
);
power_management_module_->onActiveTransactionFinished(connector_id);
}
void processRfidTap(std::string const& id_tag) {
// Check if a running transaction already uses this tag; if so it should be interpreted as a stop
// request.
for (auto& x : active_transactions_) {
if (!x.second.has_value())
continue;
if (string::EqualsIgnoreCaseAscii(x.second->id_tag, id_tag)) {
stopTransaction(x.first, ocpp1_6::Reason::kLocal);
return;
}
}
// Need to check if there's availabe connector for another transaction, otherwise ignore the rfid tap event.
// This is for the OCTT 1.6 test case _068 where a different RFID should not trigger the sending of the
// Authorize request if there is an active transaction.
bool found_available_connector = false;
for (auto const& entry: station_->getConnectorMetadata()) {
if (entry.first.id == 0) continue;
auto const connector_status = station_->pollConnectorStatus(entry.first);
if (!connector_status.has_value())
continue;
if (connector_status->connector_available) {
found_available_connector = true;
break;
}
}
if (!found_available_connector)
return;
// Add the PlugAndCharge entry
pending_start_req_[0] = transaction_module1_6::PendingStartRequest{platform_, 0, id_tag};
}
void processVehicleConnectedStateChanged(int connector_id, bool connected) {
if (active_transactions_[connector_id].has_value()) {
if (!connected) {
stopTransaction(connector_id, ocpp1_6::Reason::kEVDisconnected);
}
// Note: PlugAndCharge should only trigger if there's no activate transaction.
return;
}
auto plug_and_charge_id = settings_->PlugAndChargeId.getValue();
if (connected && !plug_and_charge_id.empty()) {
// If there's another potentially applicable start operation pending (remote start request, RFID tap,
// etc) it should take precedence and autostart should be ignored.
if (pending_start_req_[0].has_value() || pending_start_req_[connector_id].has_value())
return;
// Add the PlugAndCharge entry
pending_start_req_[connector_id] = transaction_module1_6::PendingStartRequest {
platform_,
connector_id,
plug_and_charge_id
};
}
}
void processPendingStartRequest(ocpp1_6::OcppRemote& remote, std::optional<transaction_module1_6::PendingStartRequest>& pending) {
if (!pending.has_value())
return;
// If the request has expired remove it
auto const elapsed = platform_->steadyClockNow() - pending->created_timestamp;
if (elapsed >= settings_->ConnectionTimeOut.getValue()*1000) {
pending = std::nullopt;
return;
}
// Attempt to authorize the request if necessary
if (!pending->authorize_finished) {
if (treatAsConnected()) {
if (!pending->pending_authorize_req.operationInProgress()) {
pending->pending_authorize_req.setWithTimeout(
settings_->DefaultMessageTimeout.getValue(),
remote.sendAuthorizeReq(ocpp1_6::AuthorizeReq {pending->tag_id})
);
}
return;
} else if (!settings_->AllowOfflineTxForUnknownId.getValue()) {
return;
}
}
// Attempt to start a transaction
for (auto const& entry : station_->getConnectorMetadata()) {
auto const id = entry.second.connector_id1_6;
if (pending->connector_id != 0 && pending->connector_id != id)
continue;
auto const status = station_->pollConnectorStatus(entry.first);
if (!status.has_value() || !status->vehicle_connected)
continue;
if (active_transactions_[id].has_value())
continue;
// Start new transaction
startTransaction(id, pending->tag_id, pending->charging_profile);
pending = std::nullopt;
}
}
void addReading(
transaction_module1_6::TransactionContainer& entry,
ocpp1_6::ReadingContext const& context
) {
auto const evse = station_->lookupConnectorId1_6(entry.connector_id);
if (!evse.has_value()) {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - connector ID does not exist: " << entry.connector_id;
return;
}
auto enabled = parseMeasurandsString(settings_->MeterValuesSampledData.getValue());
auto sampled_values = station_->pollMeterValues1_6(evse.value());
sampled_values.erase(
std::remove_if(
sampled_values.begin(),
sampled_values.end(),
[&](ocpp1_6::SampledValue const& value) {
auto measurand = value.measurand.value_or(ocpp1_6::Measurand::kEnergyActiveImportRegister);
return enabled.find(measurand) == enabled.end();
}
),
sampled_values.end()
);
// Update the context
for (auto& value : sampled_values)
value.context = context;
// TODO: It may be helpful to batch these while offline so that fewer requests are sent when the station
// reconnects instead of persisting individual samples.
pending_messages_module_->sendRequest1_6(
ocpp1_6::MeterValuesReq {
entry.connector_id,
entry.transaction_id.value_or(0),
{
ocpp1_6::MeterValue {
platform_->systemClockNow(),
std::move(sampled_values)
}
}
},
PendingMessagePolicy {
PendingMessageType::kTransactionEvent,
entry.group_id,
settings_->TransactionMessageAttempts.getValue(),
settings_->TransactionMessageRetryInterval.getValue(),
kPriorityMeterValue,
false,
false
}
);
}
bool shouldTakeReading(std::optional<SteadyPointMillis>& last, int interval_seconds) {
// Treat a configured interval of 0 (or negative) as disabled
if (interval_seconds <= 0)
return false;
auto const now = platform_->steadyClockNow();
if (last.has_value()) {
auto const delta = now - last.value();
if (delta < interval_seconds*1000)
return false;
}
last = now;
return true;
}
bool treatAsConnected() {
if (!boot_notification_module_->registrationComplete())
return false;
auto const websocket = platform_->ocppConnection();
if (websocket == nullptr)
return false;
return websocket->isConnected();
}
static std::unordered_set<ocpp1_6::Measurand::Value> parseMeasurandsString(std::string const& measurands) {
// comma separated list, e.g. "Current.Import,Energy.Active.Import.Register"
std::unordered_set<ocpp1_6::Measurand::Value> ret;
string::SplitVisitor(measurands, ",", [&](std::string const& value) {
auto value_as_enum = ocpp1_6::Measurand::from_string(value);
if (value_as_enum == ocpp1_6::Measurand::kValueNotFoundInEnum) {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected measurand: " << value;
} else {
ret.insert(value_as_enum);
}
});
return ret;
}
private:
std::shared_ptr<PlatformInterface> platform_;
std::shared_ptr<BootNotificationModule> boot_notification_module_;
std::shared_ptr<PowerManagementModule1_6> power_management_module_;
std::shared_ptr<PendingMessagesModule> pending_messages_module_;
std::shared_ptr<ConnectorStatusModule> connector_status_module_;
std::shared_ptr<StationInterface> station_;
std::shared_ptr<PendingMessagesModule::saved_message_supplier> stop_transaction_supplier_;
std::shared_ptr<Settings> settings_;
std::atomic<uint64_t> unique_index_;
// key: connector ID
std::unordered_map<int, std::optional<transaction_module1_6::PendingStartRequest>> pending_start_req_;
// key: connector ID
std::unordered_map<int, bool> last_plugged_in_state_;
std::optional<ocpp1_6::IdToken> last_rfid_tag_id_ = std::nullopt;
// Note: a transaction will remain here until the connector is unplugged or a new transaction starts
// key: connector ID
std::unordered_map<int, std::optional<transaction_module1_6::TransactionContainer>> active_transactions_;
std::optional<int> connector_hold_id_ {std::nullopt};
std::optional<int> force_stop_transaction_ = std::nullopt;
#if 0 // For OCTT _012
std::unordered_map<int, SteadyPointMillis> pending_stop_transaction_times_;
#endif
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_TRANSACTION_MODULE1_6_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,324 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_DATE_TIME_H
#define CHARGELAB_OPEN_FIRMWARE_DATE_TIME_H
#include "openocpp/model/system_types.h"
#include "openocpp/helpers/json.h"
#include "openocpp/common/logging.h"
#include <string>
#include <utility>
#include <ctime>
#include <stdio.h>
#include <chrono>
#if defined (USE_HH_DATE)
#include <date/date.h>
#endif
namespace chargelab::common {
class DateTime {
static constexpr int kMilliSecondSize = 3;
public:
DateTime() = default;
DateTime(const DateTime& other) = default;
DateTime& operator=(const DateTime& other) = default;
DateTime(SystemTimeMillis timestamp) : text_(timestampToText(timestamp)), timestamp_(timestamp) {
}
DateTime(std::string text) : text_(text), timestamp_(textToTimestamp(text)) {
}
[[nodiscard]] std::optional<std::string> getText() const {
return text_;
}
[[nodiscard]] std::optional<SystemTimeMillis> getTimestamp() const {
return timestamp_;
}
static void write_json(json::JsonWriter& writer, DateTime const& value) {
if (value.text_.has_value()) {
writer.String(value.text_.value());
} else {
writer.Null();
}
}
static bool read_json(json::JsonReader& reader, DateTime& value) {
auto const token = reader.nextToken();
if (!token.has_value())
return false;
if (!std::holds_alternative<json::StringType>(token.value()))
return false;
auto const& text = std::get<json::StringType>(token.value());
value.text_ = std::string{text.str, text.length};
value.timestamp_ = textToTimestamp(value.text_.value());
return true;
}
bool operator==(DateTime const& rhs) const {
return getTimestamp() == rhs.getTimestamp();
}
bool operator!=(DateTime const& rhs) const {
return !operator==(rhs);
}
[[nodiscard]] bool isAfter(SystemTimeMillis ts, bool default_value) const {
if (!timestamp_.has_value())
return default_value;
return timestamp_.value() - ts > 0;
}
[[nodiscard]] bool isBefore(SystemTimeMillis ts, bool default_value) const {
if (!timestamp_.has_value())
return default_value;
return timestamp_.value() - ts < 0;
}
#if !defined(USE_HH_DATE)
static std::optional<std::string> timestampToText(SystemTimeMillis const& timestamp) {
time_t time = timestamp/1000;
int millisecond = timestamp%1000;
if (millisecond < 0) { // adjust the minus value to be positive, borrow 1 second
time -= 1;
millisecond += 1000;
}
auto tm = gmtime(&time);
char buf[100]{};
sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
tm->tm_hour, tm->tm_min, tm->tm_sec);
return std::string(buf);
}
static std::optional<SystemTimeMillis> textToTimestamp(std::string const& text) {
auto text2 = removeExcessiveFractionDigits(text);
auto timezone_minutes = retrieveTimezoneMinutes(text2);
if (!timezone_minutes.has_value()) {
CHARGELAB_LOG_MESSAGE(error) << "wrong time zone format:" << text;
return std::nullopt;
}
// "2022-12-17T14:37:09.894Z"
auto tm = parseTm(text2);
if (tm) {
std::int64_t ret = timegm2(&tm.value()); // timegm() is not supported by ESP32
ret -= timezone_minutes.value() * 60;
return static_cast<SystemTimeMillis>(ret * 1000 + retrieveMillisecond(text2));
}
CHARGELAB_LOG_MESSAGE(error) << "wrong date time format:" << text;
return std::nullopt;
}
#else
static std::optional<std::string> timestampToText(SystemTimeMillis const& timestamp) {
date::sys_time<std::chrono::milliseconds> ts {std::chrono::milliseconds(timestamp)};
CHARGELAB_TRY {
std::ostringstream out;
// TODO: Need to confirm this *always* uses UTC timezone
out << date::format("%FT%TZ", ts);
if (!out.fail()) {
return out.str();
}
} CHARGELAB_CATCH {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected exception converting timestamp to date/time string: " << e.what();
}
return std::nullopt;
}
static std::optional<SystemTimeMillis> textToTimestamp(std::string const& text) {
auto text2 = removeExcessiveFractionDigits(text);
date::sys_time<std::chrono::milliseconds> ts;
CHARGELAB_TRY {
std::istringstream in{text2};
in >> date::parse("%FT%TZ", ts);
if (!in.fail()) {
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(ts.time_since_epoch()).count();
return static_cast<SystemTimeMillis> (millis);
}
} CHARGELAB_CATCH {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected exception converting date/time string to timestamp: " << e.what();
}
CHARGELAB_TRY {
std::istringstream in{text2};
in >> date::parse("%FT%T%Ez", ts);
if (!in.fail()) {
auto const millis = std::chrono::duration_cast<std::chrono::milliseconds>(ts.time_since_epoch()).count();
return static_cast<SystemTimeMillis> (millis);
}
} CHARGELAB_CATCH {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected exception converting date/time string to timestamp: " << e.what();
}
return std::nullopt;
}
#endif
private:
static std::string removeExcessiveFractionDigits(std::string const& time_text) {
int pos = time_text.find('.');
if (pos == -1) return time_text;
int decimal_digit_count = 0;
unsigned int i = pos + 1;
while (i < time_text.length() && decimal_digit_count < kMilliSecondSize) {
if (!std::isdigit(time_text[i])) break;
++decimal_digit_count;
++i;
}
if (decimal_digit_count == kMilliSecondSize) {
auto start = i;
while (i < time_text.length()) {
if (!std::isdigit(time_text[i])) break;
++i;
}
if (start != i) {
return time_text.substr(0, start) + time_text.substr(i);
}
}
return time_text;
}
static int retrieveMillisecond(std::string const& timestamp) {
auto find = timestamp.find('.');
if (find == std::string::npos) {
return 0;
}
int milliseconds = 0;
unsigned int multiplier = 100;
for (unsigned int i = find+1; i < timestamp.length(); ++i) {
if (!std::isdigit(timestamp[i])) break;
milliseconds += (timestamp[i] - '0') * multiplier;
multiplier /= 10;
}
return milliseconds;
}
// timestamp: e.g. "2022-10-17T14:37:09.894+03:00" https://www.w3.org/TR/NOTE-datetime
static std::optional<int> retrieveTimezoneMinutes(std::string & timestamp) {
auto find = timestamp.find('Z');
if (find != std::string::npos) {
timestamp = timestamp.substr(0, find); // ignore anything after 'Z'
return 0;
}
bool negative = false;
find = timestamp.find('+');
if (find == std::string::npos) {
find = timestamp.rfind('-');
negative = true;
}
if (find == std::string::npos) return std::nullopt;
int hour = 0;
int minute = 0;
auto find2 = timestamp.find(':', find);
if (find2 == std::string::npos) { // only have hours
if (sscanf(timestamp.c_str() + find + 1, "%2d", &hour) != 1) return std::nullopt;
} else {
if (sscanf(timestamp.c_str() + find + 1, "%2d:%2d", &hour, &minute) != 2) return std::nullopt;
}
timestamp = timestamp.substr(0, find); // remove timezone from the timestamp for future process
return (hour*60 + minute)*(negative ? -1 : 1);
}
static std::optional<std::tm> parseTm(std::string const& timestamp) {
int year, month, day, hour, minute, second;
if (sscanf(timestamp.c_str(), "%4d-%2d-%2dT%2d:%2d:%2d", &year, &month, &day, &hour, &minute, &second) != 6) {
return std::nullopt;
}
//
if (month < 1 || month > 12 || day < 1 || day > getDaysOfMonth(year, month) || hour < 0 || hour >= 24 ||
minute < 0 || minute >= 60 || second < 0 || second >= 60) { // no leap seconds supported
return std::nullopt;
}
std::tm tm{};
tm.tm_sec = second;
tm.tm_min = minute;
tm.tm_hour = hour;
tm.tm_mday = day;
tm.tm_mon = month-1;
tm.tm_year = year-1900;
return tm;
};
static std::time_t timegm2(std::tm const *tm) {
if (tm == nullptr) return -1;
if (tm->tm_mon >= 12 || tm->tm_hour >= 24 || tm->tm_min >= 60 || tm->tm_sec >= 61) {
return -1;
}
if (getDaysOfMonth(tm->tm_year + 1900, tm->tm_mon + 1) == -1) {
return -1;
}
// get the day to the tm date since 1900-01-01 00:00:00 +0000, UTC instead of the Epoch (1970-01-01 00:00:00 +0000, UTC)
std::int64_t total_days = 0; //
int year = tm->tm_year + 1900;
auto const year_since_epch = year - 1970;
int leap_years = totalLeapYears(year - 1) - totalLeapYears(1970);
total_days = year_since_epch*365 + leap_years;
for (int i = 0; i < tm->tm_mon; ++i) {
total_days += getDaysOfMonth(year, i+1);
}
total_days += tm->tm_mday - 1; // tm_mday is 1-31
std::int64_t total_hours = total_days*24 + tm->tm_hour;
std::time_t total_seconds = (total_hours*60 + tm->tm_min)*60 + tm->tm_sec;
return total_seconds /*+ kSecondsFrom1990*/;
}
static bool isLeapYear(unsigned int year) {
return (year%4 == 0) && (!(year%100 == 0) || (year%400 == 0));
}
// month: 1-based
static int getDaysOfMonth(unsigned int year, int month) {
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
return 31;
case 4:
case 6:
case 9:
case 11:
return 30;
case 2:
return isLeapYear(year) ? 29 : 28;
default:
return -1;
}
}
static int totalLeapYears(int year) {
return year/4 - year/100 + year/400;
}
private:
std::optional<std::string> text_ = std::nullopt;
std::optional<SystemTimeMillis> timestamp_ = std::nullopt;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_DATE_TIME_H

View File

@@ -0,0 +1,12 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_PROTOCOL_CONSTANTS_H
#define CHARGELAB_OPEN_FIRMWARE_PROTOCOL_CONSTANTS_H
namespace chargelab {
class ProtocolConstants {
public:
static constexpr char const* kProtocolOcpp1_6 = "ocpp1.6";
static constexpr char const* kProtocolOcpp2_0_1 = "ocpp2.0.1";
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_PROTOCOL_CONSTANTS_H

View File

@@ -0,0 +1,211 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_RAW_JSON_H
#define CHARGELAB_OPEN_FIRMWARE_RAW_JSON_H
#include "openocpp/helpers/json.h"
#include <string>
#include <memory>
namespace chargelab::common {
namespace detail {
class RawJsonInterface {
public:
virtual ~RawJsonInterface() = default;
virtual std::string data() const = 0;
virtual void write_json(json::JsonWriter& writer) const = 0;
};
template <typename T>
class RawJsonLazy : public RawJsonInterface {
public:
RawJsonLazy(T value) : value_(std::move(value)) {}
std::string data() const override {
return write_json_to_string(value_);
}
void write_json(json::JsonWriter& writer) const override {
json::WriteValue<T>::write_json(writer, value_);
}
private:
T value_;
};
}
class RawJson {
private:
using text_type = std::string;
using lazy_type = std::shared_ptr<detail::RawJsonInterface>;
using data_type = std::variant<text_type, lazy_type>;
public:
RawJson() = default;
RawJson(std::string data) : data_(std::move(data)) {}
RawJson(std::shared_ptr<detail::RawJsonInterface> data) : data_(std::move(data)) {}
bool operator==(RawJson const& rhs) const {
return data() == rhs.data();
}
bool operator!=(RawJson const& rhs) const {
return !operator==(rhs);
}
std::string data() const {
if (std::holds_alternative<text_type>(data_)) {
return std::get<text_type>(data_);
} else {
return std::get<lazy_type>(data_)->data();
}
}
static bool read_json(json::JsonReader& reader, RawJson& value) {
stream::StringWriter stream;
json::JsonWriter writer {stream};
int object_stack = 0;
int array_stack = 0;
do {
auto const& next = reader.nextToken();
if (!next.has_value())
return false;
auto const& token = next.value();
if (std::holds_alternative<json::StartObjectType>(token)) {
writer.StartObject();
object_stack++;
} else if (std::holds_alternative<json::EndObjectType>(token)) {
writer.EndObject();
object_stack--;
} else if (std::holds_alternative<json::StartArrayType>(token)) {
writer.StartArray();
array_stack++;
} else if (std::holds_alternative<json::EndArrayType>(token)) {
writer.EndArray();
array_stack--;
} else if (std::holds_alternative<json::NullType>(token)) {
writer.Null();
} else if (std::holds_alternative<json::BoolType>(token)) {
auto const& x = std::get<json::BoolType>(token);
writer.Bool(x.value);
} else if (std::holds_alternative<json::IntType>(token)) {
auto const& x = std::get<json::IntType>(token);
writer.Int(x.value);
} else if (std::holds_alternative<json::UintType>(token)) {
auto const& x = std::get<json::UintType>(token);
writer.Uint(x.value);
} else if (std::holds_alternative<json::Int64Type>(token)) {
auto const& x = std::get<json::Int64Type>(token);
writer.Int64(x.value);
} else if (std::holds_alternative<json::Uint64Type>(token)) {
auto const& x = std::get<json::Uint64Type>(token);
writer.Uint64(x.value);
} else if (std::holds_alternative<json::DoubleType>(token)) {
auto const& x = std::get<json::DoubleType>(token);
writer.Double(x.value);
} else if (std::holds_alternative<json::RawNumberType>(token)) {
auto const& x = std::get<json::RawNumberType>(token);
writer.RawNumber(x.str, x.length);
} else if (std::holds_alternative<json::StringType>(token)) {
auto const& x = std::get<json::StringType>(token);
writer.String(x.str, x.length);
} else if (std::holds_alternative<json::KeyType>(token)) {
auto const& x = std::get<json::KeyType>(token);
writer.Key(x.str, x.length);
} else {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected JSON token";
}
} while (object_stack > 0 || array_stack > 0);
value.data_ = stream.str();
return true;
}
static void write_json(json::JsonWriter& writer, RawJson const& value) {
if (std::holds_alternative<lazy_type>(value.data_)) {
std::get<lazy_type>(value.data_)->write_json(writer);
return;
}
stream::StringReader stream {std::get<text_type>(value.data_)};
json::JsonReader reader {stream};
while (true) {
auto const& next = reader.nextToken();
if (!next.has_value())
break;
auto const& token = next.value();
if (std::holds_alternative<json::StartObjectType>(token)) {
writer.StartObject();
} else if (std::holds_alternative<json::EndObjectType>(token)) {
writer.EndObject();
} else if (std::holds_alternative<json::StartArrayType>(token)) {
writer.StartArray();
} else if (std::holds_alternative<json::EndArrayType>(token)) {
writer.EndArray();
} else if (std::holds_alternative<json::NullType>(token)) {
writer.Null();
} else if (std::holds_alternative<json::BoolType>(token)) {
auto const& x = std::get<json::BoolType>(token);
writer.Bool(x.value);
} else if (std::holds_alternative<json::IntType>(token)) {
auto const& x = std::get<json::IntType>(token);
writer.Int(x.value);
} else if (std::holds_alternative<json::UintType>(token)) {
auto const& x = std::get<json::UintType>(token);
writer.Uint(x.value);
} else if (std::holds_alternative<json::Int64Type>(token)) {
auto const& x = std::get<json::Int64Type>(token);
writer.Int64(x.value);
} else if (std::holds_alternative<json::Uint64Type>(token)) {
auto const& x = std::get<json::Uint64Type>(token);
writer.Uint64(x.value);
} else if (std::holds_alternative<json::DoubleType>(token)) {
auto const& x = std::get<json::DoubleType>(token);
writer.Double(x.value);
} else if (std::holds_alternative<json::RawNumberType>(token)) {
auto const& x = std::get<json::RawNumberType>(token);
writer.RawNumber(x.str, x.length);
} else if (std::holds_alternative<json::StringType>(token)) {
auto const& x = std::get<json::StringType>(token);
writer.String(x.str, x.length);
} else if (std::holds_alternative<json::KeyType>(token)) {
auto const& x = std::get<json::KeyType>(token);
writer.Key(x.str, x.length);
} else {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected JSON token";
}
}
}
template <typename T>
static RawJson from_value(T const& value) {
RawJson result {};
stream::StringReader stream {write_json_to_string(value)};
json::JsonReader reader {stream};
if (!read_json(reader, result)) {
CHARGELAB_LOG_MESSAGE(error) << "Failed creating RawJson from record";
return RawJson {};
}
return result;
}
template <typename T>
static RawJson from_value_lazy(T const& value) {
return RawJson{std::shared_ptr<detail::RawJsonInterface> {new detail::RawJsonLazy<T> {value}}};
}
static RawJson empty_object() {
return RawJson{};
}
private:
data_type data_ = "{}";
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_RAW_JSON_H

View File

@@ -0,0 +1,124 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_SMALL_STRING_H
#define CHARGELAB_OPEN_FIRMWARE_SMALL_STRING_H
#include "openocpp/common/logging.h"
#include <string>
#include <exception>
namespace chargelab {
class SmallString {
public:
~SmallString() {
if (allocated_) {
delete [] str_;
str_ = nullptr;
allocated_ = false;
}
}
SmallString() {
str_ = nullptr;
size_ = 0;
allocated_ = false;
}
// TODO: This can be improved with various overrides, wrappers, and constexpr to restrict this constructor to
// string literals more reliably, but this is likely sufficient for now.
template <std::size_t N>
SmallString(char const (&literal)[N]) {
str_ = literal;
size_ = N-1;
allocated_ = false;
}
SmallString(std::string const& text) {
size_ = text.size();
allocated_ = true;
auto block = new char[size_];
std::memcpy(block, text.data(), size_);
str_ = block;
}
SmallString(SmallString const& other) {
if (other.allocated_) {
size_ = other.size_;
allocated_ = true;
auto block = new char[size_];
std::memcpy(block, other.str_, other.size_);
str_ = block;
} else {
str_ = other.str_;
size_ = other.size_;
allocated_ = other.allocated_;
}
}
SmallString(SmallString&& other) noexcept {
std::swap(str_, other.str_);
std::swap(size_, other.size_);
std::swap(allocated_, other.allocated_);
}
SmallString& operator=(SmallString const& other) {
if (this == &other)
return *this;
if (allocated_) {
delete str_;
str_ = nullptr;
allocated_ = false;
}
if (other.allocated_) {
size_ = other.size_;
allocated_ = true;
auto block = new char[size_];
std::memcpy(block, other.str_, other.size_);
str_ = block;
} else {
str_ = other.str_;
size_ = other.size_;
allocated_ = other.allocated_;
}
return *this;
}
SmallString& operator=(SmallString&& other) noexcept {
if (this == &other)
return *this;
std::swap(str_, other.str_);
std::swap(size_, other.size_);
std::swap(allocated_, other.allocated_);
return *this;
}
[[nodiscard]] std::string value() const {
if (str_ != nullptr) {
return {str_, size_};
} else {
return {};
}
}
bool operator==(SmallString const& rhs) const {
return value() == rhs.value();
}
bool operator!=(SmallString const& rhs) const {
return !operator==(rhs);
}
private:
char const* str_ = nullptr;
std::size_t size_ = 0;
bool allocated_ = false;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_SMALL_STRING_H

View File

@@ -0,0 +1,137 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_VECTOR_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_VECTOR_H
#include <variant>
#include "openocpp/helpers/json.h"
namespace chargelab {
template <typename T, bool Optional = false, int MinElements = 0, int MaxElements = -1>
struct Vector {
public:
using this_type = Vector<T, Optional, MinElements, MaxElements>;
using data_type = std::vector<T>;
using visitor_type = std::function<void(std::function<void(T const&)>)>;
public:
Vector() {}
Vector(data_type data) : supplier_(std::move(data)) {}
Vector(visitor_type visitor) : supplier_(std::move(visitor)) {}
Vector(const this_type& other) = default;
this_type& operator=(const this_type& other) = default;
data_type value() const {
if (std::holds_alternative<data_type>(supplier_)) {
return std::get<data_type>(supplier_);
} else {
std::vector<T> data;
std::get<visitor_type>(supplier_)([&](auto const& x) {
data.push_back(x);
});
return data;
}
}
template <typename Visitor>
void visit(Visitor&& visitor) const {
if (std::holds_alternative<data_type>(supplier_)) {
auto const& data = std::get<data_type>(supplier_);
for (auto const& x : data)
visitor(x);
} else {
std::get<visitor_type>(supplier_)(std::forward<Visitor>(visitor));
}
}
std::size_t size() const {
std::size_t result = 0;
visit([&](auto const&) {
result++;
});
return result;
}
static bool read_json(json::JsonReader& reader, this_type& value) {
auto next = reader.peekToken();
if (!next.has_value())
return false;
if (std::holds_alternative<json::NullType>(next.value())) {
value.supplier_ = data_type {};
} else {
data_type data;
json::ReadValue<data_type>::read_json(reader, data);
value.supplier_ = std::move(data);
}
return true;
}
static void write_json(json::JsonWriter& writer, this_type const& value) {
if (std::holds_alternative<data_type>(value.supplier_)) {
json::WriteValue<data_type>::write_json(writer, std::get<data_type>(value.supplier_));
} else {
writer.StartArray();
std::get<visitor_type>(value.supplier_)([&](auto const& element) {
json::WriteValue<T>::write_json(writer, element);
});
writer.EndArray();
}
}
static bool include_field(this_type const& value) {
return !Optional || value.size() > 0;
}
static bool is_required() {
return !Optional;
}
static bool validate(this_type const& value) {
auto const size = value.size();
if (MinElements >= 0) {
if (size < MinElements)
return false;
}
if (MaxElements >= 0) {
if (size > MaxElements)
return false;
}
bool valid = true;
value.visit([&](auto const& element) {
if (!valid)
return;
valid = json::Validate<T>::validate(element);
});
return valid;
}
friend bool operator==(this_type const& lhs, this_type const& rhs) {
auto const& lhs_data = lhs.value();
auto const& rhs_data = rhs.value();
if (lhs_data.size() != rhs_data.size())
return false;
for (auto it_lhs=lhs_data.begin(), it_rhs=rhs_data.begin(); it_lhs != lhs_data.end(); ++it_lhs, ++it_rhs) {
if (*it_lhs != *it_rhs)
return false;
}
return true;
}
friend bool operator!=(this_type const& lhs, this_type const& rhs) {
return !(lhs == rhs);
}
private:
std::variant<data_type, visitor_type> supplier_ = data_type {};
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_VECTOR_H

View File

@@ -0,0 +1,58 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_REQUEST_HANDLER_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_REQUEST_HANDLER_H
#include "openocpp/protocol/ocpp1_6/messages/authorize.h"
#include "openocpp/protocol/ocpp1_6/messages/boot_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/cancel_reservation.h"
#include "openocpp/protocol/ocpp1_6/messages/change_availability.h"
#include "openocpp/protocol/ocpp1_6/messages/change_configuration.h"
#include "openocpp/protocol/ocpp1_6/messages/clear_cache.h"
#include "openocpp/protocol/ocpp1_6/messages/clear_charging_profile.h"
#include "openocpp/protocol/ocpp1_6/messages/data_transfer.h"
#include "openocpp/protocol/ocpp1_6/messages/diagnostics_status_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/firmware_status_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/get_composite_schedule.h"
#include "openocpp/protocol/ocpp1_6/messages/get_configuration.h"
#include "openocpp/protocol/ocpp1_6/messages/get_diagnostics.h"
#include "openocpp/protocol/ocpp1_6/messages/get_local_list_version.h"
#include "openocpp/protocol/ocpp1_6/messages/heartbeat.h"
#include "openocpp/protocol/ocpp1_6/messages/meter_values.h"
#include "openocpp/protocol/ocpp1_6/messages/remote_start_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/remote_stop_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/reserve_now.h"
#include "openocpp/protocol/ocpp1_6/messages/reset.h"
#include "openocpp/protocol/ocpp1_6/messages/send_local_list.h"
#include "openocpp/protocol/ocpp1_6/messages/set_charging_profile.h"
#include "openocpp/protocol/ocpp1_6/messages/start_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/status_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/stop_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/trigger_message.h"
#include "openocpp/protocol/ocpp1_6/messages/unlock_connector.h"
#include "openocpp/protocol/ocpp1_6/messages/update_firmware.h"
#include "openocpp/protocol/ocpp1_6/types/call_result.h"
#include "openocpp/protocol/ocpp1_6/handlers/ocpp_remote.h"
#include <variant>
namespace chargelab::ocpp1_6 {
class AbstractRequestHandler {
public:
#define CHARGELAB_REQUEST_HANDLER_TEMPLATE(type) \
virtual std::optional<ocpp1_6::ResponseToRequest<ocpp1_6::type ## Rsp>> on ## type ## Req(ocpp1_6::type ## Req const&) { \
return std::nullopt; \
}
CHARGELAB_PASTE(CHARGELAB_REQUEST_HANDLER_TEMPLATE, CHARGELAB_OCPP_1_6_ACTION_IDS)
#undef CHARGELAB_REQUEST_HANDLER_TEMPLATE
virtual std::optional<ocpp1_6::ResponseToRequest<common::RawJson>> onCall(
ocpp1_6::ActionId const& actionId,
common::RawJson const& payload
) {
(void)actionId;
(void)payload;
return std::nullopt;
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_REQUEST_HANDLER_H

View File

@@ -0,0 +1,48 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_RESPONSE_HANDLER_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_RESPONSE_HANDLER_H
#include "openocpp/protocol/ocpp1_6/messages/authorize.h"
#include "openocpp/protocol/ocpp1_6/messages/boot_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/cancel_reservation.h"
#include "openocpp/protocol/ocpp1_6/messages/change_availability.h"
#include "openocpp/protocol/ocpp1_6/messages/change_configuration.h"
#include "openocpp/protocol/ocpp1_6/messages/clear_cache.h"
#include "openocpp/protocol/ocpp1_6/messages/clear_charging_profile.h"
#include "openocpp/protocol/ocpp1_6/messages/data_transfer.h"
#include "openocpp/protocol/ocpp1_6/messages/diagnostics_status_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/firmware_status_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/get_composite_schedule.h"
#include "openocpp/protocol/ocpp1_6/messages/get_configuration.h"
#include "openocpp/protocol/ocpp1_6/messages/get_diagnostics.h"
#include "openocpp/protocol/ocpp1_6/messages/get_local_list_version.h"
#include "openocpp/protocol/ocpp1_6/messages/heartbeat.h"
#include "openocpp/protocol/ocpp1_6/messages/meter_values.h"
#include "openocpp/protocol/ocpp1_6/messages/remote_start_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/remote_stop_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/reserve_now.h"
#include "openocpp/protocol/ocpp1_6/messages/reset.h"
#include "openocpp/protocol/ocpp1_6/messages/send_local_list.h"
#include "openocpp/protocol/ocpp1_6/messages/set_charging_profile.h"
#include "openocpp/protocol/ocpp1_6/messages/start_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/status_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/stop_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/trigger_message.h"
#include "openocpp/protocol/ocpp1_6/messages/unlock_connector.h"
#include "openocpp/protocol/ocpp1_6/messages/update_firmware.h"
#include "openocpp/protocol/ocpp1_6/types/call_result.h"
#include <variant>
namespace chargelab::ocpp1_6 {
class AbstractResponseHandler {
public:
#define CHARGELAB_RESPONSE_HANDLER_TEMPLATE(type) \
virtual void on ## type ## Rsp(std::string const&, ocpp1_6::ResponseMessage<type ## Rsp> const&) {}
CHARGELAB_PASTE(CHARGELAB_RESPONSE_HANDLER_TEMPLATE, CHARGELAB_OCPP_1_6_ACTION_IDS)
#undef CHARGELAB_RESPONSE_HANDLER_TEMPLATE
virtual void onCallRsp(std::string const&, ocpp1_6::ResponseMessage<common::RawJson> const&) {}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_RESPONSE_HANDLER_H

View File

@@ -0,0 +1,20 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_SERVICE_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_SERVICE_H
#include "openocpp/protocol/ocpp1_6/handlers/ocpp_remote.h"
#include "openocpp/protocol/common/raw_json.h"
namespace chargelab::ocpp1_6 {
class AbstractService {
public:
virtual void runStep(OcppRemote& remote) {
(void)remote;
};
virtual void onUnmanagedMessage(std::string const& message) {
(void)message;
}
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_ABSTRACT_SERVICE_H

View File

@@ -0,0 +1,537 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_MESSAGE_HANDLER_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_MESSAGE_HANDLER_H
#include "openocpp/protocol/ocpp1_6/handlers/abstract_service.h"
#include "openocpp/protocol/ocpp1_6/handlers/abstract_request_handler.h"
#include "openocpp/protocol/ocpp1_6/handlers/abstract_response_handler.h"
#include "openocpp/protocol/ocpp1_6/handlers/ocpp_remote.h"
#include "openocpp/protocol/ocpp1_6/types/message_type.h"
#include "openocpp/protocol/ocpp1_6/types/error_code.h"
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/call_result.h"
#include "openocpp/interface/element/websocket_interface.h"
#include "openocpp/interface/component/system_interface.h"
#include "openocpp/module/abstract_module.h"
#include "openocpp/common/logging.h"
#include "openocpp/common/ring_buffer.h"
#include "openocpp/helpers/string.h"
#include "openocpp/common/settings.h"
#include <memory>
#include <utility>
#include <random>
namespace chargelab::ocpp1_6 {
namespace detail {
struct PendingCall {
std::string unique_id;
ActionId action_id;
SteadyPointMillis timestamp;
CHARGELAB_JSON_INTRUSIVE(PendingCall, unique_id, action_id, timestamp)
};
}
class OcppMessageHandler {
private:
static constexpr const int kMaxMessageProcessedPerStep = 4;
public:
OcppMessageHandler(
std::shared_ptr<Settings> settings,
std::shared_ptr<SystemInterface> system,
std::vector<std::shared_ptr<AbstractModuleInterface>> modules,
std::function<bool()> registrationComplete
)
: settings_(std::move(settings)),
system_(std::move(system)),
modules_(std::move(modules)),
registration_complete_(std::move(registrationComplete))
{
updatePointers();
}
void runStep(std::shared_ptr<WebsocketInterface> const& websocket) {
for (auto& x : pure_services_)
x->runUnconditionally();
if (websocket == nullptr) {
CHARGELAB_LOG_MESSAGE(error) << "Unexpected null websocket pointer provided";
return;
}
auto const subprotocol = websocket->getSubprotocol();
if (!subprotocol.has_value() || !string::EqualsIgnoreCaseAscii(subprotocol.value(), "ocpp1.6")) {
CHARGELAB_LOG_MESSAGE(trace) << "Skipping OCPP 1.6 message handler based on websocket subprotocol: " << subprotocol;
return;
}
for (int i=0; i < kMaxMessageProcessedPerStep; i++) {
auto message = websocket->pollMessages();
if (!message.has_value())
break;
CHARGELAB_TRY {
onMessage(*websocket, message.value());
} CHARGELAB_CATCH {
CHARGELAB_LOG_MESSAGE(debug) << "Unexpected failed processing message: " << e.what();
dispatchUnexpectedMessage(message.value());
}
}
OcppRemote remote {
*websocket,
registration_complete_,
[this](std::string const& unique_id, ActionId const& action) {
auto const now = system_->steadyClockNow();
if (last_call_.has_value()) {
auto const elapsed_seconds = (now - last_call_->timestamp)/1000;
if (elapsed_seconds < settings_->DefaultMessageTimeout.getValue()) {
CHARGELAB_LOG_MESSAGE(info) << "Call already in process - blocking call '" << action << "' waiting for: " << last_call_;
return false;
}
CHARGELAB_LOG_MESSAGE(info) << "Call already in process but timeout elapsed - allowing call '" << action << "' waiting for: " << last_call_;
}
last_call_ = detail::PendingCall {unique_id, action, now};
return true;
}
};
for (auto& x : services_)
x->runStep(remote);
}
template <typename T, typename... Args>
void addAfter(std::shared_ptr<T> module, Args&&... args) {
auto index = getMaxIndex(std::forward<Args>(args)...);
if (index.has_value()) {
modules_.insert(modules_.begin()+index.value()+1, std::move(module));
} else {
modules_.push_back(std::move(module));
}
updatePointers();
}
template <typename T, typename... Args>
void addBefore(std::shared_ptr<T> module, Args&&... args) {
auto index = getMinIndex(std::forward<Args>(args)...);
if (index.has_value()) {
modules_.insert(modules_.begin()+index.value(), std::move(module));
} else {
modules_.push_back(std::move(module));
}
updatePointers();
}
private:
void updatePointers() {
services_.clear();
request_handlers_.clear();
response_handlers_.clear();
pure_services_.clear();
for (auto const& x : modules_) {
if (x != nullptr) {
auto implementations = x->getImplementations();
addIfNotNull(services_, implementations.ocpp1_6.service);
addIfNotNull(request_handlers_, implementations.ocpp1_6.request_handler);
addIfNotNull(response_handlers_, implementations.ocpp1_6.response_handler);
addIfNotNull(pure_services_, implementations.pure_service);
}
}
}
std::optional<std::size_t> getMinIndex() {
return std::nullopt;
}
template <typename Head, typename... Args>
std::optional<std::size_t> getMinIndex(Head&& head, Args&&... args) {
auto const next = getMinIndex(std::forward<Args>(args)...);
for (std::size_t i=0; i < modules_.size(); i++) {
if (head == modules_[i]) {
if (next.has_value()) {
return std::min(i, next.value());
} else {
return i;
}
}
}
return next;
}
std::optional<std::size_t> getMaxIndex() {
return std::nullopt;
}
template <typename Head, typename... Args>
std::optional<std::size_t> getMaxIndex(Head&& head, Args&&... args) {
auto const next = getMaxIndex(std::forward<Args>(args)...);
for (std::size_t i=0; i < modules_.size(); i++) {
if (head == modules_[i]) {
if (next.has_value()) {
return std::max(i, next.value());
} else {
return i;
}
}
}
return next;
}
private:
void onMessage(WebsocketInterface& websocket, std::string const& message) {
CHARGELAB_LOG_MESSAGE(debug) << "Received OCPP message: " << message;
stream::StringReader stream {message};
json::JsonReader reader {stream};
if (!json::expect_type<json::StartArrayType>(reader)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - expected array: " << message;
dispatchUnexpectedMessage(message);
return;
}
MessageType message_type;
if (!json::ReadValue<MessageType>::read_json(reader, message_type)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad message type: " << message;
dispatchUnexpectedMessage(message);
return;
}
switch (message_type) {
case MessageType::kValueNotFoundInEnum:
dispatchUnexpectedMessage(message);
return;
case MessageType::kCall:
{
std::string unique_id;
if (!json::ReadValue<std::string>::read_json(reader, unique_id)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad unique ID: " << message;
dispatchUnexpectedMessage(message);
return;
}
ActionId action_id;
if (!json::ReadValue<ActionId>::read_json(reader, action_id)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad action ID: " << message;
dispatchUnexpectedMessage(message);
return;
}
switch (action_id) {
default:
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - bad action ID: " << message;
dispatchUnexpectedMessage(message);
return;
#define CHARGELAB_ON_CALL_IMPL(TYPE) \
case ActionId::k ## TYPE: \
{ \
TYPE ## Req parsed {}; \
if (!json::ReadValue<TYPE ## Req>::read_json(reader, parsed)) { \
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed deserializing message payload: " << message; \
dispatchUnexpectedMessage(message); \
break; \
} \
\
dispatchCall( \
websocket, \
action_id, \
unique_id, \
common::RawJson::from_value_lazy(parsed), \
[]( \
AbstractRequestHandler* handler, \
bool first, \
TYPE ## Req const& req, \
WebsocketInterface& websocket, \
std::string const& unique_id \
) { \
auto const response = handler->on ## TYPE ## Req(req); \
if (response.has_value()) { \
if (first) { \
sendResponse(websocket, unique_id, response.value()); \
} \
\
return true; \
} else { \
return false; \
} \
}, \
parsed, \
websocket, \
unique_id \
); \
break; \
}
CHARGELAB_PASTE(CHARGELAB_ON_CALL_IMPL, CHARGELAB_OCPP_1_6_ACTION_IDS)
#undef CHARGELAB_ON_CALL_IMPL
}
if (!json::expect_type<json::EndArrayType>(reader))
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected elements in OCPP call: " << message;
return;
}
case MessageType::kCallResult:
{
std::string unique_id;
if (!json::ReadValue<std::string>::read_json(reader, unique_id)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad unique ID: " << message;
dispatchUnexpectedMessage(message);
return;
}
std::optional<ActionId> action_id;
if (last_call_.has_value() && string::EqualsIgnoreCaseAscii(last_call_->unique_id, unique_id)) {
action_id = last_call_->action_id;
last_call_ = std::nullopt;
}
if (!action_id.has_value()) {
CHARGELAB_LOG_MESSAGE(warning) << "Response ID not found in pending call list - treating as unexpected message: " << unique_id;
dispatchUnexpectedMessage(message);
return;
}
switch (action_id.value()) {
default:
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - bad action ID: " << message;
dispatchUnexpectedMessage(message);
return;
#define CHARGELAB_ON_CALL_RESPONSE_IMPL(TYPE) \
case ActionId::k ## TYPE: \
{ \
TYPE ## Rsp parsed {}; \
if (!json::ReadValue<TYPE ## Rsp>::read_json(reader, parsed)) { \
CHARGELAB_LOG_MESSAGE(error) << "Unexpected state - failed deserializing message payload: " << message; \
dispatchUnexpectedMessage(message); \
return; \
} \
\
for (auto& ptr : response_handlers_) { \
if (ptr != nullptr) { \
ptr->on ## TYPE ## Rsp(unique_id, parsed); \
ptr->onCallRsp(unique_id, common::RawJson::from_value_lazy(parsed)); \
} \
} \
break; \
}
CHARGELAB_PASTE(CHARGELAB_ON_CALL_RESPONSE_IMPL, CHARGELAB_OCPP_1_6_ACTION_IDS)
#undef CHARGELAB_ON_CALL_RESPONSE_IMPL
}
// Note: ignoring and allowing
if (!json::expect_type<json::EndArrayType>(reader))
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected elements in OCPP call: " << message;
return;
}
case MessageType::kCallError:
{
std::string unique_id;
if (!json::ReadValue<std::string>::read_json(reader, unique_id)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad unique ID: " << message;
dispatchUnexpectedMessage(message);
return;
}
ErrorCode error_code;
if (!json::ReadValue<ErrorCode>::read_json(reader, error_code)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad error code: " << message;
dispatchUnexpectedMessage(message);
return;
}
std::string description;
if (!json::ReadValue<std::string>::read_json(reader, description)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad description: " << message;
dispatchUnexpectedMessage(message);
return;
}
common::RawJson details;
if (!json::ReadValue<common::RawJson>::read_json(reader, details)) {
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - missing or bad details: " << message;
dispatchUnexpectedMessage(message);
return;
}
// Note: ignoring and allowing
if (!json::expect_type<json::EndArrayType>(reader))
CHARGELAB_LOG_MESSAGE(warning) << "Unexpected elements in OCPP call: " << message;
auto const error = CallError {error_code, description, details};
CHARGELAB_LOG_MESSAGE(debug) << "Received OCPP error: id=" << unique_id << ", error=" << error;
std::optional<ActionId> action_id;
if (last_call_.has_value() && string::EqualsIgnoreCaseAscii(last_call_->unique_id, unique_id)) {
action_id = last_call_->action_id;
last_call_ = std::nullopt;
}
if (!action_id.has_value()) {
dispatchUnexpectedMessage(message);
return;
}
switch (action_id.value()) {
default:
CHARGELAB_LOG_MESSAGE(warning) << "Bad message payload - bad action ID: " << message;
dispatchUnexpectedMessage(message);
return;
#define CHARGELAB_ON_CALL_ERROR_IMPL(TYPE) \
case ActionId::k ## TYPE: \
{ \
for (auto& ptr : response_handlers_) { \
if (ptr != nullptr) { \
ptr->on ## TYPE ## Rsp(unique_id, error); \
} \
} \
break; \
}
CHARGELAB_PASTE(CHARGELAB_ON_CALL_ERROR_IMPL, CHARGELAB_OCPP_1_6_ACTION_IDS)
#undef CHARGELAB_ON_CALL_ERROR_IMPL
}
for (auto& ptr : response_handlers_) {
if (ptr != nullptr) {
ptr->onCallRsp(unique_id, error);
}
}
return;
}
}
}
template<typename T>
static void sendResponse(WebsocketInterface& websocket, std::string const& unique_id, ResponseToRequest<T> const& result) {
if (std::holds_alternative<T>(result)) {
websocket.sendCustom([&](ByteWriterInterface& stream) {
json::JsonWriter writer {stream};
writer.StartArray();
writer.Int((int)MessageType::kCallResult);
writer.String(unique_id);
json::WriteValue<T>::write_json(writer, std::get<T>(result));
writer.EndArray();
});
} else if (std::holds_alternative<CallError>(result)) {
sendError(websocket, unique_id, std::get<CallError>(result));
} else {
std::get<CustomResponse>(result)(websocket, unique_id);
}
}
static void sendError(WebsocketInterface& websocket, std::string const& unique_id, CallError const& error) {
websocket.sendCustom([&](ByteWriterInterface& stream) {
json::JsonWriter writer {stream};
writer.StartArray();
writer.Int((int)MessageType::kCallError);
writer.String(unique_id);
json::WriteValue<ErrorCode>::write_json(writer, error.code);
writer.String(error.description);
json::WriteValue<common::RawJson>::write_json(writer, error.details);
writer.EndArray();
});
}
template <typename F, typename... Args>
void dispatchCall(
WebsocketInterface& websocket,
ActionId const& action_id,
std::string const& unique_id,
common::RawJson const& payload,
F&& function,
Args&&... args
) {
bool responded = false;
for (auto& ptr : request_handlers_) {
if (ptr != nullptr) {
if (!responded) {
responded = function(ptr, true, std::forward<Args>(args)...);
} else if (allowFallthrough(action_id)) {
function(ptr, false, std::forward<Args>(args)...);
}
if (!responded) {
auto response = ptr->onCall(action_id, payload);
if (response.has_value()) {
sendResponse(websocket, unique_id, response.value());
responded = true;
}
} else if (allowFallthrough(action_id)) {
ptr->onCall(action_id, payload);
}
}
}
if (!responded) {
sendError(websocket, unique_id, CallError {
ErrorCode::kNotImplemented,
"Not implemented",
common::RawJson::empty_object()
});
}
}
bool allowFallthrough(ActionId const& action_id) {
switch (action_id) {
default:
return false;
case ActionId::kTriggerMessage:
case ActionId::kChangeConfiguration:
return true;
}
}
void dispatchUnexpectedMessage(std::string const& message) {
CHARGELAB_LOG_MESSAGE(debug) << "Dispatching unexpected message: " << message;
for (auto& ptr : services_) {
if (ptr != nullptr)
ptr->onUnmanagedMessage(message);
}
}
template <typename T>
static void addIfNotNull(std::vector<T*>& container, T* x) {
if (x != nullptr) {
container.push_back(x);
}
}
private:
std::shared_ptr<Settings> settings_;
std::shared_ptr<SystemInterface> system_;
std::vector<std::shared_ptr<AbstractModuleInterface>> modules_;
std::function<bool()> registration_complete_;
std::vector<std::shared_ptr<void>> wrappers_;
std::vector<AbstractService*> services_;
std::vector<AbstractRequestHandler*> request_handlers_;
std::vector<AbstractResponseHandler*> response_handlers_;
std::vector<chargelab::detail::PureServiceInterface*> pure_services_;
std::optional<detail::PendingCall> last_call_ = std::nullopt;
};
}
#undef CHARGELAB_MERGE_NAMES
#undef CHARGELAB_CALL_FUNCTION_TEMPLATE
#undef CHARGELAB_CALL_RESPONSE_TEMPLATE
#undef CHARGELAB_CALL_ERROR_TEMPLATE
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_MESSAGE_HANDLER_H

View File

@@ -0,0 +1,172 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_REMOTE_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_REMOTE_H
#include "openocpp/protocol/ocpp1_6/messages/authorize.h"
#include "openocpp/protocol/ocpp1_6/messages/boot_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/cancel_reservation.h"
#include "openocpp/protocol/ocpp1_6/messages/change_availability.h"
#include "openocpp/protocol/ocpp1_6/messages/change_configuration.h"
#include "openocpp/protocol/ocpp1_6/messages/clear_cache.h"
#include "openocpp/protocol/ocpp1_6/messages/clear_charging_profile.h"
#include "openocpp/protocol/ocpp1_6/messages/data_transfer.h"
#include "openocpp/protocol/ocpp1_6/messages/diagnostics_status_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/firmware_status_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/get_composite_schedule.h"
#include "openocpp/protocol/ocpp1_6/messages/get_configuration.h"
#include "openocpp/protocol/ocpp1_6/messages/get_diagnostics.h"
#include "openocpp/protocol/ocpp1_6/messages/get_local_list_version.h"
#include "openocpp/protocol/ocpp1_6/messages/heartbeat.h"
#include "openocpp/protocol/ocpp1_6/messages/meter_values.h"
#include "openocpp/protocol/ocpp1_6/messages/remote_start_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/remote_stop_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/reserve_now.h"
#include "openocpp/protocol/ocpp1_6/messages/reset.h"
#include "openocpp/protocol/ocpp1_6/messages/send_local_list.h"
#include "openocpp/protocol/ocpp1_6/messages/set_charging_profile.h"
#include "openocpp/protocol/ocpp1_6/messages/start_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/status_notification.h"
#include "openocpp/protocol/ocpp1_6/messages/stop_transaction.h"
#include "openocpp/protocol/ocpp1_6/messages/trigger_message.h"
#include "openocpp/protocol/ocpp1_6/messages/unlock_connector.h"
#include "openocpp/protocol/ocpp1_6/messages/update_firmware.h"
#include "openocpp/protocol/ocpp1_6/types/call_result.h"
#include "openocpp/protocol/ocpp1_6/types/message_type.h"
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/interface/element/websocket_interface.h"
#include <utility>
#include <random>
namespace chargelab::ocpp1_6 {
class OcppRemote {
private:
using OnManagedCall = std::function<bool(std::string const&, ActionId const&)>;
public:
explicit OcppRemote(
WebsocketInterface& websocket_interface,
std::function<bool()> registration_complete,
OnManagedCall on_managed_message
)
: websocket_interface_(websocket_interface),
registration_complete_(std::move(registration_complete)),
on_managed_message_(std::move(on_managed_message))
{
std::random_device random_device;
std::default_random_engine random_engine {random_device()};
std::uniform_int_distribution<unsigned long> distribution(
std::numeric_limits<unsigned long>::min(),
std::numeric_limits<unsigned long>::max()
);
request_id_ = distribution(random_engine);
}
public:
#define CHARGELAB_REQUEST_HANDLER_TEMPLATE(type) \
std::optional<std::string> send ## type ## Req(const type ## Req &req) { \
return executeCall(ActionId::k ## type, req); \
}
CHARGELAB_PASTE(CHARGELAB_REQUEST_HANDLER_TEMPLATE, CHARGELAB_OCPP_1_6_ACTION_IDS)
#undef CHARGELAB_REQUEST_HANDLER_TEMPLATE
template <typename T>
std::optional<std::string> sendCall(T const& req) {
return executeCall(T::kActionId, req);
}
bool sendCall(std::string const& unique_id, ActionId const& action, std::string const& payload) {
if (!websocket_interface_.isConnected())
return false;
if (!registration_complete_()) {
switch (action) {
default:
CHARGELAB_LOG_MESSAGE(info) << "Registration not complete - blocking call: " << action;
return false;
// Allow these requests to be sent while registration is pending
case ActionId::kBootNotification:
break;
}
}
if (!on_managed_message_(unique_id, action))
return false;
websocket_interface_.sendCustom([&](ByteWriterInterface& stream) {
json::JsonWriter writer {stream};
writer.StartArray();
writer.Int((int)MessageType::kCall);
writer.String(unique_id);
writer.String(action.to_string()),
json::WriteValue<common::RawJson>::write_json(writer, {payload});
writer.EndArray();
});
return true;
}
bool sendUnmanagedMessage(std::function<void(ByteWriterInterface&)> payload) {
if (!websocket_interface_.isConnected())
return false;
websocket_interface_.sendCustom(std::move(payload));
return true;
}
unsigned long getRequestId() {
return request_id_++;
}
private:
template <typename T>
std::optional<std::string> executeCall(ActionId const& action, T const& payload) {
if (!websocket_interface_.isConnected())
return std::nullopt;
if (!registration_complete_()) {
switch (action) {
default:
CHARGELAB_LOG_MESSAGE(info) << "Registration not complete - blocking call: " << action;
return std::nullopt;
// Allow these requests to be sent while registration is pending
case ActionId::kBootNotification:
break;
}
}
CHARGELAB_TRY {
auto unique_id = std::to_string(request_id_++);
if (!on_managed_message_(unique_id, action))
return std::nullopt;
websocket_interface_.sendCustom([&](ByteWriterInterface& stream) {
json::JsonWriter writer {stream};
writer.StartArray();
writer.Int((int)MessageType::kCall);
writer.String(unique_id);
writer.String(action.to_string()),
json::WriteValue<T>::write_json(writer, payload);
writer.EndArray();
});
return unique_id;
} CHARGELAB_CATCH {
CHARGELAB_LOG_MESSAGE(error) << "Failed sending call with: " << e.what();
return std::nullopt;
}
}
private:
WebsocketInterface& websocket_interface_;
std::function<bool()> registration_complete_;
OnManagedCall on_managed_message_;
std::atomic<unsigned long> request_id_;
};
}
#undef CHARGELAB_MERGE_NAMES
#undef CHARGELAB_REQUEST_HANDLER_TEMPLATE
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_OCPP_REMOTE_H

View File

@@ -0,0 +1,22 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZE_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZE_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/id_token.h"
#include "openocpp/protocol/ocpp1_6/types/id_tag_info.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct AuthorizeReq {
IdToken idTag;
CHARGELAB_JSON_INTRUSIVE_CALL(AuthorizeReq, kAuthorize, idTag)
};
struct AuthorizeRsp {
IdTagInfo idTagInfo;
CHARGELAB_JSON_INTRUSIVE(AuthorizeRsp, idTagInfo)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZE_H

View File

@@ -0,0 +1,35 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_BOOT_NOTIFICATION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_BOOT_NOTIFICATION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
#include "openocpp/protocol/ocpp1_6/types/registration_status.h"
#include "openocpp/helpers/json.h"
#include <optional>
namespace chargelab::ocpp1_6 {
struct BootNotificationReq {
std::optional<CiString25Type> chargeBoxSerialNumber = std::nullopt;
CiString20Type chargePointModel {};
std::optional<CiString25Type> chargePointSerialNumber = std::nullopt;
CiString20Type chargePointVendor {};
std::optional<CiString50Type> firmwareVersion = std::nullopt;
std::optional<CiString20Type> iccid = std::nullopt;
std::optional<CiString20Type> imsi = std::nullopt;
std::optional<CiString25Type> meterSerialNumber = std::nullopt;
std::optional<CiString25Type> meterType = std::nullopt;
CHARGELAB_JSON_INTRUSIVE_CALL(BootNotificationReq, kBootNotification, chargeBoxSerialNumber, chargePointModel, chargePointSerialNumber, chargePointVendor, firmwareVersion, iccid, imsi, meterSerialNumber, meterType)
};
struct BootNotificationRsp {
DateTime currentTime;
int interval;
RegistrationStatus status;
CHARGELAB_JSON_INTRUSIVE(BootNotificationRsp, currentTime, interval, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_BOOT_NOTIFICATION_H

View File

@@ -0,0 +1,23 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CANCEL_RESERVATION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_CANCEL_RESERVATION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/cancel_reservation_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct CancelReservationReq {
int reservationId;
CHARGELAB_JSON_INTRUSIVE_CALL(CancelReservationReq, kCancelReservation, reservationId)
};
struct CancelReservationRsp {
CancelReservationStatus status;
CHARGELAB_JSON_INTRUSIVE(CancelReservationRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CANCEL_RESERVATION_H

View File

@@ -0,0 +1,25 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_AVAILABILITY_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_AVAILABILITY_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/availability_type.h"
#include "openocpp/protocol/ocpp1_6/types/availability_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct ChangeAvailabilityReq {
int connectorId;
AvailabilityType type;
CHARGELAB_JSON_INTRUSIVE_CALL(ChangeAvailabilityReq, kChangeAvailability, connectorId, type)
};
struct ChangeAvailabilityRsp {
AvailabilityStatus status;
CHARGELAB_JSON_INTRUSIVE(ChangeAvailabilityRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_AVAILABILITY_H

View File

@@ -0,0 +1,25 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_CONFIGURATION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_CONFIGURATION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/protocol/ocpp1_6/types/configuration_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct ChangeConfigurationReq {
CiString50Type key;
CiString500Type value;
CHARGELAB_JSON_INTRUSIVE_CALL(ChangeConfigurationReq, kChangeConfiguration, key, value)
};
struct ChangeConfigurationRsp {
ConfigurationStatus status;
CHARGELAB_JSON_INTRUSIVE(ChangeConfigurationRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CHANGE_CONFIGURATION_H

View File

@@ -0,0 +1,22 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CACHE_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CACHE_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/clear_cache_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct ClearCacheReq {
CHARGELAB_JSON_INTRUSIVE_EMPTY_CALL(ClearCacheReq, kClearCache)
};
struct ClearCacheRsp {
ClearCacheStatus status;
CHARGELAB_JSON_INTRUSIVE(ClearCacheRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CACHE_H

View File

@@ -0,0 +1,27 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CHARGING_PROFILE_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CHARGING_PROFILE_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/charging_profile_purpose_type.h"
#include "openocpp/protocol/ocpp1_6/types/clear_charging_profile_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct ClearChargingProfileReq {
std::optional<int> id;
std::optional<int> connectorId;
std::optional<ChargingProfilePurposeType> chargingProfilePurpose;
std::optional<int> stackLevel;
CHARGELAB_JSON_INTRUSIVE_CALL(ClearChargingProfileReq, kClearChargingProfile, id, connectorId, chargingProfilePurpose, stackLevel)
};
struct ClearChargingProfileRsp {
ClearChargingProfileStatus status;
CHARGELAB_JSON_INTRUSIVE(ClearChargingProfileRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_CLEAR_CHARGING_PROFILE_H

View File

@@ -0,0 +1,27 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_DATA_TRANSFER_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_DATA_TRANSFER_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/protocol/ocpp1_6/types/data_transfer_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct DataTransferReq {
CiString255Type vendorId;
std::optional<CiString50Type> messageId;
std::optional<std::string> data;
CHARGELAB_JSON_INTRUSIVE_CALL(DataTransferReq, kDataTransfer, vendorId, messageId, data)
};
struct DataTransferRsp {
DataTransferStatus status {};
std::optional<std::string> data {};
CHARGELAB_JSON_INTRUSIVE(DataTransferRsp, status, data)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_DATA_TRANSFER_H

View File

@@ -0,0 +1,22 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_DIAGNOSTICS_STATUS_NOTIFICATION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_DIAGNOSTICS_STATUS_NOTIFICATION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/diagnostics_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct DiagnosticsStatusNotificationReq {
DiagnosticsStatus status;
CHARGELAB_JSON_INTRUSIVE_CALL(DiagnosticsStatusNotificationReq, kDiagnosticsStatusNotification, status)
};
struct DiagnosticsStatusNotificationRsp {
CHARGELAB_JSON_INTRUSIVE_EMPTY(DiagnosticsStatusNotificationRsp)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_DIAGNOSTICS_STATUS_NOTIFICATION_H

View File

@@ -0,0 +1,22 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_FIRMWARE_STATUS_NOTIFICATION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_FIRMWARE_STATUS_NOTIFICATION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/firmware_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct FirmwareStatusNotificationReq {
FirmwareStatus status;
CHARGELAB_JSON_INTRUSIVE_CALL(FirmwareStatusNotificationReq, kFirmwareStatusNotification, status)
};
struct FirmwareStatusNotificationRsp {
CHARGELAB_JSON_INTRUSIVE_EMPTY(FirmwareStatusNotificationRsp)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_FIRMWARE_STATUS_NOTIFICATION_H

View File

@@ -0,0 +1,31 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_GET_COMPOSITE_SCHEDULE_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_GET_COMPOSITE_SCHEDULE_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/charging_rate_unit_type.h"
#include "openocpp/protocol/ocpp1_6/types/get_composite_schedule_status.h"
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
#include "openocpp/protocol/ocpp1_6/types/charging_schedule.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct GetCompositeScheduleReq {
int connectorId;
int duration;
std::optional<ChargingRateUnitType> chargingRateUnit;
CHARGELAB_JSON_INTRUSIVE_CALL(GetCompositeScheduleReq, kGetCompositeSchedule, connectorId, duration, chargingRateUnit)
};
struct GetCompositeScheduleRsp {
GetCompositeScheduleStatus status;
std::optional<int> connectorId;
std::optional<DateTime> scheduleStart;
std::optional<ChargingSchedule> chargingSchedule;
CHARGELAB_JSON_INTRUSIVE(GetCompositeScheduleRsp, status, connectorId, scheduleStart, chargingSchedule)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_GET_COMPOSITE_SCHEDULE_H

View File

@@ -0,0 +1,27 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_GET_CONFIGURATION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_GET_CONFIGURATION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/protocol/ocpp1_6/types/key_value.h"
#include "openocpp/protocol/common/vector.h"
#include "openocpp/helpers/json.h"
#include <optional>
#include <vector>
namespace chargelab::ocpp1_6 {
struct GetConfigurationReq {
Vector<CiString50Type, true> key;
CHARGELAB_JSON_INTRUSIVE_CALL(GetConfigurationReq, kGetConfiguration, key)
};
struct GetConfigurationRsp {
Vector<KeyValue, true> configurationKey;
Vector<CiString50Type, true> unknownKey;
CHARGELAB_JSON_INTRUSIVE(GetConfigurationRsp, configurationKey, unknownKey)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_GET_CONFIGURATION_H

View File

@@ -0,0 +1,28 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_GET_DIAGNOSTICS_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_GET_DIAGNOSTICS_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/any_uri.h"
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct GetDiagnosticsReq {
AnyUri location;
std::optional<int> retries;
std::optional<int> retryInterval;
std::optional<DateTime> startTime;
std::optional<DateTime> stopTime;
CHARGELAB_JSON_INTRUSIVE_CALL(GetDiagnosticsReq, kGetDiagnostics, location, retries, retryInterval, startTime, stopTime)
};
struct GetDiagnosticsRsp {
std::optional<std::string> fileName;
CHARGELAB_JSON_INTRUSIVE(GetDiagnosticsRsp, fileName)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_GET_DIAGNOSTICS_H

View File

@@ -0,0 +1,20 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_GET_LOCAL_LIST_VERSION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_GET_LOCAL_LIST_VERSION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct GetLocalListVersionReq {
CHARGELAB_JSON_INTRUSIVE_EMPTY_CALL(GetLocalListVersionReq, kGetLocalListVersion)
};
struct GetLocalListVersionRsp {
int listVersion;
CHARGELAB_JSON_INTRUSIVE(GetLocalListVersionRsp, listVersion)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_GET_LOCAL_LIST_VERSION_H

View File

@@ -0,0 +1,22 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_HEARTBEAT_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_HEARTBEAT_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct HeartbeatReq {
CHARGELAB_JSON_INTRUSIVE_EMPTY_CALL(HeartbeatReq, kHeartbeat)
};
struct HeartbeatRsp {
DateTime currentTime;
CHARGELAB_JSON_INTRUSIVE(HeartbeatRsp, currentTime)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_HEARTBEAT_H

View File

@@ -0,0 +1,25 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_METER_VALUES_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_METER_VALUES_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/meter_value.h"
#include "openocpp/protocol/common/vector.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct MeterValuesReq {
int connectorId;
std::optional<int> transactionId;
std::vector<MeterValue> meterValue;
CHARGELAB_JSON_INTRUSIVE_CALL(MeterValuesReq, kMeterValues, connectorId, transactionId, meterValue)
};
struct MeterValuesRsp {
CHARGELAB_JSON_INTRUSIVE_EMPTY(MeterValuesRsp)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_METER_VALUES_H

View File

@@ -0,0 +1,28 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_REMOTE_START_TRANSACTION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_REMOTE_START_TRANSACTION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/protocol/ocpp1_6/types/id_token.h"
#include "openocpp/protocol/ocpp1_6/types/charging_profile.h"
#include "openocpp/protocol/ocpp1_6/types/remote_start_stop_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct RemoteStartTransactionReq {
std::optional<int> connectorId;
IdToken idTag;
std::optional<ChargingProfile> chargingProfile;
CHARGELAB_JSON_INTRUSIVE_CALL(RemoteStartTransactionReq, kRemoteStartTransaction, connectorId, idTag, chargingProfile)
};
struct RemoteStartTransactionRsp {
RemoteStartStopStatus status;
CHARGELAB_JSON_INTRUSIVE(RemoteStartTransactionRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_REMOTE_START_TRANSACTION_H

View File

@@ -0,0 +1,24 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_REMOTE_STOP_TRANSACTION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_REMOTE_STOP_TRANSACTION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/protocol/ocpp1_6/types/remote_start_stop_status.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct RemoteStopTransactionReq {
int transactionId;
CHARGELAB_JSON_INTRUSIVE_CALL(RemoteStopTransactionReq, kRemoteStopTransaction, transactionId)
};
struct RemoteStopTransactionRsp {
RemoteStartStopStatus status;
CHARGELAB_JSON_INTRUSIVE(RemoteStopTransactionRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_REMOTE_STOP_TRANSACTION_H

View File

@@ -0,0 +1,29 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_RESERVE_NOW_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_RESERVE_NOW_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
#include "openocpp/protocol/ocpp1_6/types/id_token.h"
#include "openocpp/protocol/ocpp1_6/types/reservation_status.h"
#include "openocpp/helpers/json.h"
#include <optional>
namespace chargelab::ocpp1_6 {
struct ReserveNowReq {
int connectorId;
DateTime expiryDate;
IdToken idTag;
std::optional<IdToken> parentIdTag;
int reservationId;
CHARGELAB_JSON_INTRUSIVE_CALL(ReserveNowReq, kReserveNow, connectorId, expiryDate, idTag, parentIdTag, reservationId)
};
struct ReserveNowRsp {
ReservationStatus status;
CHARGELAB_JSON_INTRUSIVE(ReserveNowRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_RESERVE_NOW_H

View File

@@ -0,0 +1,22 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_RESET_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_RESET_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/reset_type.h"
#include "openocpp/protocol/ocpp1_6/types/reset_status.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct ResetReq {
ResetType type;
CHARGELAB_JSON_INTRUSIVE_CALL(ResetReq, kReset, type)
};
struct ResetRsp {
ResetStatus status;
CHARGELAB_JSON_INTRUSIVE(ResetRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_RESET_H

View File

@@ -0,0 +1,25 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_SEND_LOCAL_LIST_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_SEND_LOCAL_LIST_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/authorization_data.h"
#include "openocpp/protocol/ocpp1_6/types/update_type.h"
#include "openocpp/protocol/ocpp1_6/types/update_status.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct SendLocalListReq {
int listVersion;
std::optional<std::vector<AuthorizationData>> localAuthorizationList;
UpdateType updateType;
CHARGELAB_JSON_INTRUSIVE_CALL(SendLocalListReq, kSendLocalList, listVersion, localAuthorizationList, updateType)
};
struct SendLocalListRsp {
UpdateStatus status;
CHARGELAB_JSON_INTRUSIVE(SendLocalListRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_SEND_LOCAL_LIST_H

View File

@@ -0,0 +1,23 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_SET_CHARGING_PROFILE_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_SET_CHARGING_PROFILE_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/charging_profile.h"
#include "openocpp/protocol/ocpp1_6/types/charging_profile_status.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct SetChargingProfileReq {
int connectorId;
ChargingProfile csChargingProfiles;
CHARGELAB_JSON_INTRUSIVE_CALL(SetChargingProfileReq, kSetChargingProfile, connectorId, csChargingProfiles)
};
struct SetChargingProfileRsp {
ChargingProfileStatus status;
CHARGELAB_JSON_INTRUSIVE(SetChargingProfileRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_SET_CHARGING_PROFILE_H

View File

@@ -0,0 +1,31 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_START_TRANSACTION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_START_TRANSACTION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/protocol/ocpp1_6/types/id_token.h"
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
#include "openocpp/protocol/ocpp1_6/types/id_tag_info.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct StartTransactionReq {
int connectorId;
IdToken idTag;
int meterStart;
std::optional<int> reservationId;
DateTime timestamp;
CHARGELAB_JSON_INTRUSIVE_CALL(StartTransactionReq, kStartTransaction, connectorId, idTag, meterStart, reservationId, timestamp)
};
struct StartTransactionRsp {
IdTagInfo idTagInfo;
int transactionId;
CHARGELAB_JSON_INTRUSIVE(StartTransactionRsp, idTagInfo, transactionId)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_START_TRANSACTION_H

View File

@@ -0,0 +1,29 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_STATUS_NOTIFICATION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_STATUS_NOTIFICATION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/charge_point_error_code.h"
#include "openocpp/protocol/ocpp1_6/types/charge_point_status.h"
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
#include "openocpp/protocol/ocpp1_6/types/ci_string_type.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct StatusNotificationReq {
int connectorId;
ChargePointErrorCode errorCode;
std::optional<CiString50Type> info;
ChargePointStatus status;
std::optional<DateTime> timestamp;
std::optional<CiString255Type> vendorId;
std::optional<CiString50Type> vendorErrorCode;
CHARGELAB_JSON_INTRUSIVE_CALL(StatusNotificationReq, kStatusNotification, connectorId, errorCode, info, status, timestamp, vendorId, vendorErrorCode)
};
struct StatusNotificationRsp {
CHARGELAB_JSON_INTRUSIVE_EMPTY(StatusNotificationRsp)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_STATUS_NOTIFICATION_H

View File

@@ -0,0 +1,32 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_STOP_TRANSACTION_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_STOP_TRANSACTION_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/id_token.h"
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
#include "openocpp/protocol/ocpp1_6/types/reason.h"
#include "openocpp/protocol/ocpp1_6/types/meter_value.h"
#include "openocpp/protocol/ocpp1_6/types/id_tag_info.h"
#include "openocpp/helpers/json.h"
#include <string>
namespace chargelab::ocpp1_6 {
struct StopTransactionReq {
IdToken idTag;
int meterStop;
DateTime timestamp;
int transactionId;
Reason reason;
std::vector<MeterValue> transactionData;
CHARGELAB_JSON_INTRUSIVE_CALL(StopTransactionReq, kStopTransaction, idTag, meterStop, timestamp, transactionId, reason, transactionData)
};
struct StopTransactionRsp {
std::optional<IdTagInfo> idTagInfo;
CHARGELAB_JSON_INTRUSIVE(StopTransactionRsp, idTagInfo)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_STOP_TRANSACTION_H

View File

@@ -0,0 +1,23 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_TRIGGER_MESSAGE_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_TRIGGER_MESSAGE_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/message_trigger.h"
#include "openocpp/protocol/ocpp1_6/types/trigger_message_status.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct TriggerMessageReq {
MessageTrigger requestedMessage;
std::optional<int> connectorId;
CHARGELAB_JSON_INTRUSIVE_CALL(TriggerMessageReq, kTriggerMessage, requestedMessage, connectorId)
};
struct TriggerMessageRsp {
TriggerMessageStatus status;
CHARGELAB_JSON_INTRUSIVE(TriggerMessageRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_TRIGGER_MESSAGE_H

View File

@@ -0,0 +1,21 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_UNLOCK_CONNECTOR_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_UNLOCK_CONNECTOR_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/unlock_status.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct UnlockConnectorReq {
int connectorId;
CHARGELAB_JSON_INTRUSIVE_CALL(UnlockConnectorReq, kUnlockConnector, connectorId)
};
struct UnlockConnectorRsp {
UnlockStatus status;
CHARGELAB_JSON_INTRUSIVE(UnlockConnectorRsp, status)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_UNLOCK_CONNECTOR_H

View File

@@ -0,0 +1,24 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_UPDATE_FIRMWARE_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_UPDATE_FIRMWARE_H
#include "openocpp/protocol/ocpp1_6/types/action_id.h"
#include "openocpp/protocol/ocpp1_6/types/any_uri.h"
#include "openocpp/protocol/ocpp1_6/types/date_time.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct UpdateFirmwareReq {
AnyUri location;
std::optional<int> retries;
DateTime retrieveDate;
std::optional<int> retryInterval;
CHARGELAB_JSON_INTRUSIVE_CALL(UpdateFirmwareReq, kUpdateFirmware, location, retries, retrieveDate, retryInterval)
};
struct UpdateFirmwareRsp {
CHARGELAB_JSON_INTRUSIVE_EMPTY(UpdateFirmwareRsp)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_UPDATE_FIRMWARE_H

View File

@@ -0,0 +1,40 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_ACTION_ID_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_ACTION_ID_H
#include "openocpp/helpers/json.h"
#define CHARGELAB_OCPP_1_6_ACTION_IDS \
Authorize, \
BootNotification, \
CancelReservation, \
ChangeAvailability, \
ChangeConfiguration, \
ClearCache, \
ClearChargingProfile, \
DataTransfer, \
DiagnosticsStatusNotification, \
FirmwareStatusNotification, \
GetCompositeSchedule, \
GetConfiguration, \
GetDiagnostics, \
GetLocalListVersion, \
Heartbeat, \
MeterValues, \
RemoteStartTransaction, \
RemoteStopTransaction, \
ReserveNow, \
Reset, \
SendLocalList, \
SetChargingProfile, \
StartTransaction, \
StatusNotification, \
StopTransaction, \
TriggerMessage, \
UnlockConnector, \
UpdateFirmware
namespace chargelab::ocpp1_6 {
CHARGELAB_JSON_ENUM(ActionId, CHARGELAB_OCPP_1_6_ACTION_IDS)
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_ACTION_ID_H

View File

@@ -0,0 +1,26 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_ANY_URI_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_ANY_URI_H
#include <string>
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
class AnyUri {
public:
AnyUri() {}
explicit AnyUri(std::string value) : value_(std::move(value)) {}
AnyUri(const AnyUri& other) = default;
AnyUri& operator=(const AnyUri& other) = default;
CHARGELAB_JSON_AS_PRIMITIVE(AnyUri, value_)
[[nodiscard]] std::string const& value() const {
return value_;
}
private:
std::string value_;
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_ANY_URI_H

View File

@@ -0,0 +1,19 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZATION_DATA_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZATION_DATA_H
#include <string>
#include "openocpp/protocol/ocpp1_6/types/id_tag_info.h"
#include "openocpp/protocol/ocpp1_6/types/id_token.h"
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
struct AuthorizationData {
std::optional<IdToken> idTag;
std::optional<IdTagInfo> idTagInfo;
CHARGELAB_JSON_INTRUSIVE(AuthorizationData, idTag, idTagInfo)
};
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZATION_DATA_H

View File

@@ -0,0 +1,16 @@
#ifndef CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZATION_STATUS_H
#define CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZATION_STATUS_H
#include "openocpp/helpers/json.h"
namespace chargelab::ocpp1_6 {
CHARGELAB_JSON_ENUM(AuthorizationStatus,
Accepted,
Blocked,
Expired,
Invalid,
ConcurrentTx
)
}
#endif //CHARGELAB_OPEN_FIRMWARE_1_6_AUTHORIZATION_STATUS_H

Some files were not shown because too many files have changed in this diff Show More