Pluviometro su ESP32

Ciao a tutti,
sto di nuovo mettendo mani su un sistema di controllo di elettrovalvole per l'irrigazione e riscontro dei problemi con un maledetto pluviometro a bascula!

Sto usando un ESP32 e uso MQTT per passare i dati dalla pagina web di NodeRED su Raspberry Pi 4 dove vengono visualizzati.

Il pluviometro è programmato sotto Intrrupt e per debouncing nella sua ISR disabilito l'interrupt e tramite timer lo riabilito dopo 100ms.

Tutto questo funziona, però quando faccio operazioni sui relè (5V con pilotati da BC337) delle valvole a 24VAC (solo sono collegate) la misura viene notevolmente alterata!

Oltre a questo (che ho risolto via software) noto che il dato di pioggia nelle ultime 24h che calcolo viene ciclicamente perso quando piove quindi suppongo che l'interrupt vada in conflitto con qualcosa e causi il reboot della scheda!

Ho già inserito in parallelo il classico filtro R-C 100n 100Ohm; la PCB l'ho disegnata io e fatta realizzare presso JLCBCB.
In altre discussioni si era pensato per evitare il deboucing ad un trimmer di Schmitt che ho sostituito con la soluzione software perché avevo già fatto le schede.

Avete qualche idea su quale possa essere il problema e in caso qualche consiglio da dare per a capire qual'è il problema

Il codice lo trovate qui
Lo schema qui

Ringrazio tutti quelli che risponderanno e sono a disposizione per ulteriori chiarimenti
Alan Masutti

Lo sketch è piuttosto articolato (aggiungerei anche troppo per alcuni aspetti) e con le poche informazioni che hai fornito è difficile azzardare ipotesi sul perché del reboot.

Dovresti cercare di circoscrivere maggiormente il problema. Ad esempio se riesci a estrapolare la causa dell'eccezione è già un primo passo.

arduino-esp32/ResetReason.ino

Ciao cotestatnt,
So che è in po' articolato, ma a sgravare le mie colpe è una delle successive modifiche ad uno dei miei primissimi progetti (forse proprio il primo); cosi a memoria credo che la parte più fatta male sia la gestione delle ore di programmazione fatte con le stringhe, erro?
In caso non lo fosse se tu potessi darmi un'opinione su cosa fare per semplificarlo sono ben contento di ascoltare consigli ed imparare cose nuove;

Ora provo ad inserire il resetReason nel codice, magari attacco una SD per fare da log error....

In oltre aggiungerei, se posso, un altra domanda senza evitare di dover aggiungere un altro topic. Devo fare una modifica per il conteggio della pioggia caduta nelle ultime 24h; in questo caso è facile perchè pensavo di inviare tutto a NodeRed e farlo tramite CSV, ma ho una versione (da un altra parte) che non ha il WiFi per la parte di controllo ma solo il Bluetooth e quindi deve arrangiarsi in stand-alone per questa funzione; la domanda è: posso usare lo SPIFFS per salvare il mio CSV e fare la cosa li o il fatto che sia SRAM (10000 cicli di scrittura) fa diventare la cosa poco duratura?

Grazie in anticipo

Alan Masutti

Ps. se non va bene la seconda domanda ditemi che la tolgo!!

Ciao @alanmasutti.
Si la gestione delle ore/minuti è sicuramente perfettibile. L'errore più comune in questi casi è ragionare nei termini in cui siamo abituati come essere umani cioè con ore, minuti, secondi.
Con un microcontrollore è molto più semplice invece lavorare nel formato Unix Time ( il numero di secondi trascorsi dal 01/01/1970 00:00:00)
In questo modo si riduce tutto ad una singola variabile unsigned long.

Inoltre l'ESP32 ha tutto il necessario per sincronizzare il tempo di sistema già a livello di core e non è necessario fare le chiamate manuali al server NTP.
Ti faccio un paio di copia e incolla di quanto faccio di solito in questi casi.

