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,131 @@
// 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 "CanBus.hpp"
#include <everest/logging.hpp>
using namespace std::chrono_literals;
namespace {
// Timer configuration constants
constexpr auto CAN_RECOVERY_TIMER_INTERVAL = 1000ms;
constexpr auto CAN_POLL_STATUS_TIMER_INTERVAL = 1000ms;
} // namespace
CanBus::CanBus() : rx_thread_online{true}, can_bus(nullptr) {
}
CanBus::~CanBus() {
close_device();
}
bool CanBus::open_device(const std::string& dev) {
can_bus = std::make_unique<can::socket_can>(dev);
can_bus->set_rx_handler([&](auto const& pl, auto&) {
uint32_t can_id = pl.get_can_id();
this->rx_handler(can_id, pl.payload);
});
can_bus->set_error_handler([&](auto err, auto msg) {
if (err != 0) {
EVLOG_error << "CAN error: " << err << " - " << msg << std::endl;
on_error.store(true);
} else {
EVLOG_info << "CAN error cleared: " << msg << std::endl;
on_error.store(false);
}
});
ev_handler.register_event_handler(can_bus.get());
recovery_timer.set_timeout(CAN_RECOVERY_TIMER_INTERVAL);
ev_handler.register_event_handler(&recovery_timer, [&](event::fd_event_handler::event_list const& events) {
if (on_error.load()) {
EVLOG_error << "CAN error detected, attempting recovery";
can_bus->reset();
}
});
poll_status_timer.set_timeout(CAN_POLL_STATUS_TIMER_INTERVAL);
ev_handler.register_event_handler(
&poll_status_timer, [&](event::fd_event_handler::event_list const& events) { poll_status_handler(); });
rx_thread_handle = std::thread(&CanBus::rx_thread, this);
return true;
}
bool CanBus::close_device() {
if (!can_bus) {
return true; // Already closed
}
EVLOG_info << "Closing CAN device";
// Stop the RX thread first
rx_thread_online = false;
rx_thread_cv.notify_one();
if (rx_thread_handle.joinable()) {
rx_thread_handle.join();
}
// Unregister event handlers (this stops timers and cleans up any pending events)
ev_handler.unregister_event_handler(&recovery_timer);
ev_handler.unregister_event_handler(&poll_status_timer);
ev_handler.unregister_event_handler(can_bus.get());
// Close CAN socket
can_bus.reset();
// Reset error state
on_error.store(false);
EVLOG_info << "CAN device closed successfully";
return true;
}
void CanBus::rx_thread() {
EVLOG_info << "Starting CAN RX thread" << std::endl;
ev_handler.run(rx_thread_online);
}
static std::string bytes_to_hex(const std::vector<uint8_t>& bytes) {
std::stringstream ss;
ss << std::hex << std::setfill('0');
for (size_t i = 0; i < bytes.size(); ++i) {
ss << std::setw(2) << static_cast<unsigned>(bytes[i]);
}
return ss.str();
}
bool CanBus::_tx(uint32_t can_id, const std::vector<uint8_t>& payload) {
// EVLOG_debug << "CAN frame sent using ID:" << std::hex << can_id << "#" << bytes_to_hex(payload);
// Validate payload size for CAN protocol compliance
if (payload.size() > 8) {
EVLOG_error << "CAN payload too large (" << payload.size() << " bytes), max 8 bytes allowed";
return false;
}
// Winline protocol uses 29-bit extended CAN IDs, so we need to set the extended frame flag
everest::lib::io::can::can_dataset data;
data.set_can_id_with_flags(can_id | CAN_EFF_FLAG);
data.payload = payload;
if (on_error.load()) {
EVLOG_error << "CAN error detected, not sending frame";
return false;
}
return can_bus->tx(data);
}

View File

@@ -0,0 +1,44 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef CAN_BUS_HPP
#define CAN_BUS_HPP
#include "CanPackets.hpp"
#include <atomic>
#include <condition_variable>
#include <linux/can.h>
#include <mutex>
#include <thread>
#include <everest/io/can/socket_can.hpp>
#include <everest/io/event/fd_event_handler.hpp>
#include <everest/io/event/timer_fd.hpp>
using namespace everest::lib::io;
class CanBus {
public:
CanBus();
virtual ~CanBus();
bool open_device(const std::string& dev);
bool close_device();
protected:
virtual void rx_handler(uint32_t can_id, const std::vector<uint8_t>& payload) = 0;
virtual void poll_status_handler() = 0;
bool _tx(uint32_t can_id, const std::vector<uint8_t>& payload);
private:
std::unique_ptr<can::socket_can> can_bus;
std::atomic_bool on_error{false};
event::fd_event_handler ev_handler;
event::timer_fd recovery_timer;
event::timer_fd poll_status_timer;
std::atomic_bool rx_thread_online;
std::thread rx_thread_handle;
std::condition_variable rx_thread_cv;
void rx_thread();
};
#endif // CAN_BUS_HPP

View File

@@ -0,0 +1,584 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "CanPackets.hpp"
#include "Conversions.hpp"
#include <everest/logging.hpp>
#include <algorithm>
#include <cstring>
#include <iomanip>
#include <sstream>
namespace can_packet_acdc {
namespace {
// Winline CAN ID bit positions (29-bit extended CAN ID)
// Bits 31-29: Not used (always 0)
// Bits 28-20: PROTNO (9 bits) = 0x060
// Bit 19: PTP (1 bit)
// Bits 18-11: DSTADDR (8 bits)
// Bits 10-3: SRCADDR (8 bits)
// Bits 2-0: Group (3 bits)
}
// Winline CAN ID encoding/decoding functions
uint32_t encode_can_id(uint8_t source_address, uint8_t destination_address, uint8_t group_number, bool point_to_point) {
uint32_t id = 0;
// Bits 2-0: Group number (3 bits)
id |= (group_number & WinlineProtocol::GROUP_MASK) << WinlineProtocol::GROUP_SHIFT;
// Bits 10-3: Source Address (8 bits)
id |= (source_address & WinlineProtocol::ADDRESS_MASK) << WinlineProtocol::SRCADDR_SHIFT;
// Bits 18-11: Destination Address (8 bits)
id |= (destination_address & WinlineProtocol::ADDRESS_MASK) << WinlineProtocol::DSTADDR_SHIFT;
// Bit 19: Point-to-point flag (1 bit)
id |= (point_to_point ? 1 : 0) << WinlineProtocol::PTP_SHIFT;
// Bits 28-20: Protocol number (9 bits) - always 0x060 for Winline
id |= (WinlineProtocol::PROTNO & WinlineProtocol::PROTNO_MASK) << WinlineProtocol::PROTNO_SHIFT;
return id;
}
uint8_t destination_address_from_can_id(uint32_t id) {
return (id >> WinlineProtocol::DSTADDR_SHIFT) & WinlineProtocol::ADDRESS_MASK;
}
uint8_t source_address_from_can_id(uint32_t id) {
return (id >> WinlineProtocol::SRCADDR_SHIFT) & WinlineProtocol::ADDRESS_MASK;
}
uint8_t group_number_from_can_id(uint32_t id) {
return (id >> WinlineProtocol::GROUP_SHIFT) & WinlineProtocol::GROUP_MASK;
}
bool point_to_point_from_can_id(uint32_t id) {
return ((id >> WinlineProtocol::PTP_SHIFT) & WinlineProtocol::PTP_MASK) != 0;
}
uint16_t protocol_number_from_can_id(uint32_t id) {
return (id >> WinlineProtocol::PROTNO_SHIFT) & WinlineProtocol::PROTNO_MASK;
}
// Command building helpers for Winline protocol
uint32_t build_read_command_id(uint8_t source_address, uint8_t destination_address, uint8_t group_number,
bool point_to_point) {
return encode_can_id(source_address, destination_address, group_number, point_to_point);
}
uint32_t build_set_command_id(uint8_t source_address, uint8_t destination_address, uint8_t group_number,
bool point_to_point) {
return encode_can_id(source_address, destination_address, group_number, point_to_point);
}
// Command frame builders for Winline register-based communication
std::vector<uint8_t> build_read_command(uint16_t register_number) {
std::vector<uint8_t> data(8, 0); // Initialize 8-byte payload with zeros
// Byte 0: Function code for READ operation
data[0] = WinlineProtocol::FUNCTION_READ;
// Byte 1: Reserved (always 0x00)
data[1] = 0x00;
// Bytes 2-3: Register number (big-endian)
data[2] = (register_number >> 8) & 0xFF;
data[3] = register_number & 0xFF;
// Bytes 4-7: Reserved (always 0x00)
data[4] = 0x00;
data[5] = 0x00;
data[6] = 0x00;
data[7] = 0x00;
return data;
}
std::vector<uint8_t> build_set_command(uint16_t register_number, const std::vector<uint8_t>& data_payload) {
std::vector<uint8_t> data(8, 0); // Initialize 8-byte payload with zeros
// Byte 0: Function code for SET operation
data[0] = WinlineProtocol::FUNCTION_SET;
// Byte 1: Reserved (always 0x00)
data[1] = 0x00;
// Bytes 2-3: Register number (big-endian)
data[2] = (register_number >> 8) & 0xFF;
data[3] = register_number & 0xFF;
// Bytes 4-7: Data to set (copy from payload, up to 4 bytes)
size_t copy_size = std::min(data_payload.size(), size_t(4));
for (size_t i = 0; i < copy_size; ++i) {
data[4 + i] = data_payload[i];
}
return data;
}
std::vector<uint8_t> build_set_command_float(uint16_t register_number, float value) {
std::vector<uint8_t> float_data;
to_raw(value, float_data);
return build_set_command(register_number, float_data);
}
std::vector<uint8_t> build_set_command_integer(uint16_t register_number, uint32_t value) {
std::vector<uint8_t> int_data;
to_raw(value, int_data);
return build_set_command(register_number, int_data);
}
// packet definitions
PowerModuleStatus::PowerModuleStatus() {
}
PowerModuleStatus::PowerModuleStatus(const std::vector<uint8_t>& raw) {
// Size validation is handled at rx_handler level
// Winline status parsing based on Chart 2 in protocol document
// The status is a 32-bit integer returned from register 0x0040
// We expect the response format: [DataType|ErrorCode|Register|StatusData]
// For now, assume the raw data contains the 32-bit status value in bytes 4-7
// Check if we have enough data for standardized response format
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
// Verify this is a valid status response
if (data_type == WinlineProtocol::DATA_TYPE_INTEGER && error_code == WinlineProtocol::ERROR_NORMAL) {
// Extract 32-bit status value from bytes 4-7
uint32_t status_value = from_raw<uint32_t>(raw, 4);
// Parse Winline status bits according to Chart 2
module_fault = (status_value & (1U << 0)) != 0; // Bit 0: Module fault (red indicator)
module_protection = (status_value & (1U << 1)) != 0; // Bit 1: Module protection (yellow indicator)
// Bit 2: Reserved
sci_communication_failure =
(status_value & (1U << 3)) != 0; // Bit 3: Module internal SCI communication failure
input_mode_error = (status_value & (1U << 4)) != 0; // Bit 4: Input mode error/wiring error
input_mode_mismatch =
(status_value & (1U << 5)) != 0; // Bit 5: Input mode set by monitor doesn't match actual
// Bit 6: Reserved
dcdc_overvoltage = (status_value & (1U << 7)) != 0; // Bit 7: DCDC overvoltage
pfc_voltage_abnormal = (status_value & (1U << 8)) != 0; // Bit 8: PFC voltage abnormal
ac_overvoltage = (status_value & (1U << 9)) != 0; // Bit 9: AC overvoltage
// Bits 10-13: Reserved
ac_undervoltage = (status_value & (1U << 14)) != 0; // Bit 14: AC undervoltage
// Bit 15: Reserved
can_communication_failure = (status_value & (1U << 16)) != 0; // Bit 16: CAN communication failure
module_current_imbalance = (status_value & (1U << 17)) != 0; // Bit 17: Module current imbalance
// Bits 18-21: Reserved
dcdc_on_off_status = (status_value & (1U << 22)) != 0; // Bit 22: DCDC On/off status (0:On, 1:Off)
module_power_limiting = (status_value & (1U << 23)) != 0; // Bit 23: Module power limiting
temperature_derating = (status_value & (1U << 24)) != 0; // Bit 24: Temperature derating
ac_power_limiting = (status_value & (1U << 25)) != 0; // Bit 25: AC power limiting
// Bit 26: Reserved
fan_fault = (status_value & (1U << 27)) != 0; // Bit 27: Fan fault
dcdc_short_circuit = (status_value & (1U << 28)) != 0; // Bit 28: DCDC short circuit
// Bit 29: Reserved
dcdc_over_temperature = (status_value & (1U << 30)) != 0; // Bit 30: DCDC over temperature
dcdc_output_overvoltage = (status_value & (1U << 31)) != 0; // Bit 31: DCDC output overvoltage
} else {
EVLOG_warning << " Invalid status response - DataType: 0x" << std::hex << static_cast<int>(data_type)
<< ", ErrorCode: 0x" << static_cast<int>(error_code);
}
} else {
EVLOG_warning << " PowerModuleStatus received insufficient data (size: " << raw.size() << ")";
}
}
std::ostream& operator<<(std::ostream& out, const PowerModuleStatus& self) {
out << "PowerModuleStatus: ";
// Output active status flags with Winline naming
if (self.module_fault)
out << "module_fault ";
if (self.module_protection)
out << "module_protection ";
if (self.sci_communication_failure)
out << "sci_communication_failure ";
if (self.input_mode_error)
out << "input_mode_error ";
if (self.input_mode_mismatch)
out << "input_mode_mismatch ";
if (self.dcdc_overvoltage)
out << "dcdc_overvoltage ";
if (self.pfc_voltage_abnormal)
out << "pfc_voltage_abnormal ";
if (self.ac_overvoltage)
out << "ac_overvoltage ";
if (self.ac_undervoltage)
out << "ac_undervoltage ";
if (self.can_communication_failure)
out << "can_communication_failure ";
if (self.module_current_imbalance)
out << "module_current_imbalance ";
if (self.dcdc_on_off_status)
out << "dcdc_on_off_status ";
if (self.module_power_limiting)
out << "module_power_limiting ";
if (self.temperature_derating)
out << "temperature_derating ";
if (self.ac_power_limiting)
out << "ac_power_limiting ";
if (self.fan_fault)
out << "fan_fault ";
if (self.dcdc_short_circuit)
out << "dcdc_short_circuit ";
if (self.dcdc_over_temperature)
out << "dcdc_over_temperature ";
if (self.dcdc_output_overvoltage)
out << "dcdc_output_overvoltage ";
return out;
}
PowerModuleStatus::operator std::vector<uint8_t>() const {
// For SET operations, this would create the command payload
// Status is read-only, so this returns empty vector
std::vector<uint8_t> data;
return data;
}
// Winline Register-Based Packet Implementations
// READ operations
ReadVoltage::ReadVoltage() : voltage(0.0f) {
}
ReadVoltage::ReadVoltage(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_FLOAT && error_code == WinlineProtocol::ERROR_NORMAL) {
voltage = from_raw<float>(raw, 4);
} else {
EVLOG_warning << " Invalid voltage response - DataType: 0x" << std::hex << static_cast<int>(data_type)
<< ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadVoltage::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
ReadCurrent::ReadCurrent() : current(0.0f) {
}
ReadCurrent::ReadCurrent(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_FLOAT && error_code == WinlineProtocol::ERROR_NORMAL) {
current = from_raw<float>(raw, 4);
} else {
EVLOG_warning << " Invalid current response - DataType: 0x" << std::hex << static_cast<int>(data_type)
<< ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadCurrent::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
ReadGroupInfo::ReadGroupInfo() : group_number(0), dip_address(0) {
}
ReadGroupInfo::ReadGroupInfo(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_INTEGER && error_code == WinlineProtocol::ERROR_NORMAL) {
// Higher 16 bits: group number, Lower 16 bits: DIP address
uint32_t group_info = from_raw<uint32_t>(raw, 4);
group_number = (group_info >> 16) & 0xFF;
dip_address = group_info & 0xFF;
} else {
EVLOG_warning << " Invalid group info response - DataType: 0x" << std::hex << static_cast<int>(data_type)
<< ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadGroupInfo::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
ReadSerialNumber::ReadSerialNumber() : serial_number("") {
}
ReadSerialNumber::ReadSerialNumber(uint32_t low_bytes, uint32_t high_bytes) {
// Combine high and low bytes to create serial number string
std::stringstream ss;
ss << "SN_" << std::hex << std::setfill('0') << std::setw(8) << high_bytes << std::setw(8) << low_bytes;
serial_number = ss.str();
}
ReadSerialNumber::operator std::vector<uint8_t>() const {
// This packet requires reading two registers, so we return empty
// The caller should handle reading both REGISTER_LOW and REGISTER_HIGH
return {};
}
ReadRatedOutputPower::ReadRatedOutputPower() : power(0.0f) {
}
ReadRatedOutputPower::ReadRatedOutputPower(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_FLOAT && error_code == WinlineProtocol::ERROR_NORMAL) {
power = from_raw<float>(raw, 4);
} else {
EVLOG_warning << " Invalid power response - DataType: 0x" << std::hex << static_cast<int>(data_type)
<< ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadRatedOutputPower::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
ReadRatedOutputCurrent::ReadRatedOutputCurrent() : current(0.0f) {
}
ReadRatedOutputCurrent::ReadRatedOutputCurrent(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_FLOAT && error_code == WinlineProtocol::ERROR_NORMAL) {
current = from_raw<float>(raw, 4);
} else {
EVLOG_warning << " Invalid rated current response - DataType: 0x" << std::hex << static_cast<int>(data_type)
<< ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadRatedOutputCurrent::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
// Add missing packet constructors with standardized response parsing
ReadCurrentLimitPoint::ReadCurrentLimitPoint() : limit_point(0.0f) {
}
ReadCurrentLimitPoint::ReadCurrentLimitPoint(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_FLOAT && error_code == WinlineProtocol::ERROR_NORMAL) {
limit_point = from_raw<float>(raw, 4);
} else {
EVLOG_warning << " Invalid current limit point response - DataType: 0x" << std::hex
<< static_cast<int>(data_type) << ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadCurrentLimitPoint::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
ReadDCBoardTemperature::ReadDCBoardTemperature() : temperature(0.0f) {
}
ReadDCBoardTemperature::ReadDCBoardTemperature(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_FLOAT && error_code == WinlineProtocol::ERROR_NORMAL) {
temperature = from_raw<float>(raw, 4);
} else {
EVLOG_warning << " Invalid DC board temperature response - DataType: 0x" << std::hex
<< static_cast<int>(data_type) << ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadDCBoardTemperature::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
ReadAmbientTemperature::ReadAmbientTemperature() : temperature(0.0f) {
}
ReadAmbientTemperature::ReadAmbientTemperature(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_FLOAT && error_code == WinlineProtocol::ERROR_NORMAL) {
temperature = from_raw<float>(raw, 4);
} else {
EVLOG_warning << " Invalid ambient temperature response - DataType: 0x" << std::hex
<< static_cast<int>(data_type) << ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadAmbientTemperature::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
ReadDCDCVersion::ReadDCDCVersion() : version(0) {
}
ReadDCDCVersion::ReadDCDCVersion(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_INTEGER && error_code == WinlineProtocol::ERROR_NORMAL) {
// Version is in lower 16 bits (bytes 6-7) of response
uint32_t version_data = from_raw<uint32_t>(raw, 4);
version = static_cast<uint16_t>(version_data & 0xFFFF);
} else {
EVLOG_warning << " Invalid DCDC version response - DataType: 0x" << std::hex << static_cast<int>(data_type)
<< ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadDCDCVersion::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
ReadPFCVersion::ReadPFCVersion() : version(0) {
}
ReadPFCVersion::ReadPFCVersion(const std::vector<uint8_t>& raw) {
if (raw.size() >= 8) {
uint8_t data_type = from_raw<uint8_t>(raw, 0);
uint8_t error_code = from_raw<uint8_t>(raw, 1);
if (data_type == WinlineProtocol::DATA_TYPE_INTEGER && error_code == WinlineProtocol::ERROR_NORMAL) {
// Version is in lower 16 bits (bytes 6-7) of response
uint32_t version_data = from_raw<uint32_t>(raw, 4);
version = static_cast<uint16_t>(version_data & 0xFFFF);
} else {
EVLOG_warning << " Invalid PFC version response - DataType: 0x" << std::hex << static_cast<int>(data_type)
<< ", ErrorCode: 0x" << static_cast<int>(error_code);
}
}
}
ReadPFCVersion::operator std::vector<uint8_t>() const {
return build_read_command(REGISTER);
}
// SET operations
SetVoltage::SetVoltage(float v) : voltage(v) {
}
SetVoltage::operator std::vector<uint8_t>() const {
return build_set_command_float(REGISTER, voltage);
}
SetCurrent::SetCurrent(float c) : current(c) {
}
SetCurrent::operator std::vector<uint8_t>() const {
// Winline requires current to be scaled by 1024
uint32_t scaled_current = static_cast<uint32_t>(current * WinlineProtocol::CURRENT_SCALE_FACTOR);
return build_set_command_integer(REGISTER, scaled_current);
}
SetCurrentLimitPoint::SetCurrentLimitPoint(float limit_point) : limit_point(limit_point) {
}
SetCurrentLimitPoint::operator std::vector<uint8_t>() const {
// Current limit point is a float percentage (0.0 to 1.0)
return build_set_command_float(REGISTER, limit_point);
}
SetVoltageUpperLimit::SetVoltageUpperLimit(float voltage_limit) : voltage_limit(voltage_limit) {
}
SetVoltageUpperLimit::operator std::vector<uint8_t>() const {
// Voltage upper limit is a direct float value in volts
return build_set_command_float(REGISTER, voltage_limit);
}
SetPowerControl::SetPowerControl(bool power_on) : power_on(power_on) {
}
SetPowerControl::operator std::vector<uint8_t>() const {
uint32_t power_value = power_on ? WinlineProtocol::POWER_ON : WinlineProtocol::POWER_OFF;
return build_set_command_integer(REGISTER, power_value);
}
SetGroupNumber::SetGroupNumber(uint8_t group_num) : group_number(group_num) {
}
SetGroupNumber::operator std::vector<uint8_t>() const {
// Byte 7 lower 6 bits for group number (range 0~60), other bytes are 0
uint32_t group_value = group_number & 0x3F; // Ensure only lower 6 bits
return build_set_command_integer(REGISTER, group_value);
}
SetAltitude::SetAltitude(uint32_t alt) : altitude(alt) {
}
SetAltitude::operator std::vector<uint8_t>() const {
// Clamp altitude to valid range
uint32_t clamped_altitude =
std::max(WinlineProtocol::ALTITUDE_MIN, std::min(altitude, WinlineProtocol::ALTITUDE_MAX));
return build_set_command_integer(REGISTER, clamped_altitude);
}
SetInputMode::SetInputMode(uint32_t mode) : mode(mode) {
}
SetInputMode::operator std::vector<uint8_t>() const {
return build_set_command_integer(REGISTER, mode);
}
SetAddressMode::SetAddressMode(uint32_t addr_mode) : mode(addr_mode) {
}
SetAddressMode::operator std::vector<uint8_t>() const {
return build_set_command_integer(REGISTER, mode);
}
// Error Recovery Operations
SetOvervoltageReset::SetOvervoltageReset(bool enable) : enable(enable) {
}
SetOvervoltageReset::operator std::vector<uint8_t>() const {
uint32_t reset_value = enable ? WinlineProtocol::RESET_ENABLE : WinlineProtocol::RESET_DISABLE;
return build_set_command_integer(REGISTER, reset_value);
}
SetOvervoltageProtection::SetOvervoltageProtection(bool enable) : enable(enable) {
}
SetOvervoltageProtection::operator std::vector<uint8_t>() const {
uint32_t protection_value = enable ? WinlineProtocol::RESET_DISABLE : WinlineProtocol::RESET_ENABLE;
return build_set_command_integer(REGISTER, protection_value);
}
SetShortCircuitReset::SetShortCircuitReset(bool enable) : enable(enable) {
}
SetShortCircuitReset::operator std::vector<uint8_t>() const {
uint32_t reset_value = enable ? WinlineProtocol::RESET_ENABLE : WinlineProtocol::RESET_DISABLE;
return build_set_command_integer(REGISTER, reset_value);
}
} // namespace can_packet_acdc

View File

@@ -0,0 +1,383 @@
// 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 WinlineProtocol {
// CAN Frame Constants
constexpr uint32_t CAN_EXTENDED_FLAG = 0x80000000U;
// Winline Protocol Constants
constexpr uint16_t PROTNO = 0x060; // Protocol number (9 bits) - fixed for Winline
constexpr uint8_t FUNCTION_SET = 0x03; // SET operation function code
constexpr uint8_t FUNCTION_READ = 0x10; // READ operation function code
// Address Constants
constexpr uint8_t MODULE_ADDRESS_MIN = 0x00; // Minimum module address
constexpr uint8_t MODULE_ADDRESS_MAX = 0x3F; // Maximum module address (63 modules)
constexpr uint8_t CONTROLLER_ADDRESS = 0xF0; // Default controller address
constexpr uint8_t BROADCAST_ADDR = 0xFF; // Individual broadcast address
constexpr uint8_t GROUP_BROADCAST_ADDR = 0xFE; // Group broadcast address
// Group Constants
constexpr uint8_t GROUP_MIN = 0x00; // Minimum group number
constexpr uint8_t GROUP_MAX = 0x07; // Maximum group number (3 bits)
// Response Error Codes (Winline Protocol Specification)
constexpr uint8_t ERROR_NORMAL = 0xF0; // Normal response - only valid response code
constexpr uint8_t ERROR_FAULT = 0xF2; // Fault response (example from protocol doc)
// Note: Per Winline spec - "F0: Normal, Others: Fault, discard frame"
// Any error code != 0xF0 indicates a fault and frame should be discarded
// Error Recovery Operations
constexpr uint32_t RESET_ENABLE = 0x00010000; // Enable reset command value
constexpr uint32_t RESET_DISABLE = 0x00000000; // Disable reset command value
// Response Data Types
constexpr uint8_t DATA_TYPE_FLOAT = 0x41; // Float point data type indicator
constexpr uint8_t DATA_TYPE_INTEGER = 0x42; // Integer data type indicator
// Bit Masks for CAN ID encoding (Winline format)
constexpr uint32_t PROTNO_MASK = 0x1FF; // 9-bit mask for protocol number
constexpr uint8_t PTP_MASK = 0x01; // 1-bit mask for point-to-point flag
constexpr uint8_t GROUP_MASK = 0x07; // 3-bit mask for group number
constexpr uint8_t ADDRESS_MASK = 0xFF; // 8-bit mask for addresses
// Bit Positions for CAN ID encoding (Winline format)
constexpr uint8_t SRCADDR_SHIFT = 3; // Bits 10-3: Source address (8 bits)
constexpr uint8_t DSTADDR_SHIFT = 11; // Bits 18-11: Destination address (8 bits)
constexpr uint8_t GROUP_SHIFT = 0; // Bits 2-0: Group number (3 bits)
constexpr uint8_t PTP_SHIFT = 19; // Bit 19: Point-to-point flag
constexpr uint8_t PROTNO_SHIFT = 20; // Bits 28-20: Protocol number
// Winline Register Definitions
namespace Registers {
// Read-only registers (used with FUNCTION_READ)
constexpr uint16_t VOLTAGE = 0x0001; // Module output voltage (float)
constexpr uint16_t CURRENT = 0x0002; // Module output current (float)
constexpr uint16_t CURRENT_LIMIT_POINT = 0x0003; // Module current limit point (float)
constexpr uint16_t DC_BOARD_TEMPERATURE = 0x0004; // Module DC board temperature (float)
constexpr uint16_t INPUT_VOLTAGE = 0x0005; // Module input voltage (float)
constexpr uint16_t PFC_POSITIVE_VOLTAGE = 0x0008; // PFC positive half bus voltage (float)
constexpr uint16_t PFC_NEGATIVE_VOLTAGE = 0x000A; // PFC negative half bus voltage (float)
constexpr uint16_t AMBIENT_TEMPERATURE = 0x000B; // Panel ambient temperature (float)
constexpr uint16_t AC_PHASE_A_VOLTAGE = 0x000C; // AC phase A voltage (float)
constexpr uint16_t AC_PHASE_B_VOLTAGE = 0x000D; // AC phase B voltage (float)
constexpr uint16_t AC_PHASE_C_VOLTAGE = 0x000E; // AC phase C voltage (float)
constexpr uint16_t PFC_BOARD_TEMPERATURE = 0x0010; // PFC board temperature (float)
constexpr uint16_t RATED_OUTPUT_POWER = 0x0011; // Module rated output power (float)
constexpr uint16_t RATED_OUTPUT_CURRENT = 0x0012; // Module rated output current (float)
constexpr uint16_t STATUS = 0x0040; // Current alarm/status (integer)
constexpr uint16_t GROUP_INFO = 0x0043; // Group number & DIP switch address (integer)
constexpr uint16_t INPUT_POWER = 0x0048; // Input power (integer, unit: 1W)
constexpr uint16_t CURRENT_ALTITUDE = 0x004A; // Current set altitude (integer, unit: m)
constexpr uint16_t INPUT_WORKING_MODE = 0x004B; // Current input working mode (integer)
constexpr uint16_t SERIAL_NUMBER_LOW = 0x0054; // Node serial number low bytes (integer)
constexpr uint16_t SERIAL_NUMBER_HIGH = 0x0055; // Node serial number high bytes (integer)
constexpr uint16_t DCDC_VERSION = 0x0056; // DCDC version (integer)
constexpr uint16_t PFC_VERSION = 0x0057; // PFC version (integer)
// Read/Write registers (used with FUNCTION_SET)
constexpr uint16_t SET_ALTITUDE = 0x0017; // Set working altitude (integer, 1000-5000m)
constexpr uint16_t SET_OUTPUT_CURRENT = 0x001B; // Set output current (integer, value*1024)
constexpr uint16_t SET_GROUP_NUMBER = 0x001E; // Set group number (integer)
constexpr uint16_t SET_ADDRESS_MODE = 0x001F; // Set address assignment mode (integer)
constexpr uint16_t SET_OUTPUT_VOLTAGE = 0x0021; // Set output voltage (float)
constexpr uint16_t SET_CURRENT_LIMIT_POINT = 0x0022; // Set current limit point (float)
constexpr uint16_t SET_VOLTAGE_UPPER_LIMIT = 0x0023; // Set voltage upper limit (float)
constexpr uint16_t POWER_CONTROL = 0x0030; // Power on/off control (integer)
constexpr uint16_t SET_OVERVOLTAGE_RESET = 0x0031; // Set overvoltage reset (integer)
constexpr uint16_t SET_OVERVOLTAGE_PROTECTION = 0x003E; // Set overvoltage protection permission (integer)
constexpr uint16_t SET_SHORT_CIRCUIT_RESET = 0x0044; // Set short circuit reset (integer)
constexpr uint16_t SET_INPUT_MODE = 0x0046; // Set input mode (integer)
} // namespace Registers
// Power Control Values (for POWER_CONTROL register)
constexpr uint32_t POWER_ON = 0x00000000; // Power on value
constexpr uint32_t POWER_OFF = 0x00010000; // Power off value
// Input Mode Values (for SET_INPUT_MODE register)
constexpr uint32_t INPUT_MODE_AC = 0x00000001; // AC input mode (default)
constexpr uint32_t INPUT_MODE_DC = 0x00000002; // DC input mode
// Address Assignment Mode Values (for SET_ADDRESS_MODE register)
constexpr uint32_t ADDRESS_AUTO = 0x00000000; // Automatically assigned
constexpr uint32_t ADDRESS_DIP = 0x00010000; // Set by DIP switch (default)
// Current Scaling Factor (for SET_OUTPUT_CURRENT register)
constexpr uint32_t CURRENT_SCALE_FACTOR = 1024; // Current value = actual_current * 1024
// Altitude Limits (for SET_ALTITUDE register)
constexpr uint32_t ALTITUDE_MIN = 1000; // Minimum altitude setting (meters)
constexpr uint32_t ALTITUDE_MAX = 5000; // Maximum altitude setting (meters)
constexpr uint32_t ALTITUDE_DEFAULT = 1000; // Default altitude setting (meters)
// Unit Conversion Constants (legacy, may be useful for some conversions)
constexpr uint32_t VOLTAGE_TO_MV = 1000U; // Volts to millivolts (V * 1000 = mV)
constexpr uint32_t CURRENT_TO_MA = 1000U; // Amperes to milliamperes (A * 1000 = mA)
} // namespace WinlineProtocol
namespace can_packet_acdc {
// Winline CAN ID encoding/decoding functions
uint32_t encode_can_id(uint8_t source_address, uint8_t destination_address, uint8_t group_number, bool point_to_point);
uint8_t destination_address_from_can_id(uint32_t id);
uint8_t source_address_from_can_id(uint32_t id);
uint8_t group_number_from_can_id(uint32_t id);
bool point_to_point_from_can_id(uint32_t id);
uint16_t protocol_number_from_can_id(uint32_t id);
// Command building helpers for Winline protocol
uint32_t build_read_command_id(uint8_t source_address, uint8_t destination_address, uint8_t group_number,
bool point_to_point);
uint32_t build_set_command_id(uint8_t source_address, uint8_t destination_address, uint8_t group_number,
bool point_to_point);
// Command frame builders for Winline register-based communication
std::vector<uint8_t> build_read_command(uint16_t register_number);
std::vector<uint8_t> build_set_command(uint16_t register_number, const std::vector<uint8_t>& data);
std::vector<uint8_t> build_set_command_float(uint16_t register_number, float value);
std::vector<uint8_t> build_set_command_integer(uint16_t register_number, uint32_t value);
struct PowerModuleStatus {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::STATUS;
PowerModuleStatus();
PowerModuleStatus(const std::vector<uint8_t>& raw);
friend std::ostream& operator<<(std::ostream& out, const PowerModuleStatus& self);
operator std::vector<uint8_t>() const;
// Winline status bits (based on Chart 2 in protocol document)
bool module_fault{false}; // Bit 0: Module fault (red indicator)
bool module_protection{false}; // Bit 1: Module protection (yellow indicator)
bool sci_communication_failure{false}; // Bit 3: Module internal SCI communication failure
bool input_mode_error{false}; // Bit 4: Input mode error/wiring error
bool input_mode_mismatch{false}; // Bit 5: Input mode set by monitor doesn't match actual
bool dcdc_overvoltage{false}; // Bit 7: DCDC overvoltage
bool pfc_voltage_abnormal{false}; // Bit 8: PFC voltage abnormal (imbalance/over/under)
bool ac_overvoltage{false}; // Bit 9: AC overvoltage
bool ac_undervoltage{false}; // Bit 14: AC undervoltage
bool can_communication_failure{false}; // Bit 16: CAN communication failure
bool module_current_imbalance{false}; // Bit 17: Module current imbalance
bool dcdc_on_off_status{false}; // Bit 22: DCDC On/off status (0:On, 1:Off)
bool module_power_limiting{false}; // Bit 23: Module power limiting
bool temperature_derating{false}; // Bit 24: Temperature derating
bool ac_power_limiting{false}; // Bit 25: AC power limiting
bool fan_fault{false}; // Bit 27: Fan fault
bool dcdc_short_circuit{false}; // Bit 28: DCDC short circuit
bool dcdc_over_temperature{false}; // Bit 30: DCDC over temperature
bool dcdc_output_overvoltage{false}; // Bit 31: DCDC output overvoltage
};
// Winline Register-Based Packet Structures
// READ operations (Function 0x10 + Register)
struct ReadVoltage {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::VOLTAGE;
ReadVoltage();
ReadVoltage(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
float voltage{0.0f};
};
struct ReadCurrent {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::CURRENT;
ReadCurrent();
ReadCurrent(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
float current{0.0f};
};
struct ReadCurrentLimitPoint {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::CURRENT_LIMIT_POINT;
ReadCurrentLimitPoint();
ReadCurrentLimitPoint(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
float limit_point{0.0f};
};
struct ReadDCBoardTemperature {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::DC_BOARD_TEMPERATURE;
ReadDCBoardTemperature();
ReadDCBoardTemperature(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
float temperature{0.0f};
};
struct ReadAmbientTemperature {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::AMBIENT_TEMPERATURE;
ReadAmbientTemperature();
ReadAmbientTemperature(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
float temperature{0.0f};
};
struct ReadRatedOutputPower {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::RATED_OUTPUT_POWER;
ReadRatedOutputPower();
ReadRatedOutputPower(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
float power{0.0f};
};
struct ReadRatedOutputCurrent {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::RATED_OUTPUT_CURRENT;
ReadRatedOutputCurrent();
ReadRatedOutputCurrent(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
float current{0.0f};
};
struct ReadGroupInfo {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::GROUP_INFO;
ReadGroupInfo();
ReadGroupInfo(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
uint8_t group_number{0};
uint8_t dip_address{0};
};
struct ReadSerialNumber {
static constexpr uint16_t REGISTER_LOW = WinlineProtocol::Registers::SERIAL_NUMBER_LOW;
static constexpr uint16_t REGISTER_HIGH = WinlineProtocol::Registers::SERIAL_NUMBER_HIGH;
ReadSerialNumber();
ReadSerialNumber(uint32_t low_bytes, uint32_t high_bytes);
operator std::vector<uint8_t>() const;
std::string serial_number;
};
struct ReadDCDCVersion {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::DCDC_VERSION;
ReadDCDCVersion();
ReadDCDCVersion(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
uint16_t version{0};
};
struct ReadPFCVersion {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::PFC_VERSION;
ReadPFCVersion();
ReadPFCVersion(const std::vector<uint8_t>& raw);
operator std::vector<uint8_t>() const;
uint16_t version{0};
};
// SET operations (Function 0x03 + Register)
struct SetVoltage {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_OUTPUT_VOLTAGE;
SetVoltage(float voltage);
operator std::vector<uint8_t>() const;
float voltage{0.0f};
};
struct SetCurrent {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_OUTPUT_CURRENT;
SetCurrent(float current);
operator std::vector<uint8_t>() const;
float current{0.0f};
};
struct SetCurrentLimitPoint {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_CURRENT_LIMIT_POINT;
SetCurrentLimitPoint(float limit_point);
operator std::vector<uint8_t>() const;
float limit_point{1.0f}; // Default to 100% (no limiting)
};
struct SetVoltageUpperLimit {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_VOLTAGE_UPPER_LIMIT;
SetVoltageUpperLimit(float voltage_limit);
operator std::vector<uint8_t>() const;
float voltage_limit{0.0f};
};
struct SetPowerControl {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::POWER_CONTROL;
SetPowerControl(bool power_on);
operator std::vector<uint8_t>() const;
bool power_on{false};
};
struct SetGroupNumber {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_GROUP_NUMBER;
SetGroupNumber(uint8_t group_number);
operator std::vector<uint8_t>() const;
uint8_t group_number{0};
};
struct SetAltitude {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_ALTITUDE;
SetAltitude(uint32_t altitude);
operator std::vector<uint8_t>() const;
uint32_t altitude{WinlineProtocol::ALTITUDE_DEFAULT};
};
struct SetInputMode {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_INPUT_MODE;
SetInputMode(uint32_t mode);
operator std::vector<uint8_t>() const;
uint32_t mode{WinlineProtocol::INPUT_MODE_AC};
};
struct SetAddressMode {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_ADDRESS_MODE;
SetAddressMode(uint32_t mode);
operator std::vector<uint8_t>() const;
uint32_t mode{WinlineProtocol::ADDRESS_DIP};
};
// Error Recovery Operations
struct SetOvervoltageReset {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_OVERVOLTAGE_RESET;
SetOvervoltageReset(bool enable);
operator std::vector<uint8_t>() const;
bool enable{false};
};
struct SetOvervoltageProtection {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_OVERVOLTAGE_PROTECTION;
SetOvervoltageProtection(bool enable);
operator std::vector<uint8_t>() const;
bool enable{false};
};
struct SetShortCircuitReset {
static constexpr uint16_t REGISTER = WinlineProtocol::Registers::SET_SHORT_CIRCUIT_RESET;
SetShortCircuitReset(bool enable);
operator std::vector<uint8_t>() const;
bool enable{false};
};
} // namespace can_packet_acdc
#endif // CAN_PACKETS_HPP

View File

@@ -0,0 +1,152 @@
// 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 <stdexcept>
#include <type_traits>
#include <vector>
#include <endian.h>
// Helper template to ensure type safety for conversion operations
template <typename T> struct is_conversion_safe {
static constexpr bool value =
std::is_trivially_copyable_v<T> && std::is_standard_layout_v<T> && !std::is_pointer_v<T>;
};
template <class T>
typename std::enable_if_t<sizeof(T) == sizeof(uint8_t) && is_conversion_safe<T>::value, T>
from_raw(const std::vector<uint8_t>& raw, int idx) {
if (idx + sizeof(T) > raw.size()) {
throw std::out_of_range("from_raw: buffer access out of bounds");
}
T ret;
memcpy(&ret, &raw[idx], 1);
return ret;
}
template <class T>
typename std::enable_if_t<sizeof(T) == sizeof(uint16_t) && is_conversion_safe<T>::value, T>
from_raw(const std::vector<uint8_t>& raw, int idx) {
if (idx + sizeof(T) > raw.size()) {
throw std::out_of_range("from_raw: buffer access out of bounds");
}
uint16_t tmp;
memcpy(&tmp, raw.data() + idx, sizeof(uint16_t)); // Safe copy from buffer
tmp = be16toh(tmp); // Convert endianness
T ret;
memcpy(&ret, &tmp, sizeof(T));
return ret;
}
template <class T>
typename std::enable_if_t<sizeof(T) == sizeof(uint32_t) && is_conversion_safe<T>::value, T>
from_raw(const std::vector<uint8_t>& raw, int idx) {
if (idx + sizeof(T) > raw.size()) {
throw std::out_of_range("from_raw: buffer access out of bounds");
}
uint32_t tmp;
memcpy(&tmp, raw.data() + idx, sizeof(uint32_t)); // Safe copy from buffer
tmp = be32toh(tmp); // Convert endianness
T ret;
memcpy(&ret, &tmp, sizeof(T));
return ret;
}
template <typename T>
std::enable_if_t<std::is_floating_point<T>::value && is_conversion_safe<T>::value && (sizeof(T) == 4), T>
from_raw(const std::vector<uint8_t>& raw, std::size_t idx) {
constexpr std::size_t N = 4;
static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
if (idx + N > raw.size()) {
throw std::out_of_range("from_raw: buffer access out of bounds");
}
uint32_t tmp;
std::memcpy(&tmp, raw.data() + idx, sizeof(tmp));
tmp = be32toh(tmp);
float f;
std::memcpy(&f, &tmp, sizeof(f));
return static_cast<T>(f);
}
template <class T>
typename std::enable_if_t<sizeof(T) == sizeof(uint64_t) && is_conversion_safe<T>::value, T>
from_raw(const std::vector<uint8_t>& raw, int idx) {
if (idx + sizeof(T) > raw.size()) {
throw std::out_of_range("from_raw: buffer access out of bounds");
}
uint64_t tmp;
memcpy(&tmp, raw.data() + idx, sizeof(uint64_t)); // Safe copy from buffer
tmp = be64toh(tmp); // Convert endianness
T ret;
memcpy(&ret, &tmp, sizeof(T));
return ret;
}
template <class T>
typename std::enable_if_t<sizeof(T) == sizeof(uint8_t) && is_conversion_safe<T>::value>
to_raw(T src, std::vector<uint8_t>& dest) {
uint8_t tmp;
memcpy(&tmp, &src, sizeof(T));
dest.push_back(tmp);
}
template <class T>
typename std::enable_if_t<sizeof(T) == sizeof(uint16_t) && is_conversion_safe<T>::value>
to_raw(T src, std::vector<uint8_t>& dest) {
uint16_t tmp;
memcpy(&tmp, &src, sizeof(T));
tmp = htobe16(tmp);
// Use array for better alignment guarantees
alignas(uint16_t) uint8_t ret[sizeof(uint16_t)];
memcpy(ret, &tmp, sizeof(uint16_t));
dest.insert(dest.end(), {ret[0], ret[1]});
}
template <class T>
typename std::enable_if_t<std::is_integral<T>::value && is_conversion_safe<T>::value && (sizeof(T) == 4)>
to_raw(T src, std::vector<uint8_t>& dest) {
uint32_t tmp;
memcpy(&tmp, &src, sizeof(T));
tmp = htobe32(tmp);
// Use array for better alignment guarantees
alignas(uint32_t) uint8_t ret[sizeof(uint32_t)];
memcpy(ret, &tmp, sizeof(uint32_t));
dest.insert(dest.end(), {ret[0], ret[1], ret[2], ret[3]});
}
template <typename T>
typename std::enable_if_t<std::is_floating_point<T>::value && is_conversion_safe<T>::value && (sizeof(T) == 4)>
to_raw(T src, std::vector<uint8_t>& dest) {
uint32_t tmp = src;
memcpy(&tmp, &src, sizeof(T));
tmp = htobe32(static_cast<uint32_t>(tmp));
// Use array for better alignment guarantees
alignas(float) uint8_t ret[sizeof(float)];
memcpy(ret, &tmp, sizeof(float));
dest.insert(dest.end(), {ret[0], ret[1], ret[2], ret[3]});
}
template <class T>
typename std::enable_if_t<sizeof(T) == sizeof(uint64_t) && is_conversion_safe<T>::value>
to_raw(T src, std::vector<uint8_t>& dest) {
uint64_t tmp;
memcpy(&tmp, &src, sizeof(T));
tmp = htobe64(tmp);
// Use array for better alignment guarantees
alignas(uint64_t) uint8_t ret[sizeof(uint64_t)];
memcpy(ret, &tmp, sizeof(uint64_t));
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,214 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef WINLINE_CAN_DEVICE_HPP
#define WINLINE_CAN_DEVICE_HPP
#include "CanBus.hpp"
#include <chrono>
#include <deque> // Added for status history
#include <linux/can.h>
#include <map>
#include <mutex>
#include <sigslot/signal.hpp>
#include <vector>
class WinlineCanDevice : public CanBus {
public:
WinlineCanDevice();
~WinlineCanDevice();
enum class Error {
OverVoltage,
UnderVoltage,
OverTemperature,
FanFault,
InputPhaseLoss,
CommunicationFault,
InternalFault,
OverCurrent,
InputVoltage,
VendorError,
VendorWarning
};
enum class OperatingMode {
FIXED_ADDRESS,
GROUP_DISCOVERY
};
void set_can_device(const std::string& dev);
void set_config_values(const std::string& addrs, int group_address, int timeout, int controller_address,
int power_state_grace_period_ms, int altitude_setting_m, const std::string& input_mode,
double module_current_limit_point);
void initial_ping();
// Commands
bool switch_on_off(bool on);
bool set_voltage_current(float voltage, float current);
// Enhanced Winline group operations
bool discover_group_modules();
// Winline error recovery operations
bool reset_overvoltage_protection(uint8_t module_address);
bool reset_short_circuit_protection(uint8_t module_address);
// Altitude setting operations
bool set_altitude_all_modules();
// Current limit point setting operations
bool set_current_limit_point_all_modules();
// Input mode setting operations
bool set_input_mode_all_modules();
// Winline register-based command functions
bool send_read_register(uint8_t destination_address, uint16_t register_number, bool group = false);
bool send_set_register_float(uint8_t destination_address, uint16_t register_number, float value,
bool group = false);
bool send_set_register_integer(uint8_t destination_address, uint16_t register_number, uint32_t value,
bool group = false);
// Enhanced Winline status monitoring capabilities
bool perform_comprehensive_status_check(uint8_t module_address);
bool analyze_status_trends(uint8_t module_address);
void log_status_diagnostics(uint8_t module_address, const can_packet_acdc::PowerModuleStatus& status);
std::string get_status_summary(uint8_t module_address) const;
// Enhanced Winline power control capabilities
bool verify_power_state(uint8_t module_address, bool expected_on_state);
bool handle_power_transition(bool target_state);
void track_power_state_change(uint8_t module_address, bool new_power_state);
// Template overloads for type-safe command sending (DEPRECATED - use register functions)
template <typename PacketType> bool send_command(uint8_t destination_address, bool group = false) {
// Use static const vector to avoid repeated allocations
static const std::vector<uint8_t> empty_payload(
8, 0); // 8 zero bytes for read commands, otherwise the device returns an error
return send_command_impl(destination_address, PacketType::CMD_ID, empty_payload, group);
}
template <typename PacketType>
bool send_command(uint8_t destination_address, const PacketType& packet, bool group = false) {
return send_command_impl(destination_address, PacketType::CMD_ID, packet.operator std::vector<uint8_t>(),
group);
}
struct Telemetry {
// Core telemetry values
float voltage{0.};
float current{0.};
float current_limit_point{0.};
// Legacy InfyPower fields (retained for compatibility)
float v_ext{0.};
float i_avail{0.};
bool valid_caps{false};
// Module capabilities and limits (Winline protocol provides current and power only)
float dc_max_output_current{0.};
float dc_rated_output_power{0.};
// Temperature monitoring (Winline-specific)
float dc_board_temperature{0.};
float ambient_temperature{0.};
float pfc_board_temperature{0.};
// Status and diagnostic information
can_packet_acdc::PowerModuleStatus status;
// Module identification (Winline dual-register serial number)
std::string serial_number; // Complete formatted serial number
uint32_t serial_low{0}; // Low bytes from register 0x0054
uint32_t serial_high{0}; // High bytes from register 0x0055
// Version information
uint16_t dcdc_version{0};
uint16_t pfc_version{0};
// Winline-specific settings
uint32_t altitude_setting{1000}; // Working altitude in meters
uint32_t input_mode{1}; // 1=AC, 2=DC
uint8_t group_number{0}; // Module group assignment
uint8_t dip_address{0}; // DIP switch address
// Enhanced status monitoring
struct StatusHistory {
std::deque<can_packet_acdc::PowerModuleStatus> recent_status; // Last 10 status readings
uint32_t fault_count{0}; // Total fault occurrences
uint32_t recovery_count{0}; // Successful recovery attempts
std::chrono::time_point<std::chrono::steady_clock> last_fault_time;
std::chrono::time_point<std::chrono::steady_clock> last_recovery_time;
} status_history;
struct StatusMetrics {
uint32_t status_reads_total{0}; // Total status reads
uint32_t status_errors_total{0}; // Status read errors
std::chrono::time_point<std::chrono::steady_clock> last_status_read;
float status_read_success_rate{100.0f}; // Success rate percentage
} status_metrics;
// Enhanced power control tracking
struct PowerStateTracking {
bool expected_power_state{false}; // Expected power state (what we commanded)
bool actual_power_state{false}; // Actual power state (from status register)
bool power_state_verified{false}; // Whether power state has been verified
uint32_t power_commands_sent{0}; // Total power commands sent
uint32_t power_state_mismatches{0}; // Power state verification failures
std::chrono::time_point<std::chrono::steady_clock> last_power_command;
std::chrono::time_point<std::chrono::steady_clock> last_power_verification;
} power_tracking;
// Timing
std::chrono::time_point<std::chrono::steady_clock> last_update;
};
typedef std::map<uint8_t, Telemetry> TelemetryMap;
TelemetryMap telemetries;
// Data out
sigslot::signal<TelemetryMap> signalVoltageCurrent;
sigslot::signal<can_packet_acdc::PowerModuleStatus> signalModuleStatus;
sigslot::signal<uint8_t, Error, bool> signalError;
sigslot::signal<TelemetryMap> signalCapabilitiesUpdate;
protected:
virtual void rx_handler(uint32_t can_id, const std::vector<uint8_t>& payload);
private:
bool initialized{false}; // Set to true when we have received the very first module count packet
uint8_t controller_address{0};
std::string can_device{""};
int group_address{0};
size_t expected_module_count{0};
int device_connection_timeout_s{0};
int power_state_grace_period_ms{0};
int altitude_setting_m{0};
double module_current_limit_point{0.};
std::string input_mode{"AC"};
OperatingMode operating_mode{OperatingMode::FIXED_ADDRESS};
std::vector<uint8_t> active_module_addresses;
std::vector<uint8_t> configured_module_addresses; // Store original configured addresses for recovery
std::mutex active_modules_mutex;
void poll_status_handler() override;
size_t remove_expired_telemetry_entries();
// Helper methods to reduce code duplication in packet handling
void check_and_signal_error_status_change(uint8_t source_address,
const can_packet_acdc::PowerModuleStatus& new_status,
const can_packet_acdc::PowerModuleStatus& old_status);
// Helper for standardized module identification in logging
std::string format_module_id(uint8_t address, const std::string& serial_number = "") const;
// Helper to check and update capabilities when capability data is received
void check_and_update_capabilities(uint8_t source_address);
// Private implementation for template methods
bool send_command_impl(uint8_t destination_address, uint8_t command_number, const std::vector<uint8_t>& payload,
bool group = false);
};
#endif // WINLINE_CAN_DEVICE_HPP