Add Example and functionality to write Rules using AI agents - #927
Open
stumbaumr wants to merge 48 commits into
Open
Add Example and functionality to write Rules using AI agents#927stumbaumr wants to merge 48 commits into
stumbaumr wants to merge 48 commits into
Conversation
The vendored CurlyMoo rules engine miscompiles a standalone user-event call (`name();`) inside an `if … end` body — OP_JMP back-patch gets stomped during slot assignment, so the body never executes. PR heishamon#899 fixed the simple case in v4.1.5 but not the variant with two adjacent if-blocks whose bodies are only `dhw_start();`, which is exactly our shape in `on timer=10`. Workaround: inline the dhw_start body at both call sites and remove the `on dhw_start then … end` rule. Built-in calls (`setTimer`, `@SetForceDefrost`) inside if-bodies remain safe because they emit OP_CALL with a non-zero result slot, so the bug's trigger condition (`getval(x->a) == 0`) doesn't fire. `#dhwStarted` double-fire guard is preserved (set as the first statement of each inlined block). CLAUDE.md updated to reflect the inlining and document the bug so future edits don't reintroduce the broken pattern. See heishamon#894 (comment) for upstream report. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restore the `on dhw_start then … end` user-event and the two `dhw_start();` call sites in `timer=10`. The codegen bug that forced the inlining workaround in 7b9ea59 is fixed by IgorYbema/HeishaMon commit 913e8eb ("reset mathcnt when a call statement closes an if-block"), confirmed working on the alpha firmware installed on the device. The fix is not yet in any heishamon/HeishaMon release, so CLAUDE.md now documents a firmware floor: the `dhw_start` call sites require a build containing 913e8eb (or the first heishamon release that picks it up). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update the docs to match the rules: three-timer post-DHW recovery chain (20/21/22), corrected DHW triggers and time-of-day branches, the actual valve/defrost/power-consumption flow, and the full set of MQTT topics the script references. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Split the DHW/recovery bursts across staggered one-shot timers so no single firing emits more than two @set… commands. Renumber timers (drop 20, add 32) and move dhw_start state capture inline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Turn the unit on inside dhw_start (where the pre-DHW state is captured) instead of in timer=22, and read @DHW_Temp/@Operating_Mode_State directly in the low-tank fallback rather than via snapshot locals. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Refactor DHW cycle logic for improved performance and clarity, ensuring proper handling of valve states and command emissions.
- Update persistent-global names to lower-camelCase (post-refactor) - Boot does not disable the heat pump; note SetMaxPumpDuty/SetPump - Master clock now also snapshots #maxPumpDutyBeforeDHW - timer=21 no longer snapshots the pump-duty cap Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Added logic to set dhwStarted when defrosting during DWH production.
Remap the DHW/recovery state-machine timer IDs in the docs from the old
{21,22,30,31,32} scheme to the current {20,21,22,23,24}, and note the
@Heatpump_State==1 gating on the valve/defrost handlers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Seed $newQuietMode from @Quiet_Mode_Level at the top of the throttle block so moderate-load firings (heating delta 0.3-1.6, cooling delta -1..0) hold the current quiet level instead of writing 0 off an unset local. Sync CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Defrost-end recovery now arms timer=20 (1 s) to re-assert DHW op mode and clear #dhwStarted, rather than doing it inline; document accordingly. Also tighten the smart-pause description to match the actual gates (@Operating_Mode_State == 0 && #offAtNight == 1 && @Z1_Heat_Request_Temp > 2). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Clarify variable persistence and boot logic in HeishaMon script.
Replace the assumed "outlet 2 °C above target stops compressor" note in the throttle section with the documented behavior: thermo-off at target + 1 °C held 3 minutes, thermo-on below inlet-during-off-time − 3 °C. Includes ManualsLib and Pro Club PDF source links. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Relocate the skill from .claude/skills/heishamon-rules/SKILL.md to Examples/Rules/SKILL.md so it lives next to the worked examples it documents, rename the merged-in CLAUDE.md to RULES.md, drop the now-redundant standalone LICENSE, and update AGENTS.md/README.md references to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Added coding standards section for HeishaMon rules including code style guidelines.
A small worked example showing timer-armed heat-request suppression after the compressor stops, plus a cold-weather override. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…urves helper Behavior-preserving cleanup of the Jeisha-DHW-Radiators-Rowbuffer example: - Master clock (timer=10): wrap all time-of-day checks in a single '%minute == 0' guard and chain the fixed hours (8/9/18/19) with elseif, so the 30-second tick evaluates one comparison instead of five multi-term conditions outside minute :00. The configurable #dhwStartHour check stays an independent if. - Replace endpoint-clamping if-blocks with the built-in min()/max() functions (DHW target interpolation, heat/cool request caps). - Centralize every @SetCurves payload in a new setCurves($high, $low) helper function. - Demote throttle's per-firing scratch vars (#roomDelta, #outletDeltaHeat, #newHeatRequest) to $ locals; they are recomputed each firing and used nowhere else. They stay console-visible via the function-block locals fix in the firmware debug dump. - if/elseif for the mutually exclusive @Defrosting_State branches, whitespace cleanup. Verified behavior-identical to the previous version with the host rules harness: identical @set command sequences, values, virtual timings and persistent-global end states across simulated days, full DHW cycles (incl. defrost-during-DHW and recovery chain), low-tank fallback, a throttle sweep and the smart-pause path, plus a dhwStartHour re-tune variant. Bytecode shrinks from 5072 to 5048 bytes; the two-commands-per-firing budget is unchanged. Also fixes a stale timer=32 reference in the README (it is timer=24). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Compiles the unmodified firmware rules engine (HeishaMon/src/rules) as a Linux binary with a minimal Arduino.h shim and drives it against simulated heat-pump state: readable @topics come from a scenario- controlled table, @set... writes are recorded instead of fed back (matching the device's async command semantics), %hour/%minute are scenario-controlled and timers elapse on a virtual clock. './harness <rules.txt> parse' runs the same parse/validation the device performs at upload, with @name checking against the real topic/command tables (build.sh regenerates valid_names.h from decode.h/commands.h). The scenario mode prints every emitted @set command, which makes ruleset refactors verifiable: diff the filtered output of two versions across the built-in regression scenarios for the Jeisha-DHW-Radiators-Rowbuffer example (simulated days, full DHW cycles incl. defrost-during-DHW, low-tank fallback, throttle sweep, smart pause). Needs only g++ and python3, no Arduino toolchain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Collapse the nested arming conditions into one && chain - Send the restore command only when the -5 sentinel is actually active, removing the unconditional write on every boot/rules-save - Route every restore (expiry, boot cleanup, cold-weather override) through the single timer=1 handler - Arm the boot cleanup at 60s so all topic values are populated, and use setTimer(1, 1) in the override since setTimer(id, 0) cancels a pending timer instead of firing it (src/common/timerqueue.cpp) Verified with the host rules harness; regression scenarios live in tests/ and run via Examples/Rules/run_tests.sh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The host harness (Examples/Rules/harness/) now runs scenario files instead of hardcoded C++ scenarios: topic changes, virtual-clock timers, a settable wall clock for %hour/%minute schedules, a simulated pump feedback op, and expectcmd/expectnone assertions so scenarios double as self-checking regression tests. - Compressor-Short-Cycle-Guard/tests/: 8 scenarios (boot, normal cycle, re-entry guard, defrost/cold stops, cold-snap override, stale-suppression reboot cleanup) - Jeisha-DHW-Radiators-Rowbuffer/tests/: 13 scenarios (boot seeding, DHW start/restore/defrost chains, throttle heat+cool, schedule points, smart pause) - run_tests.sh builds the harness and runs every ruleset's tests/ - Timer semantics now mirror the firmware: setTimer(id, 0) cancels a pending timer and never fires it - SKILL.md: document setTimer(id, 0) cancel semantics, correct the inverted isset() description, warn about NULL comparisons inside && chains (verified against the engine), point to the harness Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Brings in the optimized Jeisha-DHW-Radiators-Rowbuffer ruleset (minute-gated scheduler, min/max clamps, setCurves helper) and its README updates. The harness and SKILL.md conflicts resolve to the newer scenario-driven versions from main; all 21 tests pass against the optimized ruleset, confirming the refactor is behavior-preserving. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- README.md: point the rules Examples section at Examples/Rules/, the host-side harness, and run_tests.sh - Jeisha README: replace the outdated "no build, test, or lint tooling" intro with the off-device test workflow and add an editing-guidance bullet on running/extending the tests - Compressor README: point at its tests/ and the harness Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Make the command-throughput best practice concrete: no more than two @set commands within 30 seconds across the whole ruleset, plus the EEPROM write-life caution (both per README.md) - Add a harness-verified desired-state reconcile pattern (write from a parameterless helper, verify the read-back on a 30s timer, re-send until it sticks) as the general form of the README retry example - Document an engine quirk found while validating the pattern: in a function block with $ parameters, a top-level @set write directly before a built-in call is executed out of order and silently dropped; if-wrapped statements and parameterless blocks work Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hi,
created an AGENTS.md and directory Examples with sub dirs to create Rules using AI Agents.
This should allow end users to facilitate AI agents to quickly achieve their goals using Rules.
The PR has since grown into a complete rules-authoring toolkit:
What's included
AI agent guidance
AGENTS.mdplus theheishamon-rulesskill (Examples/Rules/SKILL.md): the rules-engine language (sigils, blocks, built-ins) and its sharp edges, verified against the engine source rather than folklore — e.g.setTimer(id, 0)cancels a pending timer (it never fires),isset(x)returns1once a value is set, andNULLcomparisons inside&&chains evaluate truthy even though they are false in a bareif.Worked examples (
Examples/Rules/)Jeisha-DHW-Radiators-Rowbuffer/— a multi-year-tuned DHW/heating controller, optimized (minute-gated scheduler, min/max clamps, setCurves helper) with a block-by-block README.Compressor-Short-Cycle-Guard/— a small timer-armed-suppression ruleset, optimized to send commands only when state actually needs changing, with a README explaining each design decision.Host-side test harness (
Examples/Rules/harness/)HeishaMon/src/rules/) as a Linux binary — no device needed. Rulesets get the exact samerule_initialize()validation as at upload, including@Namechecks against the real topic/command tables fromdecode.h/commands.h.timerqueue.cppsemantics), a settable wall clock for%hour/%minuteschedules, and simulated pump feedback.expectcmd/expectnoneassertions make scenarios self-checking regression tests.Regression tests
Compressor-Short-Cycle-Guard/tests/(8 scenarios) andJeisha-DHW-Radiators-Rowbuffer/tests/(13 scenarios) assert the exact@Set…command stream for boot, DHW cycles, defrost interactions, throttling, schedule points and edge cases.Examples/Rules/run_tests.shbuilds the harness and runs everything: currently 21 passed, 0 failed. The Jeisha tests pass against both the original and the optimized ruleset, proving the refactor is behavior-preserving.The harness already paid for itself during development: it caught that a "fire immediately"
setTimer(id, 0)idiom would deadlock on real hardware, and that unset (NULL) topics silently flip&&-chained conditions.Best regards and thanks for your good work so far!
Rainer
Reference: #926
🤖 Generated with Claude Code