Add extracted tools: CitrineOS, OpenOCPP, ShapeShifter

- CitrineOS core extracted (CSMS OCPP 2.0.1)
- OpenOCPP extracted (firmware OCPP 1.6J/2.0.1)
- ShapeShifter library installed (pip install -e)
- ShapeShifter specification extracted
- EVerest extracted

TODO updated with progress
This commit is contained in:
Eric F
2026-06-08 00:38:27 -04:00
parent 468cfeaa50
commit d398a6ced2
7326 changed files with 1177561 additions and 7 deletions

View File

@@ -0,0 +1,34 @@
#
# AUTO GENERATED - MARKED REGIONS WILL BE KEPT
# template version 3
#
# module setup:
# - ${MODULE_NAME}: module name
ev_setup_cpp_module()
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
# insert your custom targets and additional config variables here
# add_subdirectory(test-tool)
target_sources(${MODULE_NAME}
PRIVATE
"can_driver_acdc/CanBus.cpp"
"can_driver_acdc/CanPackets.cpp"
"can_driver_acdc/WinlineCanDevice.cpp"
)
target_link_libraries(${MODULE_NAME}
PRIVATE
Pal::Sigslot
everest::io
)
# ev@bcc62523-e22b-41d7-ba2f-825b493a3c97:v1
target_sources(${MODULE_NAME}
PRIVATE
"main/power_supply_DCImpl.cpp"
)
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1
# insert other things like install cmds etc here
# ev@c55432ab-152c-45a9-9d2e-7281d50c69c3:v1

View File

