Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
# template version 3
|
||||
#
|
||||
|
||||
# module setup:
|
||||
# - ${MODULE_NAME}: module name
|
||||
ev_setup_cpp_module()
|
||||
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
# insert your custom targets and additional config variables here
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
main/can_broker.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(${MODULE_NAME}
|
||||
PRIVATE
|
||||
can_protocols::dpm1000
|
||||
everest::gpio
|
||||
)
|
||||
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
|
||||
|
||||
target_sources(${MODULE_NAME}
|
||||
PRIVATE
|
||||
"main/power_supply_DCImpl.cpp"
|
||||
)
|
||||
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
# insert other things like install cmds etc here
|
||||
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "DPM1000.hpp"
|
||||
|
||||
namespace module {
|
||||
|
||||
void DPM1000::init() {
|
||||
invoke_init(*p_main);
|
||||
}
|
||||
|
||||
void DPM1000::ready() {
|
||||
invoke_ready(*p_main);
|
||||
}
|
||||
|
||||
} // namespace module
|
||||
@@ -0,0 +1,69 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef DPM1000_HPP
|
||||
#define DPM1000_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 2
|
||||
//
|
||||
|
||||
#include "ld-ev.hpp"
|
||||
|
||||
// headers for provided interface implementations
|
||||
#include <generated/interfaces/power_supply_DC/Implementation.hpp>
|
||||
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
// insert your custom include headers here
|
||||
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
|
||||
|
||||
namespace module {
|
||||
|
||||
struct Conf {
|
||||
std::string device;
|
||||
int device_address;
|
||||
double power_limit_W;
|
||||
double current_limit_A;
|
||||
double voltage_limit_V;
|
||||
std::string series_parallel_mode;
|
||||
std::string discharge_gpio_chip;
|
||||
int discharge_gpio_line;
|
||||
bool discharge_gpio_polarity;
|
||||
bool debug_print_all_telemetry;
|
||||
};
|
||||
|
||||
class DPM1000 : public Everest::ModuleBase {
|
||||
public:
|
||||
DPM1000() = delete;
|
||||
DPM1000(const ModuleInfo& info, std::unique_ptr<power_supply_DCImplBase> p_main, Conf& config) :
|
||||
ModuleBase(info), p_main(std::move(p_main)), config(config){};
|
||||
|
||||
const std::unique_ptr<power_supply_DCImplBase> p_main;
|
||||
const Conf& config;
|
||||
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
// insert your public definitions here
|
||||
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
|
||||
|
||||
protected:
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
// insert your protected definitions here
|
||||
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
|
||||
|
||||
private:
|
||||
friend class LdEverest;
|
||||
void init();
|
||||
void ready();
|
||||
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
// insert your private definitions here
|
||||
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
|
||||
};
|
||||
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
// insert other definitions here
|
||||
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
|
||||
|
||||
} // namespace module
|
||||
|
||||
#endif // DPM1000_HPP
|
||||
@@ -0,0 +1,221 @@
|
||||
#include "can_broker.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <net/if.h>
|
||||
#include <poll.h>
|
||||
#include <sys/eventfd.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace dpm1000 = can::protocol::dpm1000;
|
||||
|
||||
// FIXME (aw): this helper doesn't really belong here
|
||||
static void throw_with_error(const std::string& msg) {
|
||||
throw std::runtime_error(msg + ": (" + std::string(strerror(errno)) + ")");
|
||||
}
|
||||
|
||||
CanBroker::CanBroker(const std::string& interface_name, uint8_t _device_src) : device_src(_device_src) {
|
||||
can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW);
|
||||
|
||||
if (can_fd == -1) {
|
||||
throw_with_error("Failed to open socket");
|
||||
}
|
||||
|
||||
// retrieve interface index from interface name
|
||||
struct ifreq ifr;
|
||||
|
||||
if (interface_name.size() >= sizeof(ifr.ifr_name)) {
|
||||
throw_with_error("Interface name too long: " + interface_name);
|
||||
} else {
|
||||
strcpy(ifr.ifr_name, interface_name.c_str());
|
||||
}
|
||||
|
||||
if (ioctl(can_fd, SIOCGIFINDEX, &ifr) == -1) {
|
||||
throw_with_error("Failed with ioctl/SIOCGIFINDEX on interface " + interface_name);
|
||||
}
|
||||
|
||||
// bind to the interface
|
||||
struct sockaddr_can addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.can_family = AF_CAN;
|
||||
addr.can_ifindex = ifr.ifr_ifindex;
|
||||
|
||||
if (bind(can_fd, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)) == -1) {
|
||||
throw_with_error("Failed with bind");
|
||||
}
|
||||
|
||||
event_fd = eventfd(0, 0);
|
||||
|
||||
loop_thread = std::thread(&CanBroker::loop, this);
|
||||
}
|
||||
|
||||
CanBroker::~CanBroker() {
|
||||
uint64_t quit_value = 1;
|
||||
write(event_fd, &quit_value, sizeof(quit_value));
|
||||
|
||||
loop_thread.join();
|
||||
|
||||
close(can_fd);
|
||||
close(event_fd);
|
||||
}
|
||||
|
||||
void CanBroker::loop() {
|
||||
std::array<struct pollfd, 2> pollfds = {{
|
||||
{can_fd, POLLIN, 0},
|
||||
{event_fd, POLLIN, 0},
|
||||
}};
|
||||
|
||||
while (true) {
|
||||
const auto poll_result = poll(pollfds.data(), pollfds.size(), -1);
|
||||
|
||||
if (poll_result == 0) {
|
||||
// timeout
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pollfds[0].revents & POLLIN) {
|
||||
struct can_frame frame;
|
||||
read(can_fd, &frame, sizeof(frame));
|
||||
handle_can_input(frame);
|
||||
}
|
||||
|
||||
if (pollfds[1].revents & POLLIN) {
|
||||
uint64_t tmp;
|
||||
read(event_fd, &tmp, sizeof(tmp));
|
||||
// new event, for now, we do not care, later on we could check, if it is an exit event code
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CanBroker::set_state(bool enabled) {
|
||||
struct can_frame frame;
|
||||
dpm1000::power_on(frame, enabled, enabled);
|
||||
dpm1000::set_header(frame, monitor_id, device_src);
|
||||
|
||||
write_to_can(frame);
|
||||
|
||||
// Do an extra module ON command as sometimes the bits in the header are not enough to actually switch on
|
||||
set_data_int(dpm1000::def::SetValueType::SWITCH_ON_OFF_SETTING, (enabled ? 0 : 1));
|
||||
}
|
||||
|
||||
CanBroker::AccessReturnType CanBroker::dispatch_frame(const struct can_frame& frame, uint16_t id,
|
||||
uint32_t* return_payload) {
|
||||
// wait until we get access
|
||||
std::lock_guard<std::mutex> access_lock(access_mtx);
|
||||
|
||||
std::unique_lock<std::mutex> request_lock(request.mutex);
|
||||
write_to_can(frame);
|
||||
request.msg_type = id;
|
||||
request.state = CanRequest::State::ISSUED;
|
||||
|
||||
const auto finished = request.cv.wait_for(request_lock, ACCESS_TIMEOUT,
|
||||
[this]() { return request.state != CanRequest::State::ISSUED; });
|
||||
|
||||
if (not finished) {
|
||||
return AccessReturnType::TIMEOUT;
|
||||
}
|
||||
|
||||
if (request.state == CanRequest::State::FAILED) {
|
||||
return AccessReturnType::FAILED;
|
||||
}
|
||||
|
||||
// success
|
||||
if (return_payload) {
|
||||
memcpy(return_payload, request.response.data(), sizeof(std::remove_pointer_t<decltype(return_payload)>));
|
||||
}
|
||||
|
||||
return AccessReturnType::SUCCESS;
|
||||
}
|
||||
|
||||
CanBroker::AccessReturnType CanBroker::read_data(dpm1000::def::ReadValueType value_type, float& result) {
|
||||
const auto id = static_cast<std::underlying_type_t<decltype(value_type)>>(value_type);
|
||||
|
||||
struct can_frame frame;
|
||||
dpm1000::request_data(frame, value_type);
|
||||
dpm1000::set_header(frame, monitor_id, device_src);
|
||||
|
||||
uint32_t tmp;
|
||||
const auto status = dispatch_frame(frame, id, &tmp);
|
||||
|
||||
if (status == AccessReturnType::SUCCESS) {
|
||||
memcpy(&result, &tmp, sizeof(result));
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
CanBroker::AccessReturnType CanBroker::read_data_int(dpm1000::def::ReadValueType value_type, uint32_t& result) {
|
||||
const auto id = static_cast<std::underlying_type_t<decltype(value_type)>>(value_type);
|
||||
|
||||
struct can_frame frame;
|
||||
dpm1000::request_data(frame, value_type);
|
||||
dpm1000::set_header(frame, monitor_id, device_src);
|
||||
|
||||
uint32_t tmp;
|
||||
const auto status = dispatch_frame(frame, id, &tmp);
|
||||
|
||||
if (status == AccessReturnType::SUCCESS) {
|
||||
result = tmp;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
CanBroker::AccessReturnType CanBroker::set_data(dpm1000::def::SetValueType value_type, float payload) {
|
||||
const auto id = static_cast<std::underlying_type_t<decltype(value_type)>>(value_type);
|
||||
|
||||
uint8_t raw_payload[sizeof(payload)];
|
||||
memcpy(raw_payload, &payload, sizeof(payload));
|
||||
|
||||
struct can_frame frame;
|
||||
dpm1000::set_data(frame, value_type, {raw_payload[3], raw_payload[2], raw_payload[1], raw_payload[0]});
|
||||
dpm1000::set_header(frame, monitor_id, device_src);
|
||||
|
||||
return dispatch_frame(frame, id);
|
||||
}
|
||||
|
||||
CanBroker::AccessReturnType CanBroker::set_data_int(dpm1000::def::SetValueType value_type, uint32_t payload) {
|
||||
const auto id = static_cast<std::underlying_type_t<decltype(value_type)>>(value_type);
|
||||
|
||||
uint8_t raw_payload[sizeof(payload)];
|
||||
memcpy(raw_payload, &payload, sizeof(payload));
|
||||
|
||||
struct can_frame frame;
|
||||
dpm1000::set_data(frame, value_type, {raw_payload[3], raw_payload[2], raw_payload[1], raw_payload[0]});
|
||||
dpm1000::set_header(frame, monitor_id, device_src);
|
||||
|
||||
return dispatch_frame(frame, id);
|
||||
}
|
||||
|
||||
void CanBroker::write_to_can(const struct can_frame& frame) {
|
||||
write(can_fd, &frame, sizeof(frame));
|
||||
}
|
||||
|
||||
void CanBroker::handle_can_input(const struct can_frame& frame) {
|
||||
if (((frame.can_id >> dpm1000::def::MESSAGE_HEADER_BIT_SHIFT) & dpm1000::def::MESSAGE_HEADER_MASK) !=
|
||||
dpm1000::def::MESSAGE_HEADER) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::mutex> request_lock(request.mutex);
|
||||
if ((request.state != CanRequest::State::ISSUED) or (request.msg_type != dpm1000::parse_msg_type(frame))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dpm1000::is_error_flag_set(frame)) {
|
||||
request.state = CanRequest::State::FAILED;
|
||||
} else {
|
||||
// this is ugly
|
||||
for (auto i = 0; i < request.response.size(); ++i) {
|
||||
request.response[i] = frame.data[7 - i];
|
||||
}
|
||||
request.state = CanRequest::State::COMPLETED;
|
||||
}
|
||||
|
||||
request_lock.unlock();
|
||||
request.cv.notify_one();
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef DPM1000_MAIN_DC_CAN_BROKER_HPP
|
||||
#define DPM1000_MAIN_DC_CAN_BROKER_HPP
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include <everest/can/protocol/dpm1000.hpp>
|
||||
|
||||
struct CanRequest {
|
||||
enum class State {
|
||||
IDLE,
|
||||
ISSUED,
|
||||
COMPLETED,
|
||||
FAILED,
|
||||
} state{State::IDLE};
|
||||
|
||||
uint16_t msg_type;
|
||||
std::array<uint8_t, 4> response;
|
||||
std::condition_variable cv;
|
||||
std::mutex mutex;
|
||||
};
|
||||
|
||||
class CanBroker {
|
||||
public:
|
||||
enum class AccessReturnType {
|
||||
SUCCESS,
|
||||
FAILED,
|
||||
TIMEOUT,
|
||||
NOT_READY,
|
||||
};
|
||||
CanBroker(const std::string& interface_name, uint8_t _device_src);
|
||||
|
||||
AccessReturnType read_data(can::protocol::dpm1000::def::ReadValueType, float& result);
|
||||
AccessReturnType read_data_int(can::protocol::dpm1000::def::ReadValueType, uint32_t& result);
|
||||
|
||||
AccessReturnType set_data(can::protocol::dpm1000::def::SetValueType, float value);
|
||||
AccessReturnType set_data_int(can::protocol::dpm1000::def::SetValueType, uint32_t value);
|
||||
void set_state(bool enabled);
|
||||
|
||||
~CanBroker();
|
||||
|
||||
private:
|
||||
constexpr static auto ACCESS_TIMEOUT = std::chrono::milliseconds(250);
|
||||
void loop();
|
||||
|
||||
void write_to_can(const struct can_frame& frame);
|
||||
AccessReturnType dispatch_frame(const struct can_frame& frame, uint16_t id, uint32_t* result = nullptr);
|
||||
|
||||
void handle_can_input(const struct can_frame& frame);
|
||||
|
||||
uint8_t device_src;
|
||||
|
||||
std::mutex access_mtx;
|
||||
CanRequest request;
|
||||
|
||||
const uint8_t monitor_id{0xf0};
|
||||
|
||||
std::thread loop_thread;
|
||||
|
||||
int event_fd{-1};
|
||||
int can_fd{-1};
|
||||
};
|
||||
|
||||
#endif // DPM1000_MAIN_DC_CAN_BROKER_HPP
|
||||
@@ -0,0 +1,294 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#include "power_supply_DCImpl.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "can_broker.hpp"
|
||||
|
||||
#include <fmt/core.h>
|
||||
#include <utils/formatter.hpp>
|
||||
|
||||
std::unique_ptr<CanBroker> can_broker;
|
||||
|
||||
namespace dpm1000 = can::protocol::dpm1000;
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
static void log_status_on_fail(const std::string& msg, CanBroker::AccessReturnType status) {
|
||||
using ReturnStatus = CanBroker::AccessReturnType;
|
||||
|
||||
std::string reason;
|
||||
switch (status) {
|
||||
case ReturnStatus::FAILED:
|
||||
reason = "failed";
|
||||
break;
|
||||
case ReturnStatus::NOT_READY:
|
||||
reason = "not ready";
|
||||
break;
|
||||
case ReturnStatus::TIMEOUT:
|
||||
reason = "timeout";
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
EVLOG_info << msg << " reason: (" << reason << ")";
|
||||
}
|
||||
|
||||
static std::string alarm_to_string(uint32_t alarm) {
|
||||
std::string alarmflags;
|
||||
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::FUSE_BURN_OUT))
|
||||
alarmflags += "[FUSE_BURN_OUT]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::PFC_DCDC_COMMUNICATION_ERROR))
|
||||
alarmflags += "[PFC_DCDC_COMMUNICATION_ERROR]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::UNBALANCED_BUS_VOLTAGE))
|
||||
alarmflags += "[UNBALANCED_BUS_VOLTAGE]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::BUS_OVER_VOLTAGE))
|
||||
alarmflags += "[BUS_OVER_VOLTAGE]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::BUS_ABNORMAL_VOLTAGE))
|
||||
alarmflags += "[BUS_ABNORMAL_VOLTAGE]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::PHASE_OVER_VOLTAGE))
|
||||
alarmflags += "[PHASE_OVER_VOLTAGE]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::ID_NUMBER_REPETITION))
|
||||
alarmflags += "[ID_NUMBER_REPETITION]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::BUS_UNDER_VOLTAGE))
|
||||
alarmflags += "[BUS_UNDER_VOLTAGE]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::PHASE_LOSS))
|
||||
alarmflags += "[PHASE_LOSS]";
|
||||
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::PHASE_UNDER_VOLTAGE))
|
||||
alarmflags += "[PHASE_UNDER_VOLTAGE]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::CAN_COMMUNICATION_FAULT))
|
||||
alarmflags += "[CAN_COMMUNICATION_FAULT]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::DCDC_UNEVEN_CURRENT_SHARING))
|
||||
alarmflags += "[DCDC_UNEVEN_CURRENT_SHARING]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::PFC_POWER_OFF))
|
||||
alarmflags += "[PFC_POWER_OFF]";
|
||||
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::FAN_FULL_SPEED))
|
||||
alarmflags += "[FAN_FULL_SPEED]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::POWER_LIMITING))
|
||||
alarmflags += "[POWER_LIMITING]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::DCDC_POWER_OFF))
|
||||
alarmflags += "[DCDC_POWER_OFF]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::TEMPERATURE_POWER_LIMITING))
|
||||
alarmflags += "[TEMPERATURE_POWER_LIMITING]";
|
||||
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::AC_POWER_LIMITING))
|
||||
alarmflags += "[AC_POWER_LIMITING]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::DCDC_EEPROM_FAULTS))
|
||||
alarmflags += "[DCDC_EEPROM_FAULTS]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::FAN_FAULTS))
|
||||
alarmflags += "[FAN_FAULTS]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::DCDC_SHORT_CIRCUIT))
|
||||
alarmflags += "[DCDC_SHORT_CIRCUIT]";
|
||||
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::PFC_EEPROM_FAULTS))
|
||||
alarmflags += "[PFC_EEPROM_FAULTS]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::DCDC_OVER_TEMPERATURE))
|
||||
alarmflags += "[DCDC_OVER_TEMPERATURE]";
|
||||
if (alarm & (1 << (int)dpm1000::def::Alarm::DCDC_OUTPUT_OVER_VOLTAGE))
|
||||
alarmflags += "[DCDC_OUTPUT_OVER_VOLTAGE]";
|
||||
|
||||
return alarmflags;
|
||||
}
|
||||
|
||||
void power_supply_DCImpl::init() {
|
||||
current = 0;
|
||||
voltage = 300;
|
||||
|
||||
config_current_limit = mod->config.current_limit_A;
|
||||
config_voltage_limit = mod->config.voltage_limit_V;
|
||||
config_power_limit = mod->config.power_limit_W;
|
||||
|
||||
if (!mod->config.discharge_gpio_chip.empty()) {
|
||||
discharge_gpio.open(mod->config.discharge_gpio_chip, mod->config.discharge_gpio_line,
|
||||
!mod->config.discharge_gpio_polarity);
|
||||
discharge_gpio.set_output(false);
|
||||
}
|
||||
|
||||
can_broker = std::make_unique<CanBroker>(mod->config.device, mod->config.device_address);
|
||||
|
||||
// ensure the module is switched off
|
||||
can_broker->set_state(false);
|
||||
|
||||
// Configure module for series or parallel mode
|
||||
// 0 is automatic switching mode
|
||||
float series_parallel_mode = 0.;
|
||||
|
||||
if (mod->config.series_parallel_mode == "Series") {
|
||||
series_parallel_mode = 1050.;
|
||||
config_min_voltage_limit = 300.;
|
||||
parallel_mode = false;
|
||||
} else if (mod->config.series_parallel_mode == "Parallel") {
|
||||
series_parallel_mode = 520.;
|
||||
if (config_voltage_limit > 520) {
|
||||
config_voltage_limit = 520;
|
||||
}
|
||||
parallel_mode = true;
|
||||
}
|
||||
|
||||
// WTF: This really uses a float to set one of the three modes automatic, series or parallel.
|
||||
auto status = can_broker->set_data(dpm1000::def::SetValueType::SERIES_PARALLEL_MODE, series_parallel_mode);
|
||||
log_status_on_fail("Set current limit failed", status);
|
||||
}
|
||||
|
||||
void power_supply_DCImpl::ready() {
|
||||
types::power_supply_DC::Capabilities caps;
|
||||
caps.bidirectional = false;
|
||||
caps.max_export_current_A = config_current_limit;
|
||||
caps.max_export_voltage_V = config_voltage_limit;
|
||||
caps.min_export_current_A = 0;
|
||||
caps.min_export_voltage_V = config_min_voltage_limit;
|
||||
caps.max_export_power_W = config_power_limit;
|
||||
caps.current_regulation_tolerance_A = 0.5;
|
||||
caps.peak_current_ripple_A = 1;
|
||||
caps.conversion_efficiency_export = 0.95;
|
||||
|
||||
publish_capabilities(caps);
|
||||
|
||||
while (true) {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(250));
|
||||
|
||||
// Send voltage, current and power limits
|
||||
|
||||
auto status = can_broker->set_data(dpm1000::def::SetValueType::CURRENT_LIMIT, current);
|
||||
log_status_on_fail("Set current limit failed", status);
|
||||
|
||||
status = can_broker->set_data(dpm1000::def::SetValueType::DEFAULT_CURRENT_LIMIT, 1.0);
|
||||
log_status_on_fail("Set default current limit failed", status);
|
||||
|
||||
status = can_broker->set_data(dpm1000::def::SetValueType::VOLTAGE, voltage);
|
||||
log_status_on_fail("Set voltage failed", status);
|
||||
|
||||
status = can_broker->set_data(dpm1000::def::SetValueType::POWER_LIMIT, 1.0);
|
||||
log_status_on_fail("Set current limit failed", status);
|
||||
|
||||
// Read voltage and current
|
||||
float tmp;
|
||||
types::power_supply_DC::VoltageCurrent vc;
|
||||
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::VOLTAGE, tmp);
|
||||
log_status_on_fail("Read voltage failed", status);
|
||||
if (status != CanBroker::AccessReturnType::SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
vc.voltage_V = tmp;
|
||||
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::CURRENT, tmp);
|
||||
log_status_on_fail("Read current failed", status);
|
||||
if (status != CanBroker::AccessReturnType::SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Publish voltage and current var
|
||||
// Current scaling depends on series/parallel mode operation.
|
||||
vc.current_A = tmp;
|
||||
if (parallel_mode) {
|
||||
vc.current_A *= 2.;
|
||||
}
|
||||
publish_voltage_current(vc);
|
||||
|
||||
// read alarm flags
|
||||
uint32_t alarm = 0;
|
||||
status = can_broker->read_data_int(dpm1000::def::ReadValueType::ALARM, alarm);
|
||||
log_status_on_fail("Read alarm failed", status);
|
||||
if (status == CanBroker::AccessReturnType::SUCCESS) {
|
||||
if (last_alarm_flags != alarm) {
|
||||
auto alarmflags = alarm_to_string(alarm);
|
||||
if (alarmflags != "") {
|
||||
EVLOG_warning << "Alarm flags changed: " << alarmflags;
|
||||
} else {
|
||||
EVLOG_info << "All Alarm flags cleared.";
|
||||
}
|
||||
last_alarm_flags = alarm;
|
||||
}
|
||||
}
|
||||
|
||||
// Discharge output if it is higher then setpoint voltage.
|
||||
// Note that this has no timeout, so HW must be designed to sustain the worst case load (e.g. 1000V) continously
|
||||
if (vc.voltage_V > (voltage + 10)) {
|
||||
discharge_gpio.set(true);
|
||||
} else {
|
||||
discharge_gpio.set(false);
|
||||
}
|
||||
|
||||
if (mod->config.debug_print_all_telemetry) {
|
||||
// read additional meta data
|
||||
float current_real_part = 0, current_limit = 0, dcdc_temperature = 0, ac_voltage = 0, voltage_limit = 0,
|
||||
pfc0_voltage = 0, pfc1_voltage = 0, env_temperature = 0, ac_voltage_phase_a = 0,
|
||||
ac_voltage_phase_b = 0, ac_voltage_phase_c = 0, pfc_temperature = 0, power_limit = 0;
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::CURRENT_REAL_PART, current_real_part);
|
||||
log_status_on_fail("Read CURRENT_REAL_PART failed", status);
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::CURRENT_LIMIT, current_limit);
|
||||
log_status_on_fail("Read CURRENT_LIMIT failed", status);
|
||||
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::DCDC_TEMPERATURE, dcdc_temperature);
|
||||
log_status_on_fail("Read DCDC_TEMPERATURE failed", status);
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::AC_VOLTAGE, ac_voltage);
|
||||
log_status_on_fail("Read AC_VOLTAGE failed", status);
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::VOLTAGE_LIMIT, voltage_limit);
|
||||
log_status_on_fail("Read VOLTAGE_LIMIT failed", status);
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::PFC0_VOLTAGE, pfc0_voltage);
|
||||
log_status_on_fail("Read PFC0_VOLTAGE failed", status);
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::PFC1_VOLTAGE, pfc1_voltage);
|
||||
log_status_on_fail("Read PFC1_VOLTAGE failed", status);
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::ENV_TEMPERATURE, env_temperature);
|
||||
log_status_on_fail("Read ENV_TEMPERATURE failed", status);
|
||||
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::AC_VOLTAGE_PHASE_A, ac_voltage_phase_a);
|
||||
log_status_on_fail("Read AC_VOLTAGE_PHASE_A failed", status);
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::AC_VOLTAGE_PHASE_B, ac_voltage_phase_b);
|
||||
log_status_on_fail("Read AC_VOLTAGE_PHASE_B failed", status);
|
||||
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::AC_VOLTAGE_PHASE_C, ac_voltage_phase_c);
|
||||
log_status_on_fail("Read AC_VOLTAGE_PHASE_C failed", status);
|
||||
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::PFC_TEMPERATURE, pfc_temperature);
|
||||
log_status_on_fail("Read PFC_TEMPERATURE failed", status);
|
||||
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::POWER_LIMIT, power_limit);
|
||||
log_status_on_fail("Read POWER_LIMIT failed", status);
|
||||
|
||||
status = can_broker->read_data(dpm1000::def::ReadValueType::ENV_TEMPERATURE, env_temperature);
|
||||
log_status_on_fail("Read ENV_TEMPERATURE failed", status);
|
||||
|
||||
EVLOG_info << fmt::format(
|
||||
"set_voltage {} set_current {} vc.current_A {} vc.voltage_V {} current_real_part {} current_limit {} "
|
||||
"dcdc_temperature {} ac_voltage {} "
|
||||
"voltage_limit "
|
||||
"{} pfc0_voltage {} pfc1_voltage {} env_temperature {} ac_voltage_phase_a {} ac_voltage_phase_b {} "
|
||||
"ac_voltage_phase_c {} pfc_temperature {} power_limit {}",
|
||||
voltage, current, vc.current_A, vc.voltage_V, current_real_part, current_limit, dcdc_temperature,
|
||||
ac_voltage, voltage_limit, pfc0_voltage, pfc1_voltage, env_temperature, ac_voltage_phase_a,
|
||||
ac_voltage_phase_b, ac_voltage_phase_c, pfc_temperature, power_limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void power_supply_DCImpl::handle_setMode(types::power_supply_DC::Mode& mode,
|
||||
types::power_supply_DC::ChargingPhase& phase) {
|
||||
if (mode == types::power_supply_DC::Mode::Export) {
|
||||
can_broker->set_state(true);
|
||||
} else {
|
||||
can_broker->set_state(false);
|
||||
}
|
||||
}
|
||||
|
||||
void power_supply_DCImpl::handle_setExportVoltageCurrent(double& voltage, double& current) {
|
||||
if (voltage <= config_voltage_limit && voltage >= config_min_voltage_limit && current <= config_current_limit) {
|
||||
this->voltage = voltage;
|
||||
this->current = current / 100.;
|
||||
} else {
|
||||
EVLOG_error << fmt::format("Out of range voltage/current settings ignored: {}V / {}A", voltage, current);
|
||||
}
|
||||
}
|
||||
|
||||
void power_supply_DCImpl::handle_setImportVoltageCurrent(double& voltage, double& current) {
|
||||
// power supply is uni directional only
|
||||
}
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright Pionix GmbH and Contributors to EVerest
|
||||
#ifndef MAIN_POWER_SUPPLY_DC_IMPL_HPP
|
||||
#define MAIN_POWER_SUPPLY_DC_IMPL_HPP
|
||||
|
||||
//
|
||||
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
|
||||
// template version 3
|
||||
//
|
||||
|
||||
#include <generated/interfaces/power_supply_DC/Implementation.hpp>
|
||||
|
||||
#include "../DPM1000.hpp"
|
||||
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
#include <atomic>
|
||||
|
||||
#include <everest/gpio/gpio.hpp>
|
||||
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
|
||||
|
||||
namespace module {
|
||||
namespace main {
|
||||
|
||||
struct Conf {};
|
||||
|
||||
class power_supply_DCImpl : public power_supply_DCImplBase {
|
||||
public:
|
||||
power_supply_DCImpl() = delete;
|
||||
power_supply_DCImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<DPM1000>& mod, Conf& config) :
|
||||
power_supply_DCImplBase(ev, "main"), 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_setMode(types::power_supply_DC::Mode& mode,
|
||||
types::power_supply_DC::ChargingPhase& phase) override;
|
||||
virtual void handle_setExportVoltageCurrent(double& voltage, double& current) override;
|
||||
virtual void handle_setImportVoltageCurrent(double& voltage, double& current) override;
|
||||
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
// insert your protected definitions here
|
||||
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
|
||||
|
||||
private:
|
||||
const Everest::PtrContainer<DPM1000>& mod;
|
||||
const Conf& config;
|
||||
|
||||
virtual void init() override;
|
||||
virtual void ready() override;
|
||||
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
std::atomic<float> voltage;
|
||||
std::atomic<float> current;
|
||||
std::atomic<uint32_t> last_alarm_flags{0};
|
||||
|
||||
float config_current_limit{0};
|
||||
float config_voltage_limit{0};
|
||||
float config_power_limit{0};
|
||||
float config_min_voltage_limit{50.};
|
||||
|
||||
Everest::Gpio discharge_gpio;
|
||||
bool parallel_mode{false};
|
||||
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
|
||||
};
|
||||
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
// insert other definitions here
|
||||
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
|
||||
|
||||
} // namespace main
|
||||
} // namespace module
|
||||
|
||||
#endif // MAIN_POWER_SUPPLY_DC_IMPL_HPP
|
||||
@@ -0,0 +1,61 @@
|
||||
description: DC Power Supply Driver
|
||||
provides:
|
||||
main:
|
||||
description: Power supply driver for DPM 1000-30 from SCU Power. Currently supports only one module.
|
||||
interface: power_supply_DC
|
||||
config:
|
||||
device:
|
||||
description: Interface name for can device
|
||||
type: string
|
||||
default: can0
|
||||
device_address:
|
||||
description: Device address (as selected on front LED panel)
|
||||
type: integer
|
||||
default: 0
|
||||
power_limit_W:
|
||||
description: Maximum Power Limit in Watt
|
||||
type: number
|
||||
maximum: 30000
|
||||
default: 30000
|
||||
current_limit_A:
|
||||
description: Maximum Current Limit in Ampere
|
||||
type: number
|
||||
maximum: 100
|
||||
default: 100
|
||||
voltage_limit_V:
|
||||
description: Maximum Voltage Limit in Volt. Will be limited by series parallel setting as well.
|
||||
type: number
|
||||
maximum: 1000
|
||||
default: 1000
|
||||
series_parallel_mode:
|
||||
description:
|
||||
Select series (300-1000V), parallel (50-500) or automatic switching mode (50-1000).
|
||||
This switches the internal configuration of one module and should not be confused with parallel operation of multiple modules.
|
||||
type: string
|
||||
enum:
|
||||
- Series
|
||||
- Parallel
|
||||
- Automatic
|
||||
default: Series
|
||||
discharge_gpio_chip:
|
||||
description: >-
|
||||
GPIO chip to use to switch external discharge load on and off. An empty string disables discharging.
|
||||
Note that the hardware load must be designed to allow permanent discharge from the highest voltage (e.g. 1000V)
|
||||
type: string
|
||||
default: ''
|
||||
discharge_gpio_line:
|
||||
description: GPIO line to use to switch discharge load
|
||||
type: integer
|
||||
default: 0
|
||||
discharge_gpio_polarity:
|
||||
description: GPIO polarity, false means active low, true means active high
|
||||
type: boolean
|
||||
default: true
|
||||
debug_print_all_telemetry:
|
||||
description: Read and print all telemetry from the power module. Helpful while debugging.
|
||||
type: boolean
|
||||
default: false
|
||||
metadata:
|
||||
license: https://opensource.org/licenses/Apache-2.0
|
||||
authors:
|
||||
- aw@pionix.de
|
||||
Reference in New Issue
Block a user