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,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
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user