Add dashboard screenshot to README
This commit is contained in:
@@ -0,0 +1,433 @@
|
||||
# anker-shelly-bridge
|
||||
|
||||
<p align="center">
|
||||
<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">
|
||||
</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`.
|
||||
|
||||
That is the whole install. `start.sh` creates its own Python environment,
|
||||
installs everything, and hands over to a guided setup with eight steps:
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 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).
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
Reference in New Issue
Block a user