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,193 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "energyImpl.hpp"
#include "energy_schedule_utils.hpp"
#include <algorithm>
#include <chrono>
#include <date/date.h>
#include <date/tz.h>
#include <string_view>
#include <utils/date.hpp>
namespace module {
namespace energy_grid {
void energyImpl::init() {
auto energy_state_handle = energy_state.handle();
energy_state_handle->energy_flow_request.uuid = mod->info.id;
energy_state_handle->energy_flow_request.node_type = types::energy::NodeType::Generic;
source_cfg = mod->info.id + "/module_config";
// Initialize with sane defaults
energy_state_handle->energy_flow_request.schedule_import = get_local_schedule();
energy_state_handle->energy_flow_request.schedule_export = get_local_schedule();
for (auto& entry : mod->r_energy_consumer) {
entry->subscribe_energy_flow_request([this](types::energy::EnergyFlowRequest const& e) {
// Received new energy_flow_request object from a child. Update in the cached object and republish.
auto energy_state_handle = energy_state.handle();
auto& children = energy_state_handle->energy_flow_request.children;
auto children_it = std::find_if(children.begin(), children.end(), [&e](const auto& child) {
return std::string_view{child.uuid} == std::string_view{e.uuid};
});
if (children_it != children.end()) {
*children_it = e;
} else {
children.push_back(std::move(e));
}
publish_complete_energy_object(*energy_state_handle);
});
}
if (!mod->r_powermeter.empty()) {
mod->r_powermeter[0]->subscribe_powermeter([this](types::powermeter::Powermeter const& p) {
EVLOG_debug << "Incoming powermeter readings: " << p;
auto energy_state_handle = energy_state.handle();
energy_state_handle->energy_flow_request.energy_usage_root = p;
publish_complete_energy_object(*energy_state_handle);
});
}
if (!mod->r_price_information.empty()) {
mod->r_price_information[0]->subscribe_energy_pricing(
[this](types::energy_price_information::EnergyPriceSchedule p) {
EVLOG_debug << "Incoming price schedule: " << p;
auto energy_state_handle = energy_state.handle();
energy_state_handle->energy_pricing = p;
publish_complete_energy_object(*energy_state_handle);
});
}
}
types::energy::ScheduleReqEntry energyImpl::get_local_schedule_req_entry() {
types::energy::ScheduleReqEntry local_schedule;
auto tp = date::utc_clock::now();
local_schedule.timestamp =
Everest::Date::to_rfc3339(date::floor<std::chrono::hours>(tp) + date::get_leap_second_info(tp).elapsed);
local_schedule.limits_to_root.ac_max_phase_count = {mod->config.phase_count, source_cfg};
local_schedule.limits_to_root.ac_max_current_A = {static_cast<float>(mod->config.fuse_limit_A), source_cfg};
local_schedule.limits_to_leaves.ac_max_phase_count = {mod->config.phase_count, source_cfg};
local_schedule.limits_to_leaves.ac_max_current_A = {static_cast<float>(mod->config.fuse_limit_A), source_cfg};
return local_schedule;
}
std::vector<types::energy::ScheduleReqEntry> energyImpl::get_local_schedule() {
const auto local_schedule = get_local_schedule_req_entry();
return std::vector<types::energy::ScheduleReqEntry>({local_schedule});
}
void energyImpl::set_external_limits(types::energy::ExternalLimits& l) {
auto energy_state_handle = energy_state.handle();
// Process import schedule
energy_state_handle->energy_flow_request.schedule_import = l.schedule_import;
if (not energy_state_handle->energy_flow_request.schedule_import.empty()) {
module::energy_grid::process_schedule_with_limits(
energy_state_handle->energy_flow_request.schedule_import, source_cfg, mod->config.fuse_limit_A,
mod->config.phase_count, mod->config.nominal_voltage_V, mod->config.enhance_external_schedule);
} else {
// At least add our local config limit even if the external limit did not set an import schedule
energy_state_handle->energy_flow_request.schedule_import = get_local_schedule();
}
// Process export schedule
energy_state_handle->energy_flow_request.schedule_export = l.schedule_export;
if (not energy_state_handle->energy_flow_request.schedule_export.empty()) {
module::energy_grid::process_schedule_with_limits(
energy_state_handle->energy_flow_request.schedule_export, source_cfg, mod->config.fuse_limit_A,
mod->config.phase_count, mod->config.nominal_voltage_V, mod->config.enhance_external_schedule);
} else {
// At least add our local config limit even if the external limit did not set an export schedule
energy_state_handle->energy_flow_request.schedule_export = get_local_schedule();
}
energy_state_handle->energy_flow_request.schedule_setpoints = l.schedule_setpoints;
}
void energyImpl::publish_complete_energy_object(const EnergyState& state) {
// This method is always called from contexts that already hold the energy_state lock
const auto& energy_flow_request = state.energy_flow_request;
const auto& energy_pricing_schedule_export = state.energy_pricing.schedule_export;
if (not energy_flow_request.schedule_export.empty() and not energy_pricing_schedule_export.empty()) {
types::energy::EnergyFlowRequest energy_complete = energy_flow_request;
merge_price_into_schedule(energy_complete.schedule_export, energy_pricing_schedule_export);
publish_energy_flow_request(energy_complete);
} else {
publish_energy_flow_request(energy_flow_request);
}
}
void energyImpl::merge_price_into_schedule(std::vector<types::energy::ScheduleReqEntry>& schedule,
const std::vector<types::energy_price_information::PricePerkWh>& price) {
auto it_schedule = schedule.begin();
auto it_price = price.begin();
std::vector<types::energy::ScheduleReqEntry> joined_schedule;
// The first element is already valid now even if the timestamp is in the future (per agreement)
auto next_entry_schedule = *it_schedule;
auto next_entry_price = *it_price;
auto currently_valid_entry_schedule = next_entry_schedule;
auto currently_valid_entry_price = next_entry_price;
while (it_schedule != schedule.end() && it_price != price.end()) {
auto tp_schedule = Everest::Date::from_rfc3339(next_entry_schedule.timestamp);
auto tp_price = Everest::Date::from_rfc3339(next_entry_price.timestamp);
if ((tp_schedule < tp_price && it_schedule != schedule.end()) || it_price == price.end()) {
currently_valid_entry_schedule = next_entry_schedule;
auto joined_entry = currently_valid_entry_schedule;
joined_entry.price_per_kwh = currently_valid_entry_price;
joined_schedule.push_back(joined_entry);
it_schedule++;
if (it_schedule != schedule.end()) {
next_entry_schedule = *it_schedule;
}
continue;
}
if ((tp_price < tp_schedule && it_price != price.end()) || it_schedule == schedule.end()) {
currently_valid_entry_price = next_entry_price;
auto joined_entry = currently_valid_entry_schedule;
joined_entry.price_per_kwh = currently_valid_entry_price;
joined_entry.timestamp = currently_valid_entry_price.timestamp;
joined_schedule.push_back(joined_entry);
it_price++;
if (it_price != price.end()) {
next_entry_price = *it_price;
}
continue;
}
}
}
void energyImpl::ready() {
auto energy_state_handle = energy_state.handle();
// publish own limits at least once
publish_energy_flow_request(energy_state_handle->energy_flow_request);
mod->signalExternalLimit.connect([this](types::energy::ExternalLimits& l) { set_external_limits(l); });
}
void energyImpl::handle_enforce_limits(types::energy::EnforcedLimits& value) {
auto energy_state_handle = energy_state.handle();
// route to children if it is not for me
// FIXME: this sends it to all children, we could do a lookup on which branch it actually is
if (value.uuid != energy_state_handle->energy_flow_request.uuid) {
for (auto& entry : mod->r_energy_consumer) {
entry->call_enforce_limits(value);
}
}
};
} // namespace energy_grid
} // namespace module

