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,34 @@
#
# 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
add_subdirectory(test-tool)
target_sources(${MODULE_NAME}
PRIVATE
"can_driver_acdc/CanDevice.cpp"
"can_driver_acdc/InfyCanDevice.cpp"
"can_driver_acdc/CanPackets.cpp"
)
target_link_libraries(${MODULE_NAME}
PRIVATE
Pal::Sigslot
everest::log
)
# 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

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "InfyPower_BEG1K075G.hpp"
namespace module {
void InfyPower_BEG1K075G::init() {
// open DCDC CAN device
if (!acdc.open_device(config.can_device.c_str())) {
EVLOG_AND_THROW(EVEXCEPTION(Everest::EverestConfigError, "Could not open CAN interface ", config.can_device));
}
invoke_init(*p_main);
}
void InfyPower_BEG1K075G::ready() {
invoke_ready(*p_main);
}
} // namespace module

View File

@@ -0,0 +1,60 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef INFY_POWER_BEG1K075G_HPP
#define INFY_POWER_BEG1K075G_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
#include "can_driver_acdc/InfyCanDevice.hpp"
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
std::string can_device;
};
class InfyPower_BEG1K075G : public Everest::ModuleBase {
public:
InfyPower_BEG1K075G() = delete;
InfyPower_BEG1K075G(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
InfyCanDevice acdc;
// 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 // INFY_POWER_BEG1K075G_HPP

View File

@@ -0,0 +1,122 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/can.h>
#include <linux/can/raw.h>
#include <iostream>
#include <vector>
#include "CanDevice.hpp"
#include <everest/logging.hpp>
CanDevice::CanDevice() : exit_rx_thread{false} {
can_fd = 0;
}
CanDevice::~CanDevice() {
close_device();
}
bool CanDevice::open_device(const char* dev) {
if (!dev || std::strlen(dev) >= IFNAMSIZ) {
fprintf(stderr, "Interface name is invalid or too long: %s\n", dev ? dev : "NULL");
return false;
}
if ((can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
perror("Socket");
return false;
}
// retrieve interface index from interface name
struct ifreq ifr;
std::memset(&ifr, 0, sizeof(ifr));
strncpy(ifr.ifr_name, dev, IFNAMSIZ - 1);
if (ioctl(can_fd, SIOCGIFINDEX, &ifr) < 0) {
perror(dev);
close(can_fd);
return false;
}
// 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, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("Bind");
close(can_fd);
return false;
}
// spawn read thread
exit_rx_thread = false;
rx_thread_handle = std::thread(&CanDevice::rx_thread, this);
return true;
}
bool CanDevice::close_device() {
if (can_fd != 0 && close(can_fd) == 0) {
can_fd = 0;
exit_rx_thread = true;
rx_thread_handle.join();
return true;
} else {
return false;
}
}
void CanDevice::rx_thread() {
can_frame frame;
while (!exit_rx_thread) {
size_t nbytes = read(can_fd, &frame, sizeof(struct can_frame));
if (nbytes < 0) {
perror("Read");
} else if (nbytes > 0) {
// Received a new CAN packet...
std::vector<uint8_t> payload;
payload.assign(frame.data, frame.data + frame.can_dlc);
rx_handler(frame.can_id, payload);
}
}
}
void CanDevice::rx_handler(uint32_t can_id, const std::vector<uint8_t>& payload) {
EVLOG_debug << "CAN frame received";
}
bool CanDevice::_tx(uint32_t can_id, const std::vector<uint8_t>& payload) {
if (can_fd == 0)
return false;
struct can_frame frame;
if (payload.size() > sizeof(frame.data)) {
throw std::runtime_error("Size of can payload data to large (" + std::to_string(payload.size()) + " bytes)");
}
frame.can_id = can_id;
frame.can_dlc = payload.size();
memcpy(frame.data, payload.data(), payload.size());
if (write(can_fd, &frame, sizeof(can_frame)) != sizeof(can_frame)) {
throw std::runtime_error(std::string("Failed to send can packet :") + strerror(errno));
return false;
}
return true;
}

View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef CAN_DEVICE_HPP
#define CAN_DEVICE_HPP
#include "CanPackets.hpp"
#include <atomic>
#include <linux/can.h>
#include <thread>
class CanDevice {
public:
CanDevice();
virtual ~CanDevice();
bool open_device(const char* dev);
bool close_device();
protected:
virtual void rx_handler(uint32_t can_id, const std::vector<uint8_t>& payload);
bool _tx(uint32_t can_id, const std::vector<uint8_t>& payload);
private:
int can_fd;
std::atomic_bool exit_rx_thread;
std::thread rx_thread_handle;
void rx_thread();
};
#endif // CAN_DEVICE_HPP

View File

@@ -0,0 +1,306 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "CanPackets.hpp"
#include "Conversions.hpp"
#include <iostream>
namespace can_packet_acdc {
// helper functions
uint32_t encode_can_id(uint8_t source_address, uint8_t destination_address, uint8_t command_number,
uint8_t device_number, uint8_t error_code) {
uint32_t id = source_address;
id |= destination_address << 8;
command_number &= 0x3F;
id |= command_number << 16;
device_number &= 0x0F;
id |= device_number << 22;
error_code &= 0x07;
id |= error_code << 26;
return id;
}
/*
destination_address is either 0x3F (broadcast) or group address
*/
uint32_t encode_can_id(uint8_t destination_address, uint8_t command_number) {
uint8_t device_number;
if (destination_address == 0x3F) {
device_number = 0x0A;
} else {
device_number = 0x0B;
}
return encode_can_id(0xF0, destination_address, command_number, device_number, 0);
}
uint8_t destination_address_from_can_id(uint32_t id) {
return (id >> 8) & 0xFF;
}
uint8_t source_address_from_can_id(uint32_t id) {
return id & 0xFF;
}
uint8_t command_number_from_can_id(uint32_t id) {
return (id >> 16) & 0x3F;
}
uint8_t error_code_from_can_id(uint32_t id) {
return (id >> 26) & 0x07;
}
// packet definitions
// GenericSetting
GenericSetting::GenericSetting(const std::vector<uint8_t> raw) {
byte0 = from_raw<uint8_t>(raw, 0);
byte1 = from_raw<uint8_t>(raw, 1);
value = from_raw<uint32_t>(raw, 4);
}
GenericSetting::GenericSetting(uint8_t _byte0, uint8_t _byte1, uint32_t _value) :
byte0(_byte0), byte1(_byte1), value(_value) {
}
std::ostream& operator<<(std::ostream& out, const GenericSetting& self) {
out << "GenericSetting: byte0: " << std::to_string(self.byte0) << " byte1: " << std::to_string(self.byte0)
<< " Value: " << std::to_string(self.value);
return out;
}
GenericSetting::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(byte0, data);
to_raw(byte1, data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(value, data);
return data;
}
// SystemDCVoltage
SystemDCVoltage::SystemDCVoltage(const std::vector<uint8_t> raw) {
volt = (float)from_raw<uint32_t>(raw, 4) / 1000.;
}
SystemDCVoltage::SystemDCVoltage(float _volt) : volt(_volt) {
}
SystemDCVoltage::SystemDCVoltage() : volt(0.) {
}
std::ostream& operator<<(std::ostream& out, const SystemDCVoltage& self) {
out << "SystemDCVoltage: " << std::to_string(self.volt);
return out;
}
SystemDCVoltage::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0x10), data);
to_raw(static_cast<uint8_t>(0x01), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint32_t>(volt * 1000), data);
return data;
}
// SystemDCCurrent
SystemDCCurrent::SystemDCCurrent(const std::vector<uint8_t> raw) {
ampere = (float)from_raw<uint32_t>(raw, 4) / 1000.;
}
SystemDCCurrent::SystemDCCurrent(float _ampere) : ampere(_ampere) {
}
SystemDCCurrent::SystemDCCurrent() : ampere(0.) {
}
std::ostream& operator<<(std::ostream& out, const SystemDCCurrent& self) {
out << "SystemDCCurrent: " << std::to_string(self.ampere);
return out;
}
SystemDCCurrent::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0x10), data);
to_raw(static_cast<uint8_t>(0x02), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint32_t>(ampere * 1000), data);
return data;
}
// PowerModuleNumber
PowerModuleNumber::PowerModuleNumber(const std::vector<uint8_t> raw) {
number = from_raw<uint16_t>(raw, 6);
}
PowerModuleNumber::PowerModuleNumber() : number(0) {
}
std::ostream& operator<<(std::ostream& out, const PowerModuleNumber& self) {
out << "PowerModuleNumber: " << std::to_string(self.number);
return out;
}
PowerModuleNumber::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0x10), data);
to_raw(static_cast<uint8_t>(0x10), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint32_t>(0x00), data);
return data;
}
// PowerModuleStatus
PowerModuleStatus::PowerModuleStatus() {
}
PowerModuleStatus::PowerModuleStatus(const std::vector<uint8_t> raw) {
uint8_t status0 = from_raw<uint8_t>(raw, 7);
uint8_t status1 = from_raw<uint8_t>(raw, 6);
uint8_t status2 = from_raw<uint8_t>(raw, 5);
output_short_current = status0 & (1 << 0);
sleeping = status0 & (1 << 4);
discharge_abnormal = status0 & (1 << 5);
dc_side_off = status1 & (1 << 0);
fault_alarm = status1 & (1 << 1);
protection_alarm = status1 & (1 << 2);
fan_fault_alarm = status1 & (1 << 3);
over_temperature_alarm = status1 & (1 << 4);
output_over_voltage_alarm = status1 & (1 << 5);
walk_in_enable = status1 & (1 << 6);
communication_interrupt_alarm = status1 & (1 << 7);
power_limit_status = status2 & (1 << 0);
id_repeat_alarm = status2 & (1 << 1);
load_sharing_alarm = status2 & (1 << 2);
input_phase_lost_alarm = status2 & (1 << 3);
input_unbalanced_alarm = status2 & (1 << 4);
input_low_voltage_alarm = status2 & (1 << 5);
input_over_voltage_protection = status2 & (1 << 6);
pfc_side_off = status2 & (1 << 7);
}
std::ostream& operator<<(std::ostream& out, const PowerModuleStatus& self) {
out << "PowerModuleStatus: " << (self.output_short_current ? "output_short_current " : "")
<< (self.sleeping ? "sleeping " : "") << (self.discharge_abnormal ? "discharge_abnormal " : "")
<< (self.dc_side_off ? "dc_side_off " : "") << (self.fault_alarm ? "fault_alarm " : "")
<< (self.protection_alarm ? "protection_alarm " : "") << (self.fan_fault_alarm ? "fan_fault_alarm " : "")
<< (self.over_temperature_alarm ? "over_temperature_alarm " : "")
<< (self.output_over_voltage_alarm ? "output_over_voltage_alarm " : "")
<< (self.walk_in_enable ? "walk_in_enable " : "")
<< (self.communication_interrupt_alarm ? "communication_interrupt_alarm " : "")
<< (self.power_limit_status ? "power_limit_status " : "") << (self.id_repeat_alarm ? "id_repeat_alarm " : "")
<< (self.load_sharing_alarm ? "load_sharing_alarm " : "")
<< (self.input_phase_lost_alarm ? "input_phase_lost_alarm " : "")
<< (self.input_unbalanced_alarm ? "input_unbalanced_alarm " : "")
<< (self.input_low_voltage_alarm ? "input_low_voltage_alarm " : "")
<< (self.input_over_voltage_protection ? "input_over_voltage_protection " : "")
<< (self.pfc_side_off ? "pfc_side_off " : "");
return out;
}
PowerModuleStatus::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0x11), data);
to_raw(static_cast<uint8_t>(0x10), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint32_t>(0x00), data);
return data;
}
// InverterStatus
InverterStatus::InverterStatus(const std::vector<uint8_t> raw) {
uint8_t status0 = from_raw<uint8_t>(raw, 7);
invert_mode = status0 & (1 << 0);
}
InverterStatus::InverterStatus() : invert_mode{false} {
}
std::ostream& operator<<(std::ostream& out, const InverterStatus& self) {
out << "InverterStatus: " << (self.invert_mode ? "inverter" : "rectifier");
return out;
}
InverterStatus::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0x11), data);
to_raw(static_cast<uint8_t>(0x11), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint32_t>(0), data);
return data;
}
// OnOff
OnOff::OnOff(bool o) : on{o} {};
OnOff::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0x11), data);
to_raw(static_cast<uint8_t>(0x10), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint16_t>(on ? 0xA0 : 0xA1), data);
return data;
}
// Walk in enable
WalkInEnable::WalkInEnable(bool e) : enabled{e} {};
WalkInEnable::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0x11), data);
to_raw(static_cast<uint8_t>(0x22), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint16_t>(enabled ? 0xA1 : 0xA0), data);
return data;
}
// WorkingMode
WorkingMode::WorkingMode(bool o) : inverter{o} {};
WorkingMode::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0x21), data);
to_raw(static_cast<uint8_t>(0x10), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<uint16_t>(inverter ? 0xA1 : 0xA0), data);
return data;
}
// PowerFactorAdjust
PowerFactorAdjust::PowerFactorAdjust(float p) : pf{p} {};
PowerFactorAdjust::operator std::vector<uint8_t>() {
std::vector<uint8_t> data;
to_raw(static_cast<uint8_t>(0x21), data);
to_raw(static_cast<uint8_t>(0x05), data);
to_raw(static_cast<uint16_t>(0x00), data);
to_raw(static_cast<int32_t>(pf * 1000.), data);
return data;
}
} // namespace can_packet_acdc

