feat(drivers): let a driver declare the commands an operator may send - #738
feat(drivers): let a driver declare the commands an operator may send#738frahlg wants to merge 1 commit into
Conversation
Settings renders a hand-written branch per driver family, so a driver with a
real control surface has no way to describe it and no way to reach a person.
The Heishamon heat pump has had a verified curve-offset command since June and
its author was told to write a Home Assistant automation instead.
A `controls` list in the DRIVER block names the command, labels it, describes
its single input — type, bounds, step, unit — and states what counts as proof
the device took it. /api/drivers/{name} and /api/drivers/catalog surface it.
Nothing sends these commands. This is the description; the path is separate
work. Signed packages already carry the shape in RuntimeCommand, but only for
drivers on the signed channel — a bundled or local driver has no policy, so
the DRIVER block is where its declaration has to live.
The existing pick helpers stop at the first closing brace and anchor to the
start of a line, both right for flat fields and both wrong for a nested list
written inline. This reads controls with its own brace matching rather than
changing how every other field is parsed for the sake of one that did not
exist yet.
A declaration the UI could not render is dropped rather than surfaced half
formed: no id, a type outside number/boolean/string, or a number missing
either bound. Bounds are pointers because zero is a real bound — an
undeclared minimum and a minimum of zero must not read the same.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fa3148ce30
ℹ️ 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".
| if strings.EqualFold(e.Path, wantPath) || strings.EqualFold(e.Filename, wantFilename) { | ||
| return e.Controls |
There was a problem hiding this comment.
Match controls to the configured Lua file
When luaPath is absolute and a different catalog source contains the same filename—for example, an explicitly selected bundled /app/drivers/foo.lua shadowed by local foo.lua—the portable-path comparison fails and this filename fallback returns the local entry's controls even though the registry loaded the bundled file. /api/drivers/{name} then attributes commands to the wrong driver; resolve against the configured file or its source-relative path before using a filename fallback.
AGENTS.md reference: AGENTS.md:L43-L47
Useful? React with 👍 / 👎.
| // table does not end the outer one. Braces inside string literals are skipped; | ||
| // a topic or a label is allowed to contain one. | ||
| func nestedBlock(block, name string) string { | ||
| loc := regexp.MustCompile(regexp.QuoteMeta(name) + `\s*=\s*\{`).FindStringIndex(block) |
There was a problem hiding this comment.
Ignore commented-out control declarations
When a driver author disables a declaration with Lua comments such as -- controls = { ... }, this unanchored search still finds the text and the remaining parser publishes those controls through both APIs. That makes commented examples or intentionally disabled commands look actively declared; strip or skip Lua comments while locating and matching the table.
AGENTS.md reference: AGENTS.md:L43-L47
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| func fieldNumber(block, name string) *float64 { | ||
| re := regexp.MustCompile(`(?:^|[\s,{])` + regexp.QuoteMeta(name) + `\s*=\s*(-?[0-9]+(?:\.[0-9]+)?)`) |
There was a problem hiding this comment.
Parse complete Lua numeric literals
When a valid Lua bound or step uses exponent notation, such as max = 1e6 or step = 1e-1, this regex matches only the prefix and ParseFloat successfully publishes 1. The API therefore silently changes the declared control range or increment instead of preserving or rejecting it; support Lua numeric forms or require a delimiter after the matched literal.
Useful? React with 👍 / 👎.
Answers the "does the driver need to declare something extra?" half of #520. Today the answer is no — there is nothing to declare. This adds it.
The gap
Settings → Devices renders hand-written branches per driver family (devices.js:23 says so: the choices stay there "until the signed driver catalog grows a general config-schema field"). So a driver with a real control surface cannot describe it. The Heishamon heat pump has had a hardware-verified
set_heat_curve_offsetsince June, and its author was told to drive it from a Home Assistant automation instead.Signed packages already carry this shape in
RuntimeCommand— typed inputs, lease policy, evidence — but only for drivers on the signed channel.rd.policyis nil for every bundled and local driver, so theDRIVERblock is where their declaration has to live.What this adds
Surfaced on
/api/drivers/{name}and/api/drivers/catalog.Nothing sends these commands. This is the description; the command path is separate work and is where the interesting problems are (lease expiry runs
driver_default_mode, which for heishamon writes the offset back to safe — so a manual setting needs a Core-side hold with heartbeat renewal, sinceLease.MaxDurationcaps at 300 s).Notes for review
pickKVBlockmatches[^}]*and stops at the first closing brace;pickStringanchors to the start of a line. Both are right for flat fields and both break on a nested list written inline. This readscontrolswith its own brace matching rather than changing how every other field is parsed.TestBundledCatalogStillParsescovers the regression that would matter — a controls reader that quietly eats an unrelated field.IsEVOrVehicleDriver/IsReadOnlyDriver: portable path first, then filename. Operators writedrivers/x.luaand/data/x.luaboth.No bundled driver declares controls yet —
drivers/is a generated snapshot and cannot be edited here. Adding the block to heishamon is a device-drivers change, after srcfl/device-drivers#60.make verifyclean.🤖 Generated with Claude Code