Priorities and conflicts¶
Direct Connect Bridge. Mirrors the Modbus Bridge page Priorities and conflicts section for section.
Two schedules can want the same battery at once, or a schedule can open while a force from the Battery Control widget is running. This page says which one acts.

| Setting | Values | Default |
|---|---|---|
| Priority | any whole number; higher wins | 0 |
| If busy | override, defer, wait | override |
Both sit in the Battery action box of the form. The API accepts any integer for
priority and any text for conflict; a value that is not defer or wait behaves as
override.
Entries on the same target¶
Schedules compete for a resource, not for a whole gateway. Each battery action claims one:
| Battery action | Resource it claims |
|---|---|
| Force charge / discharge / standby, Set operating mode, Set reserve SoC | the gateway's battery |
| Go off-grid / reconnect | the gateway's grid connection |
| Smart circuit on/off | that circuit; with no circuit set, every circuit on the gateway |
| Notify (Home Assistant), No battery action (HA only) | nothing: never competes |
On each tick, among the schedules that are ready to fire on that tick (window open, entry conditions true, hold passed, not fired yet this occurrence):
- Schedules that claim the same resource on the same gateway are put in one group.
- In each group the highest priority wins. Ties go to the oldest schedule (created first), so renaming a schedule never changes the winner.
- Every other schedule in the group is deferred for the rest of its occurrence: the
History view logs deferred — "'
' (priority N) holds battery this occurrence; this rule is priority M", the card's last result reads deferred, and the run is recorded as skipped. It does not fire later in the same window, even after the winner has finished.
Priority only decides between schedules that become ready on the same tick
A schedule that becomes ready later, while another schedule's force is already running, is not compared by priority at all. Only its If busy setting decides (see below). With the default override, a later schedule takes the battery even if its priority is lower; with defer it gives way even if its priority is higher. Measured with a mock on 2026-10-17: a priority-9 force ready at 14:06 replaced a running priority-1 force (override). And a Set operating mode schedule is never checked against a running force: the mode is written while the force runs.
Give one-shot schedules a short window
A Set operating mode schedule claims the battery only on the tick it fires, so it does not block a force schedule for its whole window. It can still beat a force schedule that becomes ready on the same tick, which is then deferred for its whole occurrence. Give mode schedules a different start time from force schedules.
Example¶
| Schedule | Priority | Window | Entry conditions |
|---|---|---|---|
| Evening discharge | 10 | 17:00 for 240 min | SoC > 20 % |
| Peak-demand shave | 20 | 17:00 for 30 min, every day | tariff.demand_window_active == 1 |
At 17:00 both open. If the demand gate passes, Peak-demand shave fires (20 > 10) and Evening discharge is deferred until tomorrow. If the demand gate fails, Evening discharge fires; when Peak-demand shave becomes ready a few ticks later its If busy decides, not its priority: with override it takes the battery. When its 30 minutes end, its window close sends a Release, which also ends the evening discharge (see Exit and release). Avoid overlapping force schedules on one gateway.
Manual control¶
If busy applies only to a schedule whose action is Force charge / discharge /
standby. It looks at the force commands the bridge has recorded as running on that
gateway's host (the dispatches table), and ignores the schedule's own:
- a force started by another schedule (including one started by Run now);
- a force started from the Battery Control widget on the Control tab (recorded as
_widget).
| If busy | API conflict |
What happens |
|---|---|---|
| override (default) | override |
Sends its own force, which replaces the running one. |
| defer | defer |
Does not send. Logs deferred — "the battery is already under another dispatch (conflict=defer)" and marks the day as done: it does not try again later that day. |
| wait | wait |
Does not send. Logs waiting — "battery busy with another dispatch (conflict=wait); will retry", and tries again on the next tick while the window is open. |
After a deferred schedule's day is marked done, it does not take over when the other force ends. A wait schedule does: it fires on the first tick after the other force's record is closed, if its window is still open.
wait logs every tick; defer can repeat on interval, cron and second windows
wait writes a waiting row on every tick (every 15 s) while the battery is busy. defer marks today's date as done, but the occurrence of an Every N minutes, Custom cron or second-or-later Recurring windows window is not a date, so such a schedule is deferred, and logged, again on every tick. From a code read; not tested. Issue #204.
What the bridge cannot see¶
-
A force from anything else: the Modbus Bridge or another Modbus client of the Modbus TCP Server (aGate :502), the FranklinWH app, or a VPP. None of these is in the bridge's
dispatchestable, so a schedule dispatches over them, whatever its If busy. The History view's battery chip shows vpp-start / vpp-stop when the VPP monitor notices external control. -
A record that was never closed. The check reads the
dispatchestable, not the aGate. A row stays active until the bridge releases that force or the boot reconcile closes it. While a stale row is active, defer and wait schedules keep giving way. Unverified how often this happens in practice.
Not in this bridge: ownership matching by action label, and supervision that re-sends a command over a manual one. This bridge does not supervise a running force (see Outcomes).
Entries on different targets¶
A schedule has no target type here, only a gateway (see Targets), so the Modbus Bridge's "different targets" case does not arise in the same way:
- An unbound schedule (Default gateway) and a schedule bound to that same gateway claim the same resource and are compared by priority on the same tick.
- A schedule with gateway scope
allclaims each gateway's resource on that gateway's own tick, so it competes separately on each gateway.
Who sent what (requester)¶
Each force the bridge sends is recorded in the dispatches table with who started it:
the schedule's id for a scheduled run or Run now, _widget for the Battery Control
widget. That record is what If busy reads, what Stop looks for, and what the boot
reconcile acts on. There is no REST endpoint that lists it.
Not in this bridge: a per-command requester in the control log
(— by schedule:<id>). See Outcomes and audit.