View File

@@ -0,0 +1,172 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef CAN_PACKETS_HPP
#define CAN_PACKETS_HPP
#include <linux/can.h>
#include <ostream>
#include <stdint.h>
#include <vector>
namespace can_packet_acdc {
uint32_t encode_can_id(uint8_t source_address, uint8_t destination_address, uint8_t command_number,
uint8_t device_number, uint8_t error_code);
uint32_t encode_can_id(uint8_t destination_address, uint8_t command_number);
uint8_t destination_address_from_can_id(uint32_t id);
uint8_t source_address_from_can_id(uint32_t id);
uint8_t command_number_from_can_id(uint32_t id);
uint8_t error_code_from_can_id(uint32_t id);
/*
CAN IDs are not just simple IDs with this inverter. More information is encoded into the 29 bit CAN IDs:
bits 0..7: Source Address
bits 18..15: Destination Address
0x3F: broadcast message
0x00..0x3E: if device number 0x0A: actual module address range, automatic allocation after power on.
if device number 0x0B: group address, set with dial on the module.
0xF0..0xF8: controller address range, default is 0xF0.
bits 16..21: Command number
0x23: read power module data, actual content encoded in first to bytes
0x24: write power module data, actual content encoded in first to bytes
Byte 0:
0x10: System basic information
0x11: Single power module basic information
0x21: Single power module AC side information
0x41: Single power module bidirectional DC/DC side information
bits 22..25: Device number
0x0A: Controller to single module
0x0B: Controller to module group
bits 26..28: Error code:
0x00: Normal
0x02: Command invalid
0x03: Data invalid
0x07: In start processing
*/
// Commands
const uint8_t CMD_READ = 0x23;
const uint8_t CMD_WRITE = 0x24;
// Addresses
const uint8_t ADDR_BROADCAST = 0x3F;
const uint8_t ADDR_MODULE = 0x00;
// RX and TX packet definitions
struct GenericSetting {
GenericSetting(uint8_t _byte0, uint8_t _byte1, uint32_t _value);
GenericSetting(const std::vector<uint8_t> raw);
friend std::ostream& operator<<(std::ostream& out, const GenericSetting& self);
operator std::vector<uint8_t>();
uint32_t value{0};
uint8_t byte0{0};
uint8_t byte1{0};
};
struct SystemDCVoltage {
SystemDCVoltage();
SystemDCVoltage(float _volt);
SystemDCVoltage(const std::vector<uint8_t> raw);
friend std::ostream& operator<<(std::ostream& out, const SystemDCVoltage& self);
operator std::vector<uint8_t>();
float volt{0};
};
struct SystemDCCurrent {
SystemDCCurrent();
SystemDCCurrent(float _ampere);
SystemDCCurrent(const std::vector<uint8_t> raw);
friend std::ostream& operator<<(std::ostream& out, const SystemDCCurrent& self);
operator std::vector<uint8_t>();
float ampere{0};
};
// RX packet definitions
struct PowerModuleNumber {
PowerModuleNumber();
PowerModuleNumber(const std::vector<uint8_t> raw);
friend std::ostream& operator<<(std::ostream& out, const PowerModuleNumber& self);
operator std::vector<uint8_t>();
uint16_t number{0};
};
struct PowerModuleStatus {
PowerModuleStatus();
PowerModuleStatus(const std::vector<uint8_t> raw);
friend std::ostream& operator<<(std::ostream& out, const PowerModuleStatus& self);
operator std::vector<uint8_t>();
bool output_short_current{false};
bool sleeping{false};
bool discharge_abnormal{false};
bool dc_side_off{false};
bool fault_alarm{false};
bool protection_alarm{false};
bool fan_fault_alarm{false};
bool over_temperature_alarm{false};
bool output_over_voltage_alarm{false};
bool walk_in_enable{false};
bool communication_interrupt_alarm{false};
bool power_limit_status{false};
bool id_repeat_alarm{false};
bool load_sharing_alarm{false};
bool input_phase_lost_alarm{false};
bool input_unbalanced_alarm{false};
bool input_low_voltage_alarm{false};
bool input_over_voltage_protection{false};
bool pfc_side_off{false};
};
struct InverterStatus {
InverterStatus();
InverterStatus(const std::vector<uint8_t> raw);
friend std::ostream& operator<<(std::ostream& out, const InverterStatus& self);
operator std::vector<uint8_t>();
bool invert_mode{false};
};
// TX packet definitions
struct OnOff {
OnOff(bool o);
operator std::vector<uint8_t>();
bool on{false};
};
struct WalkInEnable {
WalkInEnable(bool o);
operator std::vector<uint8_t>();
bool enabled{false};
};
struct WorkingMode {
WorkingMode(bool o);
operator std::vector<uint8_t>();
bool inverter{false};
};
struct PowerFactorAdjust {
PowerFactorAdjust(float pf);
operator std::vector<uint8_t>();
float pf{1.0};
};
} // namespace can_packet_acdc
#endif // CAN_PACKETS_HPP

