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,32 @@
#
# 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
"can_driver_acdc/CanDevice.cpp"
"can_driver_acdc/HwCanDevice.cpp"
"can_driver_acdc/CanPackets.cpp"
)
target_link_libraries(${MODULE_NAME}
PRIVATE
Pal::Sigslot
)
# 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,46 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "Huawei_R100040Gx.hpp"
#include <regex>
namespace module {
static std::vector<std::string> split_by_delimeters(const std::string& s, const std::string& delimeters) {
std::regex re("[" + delimeters + "]");
std::sregex_token_iterator first{s.begin(), s.end(), re, -1}, last;
return {first, last};
}
static std::vector<uint8_t> parse_module_addresses(const std::string a) {
std::vector<uint8_t> addresses;
auto adr = split_by_delimeters(a, ",");
for (const auto& ad : adr) {
addresses.push_back(std::stoi(ad));
}
return addresses;
}
void Huawei_R100040Gx::init() {
invoke_init(*p_main);
// open DCDC CAN device
acdc.open_device(config.can_device);
// configure module address
if (not config.module_addresses.empty()) {
auto module_addresses = parse_module_addresses(config.module_addresses);
EVLOG_info << "Amount of modules: " << module_addresses.size();
acdc.set_module_address(module_addresses);
} else {
acdc.set_module_autodetection();
}
}
void Huawei_R100040Gx::ready() {
invoke_ready(*p_main);
acdc.run();
acdc.switch_on(false);
}
} // namespace module

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef HUAWEI_R100040GX_HPP
#define HUAWEI_R100040GX_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/HwCanDevice.hpp"
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
std::string can_device;
std::string module_addresses;
int max_export_current_A;
int max_export_power_W;
};
class Huawei_R100040Gx : public Everest::ModuleBase {
public:
Huawei_R100040Gx() = delete;
Huawei_R100040Gx(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
HwCanDevice 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 // HUAWEI_R100040GX_HPP

View File

@@ -0,0 +1,157 @@
// 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 <fcntl.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"
CanDevice::CanDevice() : exit_rx_thread{false} {
can_fd = 0;
}
CanDevice::~CanDevice() {
close_device();
}
void CanDevice::open_device(const std::string& dev) {
can_device = dev;
try_open_device_internal();
// spawn read thread
exit_rx_thread = false;
rx_thread_handle = std::thread(&CanDevice::rx_thread, this);
}
bool CanDevice::try_open_device_internal() {
if (is_open) {
return false;
}
is_open = false;
if ((can_fd = socket(PF_CAN, SOCK_RAW, CAN_RAW)) < 0) {
return false;
} else {
// retrieve interface index from interface name
struct ifreq ifr;
strcpy(ifr.ifr_name, can_device.c_str());
if (ioctl(can_fd, SIOCGIFINDEX, &ifr) < 0) {
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) {
close(can_fd);
return false;
}
// set non blocking
int v = fcntl(can_fd, F_GETFD, 0);
fcntl(can_fd, F_SETFD, v | O_NONBLOCK);
is_open = true;
return true;
}
}
bool CanDevice::close_device() {
is_open = false;
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::close_device_internal() {
is_open = false;
if (can_fd != 0 && close(can_fd) == 0) {
can_fd = 0;
}
}
void CanDevice::rx_thread() {
can_frame frame;
while (!exit_rx_thread) {
if (is_open) {
/* Read non-blocking from CAN Bus.
Note that an implementation with select/poll seems to be unreliable on socket CAN.
Sometimes select() returns 0 even though there are bytes to read. If that happens, it will never return
anything but 0 again. Sometimes it returns 1, but the subsequent read blocks (even in non blocking mode).
Maybe this can be fixed by adjusting some buffering in ioctl, but no working solution has been found.
*/
size_t nbytes = read(can_fd, &frame, sizeof(struct can_frame));
if (nbytes <= 0) {
// If there is nothing to read, give it some time before we try again.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
} 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);
}
} else {
std::this_thread::sleep_for(std::chrono::milliseconds(250));
}
}
}
void CanDevice::rx_handler(uint32_t can_id, const std::vector<uint8_t>& payload) {
std::cout << "CAN frame received" << std::endl;
}
void CanDevice::connection_established() {
std::cout << "Connection established" << std::endl;
}
bool CanDevice::_tx(uint32_t can_id, const std::vector<uint8_t>& payload) {
try_open_device_internal();
if (not is_open) {
return false;
}
struct can_frame frame;
if (payload.size() > sizeof(frame.data)) {
return false;
}
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)) {
// On CAN, frames cannot be partially written. If we cannot write the packet, something is wrong with the bus.
// Close device.
close_device_internal();
return false;
}
return true;
}

View File