@@ -0,0 +1,435 @@
# Winline Power Supply Driver
Driver module for Winline power supplies supporting the Winline CAN protocol.
This module can control multiple Winline modules using either fixed addressing
or automatic group discovery.
## Overview
This is an **EVerest Hardware Driver** module that implements the standardized
`power_supply_DC` interface. It translates EVerest's generic power supply
commands into Winline-specific CAN protocol messages, enabling seamless
integration of Winline hardware into EVerest-based charging systems.
**Key Integration Points:**
- **Implements**: `power_supply_DC` interface for standardized power supply
control
- **Communicates**: Via CAN bus using Winline protocol
- **Provides**: Voltage/current control, telemetry, error reporting, and
capability detection
- **Integrates**: With EVerest's error management, logging, and configuration
systems
## Features
- **Multi-Module Support**: Control multiple Winline modules simultaneously
with current sharing
- **Dual Operating Modes**: Fixed address mode or automatic group discovery
mode
- **Comprehensive Error Handling**: Maps all Winline errors to standardized
power_supply_DC framework errors
- **Serial Number Identification**: Uses register-based serial number reading
for unique device identification
- **Thread-Safe Operation**: Robust concurrent access protection for
telemetry data
- **Automatic Recovery**: Detects and recovers from offline modules
automatically
- **Enhanced Status Monitoring**: Comprehensive status tracking and trend
analysis
- **Power State Verification**: Verifies power commands with actual module
status
## Configuration
### Basic Configuration
```yaml
# Required: CAN interface
can_device: "can0"
# Choose ONE of the following addressing modes:
# Option 1: Fixed Address Mode (recommended for known setups)
module_addresses: "0,1,2" # Comma-separated list of module addresses
# Option 2: Group Discovery Mode (for automatic allocation)
group_address: 1 # Group number matching module settings
module_addresses: "" # Must be empty for group discovery
# Optional settings
device_connection_timeout_s: 15 # Module offline timeout
conversion_efficiency_export: 0.95 # Power conversion efficiency
controller_address: 240 # CAN controller address
power_state_grace_period_ms: 2000 # Grace period for power state verification
altitude_setting_m: 1000 # Working altitude in meters
input_mode: "AC" # Input mode: "AC" or "DC"
module_current_limit_point: 1.0 # Current limit multiplier
```
### Configuration Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `can_device` | string | `"can0"` | CAN interface name (e.g., "can0", "vcan0") |
| `module_addresses` | string | `""` | Fixed addresses: "0,1,2" or empty for group discovery |
| `group_address` | integer | `0` | Group number for discovery mode |
| `device_connection_timeout_s` | integer | `15` | Module offline timeout |
| `conversion_efficiency_export` | number | `0.95` | Power conversion efficiency (0.0-1.0) |
| `controller_address` | integer | `240` | CAN controller address |
| `power_state_grace_period_ms` | integer | `2000` | Grace period for power state verification |
| `altitude_setting_m` | integer | `1000` | Working altitude in meters |
| `input_mode` | string | `"AC"` | Input mode: "AC" or "DC" |
| `module_current_limit_point` | number | `1.0` | Current limit multiplier |
## Operating Modes
### 1. Fixed Address Mode (Recommended)
Use when you know the exact addresses of your Winline modules.
**Configuration:**
```yaml
module_addresses: "0,1,2" # List specific module addresses
group_address: 0 # Ignored in this mode
```
**Advantages:**
- Predictable, deterministic addressing
- Faster startup (no discovery phase)
- Direct control over which modules are used
**Use Cases:**
- Production installations with known hardware
- Testing with specific module configurations
- When you need guaranteed addressing consistency
### 2. Group Discovery Mode
Use when modules are configured for automatic address allocation.
**Configuration:**
```yaml
module_addresses: "" # Must be empty
group_address: 1 # Must match module group setting
```
**Advantages:**
- Automatic module detection
- Hot-plugging support
- Easier setup for dynamic configurations
- Adapts to hardware changes
**Requirements:**
- Module group settings must match group_address
- All modules in group must be powered on during discovery
**Use Cases:**
- Development and testing environments
- Field installations with variable hardware
- When module addresses may change
## CAN Protocol Details
### Protocol Specification
- **Standard**: Winline CAN Communication Protocol
- **Bus Type**: CAN 2.0B Extended Frame Format (29-bit identifiers)
- **Baud Rate**: 125 kbps
- **Message Length**: 8 bytes (fixed)
- **Termination**: 120Ω resistors at both ends
### CAN ID Structure (29-bit)
```
Bits 28-26: Error Code (3 bits)
Bits 25-22: Device Number (4 bits)
Bits 21-16: Command Number (6 bits)
Bits 15-8: Destination Address (8 bits)
Bits 7-0: Source Address (8 bits) - Controller address
```
### Key Registers Used
| Register | Address | Description | Usage |
|----------|---------|-------------|-------|
| Voltage | 0x0001 | Read output voltage | Regular telemetry |
| Current | 0x0002 | Read output current | Regular telemetry |
| Status | 0x0003 | Read status flags | Error monitoring |
| Rated Power | 0x0004 | Read rated output power | Capability detection |
| Rated Current | 0x0005 | Read rated output current | Capability detection |
| Serial Low | 0x0054 | Read serial number (low) | Device identification |
| Serial High | 0x0055 | Read serial number (high) | Device identification |
| Group Info | 0x0043 | Read group information | Group discovery |
| Set Voltage | 0x0021 | Set output voltage | Control |
| Set Current | 0x001B | Set output current | Control |
| Power Control | 0x001A | Enable/disable module | Control |
### Communication Flow
#### Initialization
1. **Discovery** (Group mode): Send Group Info request to detect modules
2. **Capabilities**: Read max voltage/current/power from each module
3. **Serial Numbers**: Read serial number registers for device identification
4. **Configuration**: Set altitude, input mode, and current limit point
5. **Safety**: Ensure all modules start in OFF state
#### Regular Operation (1-second intervals)
1. **Telemetry**: Read voltage, current, status from all modules
2. **Error Monitoring**: Check status flags for alarm conditions
3. **Timeout Detection**: Remove unresponsive modules from active list
4. **Recovery**: Automatically re-add modules that come back online
5. **Power Verification**: Verify power commands with actual module status
## Error Handling
### Error Mapping
Winline errors are automatically mapped to standardized power_supply_DC errors:
| Winline Error | power_supply_DC Error | Severity | Description |
|---------------|------------------------|----------|-------------|
| OverVoltage | OverVoltageDC | High | Output voltage too high |
| UnderVoltage | UnderVoltageDC | High | Output voltage too low |
| OverTemperature | OverTemperature | High | Module overheating |
| OverCurrent | OverCurrentDC | High | Output current too high |
| InputVoltage | UnderVoltageAC | High | AC input voltage issues |
| InternalFault | HardwareFault | High | Internal module fault |
| InputPhaseLoss | VendorError | High | AC phase loss |
| FanFault | VendorWarning | Medium | Cooling fan failure |
| CommunicationFault | CommunicationFault | High | Lost communication |
### Error Recovery
- **Individual Module Errors**: Other modules continue operating
- **Communication Timeout**: Modules automatically removed from active list
- **Communication Recovery**: Modules automatically re-added when responding
- **System-Wide Faults**: All modules forced off for safety
- **Automatic Recovery**: Attempts automatic recovery for overvoltage and
short circuit faults
### Logging Format
Error messages follow standardized format for easy identification:
```
Winline[0x00/SN_12345678]: Module fault alarm activated
Winline[0x01]: Communication fault detected, FORCE mode to off
```
## Current Sharing
When multiple modules are active, current is automatically shared equally:
**Example with 3 modules:**
- Request: 150A total
- Per-module: 50A each
- Voltage: Same for all modules
**Load Balancing:**
- Automatic equal current distribution
- Voltage synchronized across all modules
- Individual module limits respected
- Failed modules automatically excluded
## Troubleshooting
### Common Issues
#### 1. No modules detected
**Symptoms**: "No active modules" warnings
**Causes**:
- CAN interface down: `sudo ip link set can0 up type can bitrate 125000`
- Wrong CAN device name: Check `can_device` configuration
- Modules not powered or wrong group setting
**Solutions**:
```bash
# Check CAN interface
ip link show can0
# Bring up CAN interface
sudo ip link set can0 up type can bitrate 125000
# Monitor CAN traffic
candump can0
```
#### 2. Modules going offline
**Symptoms**: "module communication expired" messages
**Causes**:
- CAN bus errors or noise
- Loose connections
- Power supply issues
**Solutions**:
- Check CAN bus termination (120Ω resistors)
- Verify all connections are secure
- Monitor for CAN errors in system logs
#### 3. Current not shared properly
**Symptoms**: Uneven current distribution
**Causes**:
- Modules have different capabilities
- Some modules in fault state
- Configuration mismatch
**Solutions**:
- Check module status in logs
- Verify all modules have same specifications
- Check for error conditions
#### 4. Group discovery not working
**Symptoms**: No modules found in group mode
**Causes**:
- Wrong group_address setting
- Module group settings not matching
- Modules not ready during discovery
**Solutions**:
- Verify module group settings match group_address
- Ensure all modules powered on before starting
### Debug Information
Enable debug logging to see detailed protocol information:
```yaml
# In your EVerest configuration
logging:
Winline: debug
```
Monitor CAN traffic:
```bash
# Raw CAN monitoring
candump can0
# With timestamp
candump -t z can0
# Filter Winline traffic (controller address 0xF0)
candump can0 | grep F0
```
## Hardware Requirements
### CAN Bus Setup
- **Topology**: Linear bus with 120Ω termination at both ends
- **Cable**: Twisted pair CAN cable (CAN_H, CAN_L)
- **Max Distance**: 40m at 125 kbps
- **Max Devices**: Multiple Winline modules + 1 controller
### Module Configuration
- Set module addresses or group settings before powering on
- Ensure all modules use same firmware version
- Configure appropriate current/voltage limits
- Set correct altitude and input mode
## Development Notes
### Architecture Constraints (Critical Implementation Details)
#### Single-Threaded CAN Communication
**IMPORTANT**: The entire CAN communication system operates in a single thread:
- `rx_handler()` and `poll_status_handler()` execute in the same thread
- No concurrent CAN operations - all message processing is sequential
- This design ensures deterministic message handling and eliminates race
conditions
- **Implication**: Blocking operations in handlers affect entire CAN
communication
#### Configuration Immutability
**CRITICAL**: Configuration is set exactly once during initialization:
- `set_config_values()` called only once at startup
- No runtime configuration changes supported
- Module addresses and operating mode cannot be changed after initialization
- **Implication**: Configuration errors require full module restart
### Security Model
#### No CAN Bus Security
**IMPORTANT**: Winline CAN protocol has no built-in security:
- **No encryption**: All CAN messages transmitted in plain text
- **No authentication**: No verification of message sender identity
- **No message integrity**: Only basic CRC provided by CAN hardware
- **Physical security required**: Secure the CAN bus physically
#### Message Validation Strategy
**Minimal Validation by Design**:
- **Only validation**: Controller address verification
- **Only malformation check**: Message must be exactly 8 bytes
- **No payload validation**: Content validity checking beyond packet structure
- **Rationale**: Keep protocol simple and fast for real-time operation
### Adding New Commands
1. Define register constants in `CanPackets.hpp`
2. Implement packet structures in `CanPackets.cpp`
3. Add command handling in `WinlineCanDevice::rx_handler()`
4. Add sending method if needed
### Thread Safety
- `telemetries` map: Protected by implicit single-thread access
- `active_module_addresses`: Protected by `active_modules_mutex`
- All signals are thread-safe via sigslot library
### Testing
- Use `vcan0` virtual CAN interface for development
- Mock modules can be simulated with `cansend` commands
- Unit tests recommended for packet encoding/decoding
## References
- [EVerest power_supply_DC Interface](https://everest.github.io/) - Standardized interface documentation
- [Linux SocketCAN Documentation](https://www.kernel.org/doc/html/latest/networking/can.html) - CAN bus programming guide
## Hardware Compatibility
### Supported Winline Models
- **Protocol Version**: Winline protocol and compatible versions
- **Module Types**: Various Winline power supply modules
### Manufacturer Information
- **Manufacturer**: Winline
- **Protocol Version**: Winline protocol V1.50 (this driver implementation)
**Note**: Ensure your Winline modules support the protocol version used by this driver. Older protocol versions may not be fully compatible.

View File

@@ -0,0 +1,31 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "Winline.hpp"
namespace module {
void Winline::init() {
acdc = std::make_unique<WinlineCanDevice>();
acdc->set_can_device(config.can_device);
acdc->set_config_values(config.module_addresses, config.group_address, config.device_connection_timeout_s,
config.controller_address, config.power_state_grace_period_ms, config.altitude_setting_m,
config.input_mode, config.module_current_limit_point);
// Set altitude on all modules
acdc->set_altitude_all_modules();
// Set input mode on all modules
acdc->set_input_mode_all_modules();
// Set current limit point on all modules
acdc->set_current_limit_point_all_modules();
invoke_init(*p_main);
}
void Winline::ready() {
invoke_ready(*p_main);
}
} // namespace module

View File

@@ -0,0 +1,75 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef WINLINE_HPP
#define WINLINE_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 2
//
#include "ld-ev.hpp"
// headers for provided interface implementations
#include <generated/interfaces/power_supply_DC/Implementation.hpp>
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
#include "can_driver_acdc/WinlineCanDevice.hpp"
// ev@4bf81b14-a215-475c-a1d3-0a484ae48918:v1
namespace module {
struct Conf {
std::string can_device;
std::string module_addresses;
int group_address;
int device_connection_timeout_s;
double conversion_efficiency_export;
int controller_address;
double min_export_voltage_V;
double max_export_voltage_V;
double min_export_current_A;
double max_export_current_A;
int power_state_grace_period_ms;
double current_regulation_tolerance_A;
double peak_current_ripple_A;
int altitude_setting_m;
std::string input_mode;
double module_current_limit_point;
};
class Winline : public Everest::ModuleBase {
public:
Winline() = delete;
Winline(const ModuleInfo& info, std::unique_ptr<power_supply_DCImplBase> p_main, Conf& config) :
ModuleBase(info), p_main(std::move(p_main)), config(config){};
const std::unique_ptr<power_supply_DCImplBase> p_main;
const Conf& config;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
std::unique_ptr<WinlineCanDevice> acdc;
// ev@1fce4c5e-0ab8-41bb-90f7-14277703d2ac:v1
protected:
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
// insert your protected definitions here
// ev@4714b2ab-a24f-4b95-ab81-36439e1478de:v1
private:
friend class LdEverest;
void init();
void ready();
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
// insert your private definitions here
// ev@211cfdbe-f69a-4cd6-a4ec-f8aaa3d1b6c8:v1
};
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
// insert other definitions here
// ev@087e516b-124c-48df-94fb-109508c7cda9:v1
} // namespace module
#endif // WINLINE_HPP

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

View File

@@ -0,0 +1,297 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#include "power_supply_DCImpl.hpp"
#include <iomanip>
#include <sstream>
namespace module {
namespace main {
void power_supply_DCImpl::init() {
mod->acdc->signalVoltageCurrent.connect([this](WinlineCanDevice::TelemetryMap telemetries) {
float total_current = 0;
float module_voltage = 0;
for (const auto& telemetry : telemetries) {
total_current += telemetry.second.current;
// Use the proper Winline voltage reading from register 0x0001
module_voltage = telemetry.second.voltage; // Changed from v_ext to voltage
}
types::power_supply_DC::VoltageCurrent vc;
vc.current_A = total_current;
vc.voltage_V = module_voltage;
publish_voltage_current(vc);
});
mod->acdc->signalModuleStatus.connect([this](can_packet_acdc::PowerModuleStatus status) {
// Publish mode changes
types::power_supply_DC::Mode mode;
if (status.module_fault) {
mode = types::power_supply_DC::Mode::Fault;
} else if (status.dcdc_on_off_status) {
mode = types::power_supply_DC::Mode::Off;
} else {
mode = types::power_supply_DC::Mode::Export;
}
if (this->mode.load() != mode || firsttime) {
publish_mode(mode);
firsttime = false;
}
});
mod->acdc->signalCapabilitiesUpdate.connect([this](WinlineCanDevice::TelemetryMap telemetries) {
types::power_supply_DC::Capabilities new_caps;
new_caps.bidirectional = false;
new_caps.min_export_current_A = 1;
if (telemetries.size() == 0) {
EVLOG_info << " No telemetries received, setting default capabilities";
publish_capabilities(new_caps);
return;
}
// Start with config limits as base
new_caps.min_export_voltage_V = mod->config.min_export_voltage_V;
new_caps.max_export_voltage_V = mod->config.max_export_voltage_V;
new_caps.min_export_current_A = mod->config.min_export_current_A;
new_caps.max_export_current_A = 0.0; // Will be updated with device rated values
new_caps.current_regulation_tolerance_A = mod->config.current_regulation_tolerance_A;
new_caps.peak_current_ripple_A = mod->config.peak_current_ripple_A;
// Update with device rated values (current and power from protocol)
for (const auto& telemetry : telemetries) {
if (telemetry.second.valid_caps) {
// Replace max current with device rated current (sum of all modules)
new_caps.max_export_current_A += telemetry.second.dc_max_output_current;
// Sum up total power from all modules
new_caps.max_export_power_W += telemetry.second.dc_rated_output_power;
}
}
new_caps.conversion_efficiency_export = mod->config.conversion_efficiency_export;
caps = new_caps;
EVLOG_info << " Capabilities updated: " << new_caps.max_export_voltage_V << "V / "
<< new_caps.min_export_voltage_V << "V, " << new_caps.max_export_current_A << "A, power "
<< new_caps.max_export_power_W << "W";
publish_capabilities(new_caps);
if (last_module_count != telemetries.size() && telemetries.size() > 0) {
double voltage = exportVoltage.load();
double current = exportCurrentLimit.load();
types::power_supply_DC::Mode mode = this->mode.load();
types::power_supply_DC::ChargingPhase phase = this->phase.load();
if (telemetries.size() > last_module_count) {
EVLOG_info << " Hot plug detected - module count increased from " << static_cast<int>(last_module_count)
<< " to " << telemetries.size() << " modules, redistributing " << current << "A from "
<< static_cast<int>(last_module_count) << " to " << telemetries.size() << " modules";
} else if (telemetries.size() < last_module_count) {
EVLOG_info << " Hot unplug detected - module count decreased from "
<< static_cast<int>(last_module_count) << " to " << telemetries.size()
<< " modules, redistributing " << current << "A from " << static_cast<int>(last_module_count)
<< " to " << telemetries.size() << " modules";
}
EVLOG_info << " Restoring last settings: voltage=" << voltage << "V, current=" << current
<< "A, mode=" << mode << ", phase=" << phase;
last_module_count = telemetries.size();
handle_setExportVoltageCurrent(voltage, current);
handle_setMode(mode, phase);
}
last_module_count = telemetries.size();
});
mod->acdc->signalError.connect([this](uint8_t address, WinlineCanDevice::Error error, bool active) {
const std::string error_type = map_winline_error_to_power_supply_dc(error);
const std::string error_message = create_error_message(address, error, active);
const bool is_error_active = error_state_monitor->is_error_active(error_type, "");
if (error == WinlineCanDevice::Error::CommunicationFault && active) {
EVLOG_info << " Communication fault detected - all " << static_cast<int>(last_module_count)
<< " modules unresponsive, forcing system OFF for safety";
this->mode.store(types::power_supply_DC::Mode::Off);
}
if (active && !is_error_active) {
// New error detected - raise it
EVLOG_error << error_message;
auto severity = (error == WinlineCanDevice::Error::FanFault) ? Everest::error::Severity::Medium
: Everest::error::Severity::High;
raise_error(error_factory->create_error(error_type, "", error_message, severity));
} else if (!active && is_error_active) {
// Error cleared - clear it
EVLOG_info << error_message;
clear_error(error_type);
}
});
mod->acdc->initial_ping();
}
void power_supply_DCImpl::ready() {
}
void power_supply_DCImpl::handle_setMode(types::power_supply_DC::Mode& mode,
types::power_supply_DC::ChargingPhase& phase) {
EVLOG_info << "Set mode via CAN: " << mode << " with phase " << phase;
// Enhanced power control with verification (Task 12)
bool power_result = false;
if (mode == types::power_supply_DC::Mode::Off) {
power_result = mod->acdc->handle_power_transition(false);
} else if (mode == types::power_supply_DC::Mode::Export) {
power_result = mod->acdc->handle_power_transition(true);
} else if (mode == types::power_supply_DC::Mode::Import) {
power_result = mod->acdc->handle_power_transition(true);
} else if (mode == types::power_supply_DC::Mode::Fault) {
power_result = mod->acdc->handle_power_transition(false);
}
if (power_result) {
EVLOG_info << " Mode change to " << mode << " initiated successfully";
this->mode.store(mode);
this->phase.store(phase);
} else {
EVLOG_error << " Mode change to " << mode << " failed - keeping current mode";
// Don't update stored mode/phase on failure
}
};
void power_supply_DCImpl::handle_setExportVoltageCurrent(double& voltage, double& current) {
EVLOG_info << " request setting voltage/current: " << voltage << "V / " << current << "A";
if (voltage > caps.max_export_voltage_V)
voltage = caps.max_export_voltage_V;
else if (voltage < caps.min_export_voltage_V)
voltage = caps.min_export_voltage_V;
if (current > caps.max_export_current_A)
current = caps.max_export_current_A;
else if (current < caps.min_export_current_A)
current = caps.min_export_current_A;
// Validate power limits: voltage * current must not exceed max power
double requested_power = voltage * current;
if (requested_power > caps.max_export_power_W) {
EVLOG_warning << " Requested power " << requested_power << "W exceeds max power " << caps.max_export_power_W
<< "W. Reducing current to stay within power limit.";
// Reduce current to stay within power limit
current = caps.max_export_power_W / voltage;
if (current < caps.min_export_current_A) {
EVLOG_error << " Cannot reduce current below minimum " << caps.min_export_current_A
<< "A while staying within power limit. Setting to minimum.";
current = caps.min_export_current_A;
}
}
EVLOG_info << " request setting voltage/current: " << voltage << "V / " << current
<< "A (power: " << voltage * current << "W)";
exportVoltage.store(voltage);
exportCurrentLimit.store(current);
const size_t active_module_count = last_module_count;
if (active_module_count > 0) {
const double current_per_module = exportCurrentLimit.load() / static_cast<double>(active_module_count);
EVLOG_info << " Updating voltage/current via CAN: " << exportVoltage.load() << "V / "
<< exportCurrentLimit.load() << "A total → " << current_per_module
<< "A per module - active modules: " << active_module_count;
} else {
EVLOG_info << " Updating voltage/current via CAN: " << exportVoltage.load() << "V / "
<< exportCurrentLimit.load() << "A (but no active modules detected)";
}
mod->acdc->set_voltage_current(exportVoltage.load(), exportCurrentLimit.load());
};
void power_supply_DCImpl::handle_setImportVoltageCurrent(double& voltage, double& current) {
if (caps.min_import_voltage_V.has_value() && caps.max_import_current_A.has_value()) {
if (voltage > caps.max_import_voltage_V.value())
voltage = caps.max_import_voltage_V.value();
else if (voltage < caps.min_import_voltage_V.value())
voltage = caps.min_import_voltage_V.value();
if (current > caps.max_import_current_A.value())
current = caps.max_import_current_A.value();
else if (current < caps.min_import_current_A.value())
current = caps.min_import_current_A.value();
minImportVoltage.store(voltage);
importCurrentLimit.store(current);
EVLOG_info << " Updating voltage/current via CAN: " << minImportVoltage.load() << "V / "
<< importCurrentLimit.load() << "A";
mod->acdc->set_voltage_current(minImportVoltage.load(), importCurrentLimit.load());
}
}
std::string power_supply_DCImpl::map_winline_error_to_power_supply_dc(WinlineCanDevice::Error error) {
switch (error) {
case WinlineCanDevice::Error::OverVoltage:
return "power_supply_DC/OverVoltageDC";
case WinlineCanDevice::Error::UnderVoltage:
return "power_supply_DC/UnderVoltageDC";
case WinlineCanDevice::Error::OverTemperature:
return "power_supply_DC/OverTemperature";
case WinlineCanDevice::Error::OverCurrent:
return "power_supply_DC/OverCurrentDC";
case WinlineCanDevice::Error::InternalFault:
return "power_supply_DC/HardwareFault";
case WinlineCanDevice::Error::CommunicationFault:
return "power_supply_DC/CommunicationFault";
case WinlineCanDevice::Error::InputVoltage:
return "power_supply_DC/UnderVoltageAC"; // Most common case for input voltage issues
case WinlineCanDevice::Error::FanFault:
return "power_supply_DC/VendorWarning"; // Non-critical vendor-specific warning
case WinlineCanDevice::Error::InputPhaseLoss:
return "power_supply_DC/VendorError"; // Critical vendor-specific error
case WinlineCanDevice::Error::VendorError:
return "power_supply_DC/VendorError"; // Critical vendor-specific error
case WinlineCanDevice::Error::VendorWarning:
return "power_supply_DC/VendorWarning"; // Non-critical vendor-specific warning
default:
return "power_supply_DC/VendorError"; // Fallback for unknown errors
}
}
std::string power_supply_DCImpl::create_error_message(uint8_t module_address, WinlineCanDevice::Error error,
bool active) const {
std::string action = active ? "detected" : "cleared";
std::string error_name;
switch (error) {
case WinlineCanDevice::Error::OverVoltage:
error_name = "overvoltage fault";
break;
case WinlineCanDevice::Error::UnderVoltage:
error_name = "undervoltage fault";
break;
case WinlineCanDevice::Error::OverTemperature:
error_name = "overtemperature fault";
break;
case WinlineCanDevice::Error::OverCurrent:
error_name = "overcurrent fault";
break;
case WinlineCanDevice::Error::InternalFault:
error_name = "internal fault";
break;
case WinlineCanDevice::Error::CommunicationFault:
error_name = "communication fault";
break;
case WinlineCanDevice::Error::InputVoltage:
error_name = "input voltage fault";
break;
case WinlineCanDevice::Error::FanFault:
error_name = "fan fault";
break;
case WinlineCanDevice::Error::InputPhaseLoss:
error_name = "input phase loss fault";
break;
default:
error_name = "unknown fault";
break;
}
std::stringstream ss;
ss << "Winline[0x" << std::hex << std::uppercase << static_cast<int>(module_address) << "]: " << error_name << " "
<< action;
return ss.str();
}
} // namespace main
} // namespace module

View File

@@ -0,0 +1,79 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright 2020 - 2025 Pionix GmbH and Contributors to EVerest
#ifndef MAIN_POWER_SUPPLY_DC_IMPL_HPP
#define MAIN_POWER_SUPPLY_DC_IMPL_HPP
//
// AUTO GENERATED - MARKED REGIONS WILL BE KEPT
// template version 3
//
#include <generated/interfaces/power_supply_DC/Implementation.hpp>
#include "../Winline.hpp"
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
// insert your custom include headers here
#include <atomic>
#include <memory>
// ev@75ac1216-19eb-4182-a85c-820f1fc2c091:v1
namespace module {
namespace main {
struct Conf {};
class power_supply_DCImpl : public power_supply_DCImplBase {
public:
power_supply_DCImpl() = delete;
power_supply_DCImpl(Everest::ModuleAdapter* ev, const Everest::PtrContainer<Winline>& mod, Conf& config) :
power_supply_DCImplBase(ev, "main"), mod(mod), config(config){};
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
// insert your public definitions here
// ev@8ea32d28-373f-4c90-ae5e-b4fcc74e2a61:v1
protected:
// command handler functions (virtual)
virtual void handle_setMode(types::power_supply_DC::Mode& mode,
types::power_supply_DC::ChargingPhase& phase) override;
virtual void handle_setExportVoltageCurrent(double& voltage, double& current) override;
virtual void handle_setImportVoltageCurrent(double& voltage, double& current) override;
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
// insert your protected definitions here
// ev@d2d1847a-7b88-41dd-ad07-92785f06f5c4:v1
private:
const Everest::PtrContainer<Winline>& mod;
const Conf& config;
virtual void init() override;
virtual void ready() override;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
std::atomic<types::power_supply_DC::Mode> mode{types::power_supply_DC::Mode::Off};
std::atomic<types::power_supply_DC::ChargingPhase> phase{types::power_supply_DC::ChargingPhase::CableCheck};
std::atomic<double> exportVoltage{0.};
std::atomic<double> exportCurrentLimit{0.};
std::atomic<double> minImportVoltage{0.};
std::atomic<double> importCurrentLimit{0.};
types::power_supply_DC::Capabilities caps;
bool firsttime{true};
uint8_t last_module_count{0};
// Error handling helpers
std::string map_winline_error_to_power_supply_dc(WinlineCanDevice::Error error);
std::string create_error_message(uint8_t module_address, WinlineCanDevice::Error error, bool active) const;
// ev@3370e4dd-95f4-47a9-aaec-ea76f34a66c9:v1
};
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
// insert other definitions here
// ev@3d7da0ad-02c2-493d-9920-0bbbd56b9876:v1
} // namespace main
} // namespace module
#endif // MAIN_POWER_SUPPLY_DC_IMPL_HPP

View File

@@ -0,0 +1,91 @@
description: Driver for Winline ACDC power supply. Supports multiple Winline modules using CAN protocol either in fixed address mode or group discovery mode.
config:
can_device:
description: CAN interface name
type: string
default: can0
module_addresses:
description: >-
Module Addresses to use.
If you have multiple PSUs, use individual addresses and list them comma separated, e.g. "1,2".
type: string
default: ""
group_address:
description: >-
Group address. Use this if your PSUs are using automatic allocation of the addresses.
If you have multiple PSUs, set this number in accordance with the group dial
setting of the PSUs you want to use.
Can't use module addresses and group addresses at the same time, if both are set, the module addresses will be used.
type: integer
default: 0
device_connection_timeout_s:
description: >-
Timeout in seconds to wait for a module to respond before considering it offline.
CRITICAL SAFETY: Must be greater than module internal timeout to prevent overcurrent risk.
Recommended: 15s.
type: integer
default: 15
conversion_efficiency_export:
description: Conversion efficiency of the export mode.
type: number
default: 0.95
controller_address:
description: Controller address defaults to 0xF0.
type: integer
default: 240
min_export_voltage_V:
description: Minimum export voltage limit (V)
type: number
default: 50.0
max_export_voltage_V:
description: Maximum export voltage limit (V)
type: number
default: 1000.0
min_export_current_A:
description: Minimum export current limit (A)
type: number
default: 0.0
max_export_current_A:
description: Maximum export current limit (A) - will be updated by device rated values
type: number
default: 133.3
power_state_grace_period_ms:
description: Grace period in milliseconds to wait before verifying power state changes
type: integer
default: 2000
current_regulation_tolerance_A:
description: Current regulation tolerance (A)
type: number
default: 0.5
peak_current_ripple_A:
description: Peak current ripple (A)
type: number
default: 2
altitude_setting_m:
description: >-
Working altitude setting in meters. This affects the power derating of the modules
at high altitudes. Valid range: 1000-5000m. Default: 1000m (sea level).
type: integer
default: 1000
input_mode:
description: >-
Input mode for the power supply modules. AC mode for AC input, DC mode for DC input.
This setting affects the internal configuration of the modules.
type: string
default: "AC"
enum:
- "AC"
- "DC"
module_current_limit_point:
description: Percentage of the rated current that the module will use as the current limit point (0.0-3.3325)
type: number
default: 3.3325
provides:
main:
description: Main interface
interface: power_supply_DC
config: {}
metadata:
license: https://opensource.org/licenses/Apache-2.0
authors:
- Florin Mihut