Files
2026-08-10 21:37:46 -07:00

6.6 KiB

Power profiles

A power profile is a plain YAML file linking one Anker SOLIX device to one Shelly switch channel. You can edit it in any text editor.

The Anker device is read-only. The engine reads its telemetry and never sends it a command. The only thing that gets switched is the Shelly.

Layout

device-profiles/anker/     generated, one file per Anker device
device-profiles/shelly/    generated, one file per Shelly device
power-profiles/            yours, hand-edited
state/runtime.json         last known target state per profile
logs/automation.log        action history

Workflow

solixauto discover-anker
solixauto discover-shelly
solixauto new-profile solar-failover --template solar
solixauto fields A1782-<serial>
solixauto run solar-failover --test
solixauto run solar-failover

Anatomy of a rule

- name: grid assist when solar drops
  when: pv_total < 150
  for: 90s
  then: target.on
  priority: 0

when is an expression over any field listed in the Anker device profile, under readable or derived. solixauto fields <device> prints them with current sample values.

for is the dwell time. The condition must stay true for this long before anything happens. Without it, a cloud passing over your array would cycle the relay repeatedly.

then is target.on, target.off, or none.

priority breaks ties. If two rules are ready at the same moment and disagree, the higher number wins. A low-battery override should outrank normal solar logic.

Use a deadband

This is the single most important thing to get right:

# WRONG - will chatter around 200W
- when: pv_total < 200
  then: target.on
- when: pv_total > 200
  then: target.off

# RIGHT - 150W of deadband between the two
- when: pv_total < 150
  then: target.on
- when: pv_total > 300
  then: target.off

--test warns when it detects the same threshold used for both directions.

Useful automations

Solar failover. Solar covers the load most of the day; pull from the wall only when production drops.

- name: grid assist when solar drops
  when: pv_total < 150
  for: 90s
  then: target.on

- name: release grid when solar recovers
  when: pv_total > 300
  for: 2m
  then: target.off

Low-battery charge. No solar involved.

- name: charge when battery is low
  when: battery_soc <= 15
  for: 30s
  then: target.on

- name: stop charging when battery is healthy
  when: battery_soc >= 60
  for: 2m
  then: target.off

Overnight top-up. Combine conditions.

- name: cheap overnight charging
  when: battery_soc < 80 and pv_total < 20
  for: 5m
  then: target.on

Load shedding. Cut a non-essential circuit when the battery is draining.

- name: shed load
  when: battery_soc < 30 and ac_input_power == 0
  for: 2m
  then: target.off

Thermal guard. Higher priority so it outranks everything else.

- name: stop charging when hot
  when: temperature >= 45
  for: 60s
  then: target.off
  priority: 200

Safety settings

stale_after and on_stale control what happens when telemetry stops arriving. Rules are never evaluated against stale data.

  • hold keeps the relay wherever it is. Default and safest.
  • safe_state drives the relay to the safe_state: value.
  • stop exits the engine.

limits is a hard backstop independent of dwell times:

limits:
  min_seconds_between_actions: 60
  max_actions_per_hour: 20

If a rule somehow oscillates, this caps the damage.

Notifications

Turn them on in the profile:

notifications:
  enabled: true
  channels: [ntfy]
  title: "{profile}"
  template: >-
    {source_name} battery {battery_soc}%, solar {pv_total}W.
    {target_name} turned {action}.
  throttle: 5m
  on:
    - action

Then per rule, notify: on or notify: off to include or exclude it. Omit it to inherit. A rule can also carry its own wording:

- name: emergency charge on low battery
  when: battery_soc <= 15
  for: 30s
  then: target.on
  priority: 100
  notify:
    template: >-
      {source_name} has {battery_soc}% battery remaining.
      {target_name} turned on AC power.
    priority: high

Template fields

Any field from the device profile works, plus:

{profile}         power profile name
{rule}            rule that fired
{condition}       the rule's when expression
{action}          ON or OFF
{action_word}     on or off
{source_name}     Anker device name, falling back to model
{source_model}    e.g. SOLIX F3000
{source_serial}
{target_name}     Shelly device name, falling back to model
{target_model}
{target_host}
{time}

--test checks every field name in your templates against the device profile, so a typo is caught before it ships a message reading battery ?%.

Channels

Credentials live in ../notifications.yaml, which is created with owner-only permissions. Power profiles stay free of secrets.

  • ntfy - recommended. Free, no account. Install the app, pick an unguessable topic name, subscribe. Anyone who knows the topic can read your alerts, so make it long.
  • pushover - $5 once per platform. Priority 2 alerts repeat until you acknowledge them.
  • email - SMTP. Gmail requires an App Password.
  • telegram - free bot.
  • webhook - Slack, Discord, or generic JSON.
  • desktop - local notification on the machine running the engine. Uses osascript on macOS, notify-send on Linux, PowerShell on Windows. Run solixauto doctor to see which backend was detected.

The Anker app and the Shelly app cannot receive custom push messages from an external program, so neither is an option here.

Test a channel:

solixauto notify-test
solixauto notify-test --channel ntfy

Throttling

throttle is per rule. An identical repeated message inside the window is dropped. This is separate from the switching rate limits, so a stuck condition cannot flood your phone even if the relay is behaving.

Other events

on:
  - action
  - stale

stale fires once when telemetry stops arriving and is worth enabling if you depend on the automation.

Testing

solixauto run <profile> --test

Validates syntax, checks that every field you reference actually exists on the device, warns about missing deadbands and short dwell times, connects to both devices, and prints what each rule would do right now. Nothing is switched.

solixauto run <profile> --test --offline

Same checks without connecting. Rules are evaluated against the sample values captured during discovery.