// Timezone definition
#include <time.h>
#define MYTZ "CET-1CEST,M3.5.0,M10.5.0/3"
// Struct for saving actual time
struct tm tInfo;

// nel setup, dopo essersi connessi al WiFi (il resto la fa in background l'ESP)
setup() {
....
configTzTime(MYTZ, "time.google.com", "time.windows.com", "pool.ntp.org");
}

// Nel momento in cui devo usare il tempo di sistema
getLocalTime(&tInfo);

// se è necessario accedere singolarmente ad ore, minuti, secondi:
if( tInfo.tm_hour == X && tInfo.tm_min == Y) {etc etc }

// oppure se devo "solo" stampare il tempo su seriale, display, su un file etc etc
// L'istruzione che c'è alla base è strftime
// http://www.cplusplus.com/reference/ctime/strftime
Serial.println(&tInfo, "%A, %B %d %Y %H:%M:%S");

Se invece devo verificare quando sono all'interno di un intervallo temporale faccio cosi:

  • calcolo il valore unix time corrispondente all'inizio della giornata;
  • ci aggiungo il valore corrispondente all'ora in cui deve avviarsi /fermarsi l'evento;
  • lo confronto con il valore unix time attuale e agisco di conseguenza.

Ad esempio se l'evento deve iniziare alle 08:30 e finire alle 11:45:

startTime = 08 * 3600UL + 30 * 60UL;
stopTime = 11 * 3600UL + 45 * 60UL;
  // get updated timedate and check if there is any active event
  getLocalTime(&tInfo);
  uint32_t unixTime = mktime(&tInfo);
  uint32_t startOfDay = unixTime - tInfo.tm_hour*3600UL - tInfo.tm_min*60UL - tInfo.tm_sec;
  
  // For each event stored we evaluate whether it is necessary to activate
  if (startOfDay + startTime ==  unixTime) {
    Serial.println("Event actvivated");
  }
  
  if (startOfDay + stopTime == unixTime) {
    Serial.println("Event deactvivated");
  }

Questo approccio, oltre a semplificare enormemente le operazioni di confronto, è facilmente scalabile per avere più eventi da verificare (esempio un array di struct ciclato con un for) ed ha il vantaggio di gestire in modo intrinseco gli intervalli a cavallo della mezzanotte perché il tempo in formato Unix Time incrementa sempre.

Ciao @cotestatnt,
ti ringrazio per l'ottimo consiglio datomi, ora ti farei qualche domanda: per salvare i dati in EEPROM, cosa mi consigli di fare? Salvo ore e minuti (byte) oppure in formato unix (uint32_t). Poi, se avessi bisogno di impostare manualmente gli orari di sistema? Si può?

Per l'altra questione del log hai/qualcuno ha suggerimenti?

Grazie per tutto

Alan Masutti

Ciao a tutti, facendo prove sono incappato in quel reboot di cui oarlo nel primo post! Durante la ISR il core 1 si resetta e mi da questo bactrace:

Guru Meditation Error: Core  1 panic'ed (Coprocessor exception)
Core 1 register dump:
PC      : 0x40080f8f  PS      : 0x00060031  A0      : 0x8008103c  A1      : 0x3ffbe6d0  
A2      : 0x3ffc11a4  A3      : 0x00000001  A4      : 0x00060023  A5      : 0x00000000  
A6      : 0x00000001  A7      : 0x00000001  A8      : 0x3ffc11a8  A9      : 0x3ffbe770  
A10     : 0x3ffbec00  A11     : 0x00000000  A12     : 0x3ffc13bc  A13     : 0x00000100  
A14     : 0x00000008  A15     : 0x00000000  SAR     : 0x00000018  EXCCAUSE: 0x00000004  
EXCVADDR: 0x00000000  LBEG    : 0x00000000  LEND    : 0x00000000  LCOUNT  : 0x00000000  
Core 1 was running in ISR context:
EPC1    : 0x40080f8f  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x40086ae5

 Backtrace:0x40080f8f:0x3ffbe6d0 0x40081039:0x3ffbe7b0 0x4008482d:0x3ffbe7d0 0x4014c93b:0x3ffbc680 0x400dc5bf:0x3ffbc6a0 0x4008a43d:0x3ffbc6c0 0x40088c59:0x3ffbc6e0
 