View File

@@ -0,0 +1,72 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef CONVERSIONS_HPP
#define CONVERSIONS_HPP
#include <cstdint>
#include <cstring>
#include <type_traits>
#include <endian.h>
template <class T> typename std::enable_if_t<sizeof(T) == 1, T> from_raw(const std::vector<uint8_t> raw, int idx) {
T ret = raw[idx];
return ret;
}
template <class T> typename std::enable_if_t<sizeof(T) == 2, T> from_raw(const std::vector<uint8_t> raw, int idx) {
uint16_t tmp = be16toh(*((uint16_t*)((uint8_t*)raw.data() + idx)));
T ret;
memcpy(&ret, &tmp, 2);
return ret;
}
template <class T> typename std::enable_if_t<sizeof(T) == 4, T> from_raw(const std::vector<uint8_t> raw, int idx) {
uint32_t tmp = be32toh(*((uint32_t*)((uint8_t*)raw.data() + idx)));
T ret;
memcpy(&ret, &tmp, 4);
return ret;
}
template <class T> typename std::enable_if_t<sizeof(T) == 8, T> from_raw(const std::vector<uint8_t> raw, int idx) {
uint64_t tmp = be64toh(*((uint64_t*)((uint8_t*)raw.data() + idx)));
T ret;
memcpy(&ret, &tmp, 8);
return ret;
}
template <class T> typename std::enable_if_t<sizeof(T) == 1> to_raw(T src, std::vector<uint8_t>& dest) {
uint8_t tmp;
memcpy(&tmp, &src, 1);
dest.push_back(tmp);
}
// FIXME (aw): these conversions should be optimized!
template <class T> typename std::enable_if_t<sizeof(T) == 2> to_raw(T src, std::vector<uint8_t>& dest) {
uint16_t tmp;
memcpy(&tmp, &src, 2);
tmp = htobe16(tmp);
uint8_t ret[2];
memcpy(ret, &tmp, 2);
dest.insert(dest.end(), {ret[0], ret[1]});
}
template <class T> typename std::enable_if_t<sizeof(T) == 4> to_raw(T src, std::vector<uint8_t>& dest) {
uint32_t tmp;
memcpy(&tmp, &src, 4);
tmp = htobe32(tmp);
uint8_t ret[4];
memcpy(ret, &tmp, 4);
dest.insert(dest.end(), {ret[0], ret[1], ret[2], ret[3]});
}
template <class T> typename std::enable_if_t<sizeof(T) == 8> to_raw(T src, std::vector<uint8_t>& dest) {
uint64_t tmp;
memcpy(&tmp, &src, 8);
tmp = htobe64(tmp);
uint8_t ret[8];
memcpy(ret, &tmp, 8);
dest.insert(dest.end(), {ret[0], ret[1], ret[2], ret[3], ret[4], ret[5], ret[6], ret[7]});
}
#endif // CONVERSIONS_HPP

