2026-08-12 09:46:18 -07:00
# anker-shelly-bridge
<p align="center">
2026-08-12 10:06:30 -07:00
<img src="docs/screenshot.jpg" alt="Live monitor dashboard showing battery level, solar input, grid input and load for an Anker SOLIX F3000, with Shelly switch states and a rolling history chart" width="900">
2026-08-12 09:46:18 -07:00
</p>
Automate a Shelly smart plug from live Anker SOLIX telemetry.
Your SOLIX device reports battery level, solar input, and load over Anker's
cloud MQTT service. This reads that telemetry and switches a Shelly plug on
your local network according to rules you write in a plain text file.
The common use is controlling grid charging: plug the SOLIX into a Shelly, and
let the battery level decide when to draw from the wall.
**The SOLIX device is read-only.** This tool never sends it a command. The
Shelly plug is the only thing it ever switches.
> Unofficial and unaffiliated with Anker or Allterco Robotics. SOLIX and Shelly
> are trademarks of their respective owners. Built on the community
> [anker-solix-api](https://github.com/thomluther/anker-solix-api) library.
---
## Quick start
You need a SOLIX device that connects to WiFi, a Shelly plug on the same
network, and your Anker account login.
```bash
git clone https://github.com/YOURNAME/anker-shelly-bridge.git
cd anker-shelly-bridge
./start.sh
```
On Windows, double-click `start.bat` .
2026-08-12 10:06:30 -07:00
That is the whole install. `start.sh` builds a Python environment for this
project at `~/solix-automation/venv` , installs everything into it, and hands
over to a guided setup with eight steps:
2026-08-12 09:46:18 -07:00
1. **Dependencies** : installs whatever is missing
2. **Anker account** : your login, stored locally with owner-only permissions
3. **Find your SOLIX device** : connects and captures every field it reports
4. **Find your Shelly plug** : scans the local network
5. **Check the plug** : finds schedules and timers already on it that would
conflict, and offers to remove them
6. **Notifications** : optional push to your phone, with a QR code to scan
7. **Rules** : pick a strategy and answer a few questions in plain language
8. **Test, then start** : a live dry run against your hardware that switches
nothing, then offers to install a background service
Nothing touches your hardware until step 8 asks. Ctrl-C is safe at any point.
Needs Python 3.12+ and git. If they are missing, `start.sh` tells you how to
install them for your platform.
2026-08-12 10:06:30 -07:00
Everything the project needs lives in its own environment, so installing or
removing packages elsewhere on the machine cannot break your automation. The
background service is pinned to that same interpreter.
2026-08-12 09:46:18 -07:00
---
## What it does
### Reads your SOLIX device
Connects to Anker's cloud MQTT broker, captures a live telemetry snapshot, and
writes a device profile you can read. A portable power station typically
reports around 44 fields: battery percentage, per-string solar input, AC and DC
output, temperature, port states.
It also computes derived fields, so rules can use `pv_total` instead of adding
`pv_1_power` and `pv_2_power` by hand:
| Field | Meaning |
|---|---|
| `pv_total` | all solar strings added together, in watts |
| `pv_surplus` | solar minus load; positive means the surplus is charging the battery |
| `pv_covers_load` | true when solar alone is carrying everything |
| `usb_total` | combined USB output |
| `soc_headroom` | percentage points above the configured floor |
```bash
solixauto fields <device> # every usable field name
solixauto status <device> --watch # live values
```
### Controls your Shelly plug
Local HTTP only, no cloud. Gen1 and Gen2+ including Gen4. Discovery uses mDNS
plus a subnet scan across every private network it can see, so machines with
several interfaces work.
```bash
solixauto switch <plug> on| off| status
```
Every command reads the state back from the device afterwards rather than
trusting the HTTP response.
### Runs your rules
A power profile is a YAML file linking one SOLIX device to one Shelly channel:
```yaml
rules :
- name : top up from grid when low
when : battery_soc <= 35
for : 2m
then : target.on
- name : stop charging when full enough
when : battery_soc >= 85
for : 2m
then : target.off
```
`when` is an expression over any field the device reports. `for` is how long
the condition must hold before anything happens, so a passing cloud or a
momentary load spike cannot toggle the relay. `priority` breaks ties when rules
disagree.
Expressions run in a sandbox allowing only comparisons, `and` /`or` /`not` , and
arithmetic. Function calls and attribute access are rejected at parse time, so
a power profile cannot execute code.
Full rule reference with worked examples: [docs/RULES.md ](docs/RULES.md ).
The same document is written into your data directory during setup.
---
## Live monitor
```bash
solixauto monitor
```
Opens a dashboard at `http://127.0.0.1:8765` showing every device you have
saved: battery level, solar in, grid in, load out, and each Shelly's switch
state and draw. It refreshes every five seconds and keeps a rolling chart.
Each device gets a **power bus** : solar and grid drawn from the left, load
from the right, all to one scale. If the left outweighs the right, the battery
is filling. Light and dark themes, remembered between visits.
Add `--host 0.0.0.0` to reach it from a phone on the same network.
Anker readings come from a running automation rather than a second cloud
connection, so start a service first. Two MQTT sessions on one Anker account
invite rate limiting. Shelly devices are polled directly over local HTTP and
always show live.
---
## Safety
Controlling the power supply to a battery has a specific failure mode: turn
charging off, let the battery run flat, and the device drops off the network,
at which point nothing can turn charging back on. The design is built around
preventing that.
### The battery floor
```yaml
safety :
battery_floor :
at_or_below : 20
release_at : 40
then : target.on
```
Checked before any rule. It **bypasses the rate limits** , **outranks every
rule**, and **latches** once tripped, so nothing can turn the plug off again
until the battery reaches `release_at` . Without the latch, a solar rule could
release it at 21% straight back into whatever drained it.
`release_at` must be meaningfully above `at_or_below` ; the profile is rejected
otherwise.
### Failing in the safe direction
```yaml
source :
stale_after : 300s
on_stale : safe_state
safe_state : on
```
If telemetry stops arriving, rules are never evaluated against stale values.
`hold` leaves the plug alone, `safe_state` drives it to a known state, `stop`
exits. When the plug supplies power to the SOLIX device, `safe_state: on` means
losing sight of it fails toward charging.
Rules are also never evaluated against **partial** telemetry. If a field a rule
needs has not arrived yet, nothing is evaluated at all, including the floor.
### Rate limits
```yaml
limits :
min_seconds_between_actions : 60
max_actions_per_hour : 20
```
A hard backstop independent of dwell times. If a rule somehow oscillates, this
caps the damage. The battery floor deliberately ignores it.
### Conflict detection
A Shelly can hold schedules, auto-on/auto-off timers, and webhooks on the
device itself. They run whether or not this tool is running, and they silently
override it.
```bash
solixauto conflicts <plug> # list them
solixauto conflicts <plug> --fix # remove them, asking before each change
```
Checked at discovery, at `--test` , and again at engine startup.
One limitation: schedules created as Shelly Cloud **scenes** live in the cloud
rather than on the device and cannot be seen over local HTTP. If behaviour
still looks wrong after `conflicts` is clean, check the app's scenes.
---
## Testing before you trust it
```bash
solixauto run <profile> --test
```
Validates syntax, checks every field name against what the device actually
reports, warns about missing deadbands and short dwell times, connects to both
devices, and prints what each rule would do right now. Switches nothing.
```bash
solixauto run <profile> --test --offline
```
Same checks without connecting, evaluated against values captured at discovery.
```bash
solixauto run <profile> --test --simulate battery_soc = 12
```
Force a rule to fire without waiting for real conditions. Prove your
low-battery logic works at 2pm on a sunny day, and see the exact notification
text it would send.
```bash
solixauto run <profile> --dry-run
```
The full engine loop with real telemetry, narrating every cycle, but no switch
command and no notification.
---
## Notifications
```bash
solixauto notify-setup
```
Interactive: pick a channel, it configures and tests it. For ntfy it generates
a random private topic, shows QR codes for the app store and the topic, and
sends a test push.
Supported: **ntfy** (free, no account), **Pushover** , **email** , **Telegram** ,
**webhook** (Slack/Discord/custom), and **desktop** (osascript on macOS,
notify-send on Linux, PowerShell on Windows).
Credentials live in `notifications.yaml` with owner-only permissions, never in
power profiles, so profiles stay safe to share.
Battery floor alerts fire **even when notifications are otherwise disabled** ,
at elevated priority, bypassing the throttle. Muting routine chatter should not
silence a battery emergency.
The Anker app and the Shelly app cannot receive custom push messages from an
external program, so neither is an option.
---
## Running it unattended
```bash
solixauto service <profile> # install and start
solixauto service <profile> --status
solixauto service <profile> --uninstall
```
macOS gets a LaunchAgent, Linux a systemd user unit, both starting at login and
restarting on crash. Windows prints the Task Scheduler command.
If the machine sleeps, the automation sleeps with it and the safety floor
cannot protect anything. Use an always-on machine, or disable sleep.
---
## Layout
Code lives where you cloned it. Everything else lives in `~/solix-automation` :
```
~/solix-automation/
├── venv/ Python environment created by start.sh
├── device-profiles/
│ ├── anker/ what your SOLIX device reports
│ └── shelly/ your plugs and their capabilities
├── power-profiles/ your rules, hand-editable
│ └── README.md full rule reference with worked examples
├── notifications.yaml channel credentials, mode 0600
├── state/runtime.json last known target state
└── logs/automation.log every action taken
```
Override the root with `SOLIXAUTO_HOME` .
---
## Commands
```
solixauto setup guided setup, start here
solixauto doctor check this machine is set up correctly
solixauto discover-anker find and profile SOLIX devices
solixauto discover-shelly find and profile Shelly devices
solixauto devices list saved device profiles
solixauto name <device> "<name>" name a device, rename its file
solixauto fields <device> field names usable in rules
solixauto status <device> live telemetry
solixauto switch <plug> on|off manual control
solixauto conflicts <plug> automation set on the plug itself
solixauto monitor live dashboard in your browser
solixauto new-profile <name> scaffold a power profile
solixauto profiles list power profiles
solixauto validate <profile> check without connecting
solixauto run <profile> --test test against real devices, switch nothing
solixauto run <profile> run it
solixauto notify-setup configure push notifications
solixauto notify-test send a test
solixauto notify-qr show the ntfy topic QR again
solixauto service <profile> run in the background at login
```
Profiles resolve by friendly name, serial, ID, MAC, or IP, so all of these
reach the same device:
```bash
solixauto switch "Garage Plug" on
solixauto switch 192.168.1.50 on
solixauto switch garage-plug on
```
---
## Device support
Anything the upstream
[anker-solix-api ](https://github.com/thomluther/anker-solix-api ) library
supports **and** that holds a cloud connection.
Models that pair with the Anker app over Bluetooth only, where you press a
button on the unit to make it discoverable, publish nothing to the MQTT broker
and cannot be used. The F2000 / PowerHouse 767 works this way. Discovery detects and skips
them.
Shelly Gen1 and Gen2+ including Gen4, over local HTTP.
Developed against a SOLIX F3000 (A1782) and a Shelly Plug US Gen4. Other
combinations should work; reports welcome.
---
## Known limitations
- Field mappings come from a community reverse-engineering effort, not from
Anker. Fields ending in `?` or starting with `unknown_` are unconfirmed, so
do not build rules on them.
- **Verify any field against the Anker app before trusting it.** The solar
fields in particular should be watched in daylight and compared to the app
before writing solar rules.
- Shelly Cloud scenes and app-set device names are invisible over local HTTP.
- Requires a machine that stays awake.
- Anker rate-limits logins; avoid restarting the service in a tight loop.
---
## Troubleshooting
**A device reports no telemetry.** It is powered off, asleep, off WiFi, or
Bluetooth-only. Confirm it shows online in the Anker app, then retry that
device alone with `--sn <serial>` .
**Shelly discovery finds nothing.** Try `--host 192.168.1.50` or
`--network 192.168.1.0/24` . Give your plugs DHCP reservations; `access.host` is
a fixed address in the profile.
**The automation does nothing.** Check `solixauto service <profile> --status`
and the log. Rules only act after their `for:` dwell has fully elapsed.
**Something switches the plug unexpectedly.** Run `solixauto conflicts <plug>` ,
then check the Shelly app for cloud scenes.
**Anything else.** `solixauto doctor` reports platform, dependencies,
credentials, network, and notification status in one place.
---
## Documentation
- [docs/RULES.md ](docs/RULES.md ): power profile format, every option, worked
automation examples
- [docs/TESTING.md ](docs/TESTING.md ): staged checklist for validating a new
setup before trusting it with real hardware
- [examples/ ](examples/ ): a solar failover profile and an annotated
notifications config
## Contributing
Bug reports welcome, particularly for device models not listed above. Include
the output of `solixauto doctor` and the relevant section of
`logs/automation.log` .
**Never paste `notifications.yaml`, your `.env`, or an ntfy topic** into an
issue. Device profiles contain serial numbers; redact them if that matters to
you.
## License
MIT. See [LICENSE ](LICENSE ).