View File

@@ -0,0 +1,84 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef ENERGY_GRID_ENERGY_IMPL_HPP
#define ENERGY_GRID_ENERGY_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/energy/Implementation.hpp>
#include "../EnergyNode.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
#include <everest/util/async/monitor.hpp>
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace energy_grid {
struct Conf {};
class energyImpl : public energyImplBase {
public:
energyImpl() = delete;
energyImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<EnergyNode>& mod, Conf& config) :
energyImplBase(ev, "energy_grid"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual void handle_enforce_limits(types::energy::EnforcedLimits& value) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<EnergyNode>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
// Energy state protected by monitor for thread-safe access
struct EnergyState {
// subtree including children
types::energy::EnergyFlowRequest energy_flow_request;
// contains only the pricing informations last update
types::energy_price_information::EnergyPriceSchedule energy_pricing;
};
everest::lib::util::monitor<EnergyState> energy_state;
types::energy::ScheduleReqEntry get_local_schedule_req_entry();
std::vector<types::energy::ScheduleReqEntry> get_local_schedule();
void publish_complete_energy_object(const EnergyState& state);
void set_external_limits(types::energy::ExternalLimits& l);
void merge_price_into_schedule(std::vector<types::energy::ScheduleReqEntry>& schedule,
const std::vector<types::energy_price_information::PricePerkWh>& price);
std::string source_cfg;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// Standalone function for schedule processing (used by tests)
void process_schedule_with_limits(std::vector<types::energy::ScheduleReqEntry>& schedule, const std::string& source,
double fuse_limit_A, int phase_count, double nominal_voltage_V,
bool enhance_with_current_limits);
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace energy_grid
} // namespace module
#endif // ENERGY_GRID_ENERGY_IMPL_HPP

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#include "energy_schedule_utils.hpp"
namespace module {
namespace energy_grid {
void process_schedule_with_limits(std::vector<types::energy::ScheduleReqEntry>& schedule, const std::string& source,
double fuse_limit_A, int phase_count, double nominal_voltage_V,
bool enhance_with_current_limits) {
for (auto& entry : schedule) {
// Enhance with current limits from power if enabled
if (enhance_with_current_limits) {
// Determine phase count to use (schedule entry takes priority over module config)
int effective_phase_count = phase_count;
if (entry.limits_to_root.ac_max_phase_count.has_value() &&
entry.limits_to_root.ac_max_phase_count.value().value > 0) {
effective_phase_count = entry.limits_to_root.ac_max_phase_count.value().value;
} else if (entry.limits_to_leaves.ac_max_phase_count.has_value() &&
entry.limits_to_leaves.ac_max_phase_count.value().value > 0) {
effective_phase_count = entry.limits_to_leaves.ac_max_phase_count.value().value;
}
// Default to 1 phase if no valid phase count is available
if (effective_phase_count <= 0) {
effective_phase_count = 1;
}
// Calculate current from power for limits_to_root (only if not already set)
if (entry.limits_to_root.total_power_W.has_value() &&
entry.limits_to_root.total_power_W.value().value > 0 && nominal_voltage_V > 0 &&
!entry.limits_to_root.ac_max_current_A.has_value()) {
float calculated_current = static_cast<float>(entry.limits_to_root.total_power_W.value().value /
(nominal_voltage_V * effective_phase_count));
entry.limits_to_root.ac_max_current_A = {calculated_current, source};
}
// Note: limits_to_leaves current limits are not modified to match fuse limit behavior
}
// Apply fuse limit to limits_to_root (as a safety constraint)
if (!entry.limits_to_root.ac_max_current_A.has_value() ||
entry.limits_to_root.ac_max_current_A->value > fuse_limit_A) {
entry.limits_to_root.ac_max_current_A = {static_cast<float>(fuse_limit_A), source};
}
// Apply phase count limit to limits_to_root (as a safety constraint, same model as fuse limit)
if (phase_count > 0 && (!entry.limits_to_root.ac_max_phase_count.has_value() ||
entry.limits_to_root.ac_max_phase_count->value > phase_count)) {
entry.limits_to_root.ac_max_phase_count = {phase_count, source};
}
}
}
} // namespace energy_grid
} // namespace module

View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright Pionix GmbH and Contributors to EVerest
#ifndef ENERGY_SCHEDULE_UTILS_HPP
#define ENERGY_SCHEDULE_UTILS_HPP
#include <generated/types/energy.hpp>
#include <string>
#include <vector>
namespace module {
namespace energy_grid {
/**
* @brief Processes energy schedule entries with limits and fuse constraints
*
* This function applies fuse limits to schedule entries and optionally enhances
* them with current limits calculated from power values.
*
* @param schedule The schedule entries to process
* @param source The source identifier for any modifications made
* @param fuse_limit_A The fuse limit in amperes to apply as a safety constraint
* @param phase_count The default phase count to use for calculations
* @param nominal_voltage_V The nominal voltage to use for power-to-current calculations
* @param enhance_with_current_limits If true, calculates current limits from power values
*/
void process_schedule_with_limits(std::vector<types::energy::ScheduleReqEntry>& schedule, const std::string& source,
double fuse_limit_A, int phase_count, double nominal_voltage_V,
bool enhance_with_current_limits);
} // namespace energy_grid
} // namespace module
#endif // ENERGY_SCHEDULE_UTILS_HPP