View File

@@ -0,0 +1,367 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "InfyCanDevice.hpp"
#include "CanPackets.hpp"
#include <iostream>
#include <unistd.h>
#include <everest/logging.hpp>
InfyCanDevice::InfyCanDevice() {
// spawn thread that requests some data periodically to keep the connection alive
exitTxThread = false;
txThreadHandle = std::thread(&InfyCanDevice::txThread, this);
}
InfyCanDevice::~InfyCanDevice() {
exitTxThread = true;
}
void InfyCanDevice::rx_handler(uint32_t can_id, const std::vector<uint8_t>& payload) {
EVLOG_debug << "Infy: CAN frame received. ID: 0x" << std::hex << can_id;
// We only use extended frames here
if (!(can_id & CAN_EFF_FLAG)) {
EVLOG_debug << "Infy: Ignoring, not extended protocol.";
return;
}
if (can_packet_acdc::command_number_from_can_id(can_id) == can_packet_acdc::CMD_WRITE) {
switch (can_packet_acdc::error_code_from_can_id(can_id)) {
case 0x02:
EVLOG_error << "Infy: ERROR: Command invalid.";
break;
case 0x03:
EVLOG_error << "Infy: ERROR: Data invalid.";
break;
case 0x07:
EVLOG_error << "Infy: ERROR: In start processing.";
break;
}
return;
}
// is it a reply to a read command?
if (can_packet_acdc::command_number_from_can_id(can_id) != can_packet_acdc::CMD_READ) {
return;
}
// is it for us?
if (can_packet_acdc::destination_address_from_can_id(can_id) != 0xF0) {
return;
}
uint16_t packet_type = payload[0] << 8 | payload[1];
if (can_packet_acdc::error_code_from_can_id(can_id) > 0) {
EVLOG_debug << "Infy: Parsing CAN packet type: " << std::hex << packet_type << " Error code:" << std::hex
<< (int)can_packet_acdc::error_code_from_can_id(can_id);
}
switch (packet_type) {
case 0x1001: {
telemetry.voltage = payload;
} break;
case 0x1002: {
telemetry.current = payload;
signalVoltageCurrent(telemetry.voltage.volt, telemetry.current.ampere);
} break;
case 0x1010: {
can_packet_acdc::PowerModuleNumber n(payload);
} break;
case 0x1110: {
can_packet_acdc::PowerModuleStatus s(payload);
telemetry.status = s;
} break;
case 0x1111: {
can_packet_acdc::InverterStatus s(payload);
signalModuleStatus(telemetry.status, s);
} break;
case 0x1103: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_ab_line_voltage = s.value / 1000.;
} break;
case 0x1104: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_bc_line_voltage = s.value / 1000.;
} break;
case 0x1105: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_ca_line_voltage = s.value / 1000.;
} break;
case 0x1106: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ambient_temperature = s.value / 1000.;
} break;
case 0x1130: {
can_packet_acdc::GenericSetting s(payload);
telemetry.dc_max_output_voltage = s.value / 1000.;
} break;
case 0x1131: {
can_packet_acdc::GenericSetting s(payload);
telemetry.dc_min_output_voltage = s.value / 1000.;
} break;
case 0x1132: {
can_packet_acdc::GenericSetting s(payload);
telemetry.dc_max_output_current = s.value / 1000.;
} break;
case 0x1133: {
can_packet_acdc::GenericSetting s(payload);
telemetry.dc_rated_output_power = s.value / 1000.;
} break;
case 0x2101: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_a_voltage = s.value / 1000.;
} break;
case 0x2102: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_b_voltage = s.value / 1000.;
} break;
case 0x2103: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_c_voltage = s.value / 1000.;
} break;
case 0x2104: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_a_current = s.value / 1000.;
} break;
case 0x2105: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_b_current = s.value / 1000.;
} break;
case 0x2106: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_c_current = s.value / 1000.;
} break;
case 0x2107: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_frequency = s.value / 1000.;
} break;
case 0x2109: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_a_active_power = s.value / 1000.;
} break;
case 0x210A: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_b_active_power = s.value / 1000.;
} break;
case 0x210B: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_c_active_power = s.value / 1000.;
} break;
case 0x2108: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_total_active_ower = s.value / 1000.;
} break;
case 0x210D: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_a_reactive_power = s.value / 1000.;
} break;
case 0x210E: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_b_reactive_power = s.value / 1000.;
} break;
case 0x210F: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_c_reactive_power = s.value / 1000.;
} break;
case 0x210C: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_total_reactive_ower = s.value / 1000.;
} break;
case 0x2110: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_a_apparent_power = s.value / 1000.;
} break;
case 0x2111: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_b_apparent_power = s.value / 1000.;
} break;
case 0x2112: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_phase_c_apparent_power = s.value / 1000.;
} break;
case 0x2113: {
can_packet_acdc::GenericSetting s(payload);
telemetry.ac_total_apparent_ower = s.value / 1000.;
} break;
case 0x4101: {
can_packet_acdc::GenericSetting s(payload);
telemetry.dc_high_side_voltage = s.value / 1000.;
} break;
case 0x4102: {
can_packet_acdc::GenericSetting s(payload);
telemetry.dc_high_side_current = s.value / 1000.;
} break;
default: {
can_packet_acdc::GenericSetting s(payload);
}
}
}
void InfyCanDevice::txThread() {
while (!exitTxThread) {
const int delay_us = 50000;
// request current system DC voltage. Answer will be processed by RX thread.
request_rx(can_packet_acdc::ADDR_BROADCAST, can_packet_acdc::SystemDCVoltage());
usleep(delay_us);
// request current system DC current. Answer will be processed by RX thread.
request_rx(can_packet_acdc::ADDR_BROADCAST, can_packet_acdc::SystemDCCurrent());
usleep(delay_us);
// request state. Answer will be processed by RX thread.
request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::PowerModuleStatus());
usleep(delay_us);
// request inverter state. Answer will be processed by RX thread.
request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::InverterStatus());
usleep(delay_us);
tx(can_packet_acdc::ADDR_BROADCAST, can_packet_acdc::OnOff(on));
usleep(delay_us);
if (setpoint_voltage > 150.) {
tx(can_packet_acdc::ADDR_BROADCAST, can_packet_acdc::SystemDCVoltage(setpoint_voltage));
usleep(delay_us);
tx(can_packet_acdc::ADDR_BROADCAST, can_packet_acdc::SystemDCCurrent(setpoint_current));
usleep(delay_us);
}
tx(can_packet_acdc::ADDR_BROADCAST, can_packet_acdc::WalkInEnable(walkin_enable));
usleep(delay_us);
tx(can_packet_acdc::ADDR_BROADCAST, can_packet_acdc::WorkingMode(inverter_mode));
usleep(delay_us);
}
}
bool InfyCanDevice::switch_on_off(bool _on) {
on = _on;
return true;
}
bool InfyCanDevice::set_walkin_enabled(bool on) {
walkin_enable = on;
return true;
}
bool InfyCanDevice::set_inverter_mode(bool i) {
inverter_mode = i;
return true;
}
bool InfyCanDevice::adjust_power_factor(float pf) {
return tx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::PowerFactorAdjust(pf));
}
bool InfyCanDevice::set_voltage_current(float voltage, float current) {
setpoint_current = current;
setpoint_voltage = voltage;
return true;
}
bool InfyCanDevice::set_generic_setting(uint8_t byte0, uint8_t byte1, uint32_t value) {
return tx(can_packet_acdc::ADDR_BROADCAST, can_packet_acdc::GenericSetting(byte0, byte1, value));
}
bool InfyCanDevice::set_output_mode(OutputMode mode) {
return set_generic_setting(0x11, 0x26, static_cast<std::underlying_type<OutputMode>::type>(mode));
}
bool InfyCanDevice::tx(const uint8_t destination_address, const std::vector<uint8_t>& payload) {
uint32_t can_id = can_packet_acdc::encode_can_id(destination_address, can_packet_acdc::CMD_WRITE);
can_id |= 0x80000000U; // Extended frame format
return _tx(can_id, payload);
}
bool InfyCanDevice::request_rx(const uint8_t destination_address, const std::vector<uint8_t>& payload) {
uint32_t can_id = can_packet_acdc::encode_can_id(destination_address, can_packet_acdc::CMD_READ);
can_id |= 0x80000000U; // Extended frame format
return _tx(can_id, payload);
}
std::ostream& operator<<(std::ostream& out, const InfyCanDevice::Telemetry& self) {
out << "\n------------------------------------------------\nTelemetry\n---------\n";
out << "AC line: AB:" << std::to_string(self.ac_ab_line_voltage)
<< " BC: " << std::to_string(self.ac_bc_line_voltage) << " CA: " << std::to_string(self.ac_ca_line_voltage)
<< std::endl;
out << "AC Phase Voltages: V_A:" << std::to_string(self.ac_phase_a_voltage)
<< " V_B: " << std::to_string(self.ac_phase_b_voltage) << " V_C: " << std::to_string(self.ac_phase_c_voltage)
<< std::endl;
out << "AC Phase Currents: I_A:" << std::to_string(self.ac_phase_a_current)
<< " I_B: " << std::to_string(self.ac_phase_b_current) << " I_C: " << std::to_string(self.ac_phase_c_current)
<< std::endl;
out << "AC Active power: Total: " << std::to_string(self.ac_total_active_ower)
<< "P_A:" << std::to_string(self.ac_phase_a_active_power)
<< " P_B: " << std::to_string(self.ac_phase_b_active_power)
<< " P_C: " << std::to_string(self.ac_phase_c_active_power) << std::endl;
out << "AC Re-Active power: Total: " << std::to_string(self.ac_total_reactive_ower)
<< "P_A:" << std::to_string(self.ac_phase_a_reactive_power)
<< " P_B: " << std::to_string(self.ac_phase_b_reactive_power)
<< " P_C: " << std::to_string(self.ac_phase_c_reactive_power) << std::endl;
out << "AC Apparent power: Total: " << std::to_string(self.ac_total_apparent_ower)
<< "P_A:" << std::to_string(self.ac_phase_a_apparent_power)
<< " P_B: " << std::to_string(self.ac_phase_b_apparent_power)
<< " P_C: " << std::to_string(self.ac_phase_c_apparent_power) << std::endl;
out << "AC frequency: " << self.ac_frequency << std::endl;
out << "Ambient temperature: " << self.ambient_temperature << std::endl;
out << "DC High Voltage side: Voltage: " << std::to_string(self.dc_high_side_voltage)
<< " Current: " << std::to_string(self.dc_high_side_current) << std::endl;
out << "Capabilities: dc_min: " << self.dc_min_output_voltage << "V dc_max: " << self.dc_max_output_voltage
<< "V dc_max_current: " << self.dc_max_output_current << "A max_watt: " << self.dc_rated_output_power << "W "
<< std::endl;
out << self.status << std::endl;
out << self.voltage << std::endl << self.current << std::endl;
out << "------------------------------------------------\n";
return out;
}

