Zendure EV Guard
Protect your Zendure batteries while charging your electric car
Compatible with Zendure SolarFlow 2400 AC • Shelly Pro 3EM • Smappee • Windows, Linux, macOS
Do you have Zendure batteries and an electric car?
You know the drill: when your car is plugged into the wallbox, it sucks up everything. Your Zendure batteries drain at full speed, the cycles pile up, and the lifespan of your precious cells melts away like snow in the sun.
If your car is a company vehicle and your employer pays for the electricity, it's a shame to drain your batteries to recharge it when you'll have nothing left in the evening for your home's self-consumption.
Another scenario: even if it's not a company car, maybe you simply want to prevent your batteries from draining while charging, so you can make full use of the electricity produced by your solar panels in full sunlight, without drawing from your Zendure batteries.
Zendure EV Guard solves this problem automatically. As soon as your car's charging is detected, the program pauses your batteries. When charging is complete, it reactivates them. Simple, automatic, risk-free.
How does it work?
1. Detection
Your Shelly Pro 3EM (or Smappee) measures consumption. When the power exceeds the threshold (e.g: 5000W), the program knows that your car is charging.
2. Protection
The Zendure batteries switch to OFF mode instantly. They no longer discharge, your cycles are preserved.
3. Resumption
Charging complete? The program automatically reactivates your batteries with your original settings. No manual intervention.
🔒 100% safe for your batteries
Zendure EV Guard uses exclusively the local HTTP API of your Zendure hub. It only touches two settings, the very same ones you see in the Zendure app: minSoc (the discharge floor) and outputLimit (the output power). To pause, it raises minSoc to 100% and lowers the output to 0 W. To resume, it restores your usual values.
Why both? Because one alone isn't always enough. If your hub is running in HEMS mode, it sometimes reopens its output on its own during the pause. The program notices it within a few seconds and closes it again. Test carried out at my home on August 22, 2026, battery at 30%, car at 18 kW: two openings, closed again after 10 then 5 seconds, 2 Wh in total.
No modified firmware, no cloud access, no risk. It's exactly the same as if you changed the setting manually in the Zendure app, but automatically and at the right time.
New in 1.3.12
- The program speaks French. Everything you read (emails, Pushover, Telegram, load reports) exists in French and English. One configuration key,
Messages:Language, and that's it. Technical logs remain in English, deliberately. - A report at the end of every charge. How many kWh went through the meter, how many for the car, the duration, the peak, the average. And the indicative cost, if you give the price of your kWh in
Energy:PricePerKwhCents. - The pause leak is measured and closed. A hub in HEMS mode can reopen its output during the pause. The program sees it, records it, closes it, and alerts you if it lasts.
- The archive contains everything needed to install. A script for Linux, one for macOS, one for Windows, plus the user guide in French and English. No need to copy a service file by hand anymore.
- A precautionary pause if the meter goes silent. Blind, the program can no longer see your car plug in: it puts the batteries somewhere safe and releases them at the first valid measurement.
- Your batteries are put back into service before shutdown, including when the system is what stops the program.
Prerequisites
| Equipment | Details |
|---|---|
| Zendure SolarFlow 2400 AC | Hub with local HTTP API enabled (port 80) |
| Shelly Pro 3EM or Smappee1 required | Current clamp (Shelly, real time) or Smappee energy monitor (cloud API, 5-minute blocks) to measure consumption |
| Wallbox / Charging station | Any charging station: detection is based on consumption, not on the charging station itself |
| PC, Server or NAS | Windows, Linux or macOS, the program runs in the background 24 hours a day |
Installation
1. Unzip the archive
Extract the contents of the ZIP file into the directory of your choice, for example:
- Windows:
C:\ZendureEvGuard\ - Linux:
/home/ZendureEvGuard/ - macOS:
/Applications/ZendureEvGuard/
2. Execution permissions (Linux / macOS only)
On Linux and macOS, you must make the file executable before running it:
chmod +x ZendureEvGuard
./ZendureEvGuard3. Configure appsettings.json
Open the appsettings.json file located next to the executable and fill in your settings (see the section below).
4. Launch the program
# Windows
ZendureEvGuard.exe
# Linux / macOS
./ZendureEvGuardThe program displays the connection information at startup. Use P to pause manually, R to resume, and Ctrl+C twice to stop cleanly.
5. Install it as a service (recommended)
The program only protects your batteries if it is running. Since 1.3.12, the archive contains one script per system: it installs the service, enables it at startup and launches it, from the folder where you unzipped it. Nothing is copied elsewhere.
# Linux
sudo sh install-linux.sh
# macOS
sudo sh install-macos.sh
# Windows (PowerShell as administrator)
powershell -ExecutionPolicy Bypass -File .\install-windows.ps1On Windows, the service runs under the SYSTEM account, which does not have the same data space as your session: make sure to enter Subscription.SecretKey in appsettings.json before installing.
The README.txt file included in the archive details each command, the uninstallation and the update. And USER_GUIDE_FR.md contains all the settings, offline.
Do you prefer writing the service yourself? On Linux, here is the unit that the script installs. Two lines are not decorative: TimeoutStopSec=120 gives the program time to put your batteries back into service before systemd kills it, and network-online.target prevents it from starting before the network is really there.
[Unit]
Description=ZendureEvGuard - EV Charging Battery Protection Daemon
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/ZendureEvGuard
ExecStart="/home/ZendureEvGuard/ZendureEvGuard"
Restart=on-failure
RestartSec=10
TimeoutStopSec=120
SyslogIdentifier=zendure-evguard
Environment=DOTNET_ENVIRONMENT=Production
[Install]
WantedBy=multi-user.targetThen the usual commands:
sudo systemctl daemon-reload
sudo systemctl enable --now zendure-evguard
# Voir le statut
sudo systemctl status zendure-evguard
# Voir les logs en temps reel
sudo journalctl -u zendure-evguard -f
# Redemarrer apres une mise a jour
sudo systemctl restart zendure-evguardDo not add a dedicated User= without thinking it through: the program writes its logs and its pause state next to the executable, and an account without permissions on that folder makes it fail on the first restart.
Configuration: appsettings.json
Here are all the settings you can configure. The Shelly and ZendureDevice sections are required.
Messages new in 1.3.12
The language of everything you read: emails, Pushover, Telegram, end-of-charge reports. Technical logs remain in English, so that an error message can still be searched for.
| Parameter | Description | Default |
|---|---|---|
Language | Message language: "en" or "fr". Any other value falls back to English, without preventing startup | "en" |
Mind the placement: this key goes under the Messages section, never at the root of the file. On Linux, a Language key placed at the root is overwritten by the system's LANGUAGE environment variable, and your messages switch back to English without warning.
"Messages": {
"Language": "fr"
}Shelly required
Configuration of your Shelly Pro 3EM. This is the sensor that measures your home's electricity consumption.
| Parameter | Description | Example |
|---|---|---|
Enabled | Enable the Shelly as a measurement source | true |
Host | IP address of your Shelly Pro 3EM on your local network | "192.168.1.50" |
UseDigestAuth | Enable if you have protected your Shelly with a password | false |
Username | Username (if auth is enabled) | "admin" |
Password | Password (if auth is enabled) | "monmotdepasse" |
Smappee charging station optional
The Smappee charging station can be used in two ways:
- As an EV detection source (alternative to Shelly): if Shelly is disabled, Smappee becomes the power measurement source for detecting EV charging. Detection is done via the Smappee cloud API (5-minute blocks, less responsive than Shelly in real time).
- In parallel with Shelly: if Shelly is active as the detection source, Smappee can still be enabled to continuously display consumption information (solar, home, grid) and information about your Smappee EV Wall (charging power, status, etc.) in the logs.
| Parameter | Description | Example |
|---|---|---|
Enabled | Enable Smappee (detection source if Shelly is disabled, or display of consumption/EV charger information in parallel) | false |
ClientId | Client ID of your Smappee application (API v3) | "votre-client-id" |
ClientSecret | Client Secret of your Smappee application | "votre-client-secret" |
Username | Username of your Smappee account | "user@email.com" |
Password | Password of your Smappee account | "motdepasse" |
ServiceLocationId | ID of your Smappee location (visible in the Smappee dashboard, this is not the serial number) | "190802" |
PollingIntervalSeconds | Frequency of queries to the Smappee API | 30 |
Battery Zendure required
Configuration of your Zendure SolarFlow 2400 AC hub or others. The IP address can be found in the Zendure app.
| Parameter | Description | Example |
|---|---|---|
Host | IP address of your Zendure hub on the local network | "192.168.1.127" |
Port | HTTP port of the local API (80 by default) | 80 |
DefaultOutputLimit | Maximum output power in watts to restore after EV charging | 2400 |
DefaultMinSoc | Discharge floor to restore after charging : the level below which your batteries will no longer discharge (the value is ×10 : 100 = 10%, 200 = 20%). This is not the maximum charge level. | 100 |
TimeoutSeconds | Waiting time for requests to the hub | 10 |
Understanding DefaultMinSoc
This parameter defines the discharge floor of your batteries: the level below which the hub will no longer let them discharge. The value is multiplied by 10 in the Zendure API:
100= the batteries can go down to 10%200= the batteries can go down to 20%500= the batteries stop at 50%, and half of your capacity is no longer any use
Do not confuse it with the charge ceiling. The level at which your batteries stop charging is socSet, another setting, the one you see in the Zendure app. Setting 950 here therefore does not mean "charge up to 95%" but "never go below 95%": your batteries become unusable. Since 1.3.0, the program refuses to start with a value outside 1 to 999, and warns you above 500.
It's up to you to choose! The lower the value, the more you use your batteries ; the higher it is, the more you spare the cells, at the cost of available capacity. Most users choose between 100 (10%) and 200 (20%).
EvDetection
Settings for detecting your electric vehicle's charging. The algorithm uses a hysteresis: several consecutive readings above the threshold are needed to confirm charging, and several readings below it are needed to confirm that it has stopped.
| Parameter | Description | Default |
|---|---|---|
PowerThresholdWatts | Power threshold (in watts) above which the program considers that a car is charging. Adapt it to your wallbox. | 5000 |
ConfirmationReadings | Number of consecutive readings above the threshold to confirm the start of charging | 2 |
ReleaseReadings | Number of consecutive readings below the threshold to confirm the end of charging (24 = ~2 minutes at 5s/reading) | 24 |
PollingIntervalSeconds | Measurement frequency in seconds | 5 |
PauseOnSourceUnreachableMinutes | New in 1.3.0. Duration for which the meter can remain unreachable before the batteries are paused as a precaution: blind, the program can no longer see your car plug in. The pause is lifted as soon as the first valid measurement is received. Set it to 0 to disable this safeguard. | 3 |
NotifyPrecautionaryPauseAfterMinutes | A precautionary pause applies right away, but you are only notified after this amount of time: a meter that blinks for three seconds is not worth an email | 30 |
SourceUnreachableReleaseMinutes | Duration after which a precautionary pause is released while the meter is still silent | 10 |
ReleaseHysteresisWatts | Margin below the threshold to cross again to confirm the end of charging. Avoids the back-and-forth when consumption fluctuates right around the threshold | 500 |
PostResumeCooldownSeconds | Delay after a resumption during which a new detection is ignored | 60 |
Energy new in 1.3.12
At the end of each charge, the program tells you how many kWh have been used. Give it the price of your kWh and it adds the amount. It's indicative, not an invoice: nobody measures the charging station itself, and the price is fixed even though yours may not be.
The use case I'm thinking of: several cars plug in at your place, and each one needs to know what it has used.
| Parameter | Description | Default |
|---|---|---|
PricePerKwhCents | Price per kWh in whole cents (28 = 0.28 EUR). Enter 0 to display nothing. A decimal number is rejected and read as 0, without preventing startup | 28 |
The report gives figures for two energies. The one measured at the meter, which is a fact. And the vehicle's estimated share, which subtracts the house's usual consumption: an estimate, and a maximum when your panels were exporting at the time of plugging in.
Charge terminee : 1h03 | 11,30 kWh au compteur (3,16 EUR) |
~11,30 kWh pour le vehicule (3,16 EUR) | pointe 14627 W | moyenne 11169 WNotification optional
Receive emails when your batteries pause, resume, or in case of an error. Configure your SMTP server.
| Parameter | Description | Default |
|---|---|---|
Enabled | Enable email notifications | false |
SmtpHost | SMTP server (e.g.: smtp.gmail.com) | "" |
SmtpPort | SMTP port | 587 |
UseSsl | Use TLS/SSL | true |
Username / Password | SMTP credentials | "" |
FromAddress | Sender email address | "" |
ToAddress | Recipient(s), separated by ; for multiple recipients | "" |
RateLimitMinutes | Minimum interval between two alerts (anti-spam) | 15 |
Pushover optional
Push notifications on your phone via Pushover. Create an application on pushover.net to obtain your API Token.
| Parameter | Description |
|---|---|
Enabled | Enable Pushover (true / false) |
ApiToken | API token for your Pushover application |
UserKey | Your Pushover user key |
RateLimitMinutes | Minimum interval between alerts (default: 15) |
Telegram optional
Notifications via a Telegram bot. Create a bot with @BotFather and retrieve your Chat ID.
| Parameter | Description |
|---|---|
Enabled | Enable Telegram (true / false) |
Bots | List of bots. Each one has its Name, its BotToken and its ChatIds list. That's what makes it possible to notify different people in different groups |
RateLimitMinutes | Minimum interval between alerts (default: 15) |
Since 1.2.0, you can notify several groups at once: two people via a first bot, three others via a second. Each send is isolated, an unreachable recipient no longer prevents the others from receiving the message.
"Telegram": {
"Enabled": true,
"RateLimitMinutes": 15,
"Bots": [
{
"Name": "Famille",
"BotToken": "123456:AAExxxxxxxxxxxxxxxxxxxx",
"ChatIds": [ "11111111", "22222222" ]
},
{
"Name": "Voisins",
"BotToken": "789012:BBEyyyyyyyyyyyyyyyyyyyy",
"ChatIds": [ "33333333", "44444444", "55555555" ]
}
]
}The old form, with a single BotToken and ChatId values separated by semicolons, continues to work. If you don't change anything in your configuration, your notifications continue exactly as before.
Monitoring optional
| Parameter | Description | Default |
|---|---|---|
HealthCheckIntervalSeconds | Frequency of device connectivity checks | 30 |
LogStatusIntervalSeconds | Frequency for logging the full status in the log files | 300 |
PostResumeGuardSeconds | Duration during which the program checks that the hub has kept your settings after a restart | 300 |
DeviceUnreachableAlertMinutes | Duration of a device being unreachable beyond which you are alerted | 60 |
Update optional
Since 1.2.0, the program no longer updates itself. It checks once an hour whether a newer version exists, and simply lets you know: a reminder in the logs every hour as long as you have not installed the new version, and a single message on the channels you have configured, with the direct download link.
Replacing the binary of a service that protects your batteries while it is running is the riskiest action in the product. You might as well be the one deciding when. The old AutoUpdateEnabled and CheckIntervalHours keys are no longer read: you can leave them in your file, they are ignored.
| Parameter | Description | Default |
|---|---|---|
CheckEnabled | Check every hour whether a more recent version exists | true |
NotifyOnNewVersion | Send a message to your notification channels when a version is first detected. Only once per version, never a reminder | true |
License after the trial
After the 30-day trial period, add your secret key to activate the PRO license. Your key can be found in your account at top right > "My Account". The license renews automatically in the background.
| Parameter | Description | Example |
|---|---|---|
SecretKey | Your unique secret key (GUID) from your digital3d.com account. Leave empty to use trial mode. | d79d24cc-eb02-48d7-94f8-xxxxxxxxxxxx |
Minimal configuration example
Here is a ready-to-use example. Simply replace the IP addresses with those of your devices:
{
"Messages": {
"Language": "fr"
},
"Energy": {
"PricePerKwhCents": 28
},
"Shelly": {
"Enabled": true,
"Host": "192.168.1.50"
},
"ZendureDevice": {
"Host": "192.168.1.127",
"Port": 80,
"DefaultOutputLimit": 2400,
"DefaultMinSoc": 100
},
"EvDetection": {
"PowerThresholdWatts": 5000
},
"Subscription": {
"SecretKey": "d79d24cc-eb02-48d7-94f8-xxxxxxxxxxxx"
}
}With this configuration, the program monitors your Shelly, detects any consumption above 5,000W (for example), and restores your batteries with a minimum of 10% (100 ÷ 10) and a maximum output of 2400W. The Subscription section is empty by default: the program works in trial mode for 30 days. To activate the PRO licence, add your secret key from your account at the top right.
Free trial & subscription
30 days of free trial, no commitment!
Download Zendure EV Guard and test it for 30 full days, without paying anything and with no feature limits. You will have access to 100% of the functions to make sure that the program is a perfect match for your setup.
At the end of the 30 days, a Digital3D subscription is required to continue using the program. A single subscription gives you access to all Digital3D applications (CompactForIA, AutoMouseMove, Zendure EV Guard, etc.).
How to activate your licence
- Subscribe to a plan on "One for All"
- Log in to your account on digital3d.com and copy your secret key (a unique identifier in GUID format, for example:
f89f26cc-eb03-48d7-94f8-xxxxxxxxxxxx) - Open the
appsettings.jsonfile and add your key in theSubscriptionsection:"Subscription": { "SecretKey": "votre-clef-secrete-guid" } - Restart the program: the PRO licence activates automatically and renews without any intervention on your part.
Ready to protect your batteries?
Download for free, set it up in 5 minutes, and sleep easy.
Subscription “ One for All ”: one subscription for all Digital3D applications.
Join the conversation
You need an account to comment on this article. Creating one is free and takes under a minute.
No comments yet.