feat: update float ESP-NOW runtime flow - #5

Open
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow
Open

feat: update float ESP-NOW runtime flow#5
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow

Conversation

@Skeitt

Copy link
Copy Markdown
Contributor
  • add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support

  • log and stream normalized syringe position alongside pressure and float depth

  • require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts

  • update firmware docs and protocol/parser/storage tests for the current command contract

Skeittand others added 22 commits May 29, 2026 22:57
- add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support
- log and stream normalized syringe position alongside pressure and float depth
- require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts
- update firmware docs and protocol/parser/storage tests for the current command contract
home (motor_pos=0) pushes water out → floats; negative positions move
toward the TOF and take on water → sinks. Simplify uToMotorPos to map
u in [0,1] onto negative travel, dropping the unused MOTOR_INVERT_LOGICAL
flag, and update balance() comments accordingly.
Mission log survives a power-cycle (cleared only at the start of a new
mission, no longer at boot), so a failed pool test can be read afterwards
over USB. Add a DUMP_LOG serial command to re-read it on demand without
clearing — solves the case where the monitor connects after boot.
emergencyStop() records reason + TOF distance to the flash log the instant
it fires (idempotent single write) instead of a posteriori in loop(), where
it was missed when measure() exited on phase timeout. Expose
lastStopReason()/lastStopTofMm(). TOF safety now debounces with
TOF_SAFETY_STOP_SAMPLES consecutive out-of-range reads to ignore single
glitches (bubbles, reflections) in water, and clamps the PID output to
[PID_U_MIN, PID_U_MAX] so the syringe never reaches the TOF safety limits.
Enable monitor echo + LF EOL so serial commands are visible and terminated
as the parser expects.
Default PID gains iterated against pool tests: Kp 0.17 was too weak (the
float barely moved), Kp 2.0 / Kd 0.13 oscillated (±15cm pumping). Settle on
Kp 1.0 / Ki 0 / Kd 0.5 as a damped starting point; fine-tune further at
runtime via PID_CONFIG_SET.
Reduce the descent kick-start from u=0.979 (near-full syringe, which drove
the float straight to the bottom before the PID could brake, ~26cm
overshoot) to PID_DESCENT_KICK_U=0.30, so the descent starts gently and the
PID takes over before overshooting the target.
Validated in the pool: the float converges on the target depth with ~±1cm
final oscillation. Ki vinces the buoyancy offset (recovery), Kd damps the
overshoot. Replaces the previous 1.0/0/0.5 starting point.
Il manual keyboard serve a riallineare un pistone disallineato, quindi
deve poter uscire dal range nominale (±MAX-margin) in entrambi i versi.
- motor: nuovo startJogStepsUnclamped() che bypassa volutamente il clamp
software dei fine corsa (startMoveTo/startMoveSteps restano clampati per
il firmware di missione).
- manual keyboard: startHoldMove() ora fa un jog relativo di ~2 mm
(MANUAL_JOG_STEPS) rinnovato mentre il tasto è tenuto premuto, con stop
manuale (space/x).
- rimosso l'auto-stop su distanza TOF: usava una costante rimossa e la
vecchia convenzione geometrica (estensione=positivo), incoerente con
l'homing attuale.
…iscina
Soglie tarate sulla distanza TOF reale (corretta dell'offset 6 mm):
pistone esteso ~29 mm, retratto ~79 mm, pendenza ~1.1 mm TOF/mm motore.
- TOF_SAFE_RANGE_MIN 40->32 mm: ~3 mm sopra il fondo corsa esteso (29);
oltre si apre il tappo ed entra acqua.
- TOF_SAFE_RANGE_MAX 85->82 mm: ~3 mm sopra il retratto (79); oltre =
anomalia (passi persi / verso sbagliato).
- TOF_HOMING_THRESHOLD 75->70 mm: lo stop reale cade qualche mm sopra per
polling + conferma + risoluzione grezza, restando sotto MAX.
- nuovo TOF_HOMING_CONFIRM_SAMPLES=2: conferma il trigger di homing per
ignorare campioni rumorosi singoli (stesso pattern di SAFETY_STOP_SAMPLES).
Sostituisce tofMaxExtensionStopReached() con tofGuard() che ritorna un
enum TofGuard {Ok, ExtendLimit, Emergency}, perché i due estremi fisici
richiedono azioni diverse:
- limite inferiore (siringa estesa, vicina al TOF): oltre si apre il
tappo -> ExtendLimit, il chiamante fa uno STOP PULITO senza abortire.
- limite superiore (retratta, lontana): oltre = anomalia -> Emergency.
waitForMotor/moveToMax/_balanceStrokeTo passano tutti dalla guardia
unificata (moveToMax non duplica più la logica TOF inline).
Aggiunge logHomingEvent(): scrive su flash (sopravvive al power-cycle,
in piscina la USB e scollegata) ogni tappa dell'homing - start, timeout,
no-approach/no-detect, settle, backoff, complete - per ricostruire a
posteriori dove l'homing ha sbagliato.
Le fasi di approach e home richiedono ora TOF_HOMING_CONFIRM_SAMPLES
letture consecutive sopra soglia prima di accettare il trigger.
Le routine runSyringeSet/runPidHold/runPidStep ora chiamano tofGuard()
a ogni movimento:
- Emergency -> abort della routine.
- ExtendLimit (saturazione a piena estensione): ferma il pistone e, nelle
routine PID, inibisce ulteriori comandi di estensione (riaprirebbero il
tappo) finche il PID non chiede di RISALIRE verso home.
…scent/ascent)
I nomi precedenti (deepTargetM, shallowTopTargetM, pidTimeoutS,
surfaceOffsetM) erano poco chiari e confondevano durante i test. Rinominati
in inglese con schema orientato alla fase di missione:
deepTargetM -> descentTargetM (target discesa, rif. fondo)
shallowTopTargetM -> ascentTargetM (target risalita, rif. top)
pidTimeoutS -> descentTimeoutS
surfaceOffsetM -> surfaceRestOffsetM
shallowBottomTargetM()-> ascentTargetBottomM()
depthToleranceM, holdTimeS, ascentTimeoutS, profileCount invariati.
Chiavi JSON di formatConfigJson e comando USB PROFILE_SET allineati.
Refactor di soli nomi: ordine/tipi dei campi di ProfileSetPayload e
RuntimeProfileConfig invariati, quindi layout binario e config NVS restano
compatibili (PROFILE_CONFIG_VERSION non incrementato). Nessuna modifica a
logica di controllo, range di validazione o protocollo. Aggiunti commenti
che marcano i 4 parametri essenziali vs i 4 avanzati/safety.
Rinominata la repo da Float_2025 a Float: badge CI e comando cd nel
README, URL repository in lib/DebugSerial/library.json.
La callback di ricezione ESPNOW gira nel task WiFi mentre lastCommand()
viene letto dal loop principale: l'accesso non sincronizzato a _received
poteva restituire una struct lacerata.
- _received ora protetto da portMUX: scrittura in callback e lettura/clear
in sezione critica
- lastCommand() ritorna uno snapshot per valore; aggiornati i call site in
espA che vi legavano un riferimento per evitare dangling sul temporaneo
- sendMessage(): null-termination esplicita dopo strncpy
- estratto runPidLoop() condiviso da runPidHold/runPidStep (logica TOF guard,
deadband e saturazione fondo corsa ora in un solo punto)
…lash
Dal test in piscina del 10/06: un descent timeout di 10 s memorizzato in NVS
troncava ogni fase qualunque fosse l'hold richiesto, senza alcuna traccia
diagnostica (i Debug.println si perdono sott'acqua).
- validateConfig richiede timeout >= hold + 30 s (discesa e risalita): una
config che non lascia spazio all'hold viene rifiutata; al boot una NVS
invalida torna automaticamente ai default di config.h.
- measure() scrive su flash un record phase_start con hold/timeout effettivi
e un record exit_remote_stop/exit_timeout/exit_hold_ok al break che chiude
la fase: il dump post-missione dice quale config ha girato e perche'.
…ta dalla pressione
- _leggi_csv: i .txt salvati dalla GUI (celle con unita' tipo "0.53 m",
nessun header) mandavano pandas in errore; ora ricade sul parser testo che
gia' gestisce tab, unita' e virgole decimali.
- la colonna depth del log di missione cambia riferimento fondo/top al cambio
fase (salto fittizio ~0,5 m): se c'e' la pressione la profondita' viene
ricostruita dal dato grezzo su riferimento FONDO (originale in depth_log).
- stima_u_neutral esclude i campioni a galla: prima il fallback prendeva il
float in superficie e stimava ~0,74 invece del neutro reale ~0,60.
- esempio_log.csv sostituito da esempio_gui_dump.txt (dump GUI reale del test
in piscina); fallback e URL d'esempio aggiornati.
…o automatico delle fasi
Il notebook segmenta il log da solo (colonna phase del firmware o forma
della traiettoria): solo discesa, solo salita o profilo completo, con
metriche e diagnosi per fase e fase forzabile da parametro. Due target
come nella GUI (discesa rif. FONDO, salita rif. TOP convertito +0,51 m
come ascentTargetBottomM), u_neutral stimato su entrambe le quote,
riemersione finale esclusa dalla salita e consigli PID combinati in un
unico set (kp prudente, kd/ki incisivi) come richiede il firmware.
- PID_DESCENT_KICK_U 0.30 → 0.15: con 0.30 + spinta del PID (Kp*error)
la discesa partiva troppo veloce e sfondava il target di oltre 1 m
- velocità/accelerazione/homing motore 1800 → 1400 steps/s
- aggiornato MAC_ESPA alla nuova scheda
Profondità riportata sempre riferita alla CIMA del float, così parte da ~0
in superficie; il controllo resta sul FONDO e il PID porta comunque il fondo
a 2.5 m e la cima a 40 cm. Sosta finale dopo i due profili: tiene la cima a
0.15 m sotto il pelo (antenna sommersa) fino al recupero. FLOAT_LENGTH 0.49,
PROFILE_CONFIG_VERSION 2 per ricaricare i default.
Simulatore HIL da banco (SIM_ON): il barometro e' sostituito da un modello
spinta+drag mosso dalla siringa reale, cosi' PID_STEP/PID_HOLD/GO girano a
secco col motore che si muove davvero. Comandi seriali SIM_ON/SIM_OFF/SIM_GET/
SIM_CONFIG e GO (missione completa da seriale per test end-to-end al banco);
missione fattorizzata in runVerticalProfiles()/attemptAutoRecovery().
Rifà il control core del float in Model-Based Software Design, testabile in
simulazione lungo la V (MIL -> SIL), senza toccare il firmware di gara (espA).
Aggiunge:
- lib/float_core: control core portabile in C puro, tick-based e senza I/O
(float_core_step). Riproduce missione/PID/safety/homing come funzione pura;
e' la specifica di riferimento per il modello.
- tools/mil: harness MIL nativo (plant 2o ordine + main) che chiude l'anello
su PC e verifica i requisiti MATE. Compila con gcc, senza MATLAB.
- model/: mondo Simulink/Stateflow. chart float_core (float_core_ref.slx),
plant + testbench (float_mil.slx), parametri/plant/harness (.m), spec del
chart (STATEFLOW_CHART.md), diagrammi. Equivalenza MIL vs float_core.c
certificata; SIL: codice generato da Embedded Coder identico al modello.
- src/espA_core (+ env in platformio.ini): firmware sperimentale che fa da
scheduler HAL attorno al core, parallelo a espA (non lo tocca).
Tuning della risalita fatto in MIL (PASS) e congelato in config.h:
PID_U_MIN 0.0 -> 0.20 e TARGET_SHALLOW_TOP_DEPTH 0.40 -> 0.65 (margine
anti-rottura superficie, dentro tolleranza MATE).
.gitignore: esclude gli artefatti di build Simulink/Embedded Coder (slprj/,
*.slxc, *_ert_rtw/, ...); si versionano solo i sorgenti.
Sign up for freeto 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.

2 participants

@Skeitt@Davide-Colabella
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: update float ESP-NOW runtime flow - #5

Open
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow
Open

feat: update float ESP-NOW runtime flow#5
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow

Conversation

@Skeitt

Copy link
Copy Markdown
Contributor
  • add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support

  • log and stream normalized syringe position alongside pressure and float depth

  • require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts

  • update firmware docs and protocol/parser/storage tests for the current command contract

Skeittand others added 22 commits May 29, 2026 22:57
- add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support
- log and stream normalized syringe position alongside pressure and float depth
- require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts
- update firmware docs and protocol/parser/storage tests for the current command contract
home (motor_pos=0) pushes water out → floats; negative positions move
toward the TOF and take on water → sinks. Simplify uToMotorPos to map
u in [0,1] onto negative travel, dropping the unused MOTOR_INVERT_LOGICAL
flag, and update balance() comments accordingly.
Mission log survives a power-cycle (cleared only at the start of a new
mission, no longer at boot), so a failed pool test can be read afterwards
over USB. Add a DUMP_LOG serial command to re-read it on demand without
clearing — solves the case where the monitor connects after boot.
emergencyStop() records reason + TOF distance to the flash log the instant
it fires (idempotent single write) instead of a posteriori in loop(), where
it was missed when measure() exited on phase timeout. Expose
lastStopReason()/lastStopTofMm(). TOF safety now debounces with
TOF_SAFETY_STOP_SAMPLES consecutive out-of-range reads to ignore single
glitches (bubbles, reflections) in water, and clamps the PID output to
[PID_U_MIN, PID_U_MAX] so the syringe never reaches the TOF safety limits.
Enable monitor echo + LF EOL so serial commands are visible and terminated
as the parser expects.
Default PID gains iterated against pool tests: Kp 0.17 was too weak (the
float barely moved), Kp 2.0 / Kd 0.13 oscillated (±15cm pumping). Settle on
Kp 1.0 / Ki 0 / Kd 0.5 as a damped starting point; fine-tune further at
runtime via PID_CONFIG_SET.
Reduce the descent kick-start from u=0.979 (near-full syringe, which drove
the float straight to the bottom before the PID could brake, ~26cm
overshoot) to PID_DESCENT_KICK_U=0.30, so the descent starts gently and the
PID takes over before overshooting the target.
Validated in the pool: the float converges on the target depth with ~±1cm
final oscillation. Ki vinces the buoyancy offset (recovery), Kd damps the
overshoot. Replaces the previous 1.0/0/0.5 starting point.
Il manual keyboard serve a riallineare un pistone disallineato, quindi
deve poter uscire dal range nominale (±MAX-margin) in entrambi i versi.
- motor: nuovo startJogStepsUnclamped() che bypassa volutamente il clamp
software dei fine corsa (startMoveTo/startMoveSteps restano clampati per
il firmware di missione).
- manual keyboard: startHoldMove() ora fa un jog relativo di ~2 mm
(MANUAL_JOG_STEPS) rinnovato mentre il tasto è tenuto premuto, con stop
manuale (space/x).
- rimosso l'auto-stop su distanza TOF: usava una costante rimossa e la
vecchia convenzione geometrica (estensione=positivo), incoerente con
l'homing attuale.
…iscina
Soglie tarate sulla distanza TOF reale (corretta dell'offset 6 mm):
pistone esteso ~29 mm, retratto ~79 mm, pendenza ~1.1 mm TOF/mm motore.
- TOF_SAFE_RANGE_MIN 40->32 mm: ~3 mm sopra il fondo corsa esteso (29);
oltre si apre il tappo ed entra acqua.
- TOF_SAFE_RANGE_MAX 85->82 mm: ~3 mm sopra il retratto (79); oltre =
anomalia (passi persi / verso sbagliato).
- TOF_HOMING_THRESHOLD 75->70 mm: lo stop reale cade qualche mm sopra per
polling + conferma + risoluzione grezza, restando sotto MAX.
- nuovo TOF_HOMING_CONFIRM_SAMPLES=2: conferma il trigger di homing per
ignorare campioni rumorosi singoli (stesso pattern di SAFETY_STOP_SAMPLES).
Sostituisce tofMaxExtensionStopReached() con tofGuard() che ritorna un
enum TofGuard {Ok, ExtendLimit, Emergency}, perché i due estremi fisici
richiedono azioni diverse:
- limite inferiore (siringa estesa, vicina al TOF): oltre si apre il
tappo -> ExtendLimit, il chiamante fa uno STOP PULITO senza abortire.
- limite superiore (retratta, lontana): oltre = anomalia -> Emergency.
waitForMotor/moveToMax/_balanceStrokeTo passano tutti dalla guardia
unificata (moveToMax non duplica più la logica TOF inline).
Aggiunge logHomingEvent(): scrive su flash (sopravvive al power-cycle,
in piscina la USB e scollegata) ogni tappa dell'homing - start, timeout,
no-approach/no-detect, settle, backoff, complete - per ricostruire a
posteriori dove l'homing ha sbagliato.
Le fasi di approach e home richiedono ora TOF_HOMING_CONFIRM_SAMPLES
letture consecutive sopra soglia prima di accettare il trigger.
Le routine runSyringeSet/runPidHold/runPidStep ora chiamano tofGuard()
a ogni movimento:
- Emergency -> abort della routine.
- ExtendLimit (saturazione a piena estensione): ferma il pistone e, nelle
routine PID, inibisce ulteriori comandi di estensione (riaprirebbero il
tappo) finche il PID non chiede di RISALIRE verso home.
…scent/ascent)
I nomi precedenti (deepTargetM, shallowTopTargetM, pidTimeoutS,
surfaceOffsetM) erano poco chiari e confondevano durante i test. Rinominati
in inglese con schema orientato alla fase di missione:
deepTargetM -> descentTargetM (target discesa, rif. fondo)
shallowTopTargetM -> ascentTargetM (target risalita, rif. top)
pidTimeoutS -> descentTimeoutS
surfaceOffsetM -> surfaceRestOffsetM
shallowBottomTargetM()-> ascentTargetBottomM()
depthToleranceM, holdTimeS, ascentTimeoutS, profileCount invariati.
Chiavi JSON di formatConfigJson e comando USB PROFILE_SET allineati.
Refactor di soli nomi: ordine/tipi dei campi di ProfileSetPayload e
RuntimeProfileConfig invariati, quindi layout binario e config NVS restano
compatibili (PROFILE_CONFIG_VERSION non incrementato). Nessuna modifica a
logica di controllo, range di validazione o protocollo. Aggiunti commenti
che marcano i 4 parametri essenziali vs i 4 avanzati/safety.
Rinominata la repo da Float_2025 a Float: badge CI e comando cd nel
README, URL repository in lib/DebugSerial/library.json.
La callback di ricezione ESPNOW gira nel task WiFi mentre lastCommand()
viene letto dal loop principale: l'accesso non sincronizzato a _received
poteva restituire una struct lacerata.
- _received ora protetto da portMUX: scrittura in callback e lettura/clear
in sezione critica
- lastCommand() ritorna uno snapshot per valore; aggiornati i call site in
espA che vi legavano un riferimento per evitare dangling sul temporaneo
- sendMessage(): null-termination esplicita dopo strncpy
- estratto runPidLoop() condiviso da runPidHold/runPidStep (logica TOF guard,
deadband e saturazione fondo corsa ora in un solo punto)
…lash
Dal test in piscina del 10/06: un descent timeout di 10 s memorizzato in NVS
troncava ogni fase qualunque fosse l'hold richiesto, senza alcuna traccia
diagnostica (i Debug.println si perdono sott'acqua).
- validateConfig richiede timeout >= hold + 30 s (discesa e risalita): una
config che non lascia spazio all'hold viene rifiutata; al boot una NVS
invalida torna automaticamente ai default di config.h.
- measure() scrive su flash un record phase_start con hold/timeout effettivi
e un record exit_remote_stop/exit_timeout/exit_hold_ok al break che chiude
la fase: il dump post-missione dice quale config ha girato e perche'.
…ta dalla pressione
- _leggi_csv: i .txt salvati dalla GUI (celle con unita' tipo "0.53 m",
nessun header) mandavano pandas in errore; ora ricade sul parser testo che
gia' gestisce tab, unita' e virgole decimali.
- la colonna depth del log di missione cambia riferimento fondo/top al cambio
fase (salto fittizio ~0,5 m): se c'e' la pressione la profondita' viene
ricostruita dal dato grezzo su riferimento FONDO (originale in depth_log).
- stima_u_neutral esclude i campioni a galla: prima il fallback prendeva il
float in superficie e stimava ~0,74 invece del neutro reale ~0,60.
- esempio_log.csv sostituito da esempio_gui_dump.txt (dump GUI reale del test
in piscina); fallback e URL d'esempio aggiornati.
…o automatico delle fasi
Il notebook segmenta il log da solo (colonna phase del firmware o forma
della traiettoria): solo discesa, solo salita o profilo completo, con
metriche e diagnosi per fase e fase forzabile da parametro. Due target
come nella GUI (discesa rif. FONDO, salita rif. TOP convertito +0,51 m
come ascentTargetBottomM), u_neutral stimato su entrambe le quote,
riemersione finale esclusa dalla salita e consigli PID combinati in un
unico set (kp prudente, kd/ki incisivi) come richiede il firmware.
- PID_DESCENT_KICK_U 0.30 → 0.15: con 0.30 + spinta del PID (Kp*error)
la discesa partiva troppo veloce e sfondava il target di oltre 1 m
- velocità/accelerazione/homing motore 1800 → 1400 steps/s
- aggiornato MAC_ESPA alla nuova scheda
Profondità riportata sempre riferita alla CIMA del float, così parte da ~0
in superficie; il controllo resta sul FONDO e il PID porta comunque il fondo
a 2.5 m e la cima a 40 cm. Sosta finale dopo i due profili: tiene la cima a
0.15 m sotto il pelo (antenna sommersa) fino al recupero. FLOAT_LENGTH 0.49,
PROFILE_CONFIG_VERSION 2 per ricaricare i default.
Simulatore HIL da banco (SIM_ON): il barometro e' sostituito da un modello
spinta+drag mosso dalla siringa reale, cosi' PID_STEP/PID_HOLD/GO girano a
secco col motore che si muove davvero. Comandi seriali SIM_ON/SIM_OFF/SIM_GET/
SIM_CONFIG e GO (missione completa da seriale per test end-to-end al banco);
missione fattorizzata in runVerticalProfiles()/attemptAutoRecovery().
Rifà il control core del float in Model-Based Software Design, testabile in
simulazione lungo la V (MIL -> SIL), senza toccare il firmware di gara (espA).
Aggiunge:
- lib/float_core: control core portabile in C puro, tick-based e senza I/O
(float_core_step). Riproduce missione/PID/safety/homing come funzione pura;
e' la specifica di riferimento per il modello.
- tools/mil: harness MIL nativo (plant 2o ordine + main) che chiude l'anello
su PC e verifica i requisiti MATE. Compila con gcc, senza MATLAB.
- model/: mondo Simulink/Stateflow. chart float_core (float_core_ref.slx),
plant + testbench (float_mil.slx), parametri/plant/harness (.m), spec del
chart (STATEFLOW_CHART.md), diagrammi. Equivalenza MIL vs float_core.c
certificata; SIL: codice generato da Embedded Coder identico al modello.
- src/espA_core (+ env in platformio.ini): firmware sperimentale che fa da
scheduler HAL attorno al core, parallelo a espA (non lo tocca).
Tuning della risalita fatto in MIL (PASS) e congelato in config.h:
PID_U_MIN 0.0 -> 0.20 e TARGET_SHALLOW_TOP_DEPTH 0.40 -> 0.65 (margine
anti-rottura superficie, dentro tolleranza MATE).
.gitignore: esclude gli artefatti di build Simulink/Embedded Coder (slprj/,
*.slxc, *_ert_rtw/, ...); si versionano solo i sorgenti.
Sign up for freeto 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.

2 participants

@Skeitt@Davide-Colabella
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: update float ESP-NOW runtime flow - #5

Open
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow
Open

feat: update float ESP-NOW runtime flow#5
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow

Conversation

@Skeitt

Copy link
Copy Markdown
Contributor
  • add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support

  • log and stream normalized syringe position alongside pressure and float depth

  • require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts

  • update firmware docs and protocol/parser/storage tests for the current command contract

Skeittand others added 22 commits May 29, 2026 22:57
- add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support
- log and stream normalized syringe position alongside pressure and float depth
- require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts
- update firmware docs and protocol/parser/storage tests for the current command contract
home (motor_pos=0) pushes water out → floats; negative positions move
toward the TOF and take on water → sinks. Simplify uToMotorPos to map
u in [0,1] onto negative travel, dropping the unused MOTOR_INVERT_LOGICAL
flag, and update balance() comments accordingly.
Mission log survives a power-cycle (cleared only at the start of a new
mission, no longer at boot), so a failed pool test can be read afterwards
over USB. Add a DUMP_LOG serial command to re-read it on demand without
clearing — solves the case where the monitor connects after boot.
emergencyStop() records reason + TOF distance to the flash log the instant
it fires (idempotent single write) instead of a posteriori in loop(), where
it was missed when measure() exited on phase timeout. Expose
lastStopReason()/lastStopTofMm(). TOF safety now debounces with
TOF_SAFETY_STOP_SAMPLES consecutive out-of-range reads to ignore single
glitches (bubbles, reflections) in water, and clamps the PID output to
[PID_U_MIN, PID_U_MAX] so the syringe never reaches the TOF safety limits.
Enable monitor echo + LF EOL so serial commands are visible and terminated
as the parser expects.
Default PID gains iterated against pool tests: Kp 0.17 was too weak (the
float barely moved), Kp 2.0 / Kd 0.13 oscillated (±15cm pumping). Settle on
Kp 1.0 / Ki 0 / Kd 0.5 as a damped starting point; fine-tune further at
runtime via PID_CONFIG_SET.
Reduce the descent kick-start from u=0.979 (near-full syringe, which drove
the float straight to the bottom before the PID could brake, ~26cm
overshoot) to PID_DESCENT_KICK_U=0.30, so the descent starts gently and the
PID takes over before overshooting the target.
Validated in the pool: the float converges on the target depth with ~±1cm
final oscillation. Ki vinces the buoyancy offset (recovery), Kd damps the
overshoot. Replaces the previous 1.0/0/0.5 starting point.
Il manual keyboard serve a riallineare un pistone disallineato, quindi
deve poter uscire dal range nominale (±MAX-margin) in entrambi i versi.
- motor: nuovo startJogStepsUnclamped() che bypassa volutamente il clamp
software dei fine corsa (startMoveTo/startMoveSteps restano clampati per
il firmware di missione).
- manual keyboard: startHoldMove() ora fa un jog relativo di ~2 mm
(MANUAL_JOG_STEPS) rinnovato mentre il tasto è tenuto premuto, con stop
manuale (space/x).
- rimosso l'auto-stop su distanza TOF: usava una costante rimossa e la
vecchia convenzione geometrica (estensione=positivo), incoerente con
l'homing attuale.
…iscina
Soglie tarate sulla distanza TOF reale (corretta dell'offset 6 mm):
pistone esteso ~29 mm, retratto ~79 mm, pendenza ~1.1 mm TOF/mm motore.
- TOF_SAFE_RANGE_MIN 40->32 mm: ~3 mm sopra il fondo corsa esteso (29);
oltre si apre il tappo ed entra acqua.
- TOF_SAFE_RANGE_MAX 85->82 mm: ~3 mm sopra il retratto (79); oltre =
anomalia (passi persi / verso sbagliato).
- TOF_HOMING_THRESHOLD 75->70 mm: lo stop reale cade qualche mm sopra per
polling + conferma + risoluzione grezza, restando sotto MAX.
- nuovo TOF_HOMING_CONFIRM_SAMPLES=2: conferma il trigger di homing per
ignorare campioni rumorosi singoli (stesso pattern di SAFETY_STOP_SAMPLES).
Sostituisce tofMaxExtensionStopReached() con tofGuard() che ritorna un
enum TofGuard {Ok, ExtendLimit, Emergency}, perché i due estremi fisici
richiedono azioni diverse:
- limite inferiore (siringa estesa, vicina al TOF): oltre si apre il
tappo -> ExtendLimit, il chiamante fa uno STOP PULITO senza abortire.
- limite superiore (retratta, lontana): oltre = anomalia -> Emergency.
waitForMotor/moveToMax/_balanceStrokeTo passano tutti dalla guardia
unificata (moveToMax non duplica più la logica TOF inline).
Aggiunge logHomingEvent(): scrive su flash (sopravvive al power-cycle,
in piscina la USB e scollegata) ogni tappa dell'homing - start, timeout,
no-approach/no-detect, settle, backoff, complete - per ricostruire a
posteriori dove l'homing ha sbagliato.
Le fasi di approach e home richiedono ora TOF_HOMING_CONFIRM_SAMPLES
letture consecutive sopra soglia prima di accettare il trigger.
Le routine runSyringeSet/runPidHold/runPidStep ora chiamano tofGuard()
a ogni movimento:
- Emergency -> abort della routine.
- ExtendLimit (saturazione a piena estensione): ferma il pistone e, nelle
routine PID, inibisce ulteriori comandi di estensione (riaprirebbero il
tappo) finche il PID non chiede di RISALIRE verso home.
…scent/ascent)
I nomi precedenti (deepTargetM, shallowTopTargetM, pidTimeoutS,
surfaceOffsetM) erano poco chiari e confondevano durante i test. Rinominati
in inglese con schema orientato alla fase di missione:
deepTargetM -> descentTargetM (target discesa, rif. fondo)
shallowTopTargetM -> ascentTargetM (target risalita, rif. top)
pidTimeoutS -> descentTimeoutS
surfaceOffsetM -> surfaceRestOffsetM
shallowBottomTargetM()-> ascentTargetBottomM()
depthToleranceM, holdTimeS, ascentTimeoutS, profileCount invariati.
Chiavi JSON di formatConfigJson e comando USB PROFILE_SET allineati.
Refactor di soli nomi: ordine/tipi dei campi di ProfileSetPayload e
RuntimeProfileConfig invariati, quindi layout binario e config NVS restano
compatibili (PROFILE_CONFIG_VERSION non incrementato). Nessuna modifica a
logica di controllo, range di validazione o protocollo. Aggiunti commenti
che marcano i 4 parametri essenziali vs i 4 avanzati/safety.
Rinominata la repo da Float_2025 a Float: badge CI e comando cd nel
README, URL repository in lib/DebugSerial/library.json.
La callback di ricezione ESPNOW gira nel task WiFi mentre lastCommand()
viene letto dal loop principale: l'accesso non sincronizzato a _received
poteva restituire una struct lacerata.
- _received ora protetto da portMUX: scrittura in callback e lettura/clear
in sezione critica
- lastCommand() ritorna uno snapshot per valore; aggiornati i call site in
espA che vi legavano un riferimento per evitare dangling sul temporaneo
- sendMessage(): null-termination esplicita dopo strncpy
- estratto runPidLoop() condiviso da runPidHold/runPidStep (logica TOF guard,
deadband e saturazione fondo corsa ora in un solo punto)
…lash
Dal test in piscina del 10/06: un descent timeout di 10 s memorizzato in NVS
troncava ogni fase qualunque fosse l'hold richiesto, senza alcuna traccia
diagnostica (i Debug.println si perdono sott'acqua).
- validateConfig richiede timeout >= hold + 30 s (discesa e risalita): una
config che non lascia spazio all'hold viene rifiutata; al boot una NVS
invalida torna automaticamente ai default di config.h.
- measure() scrive su flash un record phase_start con hold/timeout effettivi
e un record exit_remote_stop/exit_timeout/exit_hold_ok al break che chiude
la fase: il dump post-missione dice quale config ha girato e perche'.
…ta dalla pressione
- _leggi_csv: i .txt salvati dalla GUI (celle con unita' tipo "0.53 m",
nessun header) mandavano pandas in errore; ora ricade sul parser testo che
gia' gestisce tab, unita' e virgole decimali.
- la colonna depth del log di missione cambia riferimento fondo/top al cambio
fase (salto fittizio ~0,5 m): se c'e' la pressione la profondita' viene
ricostruita dal dato grezzo su riferimento FONDO (originale in depth_log).
- stima_u_neutral esclude i campioni a galla: prima il fallback prendeva il
float in superficie e stimava ~0,74 invece del neutro reale ~0,60.
- esempio_log.csv sostituito da esempio_gui_dump.txt (dump GUI reale del test
in piscina); fallback e URL d'esempio aggiornati.
…o automatico delle fasi
Il notebook segmenta il log da solo (colonna phase del firmware o forma
della traiettoria): solo discesa, solo salita o profilo completo, con
metriche e diagnosi per fase e fase forzabile da parametro. Due target
come nella GUI (discesa rif. FONDO, salita rif. TOP convertito +0,51 m
come ascentTargetBottomM), u_neutral stimato su entrambe le quote,
riemersione finale esclusa dalla salita e consigli PID combinati in un
unico set (kp prudente, kd/ki incisivi) come richiede il firmware.
- PID_DESCENT_KICK_U 0.30 → 0.15: con 0.30 + spinta del PID (Kp*error)
la discesa partiva troppo veloce e sfondava il target di oltre 1 m
- velocità/accelerazione/homing motore 1800 → 1400 steps/s
- aggiornato MAC_ESPA alla nuova scheda
Profondità riportata sempre riferita alla CIMA del float, così parte da ~0
in superficie; il controllo resta sul FONDO e il PID porta comunque il fondo
a 2.5 m e la cima a 40 cm. Sosta finale dopo i due profili: tiene la cima a
0.15 m sotto il pelo (antenna sommersa) fino al recupero. FLOAT_LENGTH 0.49,
PROFILE_CONFIG_VERSION 2 per ricaricare i default.
Simulatore HIL da banco (SIM_ON): il barometro e' sostituito da un modello
spinta+drag mosso dalla siringa reale, cosi' PID_STEP/PID_HOLD/GO girano a
secco col motore che si muove davvero. Comandi seriali SIM_ON/SIM_OFF/SIM_GET/
SIM_CONFIG e GO (missione completa da seriale per test end-to-end al banco);
missione fattorizzata in runVerticalProfiles()/attemptAutoRecovery().
Rifà il control core del float in Model-Based Software Design, testabile in
simulazione lungo la V (MIL -> SIL), senza toccare il firmware di gara (espA).
Aggiunge:
- lib/float_core: control core portabile in C puro, tick-based e senza I/O
(float_core_step). Riproduce missione/PID/safety/homing come funzione pura;
e' la specifica di riferimento per il modello.
- tools/mil: harness MIL nativo (plant 2o ordine + main) che chiude l'anello
su PC e verifica i requisiti MATE. Compila con gcc, senza MATLAB.
- model/: mondo Simulink/Stateflow. chart float_core (float_core_ref.slx),
plant + testbench (float_mil.slx), parametri/plant/harness (.m), spec del
chart (STATEFLOW_CHART.md), diagrammi. Equivalenza MIL vs float_core.c
certificata; SIL: codice generato da Embedded Coder identico al modello.
- src/espA_core (+ env in platformio.ini): firmware sperimentale che fa da
scheduler HAL attorno al core, parallelo a espA (non lo tocca).
Tuning della risalita fatto in MIL (PASS) e congelato in config.h:
PID_U_MIN 0.0 -> 0.20 e TARGET_SHALLOW_TOP_DEPTH 0.40 -> 0.65 (margine
anti-rottura superficie, dentro tolleranza MATE).
.gitignore: esclude gli artefatti di build Simulink/Embedded Coder (slprj/,
*.slxc, *_ert_rtw/, ...); si versionano solo i sorgenti.
Sign up for freeto 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.

2 participants

@Skeitt@Davide-Colabella
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: update float ESP-NOW runtime flow - #5

Open
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow
Open

feat: update float ESP-NOW runtime flow#5
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow

Conversation

@Skeitt

Copy link
Copy Markdown
Contributor
  • add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support

  • log and stream normalized syringe position alongside pressure and float depth

  • require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts

  • update firmware docs and protocol/parser/storage tests for the current command contract

Skeittand others added 22 commits May 29, 2026 22:57
- add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support
- log and stream normalized syringe position alongside pressure and float depth
- require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts
- update firmware docs and protocol/parser/storage tests for the current command contract
home (motor_pos=0) pushes water out → floats; negative positions move
toward the TOF and take on water → sinks. Simplify uToMotorPos to map
u in [0,1] onto negative travel, dropping the unused MOTOR_INVERT_LOGICAL
flag, and update balance() comments accordingly.
Mission log survives a power-cycle (cleared only at the start of a new
mission, no longer at boot), so a failed pool test can be read afterwards
over USB. Add a DUMP_LOG serial command to re-read it on demand without
clearing — solves the case where the monitor connects after boot.
emergencyStop() records reason + TOF distance to the flash log the instant
it fires (idempotent single write) instead of a posteriori in loop(), where
it was missed when measure() exited on phase timeout. Expose
lastStopReason()/lastStopTofMm(). TOF safety now debounces with
TOF_SAFETY_STOP_SAMPLES consecutive out-of-range reads to ignore single
glitches (bubbles, reflections) in water, and clamps the PID output to
[PID_U_MIN, PID_U_MAX] so the syringe never reaches the TOF safety limits.
Enable monitor echo + LF EOL so serial commands are visible and terminated
as the parser expects.
Default PID gains iterated against pool tests: Kp 0.17 was too weak (the
float barely moved), Kp 2.0 / Kd 0.13 oscillated (±15cm pumping). Settle on
Kp 1.0 / Ki 0 / Kd 0.5 as a damped starting point; fine-tune further at
runtime via PID_CONFIG_SET.
Reduce the descent kick-start from u=0.979 (near-full syringe, which drove
the float straight to the bottom before the PID could brake, ~26cm
overshoot) to PID_DESCENT_KICK_U=0.30, so the descent starts gently and the
PID takes over before overshooting the target.
Validated in the pool: the float converges on the target depth with ~±1cm
final oscillation. Ki vinces the buoyancy offset (recovery), Kd damps the
overshoot. Replaces the previous 1.0/0/0.5 starting point.
Il manual keyboard serve a riallineare un pistone disallineato, quindi
deve poter uscire dal range nominale (±MAX-margin) in entrambi i versi.
- motor: nuovo startJogStepsUnclamped() che bypassa volutamente il clamp
software dei fine corsa (startMoveTo/startMoveSteps restano clampati per
il firmware di missione).
- manual keyboard: startHoldMove() ora fa un jog relativo di ~2 mm
(MANUAL_JOG_STEPS) rinnovato mentre il tasto è tenuto premuto, con stop
manuale (space/x).
- rimosso l'auto-stop su distanza TOF: usava una costante rimossa e la
vecchia convenzione geometrica (estensione=positivo), incoerente con
l'homing attuale.
…iscina
Soglie tarate sulla distanza TOF reale (corretta dell'offset 6 mm):
pistone esteso ~29 mm, retratto ~79 mm, pendenza ~1.1 mm TOF/mm motore.
- TOF_SAFE_RANGE_MIN 40->32 mm: ~3 mm sopra il fondo corsa esteso (29);
oltre si apre il tappo ed entra acqua.
- TOF_SAFE_RANGE_MAX 85->82 mm: ~3 mm sopra il retratto (79); oltre =
anomalia (passi persi / verso sbagliato).
- TOF_HOMING_THRESHOLD 75->70 mm: lo stop reale cade qualche mm sopra per
polling + conferma + risoluzione grezza, restando sotto MAX.
- nuovo TOF_HOMING_CONFIRM_SAMPLES=2: conferma il trigger di homing per
ignorare campioni rumorosi singoli (stesso pattern di SAFETY_STOP_SAMPLES).
Sostituisce tofMaxExtensionStopReached() con tofGuard() che ritorna un
enum TofGuard {Ok, ExtendLimit, Emergency}, perché i due estremi fisici
richiedono azioni diverse:
- limite inferiore (siringa estesa, vicina al TOF): oltre si apre il
tappo -> ExtendLimit, il chiamante fa uno STOP PULITO senza abortire.
- limite superiore (retratta, lontana): oltre = anomalia -> Emergency.
waitForMotor/moveToMax/_balanceStrokeTo passano tutti dalla guardia
unificata (moveToMax non duplica più la logica TOF inline).
Aggiunge logHomingEvent(): scrive su flash (sopravvive al power-cycle,
in piscina la USB e scollegata) ogni tappa dell'homing - start, timeout,
no-approach/no-detect, settle, backoff, complete - per ricostruire a
posteriori dove l'homing ha sbagliato.
Le fasi di approach e home richiedono ora TOF_HOMING_CONFIRM_SAMPLES
letture consecutive sopra soglia prima di accettare il trigger.
Le routine runSyringeSet/runPidHold/runPidStep ora chiamano tofGuard()
a ogni movimento:
- Emergency -> abort della routine.
- ExtendLimit (saturazione a piena estensione): ferma il pistone e, nelle
routine PID, inibisce ulteriori comandi di estensione (riaprirebbero il
tappo) finche il PID non chiede di RISALIRE verso home.
…scent/ascent)
I nomi precedenti (deepTargetM, shallowTopTargetM, pidTimeoutS,
surfaceOffsetM) erano poco chiari e confondevano durante i test. Rinominati
in inglese con schema orientato alla fase di missione:
deepTargetM -> descentTargetM (target discesa, rif. fondo)
shallowTopTargetM -> ascentTargetM (target risalita, rif. top)
pidTimeoutS -> descentTimeoutS
surfaceOffsetM -> surfaceRestOffsetM
shallowBottomTargetM()-> ascentTargetBottomM()
depthToleranceM, holdTimeS, ascentTimeoutS, profileCount invariati.
Chiavi JSON di formatConfigJson e comando USB PROFILE_SET allineati.
Refactor di soli nomi: ordine/tipi dei campi di ProfileSetPayload e
RuntimeProfileConfig invariati, quindi layout binario e config NVS restano
compatibili (PROFILE_CONFIG_VERSION non incrementato). Nessuna modifica a
logica di controllo, range di validazione o protocollo. Aggiunti commenti
che marcano i 4 parametri essenziali vs i 4 avanzati/safety.
Rinominata la repo da Float_2025 a Float: badge CI e comando cd nel
README, URL repository in lib/DebugSerial/library.json.
La callback di ricezione ESPNOW gira nel task WiFi mentre lastCommand()
viene letto dal loop principale: l'accesso non sincronizzato a _received
poteva restituire una struct lacerata.
- _received ora protetto da portMUX: scrittura in callback e lettura/clear
in sezione critica
- lastCommand() ritorna uno snapshot per valore; aggiornati i call site in
espA che vi legavano un riferimento per evitare dangling sul temporaneo
- sendMessage(): null-termination esplicita dopo strncpy
- estratto runPidLoop() condiviso da runPidHold/runPidStep (logica TOF guard,
deadband e saturazione fondo corsa ora in un solo punto)
…lash
Dal test in piscina del 10/06: un descent timeout di 10 s memorizzato in NVS
troncava ogni fase qualunque fosse l'hold richiesto, senza alcuna traccia
diagnostica (i Debug.println si perdono sott'acqua).
- validateConfig richiede timeout >= hold + 30 s (discesa e risalita): una
config che non lascia spazio all'hold viene rifiutata; al boot una NVS
invalida torna automaticamente ai default di config.h.
- measure() scrive su flash un record phase_start con hold/timeout effettivi
e un record exit_remote_stop/exit_timeout/exit_hold_ok al break che chiude
la fase: il dump post-missione dice quale config ha girato e perche'.
…ta dalla pressione
- _leggi_csv: i .txt salvati dalla GUI (celle con unita' tipo "0.53 m",
nessun header) mandavano pandas in errore; ora ricade sul parser testo che
gia' gestisce tab, unita' e virgole decimali.
- la colonna depth del log di missione cambia riferimento fondo/top al cambio
fase (salto fittizio ~0,5 m): se c'e' la pressione la profondita' viene
ricostruita dal dato grezzo su riferimento FONDO (originale in depth_log).
- stima_u_neutral esclude i campioni a galla: prima il fallback prendeva il
float in superficie e stimava ~0,74 invece del neutro reale ~0,60.
- esempio_log.csv sostituito da esempio_gui_dump.txt (dump GUI reale del test
in piscina); fallback e URL d'esempio aggiornati.
…o automatico delle fasi
Il notebook segmenta il log da solo (colonna phase del firmware o forma
della traiettoria): solo discesa, solo salita o profilo completo, con
metriche e diagnosi per fase e fase forzabile da parametro. Due target
come nella GUI (discesa rif. FONDO, salita rif. TOP convertito +0,51 m
come ascentTargetBottomM), u_neutral stimato su entrambe le quote,
riemersione finale esclusa dalla salita e consigli PID combinati in un
unico set (kp prudente, kd/ki incisivi) come richiede il firmware.
- PID_DESCENT_KICK_U 0.30 → 0.15: con 0.30 + spinta del PID (Kp*error)
la discesa partiva troppo veloce e sfondava il target di oltre 1 m
- velocità/accelerazione/homing motore 1800 → 1400 steps/s
- aggiornato MAC_ESPA alla nuova scheda
Profondità riportata sempre riferita alla CIMA del float, così parte da ~0
in superficie; il controllo resta sul FONDO e il PID porta comunque il fondo
a 2.5 m e la cima a 40 cm. Sosta finale dopo i due profili: tiene la cima a
0.15 m sotto il pelo (antenna sommersa) fino al recupero. FLOAT_LENGTH 0.49,
PROFILE_CONFIG_VERSION 2 per ricaricare i default.
Simulatore HIL da banco (SIM_ON): il barometro e' sostituito da un modello
spinta+drag mosso dalla siringa reale, cosi' PID_STEP/PID_HOLD/GO girano a
secco col motore che si muove davvero. Comandi seriali SIM_ON/SIM_OFF/SIM_GET/
SIM_CONFIG e GO (missione completa da seriale per test end-to-end al banco);
missione fattorizzata in runVerticalProfiles()/attemptAutoRecovery().
Rifà il control core del float in Model-Based Software Design, testabile in
simulazione lungo la V (MIL -> SIL), senza toccare il firmware di gara (espA).
Aggiunge:
- lib/float_core: control core portabile in C puro, tick-based e senza I/O
(float_core_step). Riproduce missione/PID/safety/homing come funzione pura;
e' la specifica di riferimento per il modello.
- tools/mil: harness MIL nativo (plant 2o ordine + main) che chiude l'anello
su PC e verifica i requisiti MATE. Compila con gcc, senza MATLAB.
- model/: mondo Simulink/Stateflow. chart float_core (float_core_ref.slx),
plant + testbench (float_mil.slx), parametri/plant/harness (.m), spec del
chart (STATEFLOW_CHART.md), diagrammi. Equivalenza MIL vs float_core.c
certificata; SIL: codice generato da Embedded Coder identico al modello.
- src/espA_core (+ env in platformio.ini): firmware sperimentale che fa da
scheduler HAL attorno al core, parallelo a espA (non lo tocca).
Tuning della risalita fatto in MIL (PASS) e congelato in config.h:
PID_U_MIN 0.0 -> 0.20 e TARGET_SHALLOW_TOP_DEPTH 0.40 -> 0.65 (margine
anti-rottura superficie, dentro tolleranza MATE).
.gitignore: esclude gli artefatti di build Simulink/Embedded Coder (slprj/,
*.slxc, *_ert_rtw/, ...); si versionano solo i sorgenti.
Sign up for freeto 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.

2 participants

@Skeitt@Davide-Colabella
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: update float ESP-NOW runtime flow - #5

Open
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow
Open

feat: update float ESP-NOW runtime flow#5
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow

Conversation

@Skeitt

Copy link
Copy Markdown
Contributor
  • add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support

  • log and stream normalized syringe position alongside pressure and float depth

  • require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts

  • update firmware docs and protocol/parser/storage tests for the current command contract

Skeittand others added 22 commits May 29, 2026 22:57
- add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support
- log and stream normalized syringe position alongside pressure and float depth
- require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts
- update firmware docs and protocol/parser/storage tests for the current command contract
home (motor_pos=0) pushes water out → floats; negative positions move
toward the TOF and take on water → sinks. Simplify uToMotorPos to map
u in [0,1] onto negative travel, dropping the unused MOTOR_INVERT_LOGICAL
flag, and update balance() comments accordingly.
Mission log survives a power-cycle (cleared only at the start of a new
mission, no longer at boot), so a failed pool test can be read afterwards
over USB. Add a DUMP_LOG serial command to re-read it on demand without
clearing — solves the case where the monitor connects after boot.
emergencyStop() records reason + TOF distance to the flash log the instant
it fires (idempotent single write) instead of a posteriori in loop(), where
it was missed when measure() exited on phase timeout. Expose
lastStopReason()/lastStopTofMm(). TOF safety now debounces with
TOF_SAFETY_STOP_SAMPLES consecutive out-of-range reads to ignore single
glitches (bubbles, reflections) in water, and clamps the PID output to
[PID_U_MIN, PID_U_MAX] so the syringe never reaches the TOF safety limits.
Enable monitor echo + LF EOL so serial commands are visible and terminated
as the parser expects.
Default PID gains iterated against pool tests: Kp 0.17 was too weak (the
float barely moved), Kp 2.0 / Kd 0.13 oscillated (±15cm pumping). Settle on
Kp 1.0 / Ki 0 / Kd 0.5 as a damped starting point; fine-tune further at
runtime via PID_CONFIG_SET.
Reduce the descent kick-start from u=0.979 (near-full syringe, which drove
the float straight to the bottom before the PID could brake, ~26cm
overshoot) to PID_DESCENT_KICK_U=0.30, so the descent starts gently and the
PID takes over before overshooting the target.
Validated in the pool: the float converges on the target depth with ~±1cm
final oscillation. Ki vinces the buoyancy offset (recovery), Kd damps the
overshoot. Replaces the previous 1.0/0/0.5 starting point.
Il manual keyboard serve a riallineare un pistone disallineato, quindi
deve poter uscire dal range nominale (±MAX-margin) in entrambi i versi.
- motor: nuovo startJogStepsUnclamped() che bypassa volutamente il clamp
software dei fine corsa (startMoveTo/startMoveSteps restano clampati per
il firmware di missione).
- manual keyboard: startHoldMove() ora fa un jog relativo di ~2 mm
(MANUAL_JOG_STEPS) rinnovato mentre il tasto è tenuto premuto, con stop
manuale (space/x).
- rimosso l'auto-stop su distanza TOF: usava una costante rimossa e la
vecchia convenzione geometrica (estensione=positivo), incoerente con
l'homing attuale.
…iscina
Soglie tarate sulla distanza TOF reale (corretta dell'offset 6 mm):
pistone esteso ~29 mm, retratto ~79 mm, pendenza ~1.1 mm TOF/mm motore.
- TOF_SAFE_RANGE_MIN 40->32 mm: ~3 mm sopra il fondo corsa esteso (29);
oltre si apre il tappo ed entra acqua.
- TOF_SAFE_RANGE_MAX 85->82 mm: ~3 mm sopra il retratto (79); oltre =
anomalia (passi persi / verso sbagliato).
- TOF_HOMING_THRESHOLD 75->70 mm: lo stop reale cade qualche mm sopra per
polling + conferma + risoluzione grezza, restando sotto MAX.
- nuovo TOF_HOMING_CONFIRM_SAMPLES=2: conferma il trigger di homing per
ignorare campioni rumorosi singoli (stesso pattern di SAFETY_STOP_SAMPLES).
Sostituisce tofMaxExtensionStopReached() con tofGuard() che ritorna un
enum TofGuard {Ok, ExtendLimit, Emergency}, perché i due estremi fisici
richiedono azioni diverse:
- limite inferiore (siringa estesa, vicina al TOF): oltre si apre il
tappo -> ExtendLimit, il chiamante fa uno STOP PULITO senza abortire.
- limite superiore (retratta, lontana): oltre = anomalia -> Emergency.
waitForMotor/moveToMax/_balanceStrokeTo passano tutti dalla guardia
unificata (moveToMax non duplica più la logica TOF inline).
Aggiunge logHomingEvent(): scrive su flash (sopravvive al power-cycle,
in piscina la USB e scollegata) ogni tappa dell'homing - start, timeout,
no-approach/no-detect, settle, backoff, complete - per ricostruire a
posteriori dove l'homing ha sbagliato.
Le fasi di approach e home richiedono ora TOF_HOMING_CONFIRM_SAMPLES
letture consecutive sopra soglia prima di accettare il trigger.
Le routine runSyringeSet/runPidHold/runPidStep ora chiamano tofGuard()
a ogni movimento:
- Emergency -> abort della routine.
- ExtendLimit (saturazione a piena estensione): ferma il pistone e, nelle
routine PID, inibisce ulteriori comandi di estensione (riaprirebbero il
tappo) finche il PID non chiede di RISALIRE verso home.
…scent/ascent)
I nomi precedenti (deepTargetM, shallowTopTargetM, pidTimeoutS,
surfaceOffsetM) erano poco chiari e confondevano durante i test. Rinominati
in inglese con schema orientato alla fase di missione:
deepTargetM -> descentTargetM (target discesa, rif. fondo)
shallowTopTargetM -> ascentTargetM (target risalita, rif. top)
pidTimeoutS -> descentTimeoutS
surfaceOffsetM -> surfaceRestOffsetM
shallowBottomTargetM()-> ascentTargetBottomM()
depthToleranceM, holdTimeS, ascentTimeoutS, profileCount invariati.
Chiavi JSON di formatConfigJson e comando USB PROFILE_SET allineati.
Refactor di soli nomi: ordine/tipi dei campi di ProfileSetPayload e
RuntimeProfileConfig invariati, quindi layout binario e config NVS restano
compatibili (PROFILE_CONFIG_VERSION non incrementato). Nessuna modifica a
logica di controllo, range di validazione o protocollo. Aggiunti commenti
che marcano i 4 parametri essenziali vs i 4 avanzati/safety.
Rinominata la repo da Float_2025 a Float: badge CI e comando cd nel
README, URL repository in lib/DebugSerial/library.json.
La callback di ricezione ESPNOW gira nel task WiFi mentre lastCommand()
viene letto dal loop principale: l'accesso non sincronizzato a _received
poteva restituire una struct lacerata.
- _received ora protetto da portMUX: scrittura in callback e lettura/clear
in sezione critica
- lastCommand() ritorna uno snapshot per valore; aggiornati i call site in
espA che vi legavano un riferimento per evitare dangling sul temporaneo
- sendMessage(): null-termination esplicita dopo strncpy
- estratto runPidLoop() condiviso da runPidHold/runPidStep (logica TOF guard,
deadband e saturazione fondo corsa ora in un solo punto)
…lash
Dal test in piscina del 10/06: un descent timeout di 10 s memorizzato in NVS
troncava ogni fase qualunque fosse l'hold richiesto, senza alcuna traccia
diagnostica (i Debug.println si perdono sott'acqua).
- validateConfig richiede timeout >= hold + 30 s (discesa e risalita): una
config che non lascia spazio all'hold viene rifiutata; al boot una NVS
invalida torna automaticamente ai default di config.h.
- measure() scrive su flash un record phase_start con hold/timeout effettivi
e un record exit_remote_stop/exit_timeout/exit_hold_ok al break che chiude
la fase: il dump post-missione dice quale config ha girato e perche'.
…ta dalla pressione
- _leggi_csv: i .txt salvati dalla GUI (celle con unita' tipo "0.53 m",
nessun header) mandavano pandas in errore; ora ricade sul parser testo che
gia' gestisce tab, unita' e virgole decimali.
- la colonna depth del log di missione cambia riferimento fondo/top al cambio
fase (salto fittizio ~0,5 m): se c'e' la pressione la profondita' viene
ricostruita dal dato grezzo su riferimento FONDO (originale in depth_log).
- stima_u_neutral esclude i campioni a galla: prima il fallback prendeva il
float in superficie e stimava ~0,74 invece del neutro reale ~0,60.
- esempio_log.csv sostituito da esempio_gui_dump.txt (dump GUI reale del test
in piscina); fallback e URL d'esempio aggiornati.
…o automatico delle fasi
Il notebook segmenta il log da solo (colonna phase del firmware o forma
della traiettoria): solo discesa, solo salita o profilo completo, con
metriche e diagnosi per fase e fase forzabile da parametro. Due target
come nella GUI (discesa rif. FONDO, salita rif. TOP convertito +0,51 m
come ascentTargetBottomM), u_neutral stimato su entrambe le quote,
riemersione finale esclusa dalla salita e consigli PID combinati in un
unico set (kp prudente, kd/ki incisivi) come richiede il firmware.
- PID_DESCENT_KICK_U 0.30 → 0.15: con 0.30 + spinta del PID (Kp*error)
la discesa partiva troppo veloce e sfondava il target di oltre 1 m
- velocità/accelerazione/homing motore 1800 → 1400 steps/s
- aggiornato MAC_ESPA alla nuova scheda
Profondità riportata sempre riferita alla CIMA del float, così parte da ~0
in superficie; il controllo resta sul FONDO e il PID porta comunque il fondo
a 2.5 m e la cima a 40 cm. Sosta finale dopo i due profili: tiene la cima a
0.15 m sotto il pelo (antenna sommersa) fino al recupero. FLOAT_LENGTH 0.49,
PROFILE_CONFIG_VERSION 2 per ricaricare i default.
Simulatore HIL da banco (SIM_ON): il barometro e' sostituito da un modello
spinta+drag mosso dalla siringa reale, cosi' PID_STEP/PID_HOLD/GO girano a
secco col motore che si muove davvero. Comandi seriali SIM_ON/SIM_OFF/SIM_GET/
SIM_CONFIG e GO (missione completa da seriale per test end-to-end al banco);
missione fattorizzata in runVerticalProfiles()/attemptAutoRecovery().
Rifà il control core del float in Model-Based Software Design, testabile in
simulazione lungo la V (MIL -> SIL), senza toccare il firmware di gara (espA).
Aggiunge:
- lib/float_core: control core portabile in C puro, tick-based e senza I/O
(float_core_step). Riproduce missione/PID/safety/homing come funzione pura;
e' la specifica di riferimento per il modello.
- tools/mil: harness MIL nativo (plant 2o ordine + main) che chiude l'anello
su PC e verifica i requisiti MATE. Compila con gcc, senza MATLAB.
- model/: mondo Simulink/Stateflow. chart float_core (float_core_ref.slx),
plant + testbench (float_mil.slx), parametri/plant/harness (.m), spec del
chart (STATEFLOW_CHART.md), diagrammi. Equivalenza MIL vs float_core.c
certificata; SIL: codice generato da Embedded Coder identico al modello.
- src/espA_core (+ env in platformio.ini): firmware sperimentale che fa da
scheduler HAL attorno al core, parallelo a espA (non lo tocca).
Tuning della risalita fatto in MIL (PASS) e congelato in config.h:
PID_U_MIN 0.0 -> 0.20 e TARGET_SHALLOW_TOP_DEPTH 0.40 -> 0.65 (margine
anti-rottura superficie, dentro tolleranza MATE).
.gitignore: esclude gli artefatti di build Simulink/Embedded Coder (slprj/,
*.slxc, *_ert_rtw/, ...); si versionano solo i sorgenti.
Sign up for freeto 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.

2 participants

@Skeitt@Davide-Colabella
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: update float ESP-NOW runtime flow - #5

Open
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow
Open

feat: update float ESP-NOW runtime flow#5
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow

Conversation

@Skeitt

Copy link
Copy Markdown
Contributor
  • add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support

  • log and stream normalized syringe position alongside pressure and float depth

  • require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts

  • update firmware docs and protocol/parser/storage tests for the current command contract

Skeittand others added 22 commits May 29, 2026 22:57
- add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support
- log and stream normalized syringe position alongside pressure and float depth
- require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts
- update firmware docs and protocol/parser/storage tests for the current command contract
home (motor_pos=0) pushes water out → floats; negative positions move
toward the TOF and take on water → sinks. Simplify uToMotorPos to map
u in [0,1] onto negative travel, dropping the unused MOTOR_INVERT_LOGICAL
flag, and update balance() comments accordingly.
Mission log survives a power-cycle (cleared only at the start of a new
mission, no longer at boot), so a failed pool test can be read afterwards
over USB. Add a DUMP_LOG serial command to re-read it on demand without
clearing — solves the case where the monitor connects after boot.
emergencyStop() records reason + TOF distance to the flash log the instant
it fires (idempotent single write) instead of a posteriori in loop(), where
it was missed when measure() exited on phase timeout. Expose
lastStopReason()/lastStopTofMm(). TOF safety now debounces with
TOF_SAFETY_STOP_SAMPLES consecutive out-of-range reads to ignore single
glitches (bubbles, reflections) in water, and clamps the PID output to
[PID_U_MIN, PID_U_MAX] so the syringe never reaches the TOF safety limits.
Enable monitor echo + LF EOL so serial commands are visible and terminated
as the parser expects.
Default PID gains iterated against pool tests: Kp 0.17 was too weak (the
float barely moved), Kp 2.0 / Kd 0.13 oscillated (±15cm pumping). Settle on
Kp 1.0 / Ki 0 / Kd 0.5 as a damped starting point; fine-tune further at
runtime via PID_CONFIG_SET.
Reduce the descent kick-start from u=0.979 (near-full syringe, which drove
the float straight to the bottom before the PID could brake, ~26cm
overshoot) to PID_DESCENT_KICK_U=0.30, so the descent starts gently and the
PID takes over before overshooting the target.
Validated in the pool: the float converges on the target depth with ~±1cm
final oscillation. Ki vinces the buoyancy offset (recovery), Kd damps the
overshoot. Replaces the previous 1.0/0/0.5 starting point.
Il manual keyboard serve a riallineare un pistone disallineato, quindi
deve poter uscire dal range nominale (±MAX-margin) in entrambi i versi.
- motor: nuovo startJogStepsUnclamped() che bypassa volutamente il clamp
software dei fine corsa (startMoveTo/startMoveSteps restano clampati per
il firmware di missione).
- manual keyboard: startHoldMove() ora fa un jog relativo di ~2 mm
(MANUAL_JOG_STEPS) rinnovato mentre il tasto è tenuto premuto, con stop
manuale (space/x).
- rimosso l'auto-stop su distanza TOF: usava una costante rimossa e la
vecchia convenzione geometrica (estensione=positivo), incoerente con
l'homing attuale.
…iscina
Soglie tarate sulla distanza TOF reale (corretta dell'offset 6 mm):
pistone esteso ~29 mm, retratto ~79 mm, pendenza ~1.1 mm TOF/mm motore.
- TOF_SAFE_RANGE_MIN 40->32 mm: ~3 mm sopra il fondo corsa esteso (29);
oltre si apre il tappo ed entra acqua.
- TOF_SAFE_RANGE_MAX 85->82 mm: ~3 mm sopra il retratto (79); oltre =
anomalia (passi persi / verso sbagliato).
- TOF_HOMING_THRESHOLD 75->70 mm: lo stop reale cade qualche mm sopra per
polling + conferma + risoluzione grezza, restando sotto MAX.
- nuovo TOF_HOMING_CONFIRM_SAMPLES=2: conferma il trigger di homing per
ignorare campioni rumorosi singoli (stesso pattern di SAFETY_STOP_SAMPLES).
Sostituisce tofMaxExtensionStopReached() con tofGuard() che ritorna un
enum TofGuard {Ok, ExtendLimit, Emergency}, perché i due estremi fisici
richiedono azioni diverse:
- limite inferiore (siringa estesa, vicina al TOF): oltre si apre il
tappo -> ExtendLimit, il chiamante fa uno STOP PULITO senza abortire.
- limite superiore (retratta, lontana): oltre = anomalia -> Emergency.
waitForMotor/moveToMax/_balanceStrokeTo passano tutti dalla guardia
unificata (moveToMax non duplica più la logica TOF inline).
Aggiunge logHomingEvent(): scrive su flash (sopravvive al power-cycle,
in piscina la USB e scollegata) ogni tappa dell'homing - start, timeout,
no-approach/no-detect, settle, backoff, complete - per ricostruire a
posteriori dove l'homing ha sbagliato.
Le fasi di approach e home richiedono ora TOF_HOMING_CONFIRM_SAMPLES
letture consecutive sopra soglia prima di accettare il trigger.
Le routine runSyringeSet/runPidHold/runPidStep ora chiamano tofGuard()
a ogni movimento:
- Emergency -> abort della routine.
- ExtendLimit (saturazione a piena estensione): ferma il pistone e, nelle
routine PID, inibisce ulteriori comandi di estensione (riaprirebbero il
tappo) finche il PID non chiede di RISALIRE verso home.
…scent/ascent)
I nomi precedenti (deepTargetM, shallowTopTargetM, pidTimeoutS,
surfaceOffsetM) erano poco chiari e confondevano durante i test. Rinominati
in inglese con schema orientato alla fase di missione:
deepTargetM -> descentTargetM (target discesa, rif. fondo)
shallowTopTargetM -> ascentTargetM (target risalita, rif. top)
pidTimeoutS -> descentTimeoutS
surfaceOffsetM -> surfaceRestOffsetM
shallowBottomTargetM()-> ascentTargetBottomM()
depthToleranceM, holdTimeS, ascentTimeoutS, profileCount invariati.
Chiavi JSON di formatConfigJson e comando USB PROFILE_SET allineati.
Refactor di soli nomi: ordine/tipi dei campi di ProfileSetPayload e
RuntimeProfileConfig invariati, quindi layout binario e config NVS restano
compatibili (PROFILE_CONFIG_VERSION non incrementato). Nessuna modifica a
logica di controllo, range di validazione o protocollo. Aggiunti commenti
che marcano i 4 parametri essenziali vs i 4 avanzati/safety.
Rinominata la repo da Float_2025 a Float: badge CI e comando cd nel
README, URL repository in lib/DebugSerial/library.json.
La callback di ricezione ESPNOW gira nel task WiFi mentre lastCommand()
viene letto dal loop principale: l'accesso non sincronizzato a _received
poteva restituire una struct lacerata.
- _received ora protetto da portMUX: scrittura in callback e lettura/clear
in sezione critica
- lastCommand() ritorna uno snapshot per valore; aggiornati i call site in
espA che vi legavano un riferimento per evitare dangling sul temporaneo
- sendMessage(): null-termination esplicita dopo strncpy
- estratto runPidLoop() condiviso da runPidHold/runPidStep (logica TOF guard,
deadband e saturazione fondo corsa ora in un solo punto)
…lash
Dal test in piscina del 10/06: un descent timeout di 10 s memorizzato in NVS
troncava ogni fase qualunque fosse l'hold richiesto, senza alcuna traccia
diagnostica (i Debug.println si perdono sott'acqua).
- validateConfig richiede timeout >= hold + 30 s (discesa e risalita): una
config che non lascia spazio all'hold viene rifiutata; al boot una NVS
invalida torna automaticamente ai default di config.h.
- measure() scrive su flash un record phase_start con hold/timeout effettivi
e un record exit_remote_stop/exit_timeout/exit_hold_ok al break che chiude
la fase: il dump post-missione dice quale config ha girato e perche'.
…ta dalla pressione
- _leggi_csv: i .txt salvati dalla GUI (celle con unita' tipo "0.53 m",
nessun header) mandavano pandas in errore; ora ricade sul parser testo che
gia' gestisce tab, unita' e virgole decimali.
- la colonna depth del log di missione cambia riferimento fondo/top al cambio
fase (salto fittizio ~0,5 m): se c'e' la pressione la profondita' viene
ricostruita dal dato grezzo su riferimento FONDO (originale in depth_log).
- stima_u_neutral esclude i campioni a galla: prima il fallback prendeva il
float in superficie e stimava ~0,74 invece del neutro reale ~0,60.
- esempio_log.csv sostituito da esempio_gui_dump.txt (dump GUI reale del test
in piscina); fallback e URL d'esempio aggiornati.
…o automatico delle fasi
Il notebook segmenta il log da solo (colonna phase del firmware o forma
della traiettoria): solo discesa, solo salita o profilo completo, con
metriche e diagnosi per fase e fase forzabile da parametro. Due target
come nella GUI (discesa rif. FONDO, salita rif. TOP convertito +0,51 m
come ascentTargetBottomM), u_neutral stimato su entrambe le quote,
riemersione finale esclusa dalla salita e consigli PID combinati in un
unico set (kp prudente, kd/ki incisivi) come richiede il firmware.
- PID_DESCENT_KICK_U 0.30 → 0.15: con 0.30 + spinta del PID (Kp*error)
la discesa partiva troppo veloce e sfondava il target di oltre 1 m
- velocità/accelerazione/homing motore 1800 → 1400 steps/s
- aggiornato MAC_ESPA alla nuova scheda
Profondità riportata sempre riferita alla CIMA del float, così parte da ~0
in superficie; il controllo resta sul FONDO e il PID porta comunque il fondo
a 2.5 m e la cima a 40 cm. Sosta finale dopo i due profili: tiene la cima a
0.15 m sotto il pelo (antenna sommersa) fino al recupero. FLOAT_LENGTH 0.49,
PROFILE_CONFIG_VERSION 2 per ricaricare i default.
Simulatore HIL da banco (SIM_ON): il barometro e' sostituito da un modello
spinta+drag mosso dalla siringa reale, cosi' PID_STEP/PID_HOLD/GO girano a
secco col motore che si muove davvero. Comandi seriali SIM_ON/SIM_OFF/SIM_GET/
SIM_CONFIG e GO (missione completa da seriale per test end-to-end al banco);
missione fattorizzata in runVerticalProfiles()/attemptAutoRecovery().
Rifà il control core del float in Model-Based Software Design, testabile in
simulazione lungo la V (MIL -> SIL), senza toccare il firmware di gara (espA).
Aggiunge:
- lib/float_core: control core portabile in C puro, tick-based e senza I/O
(float_core_step). Riproduce missione/PID/safety/homing come funzione pura;
e' la specifica di riferimento per il modello.
- tools/mil: harness MIL nativo (plant 2o ordine + main) che chiude l'anello
su PC e verifica i requisiti MATE. Compila con gcc, senza MATLAB.
- model/: mondo Simulink/Stateflow. chart float_core (float_core_ref.slx),
plant + testbench (float_mil.slx), parametri/plant/harness (.m), spec del
chart (STATEFLOW_CHART.md), diagrammi. Equivalenza MIL vs float_core.c
certificata; SIL: codice generato da Embedded Coder identico al modello.
- src/espA_core (+ env in platformio.ini): firmware sperimentale che fa da
scheduler HAL attorno al core, parallelo a espA (non lo tocca).
Tuning della risalita fatto in MIL (PASS) e congelato in config.h:
PID_U_MIN 0.0 -> 0.20 e TARGET_SHALLOW_TOP_DEPTH 0.40 -> 0.65 (margine
anti-rottura superficie, dentro tolleranza MATE).
.gitignore: esclude gli artefatti di build Simulink/Embedded Coder (slprj/,
*.slxc, *_ert_rtw/, ...); si versionano solo i sorgenti.
Sign up for freeto 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.

2 participants

@Skeitt@Davide-Colabella
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: update float ESP-NOW runtime flow - #5

Open
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow
Open

feat: update float ESP-NOW runtime flow#5
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow

Conversation

@Skeitt

Copy link
Copy Markdown
Contributor
  • add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support

  • log and stream normalized syringe position alongside pressure and float depth

  • require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts

  • update firmware docs and protocol/parser/storage tests for the current command contract

Skeittand others added 22 commits May 29, 2026 22:57
- add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support
- log and stream normalized syringe position alongside pressure and float depth
- require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts
- update firmware docs and protocol/parser/storage tests for the current command contract
home (motor_pos=0) pushes water out → floats; negative positions move
toward the TOF and take on water → sinks. Simplify uToMotorPos to map
u in [0,1] onto negative travel, dropping the unused MOTOR_INVERT_LOGICAL
flag, and update balance() comments accordingly.
Mission log survives a power-cycle (cleared only at the start of a new
mission, no longer at boot), so a failed pool test can be read afterwards
over USB. Add a DUMP_LOG serial command to re-read it on demand without
clearing — solves the case where the monitor connects after boot.
emergencyStop() records reason + TOF distance to the flash log the instant
it fires (idempotent single write) instead of a posteriori in loop(), where
it was missed when measure() exited on phase timeout. Expose
lastStopReason()/lastStopTofMm(). TOF safety now debounces with
TOF_SAFETY_STOP_SAMPLES consecutive out-of-range reads to ignore single
glitches (bubbles, reflections) in water, and clamps the PID output to
[PID_U_MIN, PID_U_MAX] so the syringe never reaches the TOF safety limits.
Enable monitor echo + LF EOL so serial commands are visible and terminated
as the parser expects.
Default PID gains iterated against pool tests: Kp 0.17 was too weak (the
float barely moved), Kp 2.0 / Kd 0.13 oscillated (±15cm pumping). Settle on
Kp 1.0 / Ki 0 / Kd 0.5 as a damped starting point; fine-tune further at
runtime via PID_CONFIG_SET.
Reduce the descent kick-start from u=0.979 (near-full syringe, which drove
the float straight to the bottom before the PID could brake, ~26cm
overshoot) to PID_DESCENT_KICK_U=0.30, so the descent starts gently and the
PID takes over before overshooting the target.
Validated in the pool: the float converges on the target depth with ~±1cm
final oscillation. Ki vinces the buoyancy offset (recovery), Kd damps the
overshoot. Replaces the previous 1.0/0/0.5 starting point.
Il manual keyboard serve a riallineare un pistone disallineato, quindi
deve poter uscire dal range nominale (±MAX-margin) in entrambi i versi.
- motor: nuovo startJogStepsUnclamped() che bypassa volutamente il clamp
software dei fine corsa (startMoveTo/startMoveSteps restano clampati per
il firmware di missione).
- manual keyboard: startHoldMove() ora fa un jog relativo di ~2 mm
(MANUAL_JOG_STEPS) rinnovato mentre il tasto è tenuto premuto, con stop
manuale (space/x).
- rimosso l'auto-stop su distanza TOF: usava una costante rimossa e la
vecchia convenzione geometrica (estensione=positivo), incoerente con
l'homing attuale.
…iscina
Soglie tarate sulla distanza TOF reale (corretta dell'offset 6 mm):
pistone esteso ~29 mm, retratto ~79 mm, pendenza ~1.1 mm TOF/mm motore.
- TOF_SAFE_RANGE_MIN 40->32 mm: ~3 mm sopra il fondo corsa esteso (29);
oltre si apre il tappo ed entra acqua.
- TOF_SAFE_RANGE_MAX 85->82 mm: ~3 mm sopra il retratto (79); oltre =
anomalia (passi persi / verso sbagliato).
- TOF_HOMING_THRESHOLD 75->70 mm: lo stop reale cade qualche mm sopra per
polling + conferma + risoluzione grezza, restando sotto MAX.
- nuovo TOF_HOMING_CONFIRM_SAMPLES=2: conferma il trigger di homing per
ignorare campioni rumorosi singoli (stesso pattern di SAFETY_STOP_SAMPLES).
Sostituisce tofMaxExtensionStopReached() con tofGuard() che ritorna un
enum TofGuard {Ok, ExtendLimit, Emergency}, perché i due estremi fisici
richiedono azioni diverse:
- limite inferiore (siringa estesa, vicina al TOF): oltre si apre il
tappo -> ExtendLimit, il chiamante fa uno STOP PULITO senza abortire.
- limite superiore (retratta, lontana): oltre = anomalia -> Emergency.
waitForMotor/moveToMax/_balanceStrokeTo passano tutti dalla guardia
unificata (moveToMax non duplica più la logica TOF inline).
Aggiunge logHomingEvent(): scrive su flash (sopravvive al power-cycle,
in piscina la USB e scollegata) ogni tappa dell'homing - start, timeout,
no-approach/no-detect, settle, backoff, complete - per ricostruire a
posteriori dove l'homing ha sbagliato.
Le fasi di approach e home richiedono ora TOF_HOMING_CONFIRM_SAMPLES
letture consecutive sopra soglia prima di accettare il trigger.
Le routine runSyringeSet/runPidHold/runPidStep ora chiamano tofGuard()
a ogni movimento:
- Emergency -> abort della routine.
- ExtendLimit (saturazione a piena estensione): ferma il pistone e, nelle
routine PID, inibisce ulteriori comandi di estensione (riaprirebbero il
tappo) finche il PID non chiede di RISALIRE verso home.
…scent/ascent)
I nomi precedenti (deepTargetM, shallowTopTargetM, pidTimeoutS,
surfaceOffsetM) erano poco chiari e confondevano durante i test. Rinominati
in inglese con schema orientato alla fase di missione:
deepTargetM -> descentTargetM (target discesa, rif. fondo)
shallowTopTargetM -> ascentTargetM (target risalita, rif. top)
pidTimeoutS -> descentTimeoutS
surfaceOffsetM -> surfaceRestOffsetM
shallowBottomTargetM()-> ascentTargetBottomM()
depthToleranceM, holdTimeS, ascentTimeoutS, profileCount invariati.
Chiavi JSON di formatConfigJson e comando USB PROFILE_SET allineati.
Refactor di soli nomi: ordine/tipi dei campi di ProfileSetPayload e
RuntimeProfileConfig invariati, quindi layout binario e config NVS restano
compatibili (PROFILE_CONFIG_VERSION non incrementato). Nessuna modifica a
logica di controllo, range di validazione o protocollo. Aggiunti commenti
che marcano i 4 parametri essenziali vs i 4 avanzati/safety.
Rinominata la repo da Float_2025 a Float: badge CI e comando cd nel
README, URL repository in lib/DebugSerial/library.json.
La callback di ricezione ESPNOW gira nel task WiFi mentre lastCommand()
viene letto dal loop principale: l'accesso non sincronizzato a _received
poteva restituire una struct lacerata.
- _received ora protetto da portMUX: scrittura in callback e lettura/clear
in sezione critica
- lastCommand() ritorna uno snapshot per valore; aggiornati i call site in
espA che vi legavano un riferimento per evitare dangling sul temporaneo
- sendMessage(): null-termination esplicita dopo strncpy
- estratto runPidLoop() condiviso da runPidHold/runPidStep (logica TOF guard,
deadband e saturazione fondo corsa ora in un solo punto)
…lash
Dal test in piscina del 10/06: un descent timeout di 10 s memorizzato in NVS
troncava ogni fase qualunque fosse l'hold richiesto, senza alcuna traccia
diagnostica (i Debug.println si perdono sott'acqua).
- validateConfig richiede timeout >= hold + 30 s (discesa e risalita): una
config che non lascia spazio all'hold viene rifiutata; al boot una NVS
invalida torna automaticamente ai default di config.h.
- measure() scrive su flash un record phase_start con hold/timeout effettivi
e un record exit_remote_stop/exit_timeout/exit_hold_ok al break che chiude
la fase: il dump post-missione dice quale config ha girato e perche'.
…ta dalla pressione
- _leggi_csv: i .txt salvati dalla GUI (celle con unita' tipo "0.53 m",
nessun header) mandavano pandas in errore; ora ricade sul parser testo che
gia' gestisce tab, unita' e virgole decimali.
- la colonna depth del log di missione cambia riferimento fondo/top al cambio
fase (salto fittizio ~0,5 m): se c'e' la pressione la profondita' viene
ricostruita dal dato grezzo su riferimento FONDO (originale in depth_log).
- stima_u_neutral esclude i campioni a galla: prima il fallback prendeva il
float in superficie e stimava ~0,74 invece del neutro reale ~0,60.
- esempio_log.csv sostituito da esempio_gui_dump.txt (dump GUI reale del test
in piscina); fallback e URL d'esempio aggiornati.
…o automatico delle fasi
Il notebook segmenta il log da solo (colonna phase del firmware o forma
della traiettoria): solo discesa, solo salita o profilo completo, con
metriche e diagnosi per fase e fase forzabile da parametro. Due target
come nella GUI (discesa rif. FONDO, salita rif. TOP convertito +0,51 m
come ascentTargetBottomM), u_neutral stimato su entrambe le quote,
riemersione finale esclusa dalla salita e consigli PID combinati in un
unico set (kp prudente, kd/ki incisivi) come richiede il firmware.
- PID_DESCENT_KICK_U 0.30 → 0.15: con 0.30 + spinta del PID (Kp*error)
la discesa partiva troppo veloce e sfondava il target di oltre 1 m
- velocità/accelerazione/homing motore 1800 → 1400 steps/s
- aggiornato MAC_ESPA alla nuova scheda
Profondità riportata sempre riferita alla CIMA del float, così parte da ~0
in superficie; il controllo resta sul FONDO e il PID porta comunque il fondo
a 2.5 m e la cima a 40 cm. Sosta finale dopo i due profili: tiene la cima a
0.15 m sotto il pelo (antenna sommersa) fino al recupero. FLOAT_LENGTH 0.49,
PROFILE_CONFIG_VERSION 2 per ricaricare i default.
Simulatore HIL da banco (SIM_ON): il barometro e' sostituito da un modello
spinta+drag mosso dalla siringa reale, cosi' PID_STEP/PID_HOLD/GO girano a
secco col motore che si muove davvero. Comandi seriali SIM_ON/SIM_OFF/SIM_GET/
SIM_CONFIG e GO (missione completa da seriale per test end-to-end al banco);
missione fattorizzata in runVerticalProfiles()/attemptAutoRecovery().
Rifà il control core del float in Model-Based Software Design, testabile in
simulazione lungo la V (MIL -> SIL), senza toccare il firmware di gara (espA).
Aggiunge:
- lib/float_core: control core portabile in C puro, tick-based e senza I/O
(float_core_step). Riproduce missione/PID/safety/homing come funzione pura;
e' la specifica di riferimento per il modello.
- tools/mil: harness MIL nativo (plant 2o ordine + main) che chiude l'anello
su PC e verifica i requisiti MATE. Compila con gcc, senza MATLAB.
- model/: mondo Simulink/Stateflow. chart float_core (float_core_ref.slx),
plant + testbench (float_mil.slx), parametri/plant/harness (.m), spec del
chart (STATEFLOW_CHART.md), diagrammi. Equivalenza MIL vs float_core.c
certificata; SIL: codice generato da Embedded Coder identico al modello.
- src/espA_core (+ env in platformio.ini): firmware sperimentale che fa da
scheduler HAL attorno al core, parallelo a espA (non lo tocca).
Tuning della risalita fatto in MIL (PASS) e congelato in config.h:
PID_U_MIN 0.0 -> 0.20 e TARGET_SHALLOW_TOP_DEPTH 0.40 -> 0.65 (margine
anti-rottura superficie, dentro tolleranza MATE).
.gitignore: esclude gli artefatti di build Simulink/Embedded Coder (slprj/,
*.slxc, *_ert_rtw/, ...); si versionano solo i sorgenti.
Sign up for freeto 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.

2 participants

@Skeitt@Davide-Colabella
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: update float ESP-NOW runtime flow - #5

Open
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow
Open

feat: update float ESP-NOW runtime flow#5
Skeitt wants to merge 22 commits into
masterfrom
feat/new-espnow

Conversation

@Skeitt

Copy link
Copy Markdown
Contributor
  • add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support

  • log and stream normalized syringe position alongside pressure and float depth

  • require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts

  • update firmware docs and protocol/parser/storage tests for the current command contract

Skeittand others added 22 commits May 29, 2026 22:57
- add runtime profile, PID, balance and motor settings with persisted ESPA config and ESPB parser support
- log and stream normalized syringe position alongside pressure and float depth
- require acknowledged command handling, centralize LED states and clean obsolete espA_pool/build artifacts
- update firmware docs and protocol/parser/storage tests for the current command contract
home (motor_pos=0) pushes water out → floats; negative positions move
toward the TOF and take on water → sinks. Simplify uToMotorPos to map
u in [0,1] onto negative travel, dropping the unused MOTOR_INVERT_LOGICAL
flag, and update balance() comments accordingly.
Mission log survives a power-cycle (cleared only at the start of a new
mission, no longer at boot), so a failed pool test can be read afterwards
over USB. Add a DUMP_LOG serial command to re-read it on demand without
clearing — solves the case where the monitor connects after boot.
emergencyStop() records reason + TOF distance to the flash log the instant
it fires (idempotent single write) instead of a posteriori in loop(), where
it was missed when measure() exited on phase timeout. Expose
lastStopReason()/lastStopTofMm(). TOF safety now debounces with
TOF_SAFETY_STOP_SAMPLES consecutive out-of-range reads to ignore single
glitches (bubbles, reflections) in water, and clamps the PID output to
[PID_U_MIN, PID_U_MAX] so the syringe never reaches the TOF safety limits.
Enable monitor echo + LF EOL so serial commands are visible and terminated
as the parser expects.
Default PID gains iterated against pool tests: Kp 0.17 was too weak (the
float barely moved), Kp 2.0 / Kd 0.13 oscillated (±15cm pumping). Settle on
Kp 1.0 / Ki 0 / Kd 0.5 as a damped starting point; fine-tune further at
runtime via PID_CONFIG_SET.
Reduce the descent kick-start from u=0.979 (near-full syringe, which drove
the float straight to the bottom before the PID could brake, ~26cm
overshoot) to PID_DESCENT_KICK_U=0.30, so the descent starts gently and the
PID takes over before overshooting the target.
Validated in the pool: the float converges on the target depth with ~±1cm
final oscillation. Ki vinces the buoyancy offset (recovery), Kd damps the
overshoot. Replaces the previous 1.0/0/0.5 starting point.
Il manual keyboard serve a riallineare un pistone disallineato, quindi
deve poter uscire dal range nominale (±MAX-margin) in entrambi i versi.
- motor: nuovo startJogStepsUnclamped() che bypassa volutamente il clamp
software dei fine corsa (startMoveTo/startMoveSteps restano clampati per
il firmware di missione).
- manual keyboard: startHoldMove() ora fa un jog relativo di ~2 mm
(MANUAL_JOG_STEPS) rinnovato mentre il tasto è tenuto premuto, con stop
manuale (space/x).
- rimosso l'auto-stop su distanza TOF: usava una costante rimossa e la
vecchia convenzione geometrica (estensione=positivo), incoerente con
l'homing attuale.
…iscina
Soglie tarate sulla distanza TOF reale (corretta dell'offset 6 mm):
pistone esteso ~29 mm, retratto ~79 mm, pendenza ~1.1 mm TOF/mm motore.
- TOF_SAFE_RANGE_MIN 40->32 mm: ~3 mm sopra il fondo corsa esteso (29);
oltre si apre il tappo ed entra acqua.
- TOF_SAFE_RANGE_MAX 85->82 mm: ~3 mm sopra il retratto (79); oltre =
anomalia (passi persi / verso sbagliato).
- TOF_HOMING_THRESHOLD 75->70 mm: lo stop reale cade qualche mm sopra per
polling + conferma + risoluzione grezza, restando sotto MAX.
- nuovo TOF_HOMING_CONFIRM_SAMPLES=2: conferma il trigger di homing per
ignorare campioni rumorosi singoli (stesso pattern di SAFETY_STOP_SAMPLES).
Sostituisce tofMaxExtensionStopReached() con tofGuard() che ritorna un
enum TofGuard {Ok, ExtendLimit, Emergency}, perché i due estremi fisici
richiedono azioni diverse:
- limite inferiore (siringa estesa, vicina al TOF): oltre si apre il
tappo -> ExtendLimit, il chiamante fa uno STOP PULITO senza abortire.
- limite superiore (retratta, lontana): oltre = anomalia -> Emergency.
waitForMotor/moveToMax/_balanceStrokeTo passano tutti dalla guardia
unificata (moveToMax non duplica più la logica TOF inline).
Aggiunge logHomingEvent(): scrive su flash (sopravvive al power-cycle,
in piscina la USB e scollegata) ogni tappa dell'homing - start, timeout,
no-approach/no-detect, settle, backoff, complete - per ricostruire a
posteriori dove l'homing ha sbagliato.
Le fasi di approach e home richiedono ora TOF_HOMING_CONFIRM_SAMPLES
letture consecutive sopra soglia prima di accettare il trigger.
Le routine runSyringeSet/runPidHold/runPidStep ora chiamano tofGuard()
a ogni movimento:
- Emergency -> abort della routine.
- ExtendLimit (saturazione a piena estensione): ferma il pistone e, nelle
routine PID, inibisce ulteriori comandi di estensione (riaprirebbero il
tappo) finche il PID non chiede di RISALIRE verso home.
…scent/ascent)
I nomi precedenti (deepTargetM, shallowTopTargetM, pidTimeoutS,
surfaceOffsetM) erano poco chiari e confondevano durante i test. Rinominati
in inglese con schema orientato alla fase di missione:
deepTargetM -> descentTargetM (target discesa, rif. fondo)
shallowTopTargetM -> ascentTargetM (target risalita, rif. top)
pidTimeoutS -> descentTimeoutS
surfaceOffsetM -> surfaceRestOffsetM
shallowBottomTargetM()-> ascentTargetBottomM()
depthToleranceM, holdTimeS, ascentTimeoutS, profileCount invariati.
Chiavi JSON di formatConfigJson e comando USB PROFILE_SET allineati.
Refactor di soli nomi: ordine/tipi dei campi di ProfileSetPayload e
RuntimeProfileConfig invariati, quindi layout binario e config NVS restano
compatibili (PROFILE_CONFIG_VERSION non incrementato). Nessuna modifica a
logica di controllo, range di validazione o protocollo. Aggiunti commenti
che marcano i 4 parametri essenziali vs i 4 avanzati/safety.
Rinominata la repo da Float_2025 a Float: badge CI e comando cd nel
README, URL repository in lib/DebugSerial/library.json.
La callback di ricezione ESPNOW gira nel task WiFi mentre lastCommand()
viene letto dal loop principale: l'accesso non sincronizzato a _received
poteva restituire una struct lacerata.
- _received ora protetto da portMUX: scrittura in callback e lettura/clear
in sezione critica
- lastCommand() ritorna uno snapshot per valore; aggiornati i call site in
espA che vi legavano un riferimento per evitare dangling sul temporaneo
- sendMessage(): null-termination esplicita dopo strncpy
- estratto runPidLoop() condiviso da runPidHold/runPidStep (logica TOF guard,
deadband e saturazione fondo corsa ora in un solo punto)
…lash
Dal test in piscina del 10/06: un descent timeout di 10 s memorizzato in NVS
troncava ogni fase qualunque fosse l'hold richiesto, senza alcuna traccia
diagnostica (i Debug.println si perdono sott'acqua).
- validateConfig richiede timeout >= hold + 30 s (discesa e risalita): una
config che non lascia spazio all'hold viene rifiutata; al boot una NVS
invalida torna automaticamente ai default di config.h.
- measure() scrive su flash un record phase_start con hold/timeout effettivi
e un record exit_remote_stop/exit_timeout/exit_hold_ok al break che chiude
la fase: il dump post-missione dice quale config ha girato e perche'.
…ta dalla pressione
- _leggi_csv: i .txt salvati dalla GUI (celle con unita' tipo "0.53 m",
nessun header) mandavano pandas in errore; ora ricade sul parser testo che
gia' gestisce tab, unita' e virgole decimali.
- la colonna depth del log di missione cambia riferimento fondo/top al cambio
fase (salto fittizio ~0,5 m): se c'e' la pressione la profondita' viene
ricostruita dal dato grezzo su riferimento FONDO (originale in depth_log).
- stima_u_neutral esclude i campioni a galla: prima il fallback prendeva il
float in superficie e stimava ~0,74 invece del neutro reale ~0,60.
- esempio_log.csv sostituito da esempio_gui_dump.txt (dump GUI reale del test
in piscina); fallback e URL d'esempio aggiornati.
…o automatico delle fasi
Il notebook segmenta il log da solo (colonna phase del firmware o forma
della traiettoria): solo discesa, solo salita o profilo completo, con
metriche e diagnosi per fase e fase forzabile da parametro. Due target
come nella GUI (discesa rif. FONDO, salita rif. TOP convertito +0,51 m
come ascentTargetBottomM), u_neutral stimato su entrambe le quote,
riemersione finale esclusa dalla salita e consigli PID combinati in un
unico set (kp prudente, kd/ki incisivi) come richiede il firmware.
- PID_DESCENT_KICK_U 0.30 → 0.15: con 0.30 + spinta del PID (Kp*error)
la discesa partiva troppo veloce e sfondava il target di oltre 1 m
- velocità/accelerazione/homing motore 1800 → 1400 steps/s
- aggiornato MAC_ESPA alla nuova scheda
Profondità riportata sempre riferita alla CIMA del float, così parte da ~0
in superficie; il controllo resta sul FONDO e il PID porta comunque il fondo
a 2.5 m e la cima a 40 cm. Sosta finale dopo i due profili: tiene la cima a
0.15 m sotto il pelo (antenna sommersa) fino al recupero. FLOAT_LENGTH 0.49,
PROFILE_CONFIG_VERSION 2 per ricaricare i default.
Simulatore HIL da banco (SIM_ON): il barometro e' sostituito da un modello
spinta+drag mosso dalla siringa reale, cosi' PID_STEP/PID_HOLD/GO girano a
secco col motore che si muove davvero. Comandi seriali SIM_ON/SIM_OFF/SIM_GET/
SIM_CONFIG e GO (missione completa da seriale per test end-to-end al banco);
missione fattorizzata in runVerticalProfiles()/attemptAutoRecovery().
Rifà il control core del float in Model-Based Software Design, testabile in
simulazione lungo la V (MIL -> SIL), senza toccare il firmware di gara (espA).
Aggiunge:
- lib/float_core: control core portabile in C puro, tick-based e senza I/O
(float_core_step). Riproduce missione/PID/safety/homing come funzione pura;
e' la specifica di riferimento per il modello.
- tools/mil: harness MIL nativo (plant 2o ordine + main) che chiude l'anello
su PC e verifica i requisiti MATE. Compila con gcc, senza MATLAB.
- model/: mondo Simulink/Stateflow. chart float_core (float_core_ref.slx),
plant + testbench (float_mil.slx), parametri/plant/harness (.m), spec del
chart (STATEFLOW_CHART.md), diagrammi. Equivalenza MIL vs float_core.c
certificata; SIL: codice generato da Embedded Coder identico al modello.
- src/espA_core (+ env in platformio.ini): firmware sperimentale che fa da
scheduler HAL attorno al core, parallelo a espA (non lo tocca).
Tuning della risalita fatto in MIL (PASS) e congelato in config.h:
PID_U_MIN 0.0 -> 0.20 e TARGET_SHALLOW_TOP_DEPTH 0.40 -> 0.65 (margine
anti-rottura superficie, dentro tolleranza MATE).
.gitignore: esclude gli artefatti di build Simulink/Embedded Coder (slprj/,
*.slxc, *_ert_rtw/, ...); si versionano solo i sorgenti.
Sign up for freeto 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.

2 participants

@Skeitt@Davide-Colabella