View File

@@ -0,0 +1,100 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef INFY_CAN_DEVICE_HPP
#define INFY_CAN_DEVICE_HPP
#include "CanDevice.hpp"
#include <atomic>
#include <linux/can.h>
#include <mutex>
#include <sigslot/signal.hpp>
#include <thread>
class InfyCanDevice : public CanDevice {
public:
InfyCanDevice();
~InfyCanDevice();
enum class OutputMode {
Parallel = 0xA0,
Series = 0xA1,
Automatic = 0xA2
};
// Commands
bool switch_on_off(bool on);
bool set_walkin_enabled(bool on);
bool set_inverter_mode(bool inverter);
bool set_voltage_current(float voltage, float current);
bool set_output_mode(OutputMode mode);
bool adjust_power_factor(float pf);
bool set_generic_setting(uint8_t byte0, uint8_t byte1, uint32_t value);
// Data out
sigslot::signal<float, float, float> signalacdcTemperatures;
sigslot::signal<float, float> signalVoltageCurrent;
sigslot::signal<can_packet_acdc::PowerModuleStatus, can_packet_acdc::InverterStatus> signalModuleStatus;
bool request_rx(const uint8_t destination_address, const std::vector<uint8_t>& payload);
struct Telemetry {
float ac_ab_line_voltage{0.};
float ac_bc_line_voltage{0.};
float ac_ca_line_voltage{0.};
float ambient_temperature{0.};
float dc_max_output_voltage{0.};
float dc_min_output_voltage{0.};
float dc_max_output_current{0.};
float dc_rated_output_power{0.};
float ac_phase_a_current{0.};
float ac_phase_b_current{0.};
float ac_phase_c_current{0.};
float ac_phase_a_voltage{0.};
float ac_phase_b_voltage{0.};
float ac_phase_c_voltage{0.};
float ac_frequency{0.};
float ac_phase_a_active_power{0.};
float ac_phase_b_active_power{0.};
float ac_phase_c_active_power{0.};
float ac_total_active_ower{0.};
float ac_phase_a_reactive_power{0.};
float ac_phase_b_reactive_power{0.};
float ac_phase_c_reactive_power{0.};
float ac_total_reactive_ower{0.};
float ac_phase_a_apparent_power{0.};
float ac_phase_b_apparent_power{0.};
float ac_phase_c_apparent_power{0.};
float ac_total_apparent_ower{0.};
float dc_high_side_voltage{0.};
float dc_high_side_current{0.};
can_packet_acdc::SystemDCVoltage voltage;
can_packet_acdc::SystemDCCurrent current;
can_packet_acdc::PowerModuleStatus status;
} telemetry;
friend std::ostream& operator<<(std::ostream& out, const Telemetry& self);
protected:
virtual void rx_handler(uint32_t can_id, const std::vector<uint8_t>& payload);
private:
std::atomic_bool exitTxThread;
std::thread txThreadHandle;
void txThread();
bool tx(const uint8_t destination_address, const std::vector<uint8_t>& payload);
std::atomic<float> setpoint_voltage{0}, setpoint_current{0};
std::atomic_bool on;
std::atomic_bool walkin_enable;
std::atomic_bool inverter_mode;
std::mutex settingsMutex;
};
#endif // INFY_CAN_DEVICE_HPP

