Skip to content

feat: update float ESP-NOW runtime flow#5

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

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

Conversation

@Skeitt

@Skeitt Skeitt commented Jun 1, 2026

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

Skeitt and others added 19 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants