Repository navigation
Add staged PID control architecture - #9
Open
danielbrownjr wants to merge 3 commits into
Open
danielbrownjr wants to merge 3 commits into
danielbrownjr wants to merge 3 commits into
Conversation
…roller Introduce jj_control, a product-local layer over the pinned dragon-core dc_pid, and split jj_interlock into two stages under a single safety owner: - Stage 1 (jj_interlock_evaluate) runs the one set of eligibility predicates and returns a dominant block reason for diagnostics plus a controller disposition aggregated over every applicable inhibit (SUSPEND > HOLD > RUN). Check order and fault latching are unchanged; diagnostic priority no longer decides controller lifecycle. - jj_controller_step consumes that result: SUSPEND idles and resets, HOLD steps with integration frozen, RUN steps with integration. It holds no fan/fault/printer/authority/sensor predicates. - Stage 2 (jj_interlock_authorize) consumes the interlock-retained Stage-1 evaluation exactly once, matched by sequence token, and authorizes only RUN with a VALID controller and a finite 0-100 % request. Stale, replayed, or unknown tokens, and revocation or fault clear between the stages, fail cold. Controller lifecycle is IDLE/VALID/INVALID. requested_duty_pct is the unmodified dc_pid output (0 unless VALID); heater_authorized and allowed_duty_pct replace heater_requested. No product code compares temperature to target. Only fan-proof-pending HOLDs; NOT_COMMISSIONED, MANUAL_TARGET_INVALID, and every other inhibit SUSPEND via an exhaustive switch. AUTOMATIC stays idle with no setpoint. Both stages and the controller run inside the existing authority critical section. Gains are zero and the sample period mirrors the current task cadence; both are provisional pending Phase 3. No heater actuation is added. Host tests cover lifecycle transitions, overlap aggregation, provenance, and a dc_pid shadow trajectory through target crossing; a static contract check guards the controller/safety boundary. Host tests fetch dc_pid at the commit pinned in main/idf_component.yml.
Document Stage 1 eligibility, controller disposition, and Stage 2 authorization, the IDLE/VALID/INVALID lifecycle, and which parameters remain provisional pending Phase 3 evidence.
…indow Primary HMI: - Render controller state (Idle / Running / Invalid), requested duty, and allowed duty from the existing API fields via a pure heaterPresentation() helper. Idle and Running use ordinary styling; Invalid reuses the fault style and stays visible regardless of the dominant constraint, so HOLD + INVALID is not shown as a healthy hold. Missing or unknown controller data renders as "Unknown", never healthy. - Add operator messages for controller_invalid and control_sequence_invalid. No new fault or block reason. OTA regression coverage (guard unchanged; still requires both !heater_authorized and !thermal_management_required): - After an authorized cycle, Stage 1's de-authorized snapshot keeps thermal_management_required, including with cooldown_required or fault_requires_thermal_management set. - A sweep shows Stage-1 and Stage-2 snapshots always agree on thermal_management_required, so the staged window cannot create an OTA-safe condition the final state lacks.
This was referenced Sep 24, 2026
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.
Summary
Implements Jump Jet's approved PID-family control architecture through the controller/interlock boundary. No heater actuator, GPIO, PWM, or time-proportioning is added. Production remains cold-safe.
jj_controllayer over the pinned dragon-coredc_pid(same pin,4e041d8;dc_pidadded tomain/idf_component.yml).jj_interlockremains the sole heat-safety authority, now staged into two evaluations under one owner.Architecture
All three steps run inside the existing authority critical section in
jj_authority_control_step():jj_interlock_evaluate()runs the single set of eligibility predicates and returns a dominant block reason (diagnostics/HMI only) plus a controller disposition aggregated over every applicable inhibit, with conservative precedence SUSPEND > HOLD > RUN. Diagnostic priority never decides disposition (e.g. fan-proof-pending + no-authority → SUSPEND even thoughfan_proof_pendingis dominant). PR Add lease-safe control authority model #7's check order and fault latching are unchanged.jj_controller_step()consumes that eligibility without re-deriving any safety predicate: SUSPEND → IDLE + reset; HOLD → step with integration frozen, state preserved; RUN → step with integration.jj_interlock_authorize()consumes the interlock-retained Stage-1 result exactly once, matched by a same-cycle sequence token. It authorizes only RUN with a VALID controller and a finite 0–100 % request. Stale, replayed, forged, or missing reports — and revocation or fault clear between the stages — fail cold (control_sequence_invalid).Classification is an exhaustive switch with no
default, so a new block reason fails the-Werrorbuild until classified. OnlyFAN_PROOF_PENDINGHOLDs;NOT_COMMISSIONED,MANUAL_TARGET_INVALID,CONTROL_NO_AUTHORITY,STATE_REFRESH_REQUIRED, and all other inhibits SUSPEND + RESET.Controller lifecycle and outputs
dc_pidrejected it.heater_requestedis replaced by distinctrequested_duty_pct(raw, unmodifieddc_pidoutput; 0 unless VALID),heater_authorized, andallowed_duty_pct(0 unless authorized). No product code compares temperature to target — there is no target-crossing override outsidedc_pid.HMI
The primary panel now shows Controller (Idle / Running / Invalid), Requested duty, and Allowed duty. INVALID uses the existing fault styling and stays visible even when the dominant constraint is
fan_proof_pending(HOLD + INVALID). Unknown/missing controller data never renders as healthy. Messages added forcontroller_invalidandcontrol_sequence_invalid. Raw P/I/D terms stay out of the primary UI.OTA
The guard is unchanged and still requires both
!heater_authorizedand!thermal_management_required— "unauthorized" alone is not "thermally safe". Regression tests prove the Stage-1 de-authorized snapshot preservesthermal_management_required(including with cooldown or fault thermal management asserted), and a 1,152-case sweep shows Stage 1 and Stage 2 always agree on it, so the staged window cannot create a false OTA-safe condition.Validation
git diff --checkclean.ledc/mcpwm/gpio_set_levelsymbols in the image. That sandbox could not reach the Espressif component registry, soespressif/mdnswas supplied via a local, uncommittedoverride_path. GitHub CI is the authoritative registry-backed target build.dc_pidat the exact commit pinned inmain/idf_component.yml(tests/fetch_dc_pid.sh), so there is no second pin.Known follow-ups (not in this PR)
cooldown_requiredmust exist before any actuator.jj_authority.c(pre-existing from Add lease-safe control authority model #7; admission-only, cannot bypass authorization) — tracked separately.Commits
b7123d2feat(control): stage interlock authorization around a PID-family controller20da76adocs(architecture): describe the staged PID-family control loop5749120fix(portal): show controller state and duty; test OTA across staged window