View File

@@ -0,0 +1,173 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "power_supply_DCImpl.hpp"
#include <cmath>
#include <iomanip>
namespace module {
namespace main {
namespace {
constexpr double voltage_log_threshold_v = 0.5;
constexpr double current_log_threshold_a = 0.2;
bool should_log_setpoint(std::optional<double>& last_voltage, std::optional<double>& last_current, double voltage,
double current) {
if (!last_voltage.has_value() || !last_current.has_value() ||
std::abs(voltage - *last_voltage) >= voltage_log_threshold_v ||
std::abs(current - *last_current) >= current_log_threshold_a) {
last_voltage = voltage;
last_current = current;
return true;
}
return false;
}
} // namespace
void power_supply_DCImpl::init() {
caps.bidirectional = true;
caps.current_regulation_tolerance_A = 1;
caps.peak_current_ripple_A = 0.2;
caps.min_export_current_A = 1;
caps.max_export_current_A = 73.3;
caps.min_export_voltage_V = 200;
caps.max_export_voltage_V = 1000;
caps.max_export_power_W = 22000;
caps.conversion_efficiency_export = 0.95;
caps.max_import_current_A = 73.3;
caps.min_import_current_A = 0;
caps.max_import_power_W = 22000;
caps.min_import_voltage_V = 200;
caps.max_import_voltage_V = 1000;
caps.conversion_efficiency_import = 0.95;
mod->acdc.signalVoltageCurrent.connect([this](float voltage, float current) {
types::power_supply_DC::VoltageCurrent vc;
if (last_publish_mode == types::power_supply_DC::Mode::Import) {
// According to ISO 15118-20 V2G20-1034 / V2G20-1035,
// negative current indicates EV -> EVSE power transfer (discharging).
current = -current;
}
vc.current_A = current;
vc.voltage_V = voltage;
publish_voltage_current(vc);
});
mod->acdc.signalModuleStatus.connect(
[this](can_packet_acdc::PowerModuleStatus status, can_packet_acdc::InverterStatus inverter_status) {
static bool firsttime = true;
// Publish mode changes
types::power_supply_DC::Mode mode;
if (status.fault_alarm) {
mode = types::power_supply_DC::Mode::Fault;
} else if (status.dc_side_off) {
mode = types::power_supply_DC::Mode::Off;
} else if (inverter_status.invert_mode) {
mode = types::power_supply_DC::Mode::Import;
} else {
mode = types::power_supply_DC::Mode::Export;
}
if (last_publish_mode != mode || firsttime) {
publish_mode(mode);
last_publish_mode = mode;
firsttime = false;
}
});
mod->acdc.switch_on_off(false);
mod->acdc.adjust_power_factor(1.0);
mod->acdc.set_output_mode(InfyCanDevice::OutputMode::Automatic);
}
void power_supply_DCImpl::ready() {
publish_capabilities(caps);
}
void power_supply_DCImpl::handle_setMode(types::power_supply_DC::Mode& mode,
types::power_supply_DC::ChargingPhase& phase) {
std::scoped_lock lock(settings_mutex);
if (mode != last_publish_mode) {
last_logged_export_voltage.reset();
last_logged_export_current.reset();
last_logged_import_voltage.reset();
last_logged_import_current.reset();
}
if (mode == types::power_supply_DC::Mode::Off) {
mod->acdc.switch_on_off(false);
mod->acdc.set_inverter_mode(false);
} else if (mode == types::power_supply_DC::Mode::Export) {
mod->acdc.set_inverter_mode(false);
mod->acdc.switch_on_off(true);
} else if (mode == types::power_supply_DC::Mode::Import) {
mod->acdc.set_inverter_mode(true);
mod->acdc.switch_on_off(true);
} else if (mode == types::power_supply_DC::Mode::Fault) {
mod->acdc.switch_on_off(false);
mod->acdc.set_inverter_mode(false);
}
};
void power_supply_DCImpl::handle_setExportVoltageCurrent(double& voltage, double& current) {
if (voltage > caps.max_export_voltage_V)
voltage = caps.max_export_voltage_V;
else if (voltage < caps.min_export_voltage_V)
voltage = caps.min_export_voltage_V;
if (current > caps.max_export_current_A)
current = caps.max_export_current_A;
else if (current < caps.min_export_current_A)
current = caps.min_export_current_A;
std::scoped_lock lock(settings_mutex);
exportVoltage = voltage;
exportCurrentLimit = current;
if (should_log_setpoint(this->last_logged_export_voltage, this->last_logged_export_current, exportVoltage,
exportCurrentLimit)) {
EVLOG_info << std::fixed << std::setprecision(2) << "Updating voltage/current via CAN: " << exportVoltage
<< "V / " << exportCurrentLimit << "A";
}
mod->acdc.set_voltage_current(exportVoltage, exportCurrentLimit);
};
void power_supply_DCImpl::handle_setImportVoltageCurrent(double& voltage, double& current) {
if (caps.min_import_voltage_V.has_value() && caps.max_import_current_A.has_value()) {
if (voltage > caps.max_import_voltage_V.value())
voltage = caps.max_import_voltage_V.value();
else if (voltage < caps.min_import_voltage_V.value())
voltage = caps.min_import_voltage_V.value();
if (current > caps.max_import_current_A.value())
current = caps.max_import_current_A.value();
else if (current < caps.min_import_current_A.value())
current = caps.min_import_current_A.value();
std::scoped_lock lock(settings_mutex);
minImportVoltage = voltage;
importCurrentLimit = current;
if (should_log_setpoint(this->last_logged_import_voltage, this->last_logged_import_current, minImportVoltage,
importCurrentLimit)) {
EVLOG_info << std::fixed << std::setprecision(2) << "Updating voltage/current via CAN: " << minImportVoltage
<< "V / " << importCurrentLimit << "A";
}
mod->acdc.set_voltage_current(minImportVoltage, importCurrentLimit);
}
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 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 "../InfyPower_BEG1K075G.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
#include <optional>
// 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<InfyPower_BEG1K075G>& 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<InfyPower_BEG1K075G>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
std::mutex settings_mutex;
double exportVoltage{0.};
double exportCurrentLimit{0.};
double minImportVoltage{0.};
double importCurrentLimit{0.};
types::power_supply_DC::Capabilities caps;
types::power_supply_DC::Mode last_publish_mode{types::power_supply_DC::Mode::Off};
std::optional<double> last_logged_export_voltage;
std::optional<double> last_logged_export_current;
std::optional<double> last_logged_import_voltage;
std::optional<double> last_logged_import_current;
// 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

View File

@@ -0,0 +1,16 @@
description: Driver for InfyPower ACDC power supply. Currently supports BEG1K075G
with only one module and group dial set to 0.
config:
can_device:
description: CAN interface name
type: string
default: can0
provides:
main:
description: Main interface
interface: power_supply_DC
config: {}
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Cornelius Claussen

View File

@@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.10)
# set the project name
project(infy-acdc-test-tool VERSION 0.1)
# specify the C++ standard
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
# add the executable
add_executable(infy-acdc-test-tool main.cpp ../can_driver_acdc/CanDevice.cpp ../can_driver_acdc/InfyCanDevice.cpp ../can_driver_acdc/CanPackets.cpp)
target_include_directories(infy-acdc-test-tool PRIVATE ".." "../can_driver_acdc" ${everest-framework_SOURCE_DIR})
target_link_libraries(infy-acdc-test-tool PRIVATE Pal::Sigslot date::date date::date-tz everest::log)

View File

@@ -0,0 +1,78 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "InfyCanDevice.hpp"
#include <iostream>
#include <unistd.h>
#include <everest/logging.hpp>
int main(int argc, char** argv) {
InfyCanDevice can;
if (!can.open_device("can0")) {
return 1;
}
EVLOG_debug << "CAN device started.";
can.switch_on_off(false);
can.adjust_power_factor(1.0);
can.set_output_mode(InfyCanDevice::OutputMode::Automatic);
sleep(2);
can.set_generic_setting(0x21, 0x20, 420 * 1000);
can.set_generic_setting(0x21, 0x21, 5 * 1000);
can.set_generic_setting(0x21, 0x22, 440 * 1000);
can.set_generic_setting(0x21, 0x23, 5 * 1000);
can.set_generic_setting(0x21, 0x24, 200 * 1000);
can.set_generic_setting(0x21, 0x25, 5 * 1000);
can.set_generic_setting(0x21, 0x26, 190 * 1000);
can.set_generic_setting(0x21, 0x27, 5 * 1000);
can.set_generic_setting(0x21, 0x28, 53 * 1000);
can.set_generic_setting(0x21, 0x29, 5 * 1000);
can.set_generic_setting(0x21, 0x2A, 54 * 1000);
can.set_generic_setting(0x21, 0x2B, 5 * 1000);
can.set_generic_setting(0x21, 0x2C, 47 * 1000);
can.set_generic_setting(0x21, 0x2D, 5 * 1000);
can.set_generic_setting(0x21, 0x2E, 46 * 1000);
can.set_generic_setting(0x21, 0x2F, 5 * 1000);
EVLOG_debug << "Protection settings applied";
can.set_inverter_mode(false);
can.set_voltage_current(200, 10);
can.set_walkin_enabled(false);
can.switch_on_off(true);
can.set_voltage_current(200, 10);
can.adjust_power_factor(1.0);
while (true) {
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x11, 0x03, 0x00));
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x11, 0x04, 0x00));
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x11, 0x05, 0x00));
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x11, 0x06, 0x00));
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x11, 0x30, 0x00));
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x11, 0x31, 0x00));
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x11, 0x32, 0x00));
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x11, 0x33, 0x00));
for (uint8_t i = 0x01; i < 0x14; i++) {
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x21, i, 0x00));
}
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x41, 0x01, 0x00));
can.request_rx(can_packet_acdc::ADDR_MODULE, can_packet_acdc::GenericSetting(0x41, 0x02, 0x00));
usleep(50000);
EVLOG_debug << can.telemetry;
}
can.close_device();
return 0;
}