Skip to content

fix(drivers): honour the interval driver_poll returns - #1616

Merged
frahlg merged 3 commits into
masterfrom
claude/driver-poll-interval
Oct 14, 2026
Merged

frahlg merged 3 commits into
masterfrom
claude/driver-poll-interval

Conversation

@frahlg

@frahlg frahlg commented Oct 13, 2026 •

Copy link
Copy Markdown
Member

Problem and result

Core threw away the number a driver returns from driver_poll
(_, err := rd.driver.Poll(...) in registry.go). Only
host.set_poll_interval counted, so every other driver was read every
five seconds. The driver contract in srcfl/device-drivers says the return
is "milliseconds until the next call". Ferroamp asks for 1000 ms and gets
5 s. The Sungrow site meter on the home box is read every 5 s, so the
phone's live figures move every 5 s even though Lane 0 and FTW Cloud send
once a second (srcfl/ftw-app#33).

  • The run loop uses the interval the last poll returned, between one
    second and thirty seconds.
  • host.set_poll_interval wins. Once a driver calls it, Core keeps
    that interval, unbounded, as before, and ignores what driver_poll
    returns. Every driver that sets its own cadence today keeps it exactly.
  • A driver that returns nothing, zero or a negative number gets 5 s, as
    before.
  • Registry.PollInterval reports the wait the run loop armed, so driver
    repository activation waits long enough.
  • A huge Lua number is capped before it becomes a time.Duration, so it
    cannot overflow.

Why these bounds:

  • 1 s floor. Lane 0 frames go out once a second and control ticks every
    2 s by default. A faster read reaches nothing but the device's bus. Some
    catalog drivers return 200 ms (P1) or 50 ms.
  • 30 s ceiling. Half the default 60 s watchdog window, so a returned
    wait cannot make a driver stale on its own. myuplink returns 60 s after a
    good read; at 60 s plus the fetch it would go offline every minute.
    Drivers that need longer waits (vag_vehicle 300 s, tesla_cloud 60 s)
    already set them with host.set_poll_interval, which this does not bound.
    A "return wins" rule would have cut their waits to 30 s and raised their
    cloud API calls, which is why set_poll_interval wins here.

Scope and safety

Bundled drivers at the current pin that only return an interval:

Driver Before After
ambibox_v2x, ferroamp, ferroamp_dc2_v2x, pixii_pv, sdm630, sonnen, zap 5 s 1 s
myuplink (home box) 5 s 30 s
easee_cloud, tesla_wall_connector, zaptec_cloud 5 s 10 s after a failed read, else 5 s
solaredge_legacy 5 s 30 s after a failed SunSpec probe, else 5 s

None of these has hardware evidence at 1 s in FTW. The owner chose to run
all seven at 1 s. The owner's evidence for sdm630: "SDM630 kan LÄTT läsa på 1 s så
inga problem" (an SDM630 easily reads at 1 s). pixii_pv reads Core's local
MQTT queue, so 1 s adds no bus traffic. An earlier version of this text
called pixii_pv Modbus; that was wrong.

Unchanged: every driver that calls host.set_poll_interval (esphome_dsmr,
nibe_local, tesla_vehicle, tibber, zuidwijk_p1 and the vehicle drivers),
and every driver that returns 5000.

Paired PR

srcfl/device-drivers#179 — sungrow 1.6.0 returns 1000 ms and reads only
what the live view needs every second. It needs this PR to run at 1 s; on
an older Core it still reads everything every 5 s. The FTW pin moves in a
separate PR after both merge.

Verification

  • go test ./internal/drivers/ ./internal/api/ ./cmd/ftw/, then
    make verify: green.
  • New tests in poll_interval_test.go: the bounds, set_poll_interval
    winning in both directions, a fake runtime polled at the returned second
    instead of the default, a Lua driver at set_poll_interval(10) that
    returns 60000 still polled every 10 ms.
  • Not committed: sungrow 1.6.0 run under gopher-lua with this run loop and a
    fake Modbus for 12 s. 9 reads per poll each second, all 19 every 5 s, and
    the meter blob kept its counters between full reads.
  • Hardware: sungrow 1.6.0 at 1 s on the home box for 30 minutes. That
    Core ignores the return, so the test driver set the same 1 s with
    host.set_poll_interval; bus traffic is the same as this PR gives.

Hardware, 14 Oct 2026, owner's home box. Sungrow SH hybrid (device type
0x0E0E), Modbus TCP on the LAN, FTW v0.142.1-beta.1. The owner approved a
30-minute driver draft (POST /api/drivers/sungrow/draft), which reverted by
itself at 06:45. The draft was this driver plus two lines for the test:
host.set_poll_interval(1000), because that Core ignores the return, and a
timer around each Modbus read that logged a summary each minute.

Result
Polls 1712 in 29 whole minutes: 1369 fast, 343 full
Modbus reads 19,040; none failed (0 refused, 0 link, 0 reconnect waits)
Poll errors 0
Poll start to start median 1019–1020 ms in every minute, p95 at most 1042 ms, max 1247 ms
One read median 2 ms, p99 3 ms, max 210 ms (once)
Fast poll, 9 reads median 16–17 ms, max 28 ms
Full poll, 19 reads median 34–37 ms, max 241 ms
Meter reading in /api/drivers/sungrow, sampled 4 times a second new every 1020 ms (median), p95 1040 ms; 5040 ms before

