Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter
- CitrineOS core extracted (CSMS OCPP 2.0.1) - OpenOCPP extracted (firmware OCPP 1.6J/2.0.1) - ShapeShifter library installed (pip install -e) - ShapeShifter specification extracted - EVerest extracted TODO updated with progress
This commit is contained in:
@@ -0,0 +1,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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
Reference in New Issue
Block a user