@@ -0,0 +1,39 @@
// 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 <mutex>
#include <thread>
class CanDevice {
public:
CanDevice();
virtual ~CanDevice();
void open_device(const std::string& dev);
bool close_device();
bool is_running() {
return is_open;
};
protected:
virtual void rx_handler(uint32_t can_id, const std::vector<uint8_t>& payload);
virtual void connection_established();
bool _tx(uint32_t can_id, const std::vector<uint8_t>& payload);
private:
bool try_open_device_internal();
void close_device_internal();
bool is_open{false};
std::string can_device;
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,259 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "CanPackets.hpp"
#include "Conversions.hpp"
#include <iostream>
namespace Huawei {
static std::string to_string(MessageId m) {
switch (m) {
case MessageId::Invalid:
return "Invalid";
case MessageId::ControlCommand:
return "ControlCommand";
case MessageId::ConfigurationCommand:
return "ConfigurationCommand";
case MessageId::QueryCommand:
return "QueryCommand";
case MessageId::ModuleReport:
return "ModuleReport";
case MessageId::QueryAllRealtimeData:
return "QueryAllRealtimeData";
case MessageId::QueryInherentModuleInformation:
return "QueryInherentModuleInformation";
case MessageId::ReceiveELabelFromMonitoringUnit:
return "ReceiveELabelFromMonitoringUnit";
case MessageId::ReturnELabelToMonitoringUnit:
return "ReturnELabelToMonitoringUnit";
case MessageId::OnlineLoadingStartup1:
return "OnlineLoadingStartup1";
case MessageId::OnlineLoadingStartup2:
return "OnlineLoadingStartup2";
case MessageId::FrameTransmission1:
return "FrameTransmission1";
case MessageId::FrameTransmission2:
return "FrameTransmission2";
case MessageId::FrameConfirmation1:
return "FrameConfirmation1";
case MessageId::FrameConfirmation2:
return "FrameConfirmation2";
case MessageId::DownloadEnd1:
return "DownloadEnd1";
case MessageId::DownloadEnd2:
return "DownloadEnd2";
case MessageId::LoadingFailure1:
return "LoadingFailure1";
case MessageId::LoadingFailure2:
return "LoadingFailure2";
case MessageId::CommonBlackBoxDataQuery:
return "CommonBlackBoxDataQuery";
}
return "InvalidEnum/" + std::to_string(static_cast<uint16_t>(m));
}
static std::string to_string(SignalId id) {
switch (id) {
case SignalId::NotImplemented:
return "NotImplemented";
case SignalId::FeatureField:
return "FeatureField";
case SignalId::SerialNumber:
return "SerialNumber";
case SignalId::ProductionInformation1:
return "ProductionInformation1";
case SignalId::ProductionInformation2:
return "ProductionInformation2";
case SignalId::SwHwVersion:
return "SwHwVersion";
case SignalId::HardwareAddr:
return "HardwareAddr";
case SignalId::BatchQuery:
return "BatchQuery";
case SignalId::VoltageRange:
return "VoltageRange";
case SignalId::FlowLimitingRange:
return "FlowLimitingRange";
case SignalId::OutputVoltage:
return "OutputVoltage";
case SignalId::DefaultVoltage:
return "DefaultVoltage";
case SignalId::OutputOVPThreshold:
return "OutputOVPThreshold";
case SignalId::OutputCurrentLimit:
return "OutputCurrentLimit";
case SignalId::DefaultOutputCurrentLimit:
return "DefaultOutputCurrentLimit";
case SignalId::OutputPowerLimit:
return "OutputPowerLimit";
case SignalId::DefaultOutputPowerLimit:
return "DefaultOutputPowerLimit";
case SignalId::SetOnOffVoltageCurrent:
return "SetOnOffVoltageCurrent";
case SignalId::RunningTime:
return "RunningTime";
case SignalId::OutputCurrent:
return "OutputCurrent";
case SignalId::DefaultOutputCurrent:
return "DefaultOutputCurrent";
case SignalId::PFCOnOff:
return "PFCOnOff";
case SignalId::FanDutyCycle:
return "FanDutyCycle";
case SignalId::RealTimeClock:
return "RealTimeClock";
case SignalId::CanCommunicationTimeout:
return "CanCommunicationTimeout";
case SignalId::GroupSettingClearingCmd:
return "GroupSettingClearingCmd";
case SignalId::TemporaryGroupSettingClearingCmd:
return "TemporaryGroupSettingClearingCmd";
case SignalId::EmergencyPowerOffControl:
return "EmergencyPowerOffControl";
case SignalId::PowerOnOff:
return "PowerOnOff";
case SignalId::ClearOutputOVP:
return "ClearOutputOVP";
case SignalId::FullFanSpeed:
return "FullFanSpeed";
case SignalId::ModuleSearch:
return "ModuleSearch";
case SignalId::DCDCOnOff:
return "DCDCOnOff";
case SignalId::AllocateSwAddresses:
return "AllocateSwAddresses";
case SignalId::UnassociatedOutputOVP:
return "UnassociatedOutputOVP";
case SignalId::ClearShortCircuit:
return "ClearShortCircuit";
case SignalId::DisableFlowEqualization:
return "DisableFlowEqualization";
case SignalId::OutputSilentMode:
return "OutputSilentMode";
case SignalId::ClearRectifierOutput:
return "ClearRectifierOutput";
case SignalId::AutomaticOutputModeSwitch:
return "AutomaticOutputModeSwitch";
case SignalId::DefaultOutputMode:
return "DefaultOutputMode";
case SignalId::CanBaudRate:
return "CanBaudRate";
case SignalId::ACInputPower:
return "ACInputPower";
case SignalId::ACInputFrequency:
return "ACInputFrequency";
case SignalId::ACInputCurrent:
return "ACInputCurrent";
case SignalId::DCOutputPower:
return "DCOutputPower";
case SignalId::RealTimeEfficiency:
return "RealTimeEfficiency";
case SignalId::OutputVoltageQuery:
return "OutputVoltageQuery";
case SignalId::ACInputVoltageUV:
return "ACInputVoltageUV";
case SignalId::ACInputVoltageVW:
return "ACInputVoltageVW";
case SignalId::ACInputVoltageWU:
return "ACInputVoltageWU";
case SignalId::PFCTemperature:
return "PFCTemperature";
case SignalId::InternalTemperature:
return "InternalTemperature";
case SignalId::TemperatureAirIntake:
return "TemperatureAirIntake";
case SignalId::AlarmStatus:
return "AlarmStatus";
case SignalId::RatedModuleCurrent:
return "RatedModuleCurrent";
case SignalId::AlarmInformation:
return "AlarmInformation";
case SignalId::OutputCurrent1:
return "OutputCurrent1";
case SignalId::OutputCurrent2:
return "OutputCurrent2";
case SignalId::OutputVoltageCurrentStatus:
return "OutputVoltageCurrentStatus";
}
return "InvalidEnum/" + std::to_string(static_cast<uint16_t>(id));
}
static std::string to_string(ErrorType e) {
switch (e) {
case ErrorType::Success:
return "Success";
case ErrorType::InvalidCommand:
return "InvalidCommand";
case ErrorType::AddressIdentificationInProgress:
return "AddressIdentificationInProgress";
}
return "InvalidEnum/" + std::to_string(static_cast<uint16_t>(e));
}
// encode the 29 bits ID field
uint32_t encode_can_id(uint8_t module_address, MessageId message_id) {
constexpr uint8_t PROTOCOL_ID = 0x0D;
uint32_t id = PROTOCOL_ID << 23; // constant protocol id bits 23..28 (6 bits)
uint32_t m_addr = module_address & 0x7F; // ensure it is only 7 bits long
id |= (m_addr << 16); // Module address is bits 16..22 (7 bits long)
id |= (static_cast<std::underlying_type<MessageId>::type>(message_id)
<< 8); // Command/Message id is bits 8..15 (8 bits long)
id |= (1 << 7); // Source is control unit (bit 7)
id |= (0x1F << 2); // Group ID: default is all bits 1 (no grouping). Bits 2..6 (5 bits long)
// Bit 1: Set to 0 to use hardware address
// Bit 0: Set to 0 there is no further data frame
return id;
};
std::ostream& operator<<(std::ostream& out, const Packet& self) {
out << "Huawei " << (self.packet_source_control_unit ? "H->M" : "M->H")
<< " Addr: " << std::to_string(self.module_address) << " MsgId: " << to_string(self.message_id)
<< " ErrorType: " << to_string(self.error_type) << " SignalId: " << to_string(self.signal_id)
<< " Byte2/3: " << std::to_string(self.byte2) << "/" << std::to_string(self.byte3)
<< " Data: " << std::to_string(self.data) << (self.last_packet ? "" : " [+]");
return out;
}
// Packs the packet as 8 byte binary
Packet::operator std::vector<uint8_t>() {
std::vector<uint8_t> out;
// 4 bits ErrorType and 12 bits Signal ID
to_raw(static_cast<uint8_t>(((static_cast<uint8_t>(error_type) & 0x0F) << 4) |
((static_cast<uint16_t>(signal_id) & 0x0FFF) >> 8)),
out);
to_raw(static_cast<uint8_t>(((static_cast<uint16_t>(signal_id) & 0x00FF))), out);
to_raw(byte2, out); // 1 byte
to_raw(byte3, out); // 1 byte
to_raw(data, out); // 4 bytes of actual data
return out;
}
// Constructor to initialize from received raw packet
Packet::Packet(const uint32_t can_id, const std::vector<uint8_t>& raw) {
module_address = module_address_from_can_id(can_id);
message_id = message_id_from_can_id(can_id);
group_address = group_address_from_can_id(can_id);
packet_source_control_unit = packet_source_from_can_id(can_id);
protocol_id = protocol_id_from_can_id(can_id);
last_packet = last_packet_from_can_id(can_id);
signal_id = static_cast<SignalId>(static_cast<uint16_t>(((from_raw<uint8_t>(raw, 0) & 0x0F) << 8)) |
(from_raw<uint8_t>(raw, 1)));
error_type = static_cast<ErrorType>(from_raw<uint8_t>(raw, 0) >> 4);
byte2 = from_raw<uint8_t>(raw, 2);
byte3 = from_raw<uint8_t>(raw, 3);
data = from_raw<uint32_t>(raw, 4);
}
} // namespace Huawei

View File

@@ -0,0 +1,208 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef CAN_PACKETS_HPP
#define CAN_PACKETS_HPP
#include <cstdint>
#include <linux/can.h>
#include <ostream>
#include <vector>
namespace Huawei {
constexpr uint32_t RTQ_RUNNING_TIME{1 << 0};
constexpr uint32_t RTQ_AC_INPUT_POWER{1 << 1};
constexpr uint32_t RTQ_AC_INPUT_FREQ{1 << 2};
constexpr uint32_t RTQ_AC_INPUT_CURRENT{1 << 3};
constexpr uint32_t RTQ_DC_OUTPUT_POWER{1 << 4};
constexpr uint32_t RTQ_EFFICIENCY{1 << 5};
constexpr uint32_t RTQ_OUTPUT_VOLTAGE{1 << 6};
constexpr uint32_t RTQ_DC_OUTPUT_CURRENT_LIMIT{1 << 7};
// Bit 8 seems to be a bug in the documentation (same as bit 9)
constexpr uint32_t RTQ_AC_INPUT_VOLTAGE_UV{1 << 9};
constexpr uint32_t RTQ_AC_INPUT_VOLTAGE_VW{1 << 10};
constexpr uint32_t RTQ_AC_INPUT_VOLTAGE_WU{1 << 11};
constexpr uint32_t RTQ_INTERNAL_TEMPERATURE{1 << 12};
constexpr uint32_t RTQ_TEMPERATURE_AIR_INTAKE{1 << 13};
constexpr uint32_t RTQ_OUTPUT_CURRENT_1{1 << 14};
constexpr uint32_t RTQ_OUTPUT_CURRENT_2{1 << 15};
constexpr uint32_t RTQ_ALARM_STATUS{1 << 16};
constexpr uint32_t RTQ_DC_OUTPUT_VOLTAGE_SETPOINT{1 << 17};
constexpr uint32_t RTQ_DC_OUTPUT_CURRENT_PERCENTAGE{1 << 18};
constexpr uint32_t RTQ_DC_OUTPUT_CURRENT_SETPOINT{1 << 19};
constexpr uint32_t RTQ_ALARM_INFORMATION{1 << 20};
constexpr uint32_t RTQ_SETTING_QUERY{1 << 21};
constexpr uint32_t RTQ_OUTPUT_VOLTAGE_CURRENT_STATUS{1 << 22};
constexpr uint32_t RTQ_ACTUAL_OUTPUT_POWER_LIMITING{1 << 23};
enum class MessageId : uint8_t {
Invalid = 0x00,
ControlCommand = 0x80,
ConfigurationCommand = 0x81,
QueryCommand = 0x82,
ModuleReport = 0x83,
QueryAllRealtimeData = 0x40,
QueryInherentModuleInformation = 0x50,
ReceiveELabelFromMonitoringUnit = 0xD1,
ReturnELabelToMonitoringUnit = 0xD2,
OnlineLoadingStartup1 = 0xD3,
OnlineLoadingStartup2 = 0xE3,
FrameTransmission1 = 0xD4,
FrameTransmission2 = 0xE4,
FrameConfirmation1 = 0xD5,
FrameConfirmation2 = 0xE5,
DownloadEnd1 = 0xD6,
DownloadEnd2 = 0xE6,
LoadingFailure1 = 0xD7,
LoadingFailure2 = 0xE7,
CommonBlackBoxDataQuery = 0xDA,
};
// encode the 29 bits ID field
uint32_t encode_can_id(uint8_t module_address, MessageId message_id);
inline uint8_t module_address_from_can_id(const uint32_t id) {
return (id >> 16) & 0x7F;
};
inline uint8_t group_address_from_can_id(const uint32_t id) {
return (id >> 2) & 0x1F;
};
inline bool packet_source_from_can_id(const uint32_t id) {
return ((id >> 7) & 0x01) == 0x01;
};
inline bool last_packet_from_can_id(const uint32_t id) {
return (id & 0x01) == 0x00;
};
inline uint8_t protocol_id_from_can_id(const uint32_t id) {
return ((id >> 23) & 0x3F);
};
inline MessageId message_id_from_can_id(const uint32_t id) {
const uint8_t m_id = (id >> 8) & 0xFF;
return static_cast<MessageId>(m_id);
};
enum class ErrorType : uint8_t {
Success = 0x00,
InvalidCommand = 0x02,
AddressIdentificationInProgress = 0x03,
};
enum class SignalId : uint16_t {
NotImplemented = 0xFFFF,
BatchQuery = 0x000,
FeatureField = 0x001,
SerialNumber = 0x002,
ProductionInformation1 = 0x003,
ProductionInformation2 = 0x004,
SwHwVersion = 0x005,
HardwareAddr = 0x006,
VoltageRange = 0x009,
FlowLimitingRange = 0x00A,
OutputVoltage = 0x100,
DefaultVoltage = 0x101,
OutputOVPThreshold = 0x102,
OutputCurrentLimit = 0x103,
DefaultOutputCurrentLimit = 0x104,
OutputPowerLimit = 0x105,
DefaultOutputPowerLimit = 0x106,
SetOnOffVoltageCurrent = 0x108,
RunningTime = 0x10E,
OutputCurrent = 0x10F,
DefaultOutputCurrent = 0x110,
PFCOnOff = 0x111,
FanDutyCycle = 0x114,
RealTimeClock = 0x117,
CanCommunicationTimeout = 0x118,
GroupSettingClearingCmd = 0x119,
TemporaryGroupSettingClearingCmd = 0x11A,
EmergencyPowerOffControl = 0x131,
PowerOnOff = 0x132,
ClearOutputOVP = 0x133,
FullFanSpeed = 0x134,
ModuleSearch = 0x135,
DCDCOnOff = 0x136,
AllocateSwAddresses = 0x13A,
UnassociatedOutputOVP = 0x13C,
ClearShortCircuit = 0x145,
DisableFlowEqualization = 0x146,
OutputSilentMode = 0x148,
ClearRectifierOutput = 0x149,
AutomaticOutputModeSwitch = 0x14A,
DefaultOutputMode = 0x14C,
CanBaudRate = 0x14D,
ACInputPower = 0x170,
ACInputFrequency = 0x171,
ACInputCurrent = 0x172,
DCOutputPower = 0x173,
RealTimeEfficiency = 0x174,
OutputVoltageQuery = 0x175,
ACInputVoltageUV = 0x179,
ACInputVoltageVW = 0x17A,
ACInputVoltageWU = 0x17B,
PFCTemperature = 0x17E,
InternalTemperature = 0x17F,
TemperatureAirIntake = 0x180,
OutputCurrent1 = 0x181,
OutputCurrent2 = 0x182,
AlarmStatus = 0x183,
RatedModuleCurrent = 0x188,
AlarmInformation = 0x18F,
OutputVoltageCurrentStatus = 0x191,
};
// Addresses
const uint8_t ADDR_BROADCAST = 0x00;
struct Packet {
// Constructor for Packets to be sent to modules
Packet(MessageId _message_id, SignalId _signal_id, uint8_t _byte2, uint8_t _byte3, uint32_t _data) :
message_id(_message_id),
signal_id(_signal_id),
byte2(_byte2),
byte3(_byte3),
data(_data),
packet_source_control_unit(true){};
// Constructor for simple requests to module
Packet(MessageId _message_id, SignalId _signal_id) :
message_id(_message_id), signal_id(_signal_id), packet_source_control_unit(true){};
// Constructor to initialize from received raw packet
Packet(const uint32_t can_id, const std::vector<uint8_t>& raw);
friend std::ostream& operator<<(std::ostream& out, const Packet& self);
operator std::vector<uint8_t>();
void set_module_address(uint8_t address) {
module_address = address;
};
uint32_t get_can_id() {
return encode_can_id(module_address, message_id);
};
// Part of CAN ID field
uint8_t protocol_id{0x0D};
uint8_t module_address{0};
uint8_t group_address{0};
MessageId message_id{MessageId::Invalid};
// In CAN payload bytes
ErrorType error_type{ErrorType::Success};
uint8_t byte2{0};
uint8_t byte3{0};
uint32_t data{0};
SignalId signal_id{SignalId::NotImplemented};
bool packet_source_control_unit{false};
bool last_packet{true};
};
} // namespace Huawei
#endif // CAN_PACKETS_HPP

View File

@@ -0,0 +1,73 @@
// 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,298 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "HwCanDevice.hpp"
#include "CanPackets.hpp"
#include <algorithm>
#include <iostream>
#include <unistd.h>
#include <fmt/core.h>
HwCanDevice::~HwCanDevice() {
exit_tx_thread = true;
}
bool HwCanDevice::switch_on(bool on) {
// actual switching on will be handled in tx thread
requested_on = on;
return true;
}
bool HwCanDevice::switch_on_nolock(bool on) {
// Power On/Off Packet
Huawei::Packet power_on_off(Huawei::MessageId::ControlCommand, Huawei::SignalId::PowerOnOff);
power_on_off.byte3 = not on;
send_to_all_modules(power_on_off);
is_on = on;
return true;
}
bool HwCanDevice::set_voltage_current(float voltage, float current) {
requested_set_point_voltage = voltage;
requested_set_point_current = current;
return true;
}
bool HwCanDevice::set_voltage_current_nolock(float voltage, float current) {
Huawei::Packet set_voltage(Huawei::MessageId::ControlCommand, Huawei::SignalId::OutputVoltage);
set_voltage.data = voltage * 1024;
send_to_all_modules(set_voltage);
Huawei::Packet set_current(Huawei::MessageId::ControlCommand, Huawei::SignalId::OutputCurrent);
set_current.data = current * 1024 / module_addresses.size();
set_current.byte3 = module_addresses.size();
send_to_all_modules(set_current);
set_point_voltage = voltage;
set_point_current = current;
return true;
}
static std::string to_serial_number(uint8_t b2, uint8_t b3, uint8_t data) {
return fmt::format("{}:{}:{}", b2, b3, data);
}
static std::string to_version_string(uint8_t b2, uint8_t b3, uint32_t data) {
uint16_t hw_version = b2 << 8 | b3;
int dcdc_versoion_hi = data >> 24;
int dcdc_versoion_lo = data >> 16 & 0xFF;
int pfc_versoion_hi = data >> 8 & 0xFF;
int pfc_versoion_lo = data & 0xFF;
return fmt::format("HW Version: {} | DCDC: {}.{} | PFC: {}.{}", hw_version, dcdc_versoion_hi, dcdc_versoion_lo,
pfc_versoion_hi, pfc_versoion_lo);
}
static bool address_already_in_list(std::vector<uint8_t>& v, uint8_t nv) {
auto it = std::find(v.begin(), v.end(), nv);
if (it == v.end()) {
return false;
}
return true;
}
void HwCanDevice::connection_established() {
switch_on(false);
request_module_info();
}
void HwCanDevice::rx_handler(uint32_t can_id, const std::vector<uint8_t>& payload) {
// We only use extended frames here
if (!(can_id & CAN_EFF_FLAG)) {
return;
}
// parse packet
auto packet = Huawei::Packet(can_id, payload);
if (packet.packet_source_control_unit or packet.protocol_id not_eq 0x0D) {
return;
}
if (packet.error_type not_eq Huawei::ErrorType::Success) {
std::cout << "Error in CAN packet: " << packet << std::endl;
return;
}
// We received a packet from the PSU, reset timeout timer
last_communication_received = std::chrono::steady_clock::now();
uint8_t source_address = packet.module_address;
bool packet_handled = true;
if (packet.message_id == Huawei::MessageId::QueryInherentModuleInformation) {
switch (packet.signal_id) {
case Huawei::SignalId::SerialNumber:
if (module_auto_detection) {
module_serial_numbers[packet.module_address] =
"[auto detected] " + to_serial_number(packet.byte2, packet.byte3, packet.data);
if (not address_already_in_list(module_addresses, source_address)) {
// Found a new module? Add it to the list
module_addresses.push_back(source_address);
// Update capabilities
auto caps = get_capabilities();
signal_capabilities(caps);
}
} else {
module_serial_numbers[packet.module_address] =
to_serial_number(packet.byte2, packet.byte3, packet.data);
}
break;
case Huawei::SignalId::SwHwVersion:
if (not address_already_in_list(module_addresses_reported, source_address)) {
signal_serial_number(packet.module_address,
module_serial_numbers[packet.module_address] + " | " +
to_version_string(packet.byte2, packet.byte3, packet.data));
module_addresses_reported.push_back(source_address);
}
break;
default:
// Ignore all other Inherent Module Information packages
packet_handled = true;
break;
}
} else if (packet.message_id == Huawei::MessageId::QueryAllRealtimeData) {
switch (packet.signal_id) {
case Huawei::SignalId::OutputVoltageCurrentStatus: {
const float v = (packet.data & 0x0000FFFF) / 10.;
const float c = ((packet.data & 0xFFFF0000) >> 16) / 10.;
// report sum of all currents
telemetries[source_address].current = c;
// calculate total current
total_current = 0.;
for (const auto& t : telemetries) {
total_current += t.second.current;
}
telemetries[source_address].voltage = v;
// report average voltage
float voltage = 0.;
for (const auto& t : telemetries) {
voltage += t.second.voltage;
}
voltage /= telemetries.size();
signal_voltage_current(voltage, total_current);
} break;
default:
packet_handled = false;
break;
}
} else if (packet.message_id == Huawei::MessageId::ConfigurationCommand) {
// Ignore all config command replies for now
packet_handled = true;
} else if (packet.message_id == Huawei::MessageId::ControlCommand) {
// Ignore all control command replies for now
packet_handled = true;
} else {
packet_handled = false;
}
if (not packet_handled) {
std::cout << "UNHANDLED Packet received: " << packet << std::endl;
}
}
types::power_supply_DC::Capabilities HwCanDevice::get_capabilities() {
// IMPROVE ME: this could be queried from the power supply instead
types::power_supply_DC::Capabilities caps;
caps.bidirectional = false;
caps.current_regulation_tolerance_A = 1;
caps.peak_current_ripple_A = 0.5;
caps.min_export_current_A = 0;
caps.max_export_current_A = 133 * module_addresses.size();
caps.min_export_voltage_V = 150;
caps.max_export_voltage_V = 1000;
caps.max_export_power_W = 40000 * module_addresses.size();
caps.max_import_current_A = 0;
caps.min_import_current_A = 0;
caps.max_import_power_W = 0;
caps.min_import_voltage_V = 0;
caps.max_import_voltage_V = 0;
return caps;
}
void HwCanDevice::request_module_info() {
// Request information about modules once
send_to_broadcast({Huawei::MessageId::QueryInherentModuleInformation, Huawei::SignalId::BatchQuery});
}
void HwCanDevice::set_mode() {
// Set mode to automatic switching
Huawei::Packet mode(Huawei::MessageId::ConfigurationCommand, Huawei::SignalId::AutomaticOutputModeSwitch);
mode.byte3 = 0x01; // switch according to actual voltage on the terminals
send_to_all_modules(mode);
}
void HwCanDevice::tx_thread() {
Huawei::Packet query_rt_data(Huawei::MessageId::QueryAllRealtimeData, Huawei::SignalId::BatchQuery);
query_rt_data.byte3 = 0xAA; // Enable bit configuration of individual messages
query_rt_data.data = Huawei::RTQ_OUTPUT_VOLTAGE_CURRENT_STATUS;
uint8_t telemetry_cnt = 0;
bool comm_timeout_err = false;
bool comm_bus_err = false;
bool last_err_present = false;
set_mode();
while (!exit_tx_thread) {
{
// Is there a communication problem in the CAN stack?
comm_bus_err = not is_running();
// Did we receive something from the PSU within timeout?
// I.e. the CAN stack is working but the hardware does not send anything
comm_timeout_err = std::chrono::steady_clock::now() - last_communication_received > COMMS_TIMEOUT;
bool err_present = comm_bus_err or comm_timeout_err;
if (err_present not_eq last_err_present) {
signal_communication_error(err_present,
(comm_bus_err ? "CAN Bus error: No such device or wrong settings"
: "Power supply does not respond on CAN bus"));
}
last_err_present = err_present;
// Request all real time data (telemetry) as configured above
if (telemetry_cnt++ % 5 == 0) {
send_to_all_modules(query_rt_data);
}
// Configure mode roughly every second
if (not is_on and telemetry_cnt++ % 20 == 0) {
set_mode();
}
// Read serial numbers every few seconds
if (telemetry_cnt++ % 40 == 0) {
request_module_info();
}
// Do we need to switch on or off?
if (last_requested_on not_eq requested_on) {
last_requested_on = requested_on;
switch_on_nolock(requested_on);
signal_on_off(requested_on);
set_voltage_current_nolock(requested_set_point_voltage, requested_set_point_current);
}
// Do we need to set voltage/current limits?
if (requested_set_point_voltage not_eq set_point_voltage or
requested_set_point_current not_eq set_point_current) {
set_voltage_current_nolock(requested_set_point_voltage, requested_set_point_current);
}
}
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
bool HwCanDevice::tx(Huawei::Packet& packet) {
uint32_t can_id = packet.get_can_id();
can_id |= 0x80000000U; // Extended frame format
return _tx(can_id, packet);
}
std::ostream& operator<<(std::ostream& out, const HwCanDevice::Telemetry& self) {
out << "DC output: " << std::to_string(self.voltage) << "V / " << std::to_string(self.current) << "A" << std::endl;
return out;
}

View File

@@ -0,0 +1,124 @@
// 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 <map>
#include <mutex>
#include <sigslot/signal.hpp>
#include <thread>
#include <generated/interfaces/power_supply_DC/Implementation.hpp>
class HwCanDevice : public CanDevice {
public:
HwCanDevice(){
};
~HwCanDevice();
void run() {
// spawn thread that requests some data periodically to keep the connection alive
last_communication_received = std::chrono::steady_clock::now();
exit_tx_thread = false;
tx_thread_handle = std::thread(&HwCanDevice::tx_thread, this);
};
void set_module_address(const std::vector<uint8_t>& _module_addresses) {
module_addresses = _module_addresses;
// Update capabilities
auto caps = get_capabilities();
signal_capabilities(caps);
};
void set_module_autodetection() {
module_auto_detection = true;
};
// Commands
bool switch_on(bool on);
bool set_voltage_current(float voltage, float current);
int get_number_of_modules() {
return module_addresses.size();
}
void request_module_info();
// Data out
sigslot::signal<float, float> signal_voltage_current;
sigslot::signal<bool> signal_on_off; // true: on, false: off
sigslot::signal<int, const std::string&> signal_serial_number;
sigslot::signal<const types::power_supply_DC::Capabilities&> signal_capabilities;
sigslot::signal<bool, const std::string&>
signal_communication_error; // true for error, false means error was cleared, error description
struct Telemetry {
float voltage{0.};
float current{0.};
};
float total_current{0.};
std::map<uint8_t, Telemetry> telemetries;
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);
virtual void connection_established();
private:
std::atomic_bool exit_tx_thread;
std::thread tx_thread_handle;
void tx_thread();
void set_mode();
bool tx(Huawei::Packet& packet);
types::power_supply_DC::Capabilities get_capabilities();
void send_to_all_modules(Huawei::Packet packet) {
// Send to each module
for (auto module_address : module_addresses) {
packet.set_module_address(module_address);
tx(packet);
}
};
void send_to_broadcast(Huawei::Packet packet) {
packet.set_module_address(Huawei::ADDR_BROADCAST);
tx(packet);
};
std::vector<uint8_t> module_addresses{};
std::vector<uint8_t> module_addresses_reported{};
bool switch_on_nolock(bool on);
bool set_voltage_current_nolock(float voltage, float current);
std::atomic_bool module_auto_detection{false};
float set_point_voltage{0.};
float set_point_current{0.};
std::atomic<float> requested_set_point_voltage{0.};
std::atomic<float> requested_set_point_current{0.};
bool is_on{false};
std::atomic_bool requested_on{false};
bool last_requested_on{false};
std::chrono::time_point<std::chrono::steady_clock> last_communication_received;
static constexpr std::chrono::seconds COMMS_TIMEOUT{2};
std::unordered_map<uint8_t, std::string> module_serial_numbers;
};
#endif // INFY_CAN_DEVICE_HPP

View File

@@ -0,0 +1,119 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "power_supply_DCImpl.hpp"
namespace module {
namespace main {
void power_supply_DCImpl::init() {
caps.bidirectional = false;
caps.current_regulation_tolerance_A = 1;
caps.peak_current_ripple_A = 0.5;
caps.min_export_current_A = 0;
caps.max_export_current_A = mod->config.max_export_current_A;
caps.min_export_voltage_V = 150;
caps.max_export_voltage_V = 1000;
caps.max_export_power_W = mod->config.max_export_power_W;
caps.max_import_current_A = 0;
caps.min_import_current_A = 0;
caps.max_import_power_W = 0;
caps.min_import_voltage_V = 0;
caps.max_import_voltage_V = 0;
mod->acdc.signal_capabilities.connect([this](const types::power_supply_DC::Capabilities& c) {
caps = c;
// limit by config values
if (caps.max_export_current_A > mod->config.max_export_current_A) {
caps.max_export_current_A = mod->config.max_export_current_A;
}
if (caps.max_export_power_W > mod->config.max_export_power_W) {
caps.max_export_power_W = mod->config.max_export_power_W;
}
publish_capabilities(caps);
});
mod->acdc.signal_voltage_current.connect([this](float voltage, float current) {
types::power_supply_DC::VoltageCurrent vc;
vc.current_A = current;
vc.voltage_V = voltage;
publish_voltage_current(vc);
});
mod->acdc.signal_serial_number.connect([this](int module_id, const std::string& serial_number) {
EVLOG_info << "Module ID " << module_id << ": " << serial_number;
});
mod->acdc.signal_on_off.connect([this](bool on) {
if (on) {
publish_mode(types::power_supply_DC::Mode::Import);
} else {
publish_mode(types::power_supply_DC::Mode::Off);
}
});
mod->acdc.signal_communication_error.connect([this](bool err, const std::string& desc) {
if (err) {
Everest::error::Error error_object = error_factory->create_error("power_supply_DC/CommunicationFault", "",
desc, Everest::error::Severity::High);
raise_error(error_object);
comm_fault_error_raised = true;
} else {
if (comm_fault_error_raised) {
clear_error("power_supply_DC/CommunicationFault");
comm_fault_error_raised = false;
}
}
});
}
void power_supply_DCImpl::ready() {
mod->p_main->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 == types::power_supply_DC::Mode::Off) {
mod->acdc.switch_on(false);
} else if (mode == types::power_supply_DC::Mode::Export) {
mod->acdc.switch_on(true);
} else if (mode == types::power_supply_DC::Mode::Import) {
mod->acdc.switch_on(false);
} else if (mode == types::power_supply_DC::Mode::Fault) {
mod->acdc.switch_on(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);
export_voltage = voltage;
export_current_limit = current;
EVLOG_info << "Updating voltage/current via CAN: " << export_voltage << "V / " << export_current_limit << "A";
mod->acdc.set_voltage_current(export_voltage, export_current_limit);
};
void power_supply_DCImpl::handle_setImportVoltageCurrent(double& voltage, double& current) {
EVLOG_error << "Not implemented";
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,72 @@
// 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 "../Huawei_R100040Gx.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
// 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<Huawei_R100040Gx>& 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<Huawei_R100040Gx>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
std::mutex settings_mutex;
double export_voltage{0.};
double export_current_limit{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};
bool comm_fault_error_raised{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

View File

@@ -0,0 +1,42 @@
description: >-
Driver for Huawei_R100040Gx ACDC power supply. Supports multiple stacked modules.
This module never exits. If communication is lost to the power supply, a CommunicationFault is raised.
The error is cleared once communication is back up again.
The Linux can device (e.g. can0) should ideally be present and correctly configured when the module is started,
but it also works if the device comes up later. Once it was up once, it should not disappear again.
The PSU may be appear or disappear on the CAN bus itself any time.
config:
can_device:
description: CAN interface name
type: string
default: can0
module_addresses:
description: >-
Module Addresses to use. Set to "" for auto detect (Uses all modules that reply to broadcast requests on this CAN bus).
If multiple hardware addresses are listed (e.g. "120,121"), only those will be used. Current will be shared between them.
type: string
default: ""
max_export_current_A:
description: >-
Maximum current that the PSU module can deliver. When using multiple PSUs in parallel, this refers to the total current capability of all modules.
Set to 0 to use hardware defaults.
type: integer
default: 0
max_export_power_W:
description: >-
Maximum power that the PSU module can deliver. When using multiple PSUs in parallel, this refers to the total power capability of all modules.
Set to 0 to use hardware defaults.
type: integer
default: 0
provides:
main:
description: Main interface
interface: power_supply_DC
config: {}
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Andreas Heinrich
- Cornelius Claussen
- Florin Mihut
- Jan Christoph Habig