Journal, 06:10–06:55: no warning during the run. Two modbus reconnected
lines, one at each driver restart. One site dispatch inhibited (site_meter_stale) for 3 s at 06:45:04, after the revert: the bundled 1.5.10
waits 5 s for its first poll after a restart, and the same line appears at
every restart in the 24 h before. The draft start, which polled after 1 s,
had none.

Limits: 30 minutes on one site, early morning, battery full and mostly idle,
so few control writes shared the Modbus session. A WiNet-S dongle answers
more slowly than this LAN port.

Verdict: 1 s is safe for this Sungrow. Modbus traffic takes about 17 ms of
each second, and nothing failed.

This PR keeps one thing: after a driver restart, a driver on a returned
interval still waits the 5 s default for its first poll, and a site meter
shows stale for a few seconds, as it does today.

Checklist

  • The change follows VISION.md and one selected scope.
  • I checked overlapping PRs and coordinated shared files/contracts. No open PR touches these files.
  • Relevant checks cover the changed behaviour and failure paths.
  • A human reviewed changed UI in a browser, or no UI changed. No UI changed.
  • A Changeset is included, or the change is exempt. Patch.
  • Every commit has a DCO sign-off.

🤖 Generated with Claude Code

Core discarded driver_poll's return value and polled every driver
every five seconds unless it called host.set_poll_interval. The
returned wait now sets the next poll, between one second and thirty
seconds. A driver that calls host.set_poll_interval keeps that
interval, unbounded, as before.

Registry.PollInterval reports the wait the run loop armed, so driver
repository activation waits long enough for a driver on a returned
interval.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
@frahlg
frahlg marked this pull request as ready for review October 14, 2026 05:02
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-14T05:06:44.686820Z ab87ceb Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ab87ceb347

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// sets it with host.set_poll_interval, as the vehicle drivers do.
const (
minReturnedPollInterval = time.Second
maxReturnedPollInterval = 30 * time.Second

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cap returned waits below the configured watchdog

When site.watchdog_timeout_s is configured below 30 seconds—config.go accepts any nonnegative value—a driver that emits successfully and then returns a longer interval is now left idle past its watchdog deadline. For example, a 10-second watchdog with a 30-second returned wait makes WatchdogScan repeatedly mark the driver offline and send its autonomous default between otherwise successful polls; the previous five-second cadence did not. Derive this cap from the effective site/per-driver watchdog or otherwise ensure the scheduled wait remains below it.

AGENTS.md reference: AGENTS.md:L66-L67

Useful? React with 👍 / 👎.

frahlg added a commit to srcfl/device-drivers that referenced this pull request Oct 14, 2026
driver_poll returned 1000 ms, which FTW ignored until srcfl/ftw#1616.
An SDM630 often shares an RS485 bus with an inverter, and its one
76-register read takes about 0.18 s at 9600 baud. Return 5000 until
hardware shows one second is safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
frahlg added a commit to srcfl/device-drivers that referenced this pull request Oct 14, 2026
driver_poll returned 1000 ms, which FTW ignored until srcfl/ftw#1616.
Hold it at the five seconds it has run at until hardware shows one
second works. It reads MQTT, so the hold is about unproven behaviour,
not bus load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
frahlg and others added 2 commits October 14, 2026 07:51
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
The owner dropped the hold: an SDM630 easily reads at one second, and
pixii_pv reads MQTT.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
frahlg added a commit to srcfl/device-drivers that referenced this pull request Oct 14, 2026
* feat(sungrow): read live values once a second (1.6.0)

driver_poll returns 1000 ms. Power, SoC, per-phase values and the EMS
readback are read on every poll; counters, nameplate, temperature,
frequency and fault bits keep the five-second rate and ride along on
the polls between, only when that poll's own power read succeeded.

A read FTW refused during a reconnect wait no longer counts toward
treating a register as absent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>

* fix(sdm630): stay at five seconds until seen at one second (2.0.3)

driver_poll returned 1000 ms, which FTW ignored until srcfl/ftw#1616.
An SDM630 often shares an RS485 bus with an inverter, and its one
76-register read takes about 0.18 s at 9600 baud. Return 5000 until
hardware shows one second is safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>

* fix(pixii_pv): stay at five seconds until seen at one second (0.3.3)

driver_poll returned 1000 ms, which FTW ignored until srcfl/ftw#1616.
Hold it at the five seconds it has run at until hardware shows one
second works. It reads MQTT, so the hold is about unproven behaviour,
not bus load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>

* Revert "fix(pixii_pv): stay at five seconds until seen at one second (0.3.3)"

pixii_pv reads Core's local MQTT queue, so the bus-load reason for the
hold does not apply. The owner chose to let it run at one second like
ferroamp.

This reverts commit b5af185.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>

* Revert "fix(sdm630): stay at five seconds until seen at one second (2.0.3)"

The owner says an SDM630 easily reads at one second, so it runs at the
1000 ms its driver asks for.

This reverts commit 48cae6c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>

---------

Signed-off-by: Fredrik Ahlgren <fredrik@sourceful-labs.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@frahlg
frahlg merged commit 5bc76c9 into master Oct 14, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant