-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
E3DC: add Multi Connect II wallbox charger support #25703
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Implement core charger functionality with corrected RSCP protocol handling: - Fix Enabled() to use WB_EXTERN_DATA_ALG byte 2 bit 6 - Fix Enable() to use WB_REQ_SET_ABORT_CHARGING instead of WB_SET_EXTERN - Fix Status() to use WB_EXTERN_DATA_ALG with correct bit flags - Fix maxCurrent() to use WB_REQ_SET_MAX_CHARGE_CURRENT (6-32A) Add meter and phase interfaces: - api.Meter (CurrentPower) - api.MeterEnergy (TotalEnergy from DB_TEC + WB_ENERGY) - api.PhasePowers (Powers per phase) - api.PhaseCurrents (calculated from power/230V) - api.PhaseGetter (active phases bitmask) - api.CurrentLimiter (6-32A range) - api.CurrentGetter (cached current value) Status: Testing in progress, debug output still enabled. Note: Requires go-rscp library with Bool DataType support for WB_REQ_SET_ABORT_CHARGING. Upstream library may need updates.
Implement all charger interfaces for E3DC Multi Connect wallbox via RSCP: Core interfaces: - Status() using WB_EXTERN_DATA_ALG byte flags - Enable()/Enabled() via WB_REQ_SET_ABORT_CHARGING - MaxCurrent() via WB_REQ_SET_MAX_CHARGE_CURRENT (6-32A) Meter interfaces: - CurrentPower() from WB_PM_POWER_L1/L2/L3 - TotalEnergy() combining DB_TEC_WALLBOX_ENERGYALL + WB_ENERGY_ALL - Powers() per phase power readings - Currents() calculated from power/230V (voltage not accessible) Phase control: - GetPhases() via WB_NUMBER_PHASES (not WB_PM_ACTIVE_PHASES) - Phases1p3p() via WB_REQ_SET_NUMBER_PHASES (wallbox handles safe switching) - GetMinMaxCurrent() via WB_LOWER/UPPER_CURRENT_LIMIT - GetMaxCurrent() via WB_MAX_CHARGE_CURRENT Session data: - ChargedEnergy() from WB_PM_ENERGY_L1/L2/L3 - ChargeDuration() from WB_SESSION_ACTIVE_CHARGE_TIME - Identify() returns RFID tag from WB_SESSION_AUTH_DATA Tested with E3DC Multi Connect II wallbox, firmware 1.0.3.0. Note: Requires go-rscp with WB_REQ_SET_NUMBER_PHASES and Bool datatype support.
- ChargedEnergy() now uses WB_SESSION_CHARGED_ENERGY for accurate per-session energy tracking (returns 0 when no session active) - Replace license block with development status comments - All charger interfaces tested and verified via evcc CLI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds support for the E3DC Multi Connect II wallbox charger using the RSCP protocol. The implementation provides comprehensive charger functionality including phase switching (1p/3p), RFID identification, power/energy metering, and current control.
Key changes:
- New E3DC RSCP charger implementation with support for all standard charger interfaces plus phase switching and RFID
- Template definition file for E3DC RSCP charger configuration
- Helper functions for RSCP protocol message parsing and error handling
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
charger/e3dc.go |
Complete E3DC charger implementation using RSCP protocol with support for status monitoring, enable/disable control, current limiting, phase switching, power/energy metering, and RFID identification |
templates/definition/charger/e3dc-rscp.yaml |
Configuration template defining parameters (host, port, credentials, wallbox index) and capabilities (1p3p phase switching, RFID) |
charger/e3dc.go
Outdated
| // The sum of both values matches the total energy shown in the E3DC portal. | ||
| // Testing showed: DB_TEC (8319 kWh) + WB_ENERGY (699 kWh) = 9018 kWh ≈ Portal (9019 kWh) | ||
| func (wb *E3dc) TotalEnergy() (float64, error) { | ||
| // Query both energy sources in parallel |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The comment says "Query both energy sources in parallel", but the code executes the queries sequentially, not in parallel. The comment is misleading and should be updated to reflect the actual behavior:
// Query both energy sources sequentially| // Query both energy sources in parallel | |
| // Query both energy sources sequentially |
charger/e3dc.go
Outdated
| return nil, err | ||
| } | ||
|
|
||
| port, _ := strconv.Atoi(port_) |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error from strconv.Atoi(port_) is silently ignored. If the port cannot be parsed, it defaults to 0, which may cause unexpected connection failures. Consider handling the error explicitly:
port, err := strconv.Atoi(port_)
if err != nil {
return nil, fmt.Errorf("invalid port: %w", err)
}| port, _ := strconv.Atoi(port_) | |
| port, err := strconv.Atoi(port_) | |
| if err != nil { | |
| return nil, fmt.Errorf("invalid port: %w", err) | |
| } |
charger/e3dc.go
Outdated
| } | ||
|
|
||
| if l := len(res); l < length { | ||
| return nil, fmt.Errorf("invalid length: expected %d, got %d", length, l) |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The length validation uses < (less than) but the error message says "expected %d". This is misleading because the function accepts containers with length >= the specified value. Consider either:
- Using
!=if exact length is required, or - Updating the error message to clarify it's a minimum requirement (e.g., "expected at least %d, got %d")
| return nil, fmt.Errorf("invalid length: expected %d, got %d", length, l) | |
| return nil, fmt.Errorf("invalid length: expected at least %d, got %d", length, l) |
|
|
||
| return p1, p2, p3, nil | ||
| } | ||
|
|
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The power reading logic is duplicated between CurrentPower() and Powers(). Both methods fetch the same data using identical requests. Consider having CurrentPower() call Powers() and sum the results:
func (wb *E3dc) CurrentPower() (float64, error) {
p1, p2, p3, err := wb.Powers()
if err != nil {
return 0, err
}
return p1 + p2 + p3, nil
}This eliminates code duplication and ensures consistency between the two methods.
| // CurrentPower returns the sum of all phase powers. | |
| func (wb *E3dc) CurrentPower() (float64, error) { | |
| p1, p2, p3, err := wb.Powers() | |
| if err != nil { | |
| return 0, err | |
| } | |
| return p1 + p2 + p3, nil | |
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Powers is not needed and can be removed
charger/e3dc.go
Outdated
| } | ||
|
|
||
| // NewE3dcFromConfig creates an E3DC charger from generic config | ||
| func NewE3dcFromConfig(other map[string]interface{}) (api.Charger, error) { |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function signature is missing context.Context as the first parameter. All charger factory functions in the codebase follow the pattern NewXFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error). The context parameter is required for proper lifecycle management and cancellation support.
Change to:
func NewE3dcFromConfig(ctx context.Context, other map[string]interface{}) (api.Charger, error)
charger/e3dc.go
Outdated
| host, port_, err := net.SplitHostPort(util.DefaultPort(cc.Uri, 5033)) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| port, _ := strconv.Atoi(port_) |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Variable name port_ uses a trailing underscore which is unconventional in Go. Consider using a more descriptive name like portStr to indicate it's the string representation of the port.
| host, port_, err := net.SplitHostPort(util.DefaultPort(cc.Uri, 5033)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| port, _ := strconv.Atoi(port_) | |
| host, portStr, err := net.SplitHostPort(util.DefaultPort(cc.Uri, 5033)) | |
| if err != nil { | |
| return nil, err | |
| } | |
| port, _ := strconv.Atoi(portStr) |
charger/e3dc.go
Outdated
| func (wb *E3dc) ChargedEnergy() (float64, error) { | ||
| res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_SESSION, nil)) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
|
|
||
| sessionData, err := rscpContainer(*res, 1) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
|
|
||
| // Find WB_SESSION_CHARGED_ENERGY in session data | ||
| for _, msg := range sessionData { | ||
| if msg.Tag == rscp.WB_SESSION_CHARGED_ENERGY { | ||
| energy, err := rscpFloat64(msg) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| return energy / 1000.0, nil // Wh -> kWh | ||
| } | ||
| } | ||
|
|
||
| // No active session | ||
| return 0, nil | ||
| } | ||
|
|
||
| var _ api.ChargeTimer = (*E3dc)(nil) | ||
|
|
||
| // ChargeDuration implements the api.ChargeTimer interface | ||
| // Returns the duration of the current charging session | ||
| func (wb *E3dc) ChargeDuration() (time.Duration, error) { | ||
| res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_SESSION, nil)) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
|
|
||
| sessionData, err := rscpContainer(*res, 1) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
|
|
||
| // Find WB_SESSION_ACTIVE_CHARGE_TIME in session data | ||
| for _, msg := range sessionData { | ||
| if msg.Tag == rscp.WB_SESSION_ACTIVE_CHARGE_TIME { | ||
| // Session time is in milliseconds (Uint64) | ||
| ms, err := rscpFloat64(msg) | ||
| if err != nil { | ||
| return 0, err | ||
| } | ||
| return time.Duration(ms) * time.Millisecond, nil | ||
| } | ||
| } | ||
|
|
||
| // No active session | ||
| return 0, nil | ||
| } | ||
|
|
||
| var _ api.Identifier = (*E3dc)(nil) | ||
|
|
||
| // Identify implements the api.Identifier interface | ||
| // Returns the RFID tag ID used to authorize the current session | ||
| func (wb *E3dc) Identify() (string, error) { | ||
| res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_SESSION, nil)) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| sessionData, err := rscpContainer(*res, 1) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| // Find WB_SESSION_AUTH_DATA in session data | ||
| for _, msg := range sessionData { | ||
| if msg.Tag == rscp.WB_SESSION_AUTH_DATA { | ||
| return rscpString(msg) | ||
| } | ||
| } | ||
|
|
||
| // No active session or no RFID used | ||
| return "", nil | ||
| } |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The session data fetching logic is duplicated across three methods (ChargedEnergy, ChargeDuration, and Identify). Consider extracting this into a helper method to reduce code duplication:
func (wb *E3dc) getSessionData() ([]rscp.Message, error) {
res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_SESSION, nil))
if err != nil {
return nil, err
}
return rscpContainer(*res, 1)
}This would improve maintainability and reduce the risk of inconsistencies.
charger/e3dc.go
Outdated
| func (wb *E3dc) Enabled() (bool, error) { | ||
| res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_DATA, []rscp.Message{ | ||
| *rscp.NewMessage(rscp.WB_INDEX, wb.id), | ||
| *rscp.NewMessage(rscp.WB_REQ_EXTERN_DATA_ALG, nil), | ||
| })) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| wbData, err := rscpContainer(*res, 2) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| wbExtDataAlg, err := rscpContainer(wbData[1], 2) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| b, err := rscpBytes(wbExtDataAlg[1]) | ||
| if err != nil { | ||
| return false, err | ||
| } | ||
|
|
||
| // WB_EXTERN_DATA_ALG Byte 2, Bit 6: 0 = enabled, 1 = disabled (abort active) | ||
| return b[2]&(1<<6) == 0, nil | ||
| } | ||
|
|
||
| // Enable implements the api.Charger interface | ||
| func (wb *E3dc) Enable(enable bool) error { | ||
| // WB_REQ_SET_ABORT_CHARGING: true = abort/stop, false = allow/resume | ||
| _, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_DATA, []rscp.Message{ | ||
| *rscp.NewMessage(rscp.WB_INDEX, wb.id), | ||
| *rscp.NewMessage(rscp.WB_REQ_SET_ABORT_CHARGING, !enable), | ||
| })) | ||
|
|
||
| return err | ||
| } | ||
|
|
||
| // Status implements the api.Charger interface | ||
| func (wb *E3dc) Status() (api.ChargeStatus, error) { | ||
| res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_DATA, []rscp.Message{ | ||
| *rscp.NewMessage(rscp.WB_INDEX, wb.id), | ||
| *rscp.NewMessage(rscp.WB_REQ_EXTERN_DATA_ALG, nil), | ||
| })) | ||
| if err != nil { | ||
| return api.StatusNone, err | ||
| } | ||
|
|
||
| wbData, err := rscpContainer(*res, 2) | ||
| if err != nil { | ||
| return api.StatusNone, err | ||
| } | ||
|
|
||
| wbExtDataAlg, err := rscpContainer(wbData[1], 2) | ||
| if err != nil { | ||
| return api.StatusNone, err | ||
| } | ||
|
|
||
| b, err := rscpBytes(wbExtDataAlg[1]) | ||
| if err != nil { | ||
| return api.StatusNone, err | ||
| } | ||
|
|
||
| // WB_EXTERN_DATA_ALG Byte 2: Bit 5 (0x20) = charging, Bit 3 (0x08) = connected | ||
| switch { | ||
| case b[2]&0x20 != 0: | ||
| return api.StatusC, nil | ||
| case b[2]&0x08 != 0: | ||
| return api.StatusB, nil | ||
| default: | ||
| return api.StatusA, nil | ||
| } | ||
| } |
Copilot
AI
Nov 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Enabled() and Status() methods fetch the same WB_EXTERN_DATA_ALG data. Consider extracting this into a shared helper method to reduce duplication:
func (wb *E3dc) getExternDataAlg() ([]byte, error) {
res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_DATA, []rscp.Message{
*rscp.NewMessage(rscp.WB_INDEX, wb.id),
*rscp.NewMessage(rscp.WB_REQ_EXTERN_DATA_ALG, nil),
}))
if err != nil {
return nil, err
}
wbData, err := rscpContainer(*res, 2)
if err != nil {
return nil, err
}
wbExtDataAlg, err := rscpContainer(wbData[1], 2)
if err != nil {
return nil, err
}
return rscpBytes(wbExtDataAlg[1])
}Then both methods can simply call this helper and decode the specific bits they need.
|
Great PR! Please consider the curated review comments. Generally replace |
Changes based on PR evcc-io#25703 review: - Add context.Context parameter to constructor (Go conventions) - Improve port parsing error handling - Fix code duplication: powers() is now private, CurrentPower() uses it - Add getExternDataAlg() and getSessionData() helpers to reduce duplication - Add checkConfiguration() to verify wallbox settings at startup: - Sun mode: automatically disabled (conflicts with evcc control) - Auto phase switching: warning only (user may want to keep it) - Add header documentation with configuration requirements - Rename port_ to portStr for clarity Phase switching (1p/3p) tested successfully with real hardware. The wallbox handles ramping internally - evcc only needs to send the command.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- In
ChargeDuration, the comment says the session time is a Uint64 in milliseconds but the code parses it viarscpFloat64; consider using a dedicatedrscpUint64helper to avoid precision issues and better match the protocol type. - In
TotalEnergy, if noDB_TEC_WALLBOX_VALUEentry matches the configured wallboxid,dbEnergyremains 0 and this silently degrades the reported total energy; it may be safer to detect this case and return an error or at least log a warning about a possible misconfigured index. - The constructor
NewE3dcFromConfigignores the passedcontext.Contextafter creation; if the RSCP client supports it, consider threading the context through to requests or client lifetime to allow for cancellation and proper shutdown.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `ChargeDuration`, the comment says the session time is a Uint64 in milliseconds but the code parses it via `rscpFloat64`; consider using a dedicated `rscpUint64` helper to avoid precision issues and better match the protocol type.
- In `TotalEnergy`, if no `DB_TEC_WALLBOX_VALUE` entry matches the configured wallbox `id`, `dbEnergy` remains 0 and this silently degrades the reported total energy; it may be safer to detect this case and return an error or at least log a warning about a possible misconfigured index.
- The constructor `NewE3dcFromConfig` ignores the passed `context.Context` after creation; if the RSCP client supports it, consider threading the context through to requests or client lifetime to allow for cancellation and proper shutdown.
## Individual Comments
### Comment 1
<location> `charger/e3dc.go:117` </location>
<code_context>
+
+// checkConfiguration verifies wallbox settings and adjusts them for evcc control
+func (wb *E3dc) checkConfiguration() {
+ res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_DATA, []rscp.Message{
+ *rscp.NewMessage(rscp.WB_INDEX, wb.id),
+ *rscp.NewMessage(rscp.WB_REQ_SUN_MODE_ACTIVE, nil),
</code_context>
<issue_to_address>
**issue (complexity):** Consider introducing small helper functions to centralize repeated RSCP request/parse patterns and session lookups to make the charger implementation easier to read and maintain.
You can reduce a lot of repetition and magic indexing with a couple of small helpers without changing behavior.
### 1. Wrap the `WB_REQ_DATA` pattern
You repeat:
```go
res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_DATA, []rscp.Message{
*rscp.NewMessage(rscp.WB_INDEX, wb.id),
*rscp.NewMessage(rscp.WB_REQ_XYZ, nil),
// ...
}))
if err != nil {
return ...
}
wbData, err := rscpContainer(*res, N)
if err != nil {
return ...
}
```
Extract that into a helper that hides the container and length logic:
```go
func (wb *E3dc) wbData(minLen int, tags ...rscp.Tag) ([]rscp.Message, error) {
req := make([]rscp.Message, 0, len(tags)+1)
req = append(req, *rscp.NewMessage(rscp.WB_INDEX, wb.id))
for _, t := range tags {
req = append(req, *rscp.NewMessage(t, nil))
}
res, err := wb.conn.Send(*rscp.NewMessage(rscp.WB_REQ_DATA, req))
if err != nil {
return nil, err
}
return rscpContainer(*res, minLen)
}
```
Then e.g. `GetPhases` becomes:
```go
func (wb *E3dc) GetPhases() (int, error) {
wbData, err := wb.wbData(2, rscp.WB_REQ_NUMBER_PHASES)
if err != nil {
return 0, err
}
phases, err := rscpUint8(wbData[1])
if err != nil {
return 0, err
}
return int(phases), nil
}
```
Similarly for `getExternDataAlg`, `powers`, `GetMinMaxCurrent`, `GetMaxCurrent`, `Phases1p3p`, parts of `Diagnose`, etc.
### 2. Add a dedicated `rscpBool` helper
You currently inline lambdas to extract bools:
```go
if sunMode, err := rscpValue(wbData[1], func(data any) (bool, error) {
if val, ok := data.(bool); ok {
return val, nil
}
return false, errors.New("invalid type")
}); err == nil && sunMode {
// ...
}
```
This can be replaced by a simple helper:
```go
func rscpBool(msg rscp.Message) (bool, error) {
return rscpValue(msg, func(data any) (bool, error) {
b, ok := data.(bool)
if !ok {
return false, errors.New("invalid response")
}
return b, nil
})
}
```
Then your call sites simplify to:
```go
sunMode, err := rscpBool(wbData[1])
if err == nil && sunMode {
// ...
}
```
(and similarly for `autoPhaseSwitch` in `Phases1p3p` and the booleans in `Diagnose`).
### 3. Factor out session tag lookup
`ChargedEnergy`, `ChargeDuration`, and `Identify` all do the same “loop over `sessionData` and match `msg.Tag`” pattern. Extract:
```go
func (wb *E3dc) sessionMessage(tag rscp.Tag) (rscp.Message, bool, error) {
sessionData, err := wb.getSessionData()
if err != nil {
return rscp.Message{}, false, err
}
for _, msg := range sessionData {
if msg.Tag == tag {
return msg, true, nil
}
}
return rscp.Message{}, false, nil
}
```
Then:
```go
func (wb *E3dc) ChargedEnergy() (float64, error) {
msg, ok, err := wb.sessionMessage(rscp.WB_SESSION_CHARGED_ENERGY)
if err != nil || !ok {
return 0, err
}
energy, err := rscpFloat64(msg)
if err != nil {
return 0, err
}
return energy / 1000.0, nil
}
```
```go
func (wb *E3dc) ChargeDuration() (time.Duration, error) {
msg, ok, err := wb.sessionMessage(rscp.WB_SESSION_ACTIVE_CHARGE_TIME)
if err != nil || !ok {
return 0, err
}
ms, err := rscpFloat64(msg) // or a dedicated rscpUint64 if you prefer
if err != nil {
return 0, err
}
return time.Duration(ms) * time.Millisecond, nil
}
```
```go
func (wb *E3dc) Identify() (string, error) {
msg, ok, err := wb.sessionMessage(rscp.WB_SESSION_AUTH_DATA)
if err != nil || !ok {
return "", err
}
return rscpString(msg)
}
```
These small abstractions keep all behavior the same but localize the protocol details and indexing, which should address most of the “structural and cognitive complexity” concerns.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
- Add rscpBool helper for boolean RSCP values - Add rscpUint64 helper for correct millisecond parsing - Add sessionMessage helper to reduce code duplication - Add warning when wallbox index not found in DB - Improve code documentation throughout
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey there - I've reviewed your changes - here's some feedback:
- In
NewE3dc, the globalrscp.Logis unconditionally set toDebugLevelfor the entire process; consider aligning the RSCP log level with evcc's configured logger level (or enabling debug only when the charger logger is in trace/debug) to avoid excessive logging in production. - The auto phase switching check in
Phases1p3pduplicates part of the configuration validation already done incheckConfiguration; consider centralizing this check or caching the flag so you don't need to re-query RSCP on every phase switch while still failing fast if the configuration is incompatible.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `NewE3dc`, the global `rscp.Log` is unconditionally set to `DebugLevel` for the entire process; consider aligning the RSCP log level with evcc's configured logger level (or enabling debug only when the charger logger is in trace/debug) to avoid excessive logging in production.
- The auto phase switching check in `Phases1p3p` duplicates part of the configuration validation already done in `checkConfiguration`; consider centralizing this check or caching the flag so you don't need to re-query RSCP on every phase switch while still failing fast if the configuration is incompatible.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Address code review feedback: - Add ensureSunModeDisabled() check before Enable(), MaxCurrent(), and Phases1p3p() since users can re-enable sun mode in the E3DC portal at any time without restarting evcc - Add comments explaining why RSCP log level and auto phase switch checks are implemented the way they are (not bugs, intentional design) - Extract disableSunMode() helper for reuse
|
@D3c1mu2 can I kindly ask you to contact [email protected]? |
Email shoud be sended. |

Summary
WB_REQ_SET_NUMBER_PHASESImplemented Interfaces
api.ChargerStatus()api.ChargerEnable()/Enabled()api.ChargerMaxCurrent()api.MeterCurrentPower()api.MeterEnergyTotalEnergy()api.PhaseCurrentsCurrents()api.PhaseGetterGetPhases()api.PhaseSwitcherPhases1p3p()api.CurrentGetterGetMaxCurrent()api.CurrentLimiterGetMinMaxCurrent()api.ChargeRaterChargedEnergy()api.ChargeTimerChargeDuration()api.IdentifierIdentify()Test plan
Open items / Help requested
Notes
WB_REQ_SET_NUMBER_PHASEStag and Bool datatype support