Rebooting...

e tramite il tool di decodifica viene alla luce questo:

PC: 0x40080f8f: pluviometroISR() at C:\Users\alanm\AppData\Local\Temp\arduino_build_218378\sketch/main.h line 317
EXCVADDR: 0x00000000

Decoding stack results
0x40080f8f: pluviometroISR() at C:\Users\alanm\AppData\Local\Temp\arduino_build_218378\sketch/main.h line 317
0x40081039: __onPinInterrupt at C:\Users\alanm\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.4\cores\esp32\esp32-hal-gpio.c line 220
0x4014c93b: esp_pm_impl_waiti at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp32/pm_esp32.c line 492
0x400dc5bf: esp_vApplicationIdleHook at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/esp32/freertos_hooks.c line 63
0x4008a43d: prvIdleTask at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/tasks.c line 3382
0x40088c59: vPortTaskWrapper at /home/runner/work/esp32-arduino-lib-builder/esp32-arduino-lib-builder/esp-idf/components/freertos/port.c line 143

nessuno sa il motivo?

Fiducioso di trovare soluzione,
Alan Masutti

unitx time senza dubbio, il formato cosiddetto "human readable", in quanto tale, sarebbe da usare solo se è richiesta l'interazione di un utente umano.
Per quanto riguarda il codice, anche tutto quello che ha a che fare con l'EEPROM è decisamente prolisso... se usavi le istruzioni put() e get() scrivevi la metà del codice.

Inoltre l'ESP32 non ha EEPROM, la libreria fornisce i metodi solo per compatibilità, ma rimane comunque deprecata. Con questa MCU sarebbe molto meglio usare la libreria Preferences, sempre disponibile a livello di core.
Nemmeno a farlo apposta c'è un esempio dove viene mostrato come memorizzare una struct contenente ore, minuti ed altre variabili.

Per quanto riguarda l'eccezione, premesso che usare un interrupt ed un ticker per un semplice debounce mi pare un'esagerazione davvero non necessaria, l'errore sembra relativo al coprocessore.
Ad esempio è noto che nelle ISR non è possibile usare delle variabili float, ed il tempo che passi al ticker come parametro è un float.
Potresti provare con attach_ms() e vedere se risolvi, ma io eviterei proprio di usare una ISR per questa funzione.

Ciao @cotestatnt

Non so se hai notato, ma anche rain[], rain24 sono float, provo a sostituirle con delle intere e dividere per 10 successivamente in una variabile float.

Per quanto riguarda il Ticker, lo elimino subito! Salvo l'istante e controllo nel loop che dopo 100ms riattivi l'interrupt su quel pin!

Proverò ad usare la libreria Preferences se dici che può migliorare le cose!

Poi (ultima cosa) continuo ad avere indecisioni sul log per appunto la pioggia, la mia idea era di scrivere un file CSV e nella prima colonna mettere il tempo in unix, e nella seconda solo l'incremento fatto ovvero 0.30 mm, in modo da confrontare l'istante corrente con gli istanti salvati nel file e andare a sommare tutti i dati, avendo così molta più precisione (oltre che dati persistenti) nel calcolo delle ultime ore.
La questione è: Dove posso salvare il file? SRAM del core? EEPROM I2C esterna? FRAM I2C Esterna? SD? SPIFFS? USB?

Grazie

Alan Masutti

Più che migliorare, semplificare. Soprattutto nell'ottica di future modifiche: se un codice è più leggibile, metterci le mani è molto molto più semplice. Oltre al fatto che abbandoni una libreria non più supportata dagli sviluppatori.

Per quanto riguarda il log, il filesystem dell'ESP, cosi come eventuali EEPROM esterne non sono adatti a scritture frequenti. Con la SRAM in caso di riavvio perdi tutto.
Non rimangono che le opzioni SD e FRAM; io opterei per la prima anche per una questione di accessibilità al file: puoi in qualsiasi momento estrarre la scheda ed usare un pc, oppure esporne il contenuto ad esempio con l'ausilio di un web server integrato nell'ESP (eventualmente contenuto a sua volta in un'apposita cartella della SD).

Ciao @cotestatnt,
stavo per scrivere un post di ringraziamento quando ho capito di essere bloccato anche nella lettura del CSV.
Premetto che il codice è cambiato e sto parlando della versione Bluetooth (mancano alcune delle modifiche suggerite, ma appena riesco a far funzionare il log le faccio) quindi il codice si trova qui
Per gestire il pluviometro, pensavo di salvare (come detti in precedenza) in un file *.csv
un timestamp e il valore del pluviometro ad ogni interrupt (non lo faccio nella ISR altrimenti esplode)
così: "timestamp;0.30\r\n" per poi andare a leggere tutte le righe e confrontare il timestamp salvato con now in formato unix time!
Avevo trovato il CSV_Parser ma è fortemente instabile (forse sbaglio io qualcosa), ma come detto non so per quale motivo ma faceva resettare il core.

Ora le domande sono:

  1. Esiste un modo per leggere un Csv in modo sicuro senza sapere quante sono le righe?

  2. Il *.csv va bene o è meglio un JSON?

  3. Sono sulla strada giusta o sto sbagliando completamente filosofia?

Bene, dopo tutte le domande continuo a ringraziare per l'aiuto e vi saluto

Alan Masutti

Ciao @alanmasutti.

Il csv va benissimo, JSON ha un sacco di "overhead" senza però avere in questo caso alcun vantaggio.

Un file è semplicemente uno Stream di byte e quindi ti puoi muovere all'interno di esso con i relativi metodi offerti dalla classe.

Ad esempio se vuoi contare quante righe sono:

File file = SPIFFS.open("/myfile.csv", "r");
  int nRows = 0;
  if (file) { 
    while (file.find("\n")) 
        nRows++;
  }
  Serial.printf("Numero righe %d\n", nRows);

Per estrapolare i valori del timestamp con una struttura cosi semplice, secondo me non è necessario scomodare una libreria.
Ad esempio, se vogliamo recuperare il totale dell'ultima ora (o dell'intervallo che preferisci: potresti aggiungerlo come secondo parametro della funzione):

float rainLastHour (uint32_t nowTime)  {
  File file = SPIFFS.open("/myfile.csv", "r");
  float value = 0.0;
  while (file.available()) {
    // Per semplificare usiamo la classe String e i suoi metodi, anche perché su ESP32 non crea grandi problemi con la memoria.
    // Se sei un NO-String, usare un char array è solo un po' più laborioso, ma in fondo nulla di che..
    String line = file.readStringUntil('\n');    
    uint32_t timeStamp = line.substring(0, line.indexOf(";")).toInt();
    if(timeStamp > nowTime- 3600)
        value += 0.30;
  }
  file.close();
  return value;
}

Ciao @cotestatnt,
ti ringrazio infinitamente per l'aiuto e la pazienza...

Alan Masutti

Ciao, ma startTime e startTime sono delle variabili long int o altro? Vorrei provare questo sistema di clock via web ma non capisco come usare questi input per lo scheduler.

Grazie

Ciao @droidprova,
sono di tipo uint32_t ovvero unsigned long

Alan

Grazie 1000!

Ciao a tutti di nuovo,
direi che per evitare il crosposting pongo un'altra questione qui anche se riguarda solo l'irrigazione e non il pluviometro!

Mi trovo ora ad affrontare un backtrace di questo tipo:

E (7760) task_wdt: Task watchdog got triggered. The following tasks did not reset the watchdog in time:
E (7760) task_wdt:  - IDLE0 (CPU 0)
E (7760) task_wdt: Tasks currently running:
E (7760) task_wdt: CPU 0: CONNECTION_LOOP
E (7760) task_wdt: CPU 1: IDLE1
E (7760) task_wdt: Aborting.
abort() was called at PC 0x400e12b7 on core 0

Backtrace: 0x4008c498:0x3ffbe170 0x4008c6c9:0x3ffbe190 0x400e12b7:0x3ffbe1b0 0x400847b5:0x3ffbe1d0 0x40089b19:0x3ffd1fc0 0x400d6a91:0x3ffd1fe0 0x400d53c3:0x3ffd2000 0x400d53ed:0x3ffd2020 0x400d555f:0x3ffd2040 0x40088be1:0x3ffd2060

Il codice nuovo contiene anche la possibilità di fare l'update tramite OTA ed è disponibile qui

So di aver abilitato il TWDT, ma non capisco perché si triggera... Il tool ESPEcxeptionDecoder mi dice che vi è un problema con la proprietà WiFi::status() e alla riga 610 del ConnectionMenager.h nella funzione CheckConnection() va in tilt!

Sapete aiutarmi?

Alan Masutti

Ps. Se ho sbagliato a postare la domanda qui ditemi che apro un altro post!!

Trovo sia un po' più complicato di quanto sarebbe necessario il meccanismo di verifica della connessione, comunque prova ad aggiungere yield() nel loop del task (ovvero un'istruzione che resetta il watchdog).
Inoltre (come abitudine) è sempre bene inserire l'istruzione che cancella il task, anche nel caso in cui non sarà mai vera (magari decidi di avviare/fermare un task a comando).

void connectionLoopTask(void *vParameters) {
  while (1) {
    connectionLoop();
    yield();
  }
  vTaskDelete(NULL);
}

Ciao @cotestatnt,
volevo chiedere a questo punto la differenza tra yield() e esp_tack_wdt_reset() ed inoltre come è possibile che il TWDT si triggeri anche se CONNECTION_LOOP_T non è iscritto alla supervisione del timer, vedi line 414 void initWDT().

Grazie

Alan Masutti

Nel dettaglio non lo so, ma esp_tack_wdt_reset() è una funzione dell'ESP-IDF che resetta solo il watchdog del task, mentre l'altra è più generica.
Potresti provare anche ad aggiungere un microscopico vTaskDelay (al posto di yield) considerando che non è un task dal timing critico.

vTaskDelay(10); // 10 sono "ticks", il tempo reale dipende dal clock

Per quanto riguarda initWDT(), ricordo che in passato anche io ho avuto difficoltà a disattivare il WDT per lo specifico taskX.
Siccome è un approccio che cerco di evitare (disattivare il watchdog) alla fine non ho più approfondito perché trovai la soluzione corretta per il watchdog reset.

Forse usare direttamente le funzioni dell 'ESP-IDF con il framework Arduino non è "trasparente" come uno ci si aspetta? Non saprei, bisognerebbe approfondire.

Ciao @cotestatnt,

Dopo altre prove ho capito (facendo mente locale sulle modifiche fatte) che togliendo la riga 141 riguardo l'assegnazione dell'IP statico il problema non si verifica!!!

Facendo questo però il problema non lo risolvo perché mi serve comunque un IP statico!

Poi, stando a quanto posso leggere nel sito di Espressif sul WDT credo che non iscrivendo il task al TWDT automaticamante questo non supervisiona quel task; tant'è che il task che fa triggerare il timer è l'IDLE0, a quanto dice il backtrace, che per quanto poco conosca l'ESP32 e ESP-IDF non è il CONNECTION_LOOP_T

Diciamo che anche io sospetto che tra il framework di Arduino e l'ESP-IDF non ci sia del tutto la trasparenza!

Detto ciò, ti chiedo: quando dici

cosa intendi e soprattutto come mi consigli di operare?

Continuo a ringraziarti per il tempo che impieghi a mio favore e ti saluto

Alan Masutti