Skip to content

Add FoxESS EV charger with Modbus TCP support#31412

Draft
GurliGebis wants to merge 20 commits into
evcc-io:masterfrom
GurliGebis:foxess-ev-charger
Draft

Add FoxESS EV charger with Modbus TCP support#31412
GurliGebis wants to merge 20 commits into
evcc-io:masterfrom
GurliGebis:foxess-ev-charger

Conversation

@GurliGebis

@GurliGebis GurliGebis commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Currently, FoxESS EV chargers can only be connected using OCPP, which has certain limitations (and sometimes outright doesn't work, particularly for phase switching).

This adds support for connecting them using Modbus TCP instead, while preserving the existing OCPP implementation. The existing OCPP product has been renamed to "AC EV Charger (OCPP)" to disambiguate the two in the device picker.

Implemented using the discussion at #26218, the ringaction/foxess_charger Home Assistant integration and the official FoxESS Modbus TCP Protocol 1.6 specification, with assistance from Claude AI.

The Modbus protocol specification covers the A, L and C series. Tested on an A022KP1-E-B.

Phase switching is supported via Modbus (requires a hardware phase-cutting box, PBOX). FoxESS does not support phase switching over OCPP.

Note: The charger work mode must be set to "Modbus TCP" in the FoxESS app before connecting to evcc, otherwise you will get a "server device busy" error on startup.

My testing has been limited to adding the charger to evcc and checking that everything shows up correctly. I have not been able to test actual charging yet, as we are 2-3 weeks away from receiving our EV.

billede

This fixes #30377

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread charger/modbus-foxess.go Outdated
Comment thread charger/foxess-modbus.go
@andig

andig commented Jul 2, 2026

Copy link
Copy Markdown
Member

@GurliGebis thanks for the PR! Would you mind testing this before we merge? Happy to wait. I'd also ask you to contact info@evcc.io please.

@andig andig added the devices Specific device support label Jul 2, 2026
@GurliGebis

Copy link
Copy Markdown
Contributor Author

@tammycutzet I'll look into those in a few minutes.
@andig I would, but there is still 2-3 weeks before we get our EV, so testing is limited to what I can do without a car connected at the moment 🙂

@GurliGebis

Copy link
Copy Markdown
Contributor Author

@andig I have sent an email now

@GurliGebis GurliGebis force-pushed the foxess-ev-charger branch 2 times, most recently from ebcbd83 to 008fce4 Compare July 2, 2026 12:51
Comment thread charger/modbus-foxess.go Outdated
Comment thread charger/modbus-foxess.go Outdated
Comment thread charger/modbus-foxess.go Outdated
Comment thread templates/definition/charger/modbus-foxess.yaml Outdated
Comment thread templates/definition/charger/foxess-modbus.yaml
@tammycutzet

tammycutzet commented Jul 3, 2026

Copy link
Copy Markdown

Maybe this comes in handy later for you @GurliGebis. I made a small python script to read the data from the wallbox for troubleshooting my script back in the time:

import time
from pymodbus.client import ModbusTcpClient
from rich.console import Console
from rich.table import Table
from rich import box

# =========================
# CONFIG
# =========================
IP = "127.0.0.1"
PORT = 1502
SLAVE_ID = 1

console = Console()

# =========================
# LOOKUP TABLES
# =========================
evc_status = {
    0: "Idle",
    1: "Connected",
    2: "Start Requested",
    3: "Charging",
    4: "Paused",
    5: "Finished",
    6: "Fault",
    7: "Reserved",
    8: "Locked",
    9: "Phase Switching?",
}

cp_status = {
    0: "CP Fault",
    1: "12V - Not Connected",
    2: "9V - Vehicle Connected",
    3: "6V - Vehicle Ready"
}

cc_status = {
    0: "Plug Unconnected",
    1: "Plug Connected"
}

stop_reason = {
    0: "None",
    1: "Stopped on Command",
    2: "Timed Charging Completed",
    3: "S2 Timeout",
    4: "Charging Pause Timeout",
    5: "Emergency Stop",
    6: "Abnormal CP Voltage",
    7: "Connector Removed",
    8: "AC Contactor Fault",
    9: "Electronic Lock Fault",
    10: "Card Reader Fault",
    11: "Overcurrent",
    12: "Overvoltage",
    13: "Undervoltage",
    14: "Port Overtemperature",
    15: "Leakage Current",
    16: "N Line Reverse",
    17: "Frequency Fault",
    18: "Stop Button Pressed",
    19: "Breaker Fault",
    20: "Phase Loss",
    21: "PE Fault",
}

electric_lock_status = {
    0: "Unlocked",
    1: "Locked"
}

# =========================
# FAULT BITS (0x101A)
# =========================
fault_bits = {
    0: "Emergency stop",
    1: "Overvoltage",
    2: "Undervoltage",
    3: "Overcurrent",
    4: "Charging port temperature",
    5: "PE grounding",
    6: "Leakage current",
    7: "Frequency fault",
    8: "CP fault",
    9: "Connector",
    10: "AC contactor",
    11: "Electronic lock",
    12: "Breaker",
    13: "CC fault",
    14: "External meter communication",
    15: "Metering chip",
    16: "Environment temperature",
    17: "Access control",
}

# =========================
# REGISTER MAP
# =========================
registers = {
    0x1000: "Device Address",
    0x1001: "Software Version",
    0x1002: "Last Stop Reason",
    0x1003: "EVC Status",
    0x1004: "CP Status",
    0x1005: "CC Status",
    0x3005: "Time Validity",
    0x3006: "Default Current",
    0x300A: "Auto Phase Switch",
    0x300B: "Hysteresis Phase Switch",
    0x1007: "Ambient Temp",
    0x1008: "A Phase Volt",
    0x1009: "B Phase Volt",
    0x100A: "C Phase Volt",
    0x100B: "A Phase Current",
    0x100C: "B Phase Current",
    0x100D: "C Phase Current",
    0x100E: "Active Power",
    0x3002: "Power Setpoint",
    0x100F: "Electronic Lock Status",
    0x3001: "Max Charging Current",
    0x3003: "Allowable Charge Time",
    0x3004: "Allowable Charge Energy",
    0x101A: "System Fault Information",
}

# =========================
# HELPERS
# =========================
def read_reg(client, reg):
    try:
        res = client.read_holding_registers(address=reg, count=1, slave=SLAVE_ID)
        if res.isError():
            return None
        return res.registers[0]
    except:
        return None


def decode(reg, value):
    if value is None:
        return "ERROR"

    if reg == 0x1001:
        return f"{value >> 8}.{value & 0xFF}"

    if reg == 0x1002:
        return f"{value} ({stop_reason.get(value, 'Unknown')})"

    if reg == 0x1003:
        return f"{value} ({evc_status.get(value, 'Unknown')})"

    if reg == 0x1004:
        return f"{value} ({cp_status.get(value, 'Unknown')})"

    if reg == 0x1005:
        return f"{value} ({cc_status.get(value, 'Unknown')})"

    if reg == 0x1007:
        return f"{(value * 0.1) - 50:.1f} °C"

    if reg in [0x1008, 0x1009, 0x100A]:
        return f"{value * 0.1:.1f} V"

    if reg in [0x100B, 0x100C, 0x100D, 0x3001]:
        return f"{value * 0.1:.1f} A"

    if reg == 0x100E:
        return f"{value * 0.1:.1f} kW"

    if reg == 0x100F:
        return f"{value} ({electric_lock_status.get(value, 'Unknown')})"

    if reg == 0x3002:
        return f"{value * 0.1:.1f} kW"

    if reg == 0x3003:
        return f"{value * 0.1:.0f} min"

    if reg == 0x3004:
        return f"{value * 0.1:.1f} kWh"

    # =========================
    # FAULT REGISTER (NEW)
    # =========================
    if reg == 0x101A:
        active = []

        for bit, name in fault_bits.items():
            if value & (1 << bit):
                active.append(name)

        if not active:
            return "🟢  OK"

        return "🔴  " + ", ".join(active)

    return str(value)


# =========================
# MAIN LOOP
# =========================
def main():
    client = ModbusTcpClient(IP, port=PORT)
    client.connect()

    while True:
        table = Table(
            title="⚡ EV Charger SCADA Monitor",
            box=box.SIMPLE_HEAVY
        )

        table.add_column("Register", style="cyan")
        table.add_column("Name", style="white")
        table.add_column("Value", style="green")

        for reg, name in registers.items():
            value = read_reg(client, reg)
            decoded = decode(reg, value)

            table.add_row(f"0x{reg:04X}", name, decoded)

        console.clear()
        console.print(table)

        current_time = time.strftime("%Y-%m-%d %H:%M:%S")
        console.print(f"\n🕒 Last update: {current_time}")

        time.sleep(8)


if __name__ == "__main__":
    main()

@GurliGebis GurliGebis force-pushed the foxess-ev-charger branch from 9f3961a to 406901a Compare July 3, 2026 09:55
@scruysberghs

Copy link
Copy Markdown
Contributor

Feedback: FoxESS AC charger Modbus — no-PBOX (auto phase switching) has a current-mismatch / control-loop issue

Tested against a FoxESS A022KP1-E-B (firmware 1.08,1.05), Modbus-TCP on port 502, unit 1, in a no-PBOX install (the charger does automatic 1p/3p switching itself based on the power setpoint 0x3002). I replicated the register map from #31412 as an evcc type: custom charger to validate before the driver merges. Control + auto phase switching work, but I hit two issues that I believe also affect the driver in #31412.

1. Current mismatch corrupts evcc's control loop (no-PBOX mode)

In no-PBOX mode the driver controls via the power setpoint 0x3002 = round(3 * 230 * current / 100) and does not expose phases1p3p, so evcc treats the loadpoint as fixed 3-phase. When evcc requests e.g. 6 A, the driver writes a ~4.1 kW setpoint; the charger then auto-switches to 1-phase and draws ~17.7 A on L1.

Because the driver implements api.PhaseCurrents but not CurrentGetter/GetMaxCurrent, evcc takes the measured phase current branch in core/loadpoint.go (~L854):

charger logic error: current mismatch (got 17.7A measured, expected 6A) - make sure your interval is at least 30s

and then does lp.offeredCurrent = current — i.e. it adopts the measured 17.7 A as its offered current, which corrupts the control loop (it can no longer regulate down / switch back cleanly). It logs this warning every cycle.

Root cause: the driver reports per-phase currents, but controls via a 3-phase-equivalent power setpoint, so the measured per-phase current never matches evcc's commanded current whenever the charger's actual phase count differs from evcc's assumption.

Possible directions (for discussion):

  • Do not implement api.PhaseCurrents in no-PBOX mode (this is what fixed it for me — evcc then can't take that branch), or
  • Implement CurrentGetter returning the intended current so evcc uses the reported-max-current branch instead of measured phase currents, or
  • Somehow expose the actual phase count so evcc's per-phase math stays consistent.

Configuring the evcc loadpoint as single-phase does not fix it, because the driver's formula hard-codes 3 * 230 regardless of evcc's phase setting.

2. Heartbeat vs. clean stop

The heartbeat re-sends the setpoint every foxTimeValidity/2. On Enable(false) the driver writes 0x4001=2 but does not zero the setpoint or stop the heartbeat (wb.current is not reset), so 0x3002 keeps being re-sent after disable. On my charger this prevented a clean stop — writing 0x4001=2 alone stopped it (power → 0), but as soon as the setpoint was re-sent the charger resumed (it settled at the 1-phase floor, ~1.4 kW, and never stopped). Zeroing the setpoint (or pausing the heartbeat) on disable would avoid this. Worth verifying on a PBOX unit too.

3. Minor: validity window range

0x3005 (foxTimeValidity) is hard-coded to 60. On this firmware the register accepts 60–255 s (it rejects 0 and anything > ~255 with Modbus exception 3). 60 is safe/conservative — just noting the accepted range differs per firmware.


The custom charger config I used to reproduce (register map from #31412)

Modbus TCP, port 502, unit 1. Addresses shown in decimal with the hex from the PR in comments. This is deliberately without a currents: getter (workaround for issue 1) and without a heartbeat/watchdog on the setpoint (workaround for issue 2 — I set the validity window 0x3005 to its max out-of-band instead). It controls purely on the power setpoint 0x3002, so the charger does its own 1p/3p switching.

chargers:
  - name: foxess_modbus
    type: custom
    # EVC status 0x1003 -> A/B/C  (0=idle, 1/4/5=connected/pause/finish, 2/3/9=charging)
    status:
      source: js
      in:
        - name: s
          type: int
          config:
            source: modbus
            uri: <charger-ip>:502
            id: 1
            register: { address: 4099, type: holding, decode: uint16 }   # 0x1003
      script: |
        (s === 0) ? "A" : (s === 1 || s === 4 || s === 5) ? "B" : (s === 2 || s === 3 || s === 9) ? "C" : "A";
    enabled:
      source: js
      in:
        - name: s
          type: int
          config:
            source: modbus
            uri: <charger-ip>:502
            id: 1
            register: { address: 4099, type: holding, decode: uint16 }   # 0x1003
      script: |
        (s === 2 || s === 3 || s === 9);
    # start/stop 0x4001  (1=start, 2=stop)
    enable:
      source: js
      script: |
        enable ? 1 : 2;
      out:
        - name: cmd
          type: int
          config:
            source: modbus
            uri: <charger-ip>:502
            id: 1
            register: { address: 16385, type: writesingle, encoding: uint16 }   # 0x4001
    # no-PBOX: write POWER setpoint 0x3002 (0.1 kW) = round(3*230*A/100); charger picks phases.
    # NB: no watchdog/heartbeat here on purpose -> keeps Enable(false) able to stop cleanly.
    maxcurrent:
      source: js
      script: |
        Math.round(3 * 230 * maxcurrent / 100) | 0;
      out:
        - name: pwr
          type: int
          config:
            source: modbus
            uri: <charger-ip>:502
            id: 1
            register: { address: 12290, type: writemultiple, encoding: uint16 }   # 0x3002
    power:
      source: modbus
      uri: <charger-ip>:502
      id: 1
      register: { address: 4110, type: holding, decode: uint16 }   # 0x100E active power, 0.1 kW
      scale: 100
    voltages:
      - { source: modbus, uri: <charger-ip>:502, id: 1, register: { address: 4104, type: holding, decode: uint16 }, scale: 0.1 }  # 0x1008 L1
      - { source: modbus, uri: <charger-ip>:502, id: 1, register: { address: 4105, type: holding, decode: uint16 }, scale: 0.1 }  # 0x1009 L2
      - { source: modbus, uri: <charger-ip>:502, id: 1, register: { address: 4106, type: holding, decode: uint16 }, scale: 0.1 }  # 0x100A L3
    energy:
      source: modbus
      uri: <charger-ip>:502
      id: 1
      register: { address: 4120, type: holding, decode: uint32 }   # 0x1018 total energy, 0.1 kWh
      scale: 0.1
    # Intentionally NO `currents:` getter — with it, evcc measures ~17.7 A on L1 while
    # offering 6 A (1-phase delivery of a 3-phase-equivalent setpoint) and hijacks
    # offeredCurrent (core/loadpoint.go ~L854). See issue 1 above.

loadpoints:
  - title: FoxESS Modbus
    charger: foxess_modbus
    mode: "off"
    phases: 3
    mincurrent: 6      # lower (e.g. 2) lets evcc drive setpoints low enough for the charger to pick 1-phase
    maxcurrent: 16

With this, current control and the charger's internal 1p/3p switching both work, Enable(false) stops cleanly, and there's no current mismatch spam. The trade-off vs. the driver is losing evcc-side per-phase current + active-phase detection, and no setpoint heartbeat (fine in PV mode where evcc rewrites often; a steady fixed-power session can expire after the validity window).

@GurliGebis GurliGebis force-pushed the foxess-ev-charger branch 2 times, most recently from 47a79e1 to 1db0b14 Compare July 7, 2026 19:28
@GurliGebis

Copy link
Copy Markdown
Contributor Author

@scruysberghs can you test the changes I just pushed?
They should fix the two issues you mention (I'm still waiting for our car, so I cannot test anything yet myself)

Comment thread charger/foxess-modbus.go
Comment thread charger/foxess-modbus.go Outdated
Comment thread charger/foxess-modbus.go Outdated
Comment thread charger/foxess-modbus.go Outdated
return err
}

// ensureReg writes a value to a read/write register and verifies the result.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some firmware returns Modbus exception 3 (illegal data value) when you write a value that the register already holds.
So, ensureReg works around this by treating a write rejection as success if the register already contains the value we tried to write.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evcc will retry a failed write anyway on next cycle?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was done to prevent the problem tammycutzet was having when trying to write the already existing value to the charger.
I have changed it, so it instead reads the value, compares it, and only writes it to the charger if they differ.

That way, we should still guard against the issue where an existing value is being written (which can cause issues it seems).

Comment thread charger/foxess-modbus.go Outdated
Comment thread charger/foxess-modbus.go Outdated
Comment thread charger/foxess-modbus.go Outdated
Comment thread charger/foxess-modbus.go Outdated
Comment thread charger/foxess-modbus.go Outdated
Comment thread charger/foxess-modbus.go Outdated
Comment thread charger/foxess-modbus.go Outdated
Comment thread charger/foxess-modbus.go Outdated
@GurliGebis

Copy link
Copy Markdown
Contributor Author

With the latest code, I got ERROR 2026/07/11 23:03:07 heartbeat: modbus: exception '3' (illegal data value), function '16' - so I have changed it to use the ensure logic, to avoid errors when trying to write existing values.
Will test shortly

@GurliGebis

Copy link
Copy Markdown
Contributor Author

Issue is still there - will debug tomorrow and see if I can figure out what write causes it.

@andig

andig commented Jul 11, 2026

Copy link
Copy Markdown
Member

Your writeReg calls wb.conn.WriteMultipleRegisters for a single register- WB doesn't like that

@andig andig marked this pull request as draft July 11, 2026 21:54
@GurliGebis

Copy link
Copy Markdown
Contributor Author

@premultiply I'm changing it (the pbox checkbox) back to a manual setting.
I have tried several methods, but all of them acts really strange and is not consistent at all.
All the registers I can read does not give consistent values if no pbox is attached.

So even though it would be great to have it autodetect, it seems like something that I cannot get it to do. (At least not without having two identical chagers where one of them has a pbox, and the other ones doesn't - which is not happening)

GurliGebis and others added 20 commits July 12, 2026 22:24
Implements the FoxESS EV Charger (A/L/C-Series) via Modbus TCP,
based on the official Modbus TCP Protocol 1.6 specification.

- Status: maps EVC states 0-5 to A/B/C, fault/locked to error
- Enable/Disable: start (0x4001=1) and stop (0x4001=2) via FC 0x06
- MaxCurrentMillis: writes 0x3001 at 0.1A resolution via FC 0x10
- Meter: active power from 0x100E (0.1kW resolution)
- ChargeRater/MeterEnergy: session and total energy from 0x1016/0x1018
- PhaseCurrents/PhaseVoltages: per-phase readings from 0x100B-0x100D/0x1008-0x100A
- Identifier: RFID card UID from 0x101C
- PhaseSwitcher/PhaseGetter: conditional on pbox config option,
  uses direct phase switching register 0x4002 (requires hardware PBOX)

On startup the driver forces work mode 0 (Controlled) and sets the
command validity window to 60s, the maximum allowed by the spec.

refs: evcc-io#26218
Adds the YAML template for the FoxESS EV Charger (A/L/C-Series).
Supports Modbus TCP with default slave ID 1 and port 502.

The optional 'pbox' parameter enables 1p/3p phase switching via
the hardware phase-cutting box (PBOX). Without it the charger
handles phase selection internally via its own auto-switch logic.
Disambiguates from the new Modbus integration in the device picker.
EVC status 9 means auto phase switch in progress. During this
transition the charger is still actively charging, so map it to
StatusC and treat it as enabled.
…e switching

When no PBOX is present, the charger manages phase selection itself
based on the power setpoint (0x3002) rather than a current setpoint
(0x3001). Convert evcc's current value to power using P = sqrt(3) *
400V * I and write to the power register so the charger can decide
the correct phase count.
The charger reverts to its fallback current if no Modbus command is
received within the time validity window (60s). Add a goroutine that
re-sends the last setpoint every 30s to keep the charger from going
full power when evcc stops sending commands.
… registers

Some charger firmware versions reject writes to work mode (0x3000) or
time validity (0x3005) with Modbus exception 3 (illegal data value) when
the register already holds the desired value.

Add ensureReg helper that falls back to reading the register after a
failed write. If the read-back matches the intended value the init
proceeds normally, avoiding a fatal startup error.
…urrent mismatch

In no-PBOX mode the charger controls via a power setpoint and auto-selects
phases internally. evcc's sync loop previously fell through to the measured
phase current branch (core/loadpoint.go), which logged a current mismatch
warning every cycle and corrupted the control loop by adopting the measured
per-phase current as the new offered current.

Adding GetMaxCurrent() moves evcc to the primary CurrentGetter branch, which
compares against the intended setpoint and stays in sync regardless of which
phase count the charger chooses. PhaseCurrents remains unconditional so
per-phase readings are still visible in the UI.
… disable

The charger considers evcc offline if no Modbus message arrives within the
time validity window (60s) and reverts to its fallback current. The heartbeat
must keep firing even when stopped to prevent this.

Previously the heartbeat was silenced on disable by zeroing wb.current, which
risked the charger going to fallback current during extended stopped periods.

Instead, track enabled state separately. When disabled, the heartbeat re-sends
the stop command (0x4001=2) every 30s. The spec states that setpoint registers
(0x3001/0x3002) only take effect in the charging state, but at least one
firmware version resumes charging on a re-sent setpoint after a stop, so
actively repeating the stop command is safer than repeating the setpoint.
- drop unnecessary struct wrapper in NewFoxESSEVCFromConfig
- combine separate var declarations into var reg, val uint16
- use var val uint16 instead of val := uint16(0) in phases1p3p
- remove trivial unit-conversion comments
- Enabled() tracks the local enabled flag via verifyEnabled instead of
  reading the activity status register; enabled means the charger is
  permitted to charge, not that it is currently charging
- assign detectPbox result directly to wb.pbox, drop intermediate var
- remove redundant debug log on PBOX detection
- inline alarm variable into if condition in detectPbox
…wer setpoint

In no-PBOX mode, the driver sends a power setpoint so the charger can
auto-select phase count. The previous formula hardcoded 3 phases; use
lp.GetPhases() via loadpoint.Controller instead so the setpoint matches
the loadpoint's actual phase configuration.

- implement loadpoint.Controller / LoadpointControl
- drop commandedCurrent field and GetMaxCurrent (not the charger's actual setpoint)
- compute power as voltage * current * phases / 100 (0.1kW units)
…dware

Read the actual setpoint register rather than a cached value:
- PBOX mode: read foxRegMaxCurrent (0x3001), divide by 10 (0.1A units)
- no-PBOX mode: read foxRegMaxPower (0x3002), invert the power formula
  using the loadpoint phase count to recover amps from 0.1kW units
…stead of write-then-verify

Some registers reject redundant writes with Modbus exception 3 (illegal
data value). Switch from write-then-read-back-on-error to read-first,
skip write if value already matches.
…point writes

Some firmware rejects writes with exception 3 when the register already
holds the value being written. Use ensureReg in the heartbeat so the
write is skipped when the setpoint is unchanged.
…ble->disable transition

Register 0x4001 is write-only so there is no read-back to detect
whether the stop command was already sent. Re-sending stop every 30s
causes the charger to reject it with exception 3 when already stopped.

Track the last enabled state actually sent to the charger (lastEnabled)
and only send the stop command when transitioning from enabled to
disabled. The setpoint heartbeat path is unchanged: it still uses
ensureReg which skips the write when the value is already current.
…to maintain validity window

The charger times out and falls back to its default current if no Modbus
command is received within the validity window (60s), regardless of
whether charging is active. Always re-send the last setpoint in the
heartbeat rather than only when enabled.

Setpoint registers (0x3001/0x3002) only take effect in the charging
state per the spec, so writing them while stopped is safe and keeps
the validity window alive without risking unintended charging.
…rom charger on startup

On startup wb.current is 0 and wb.lastEnabled is false, so the heartbeat
has nothing to send until evcc issues its first command. If a car is
already charging when evcc connects, the validity window can expire and
the charger falls back to its default current.

Read the active setpoint register and status register at the end of New()
to seed wb.current and wb.lastEnabled from the actual charger state, so
the heartbeat can maintain the existing setpoint from the first tick.
@GurliGebis

Copy link
Copy Markdown
Contributor Author

Now it is back to having pbox as a checkbox.
Also, the keepalive is working now.

The cleanup of the NewFoxESSEVCFromConfig has been changed, since it needs to be nested when pbox is a separate setting.

So next step is to have it tested against a charger with a car connected (which I cannot do yet).

If anyone is up for testing it with a FoxESS charger, the docker image of the changes here can be found here: ghcr.io/gurligebis/evcc:0.311.1-r9

@andig

This comment has been minimized.

@github-actions

Copy link
Copy Markdown

✅ Build finished.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

devices Specific device support

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Modbus-TCP to FoxESS AC EV Charger - more features than OCPP

5 participants