diff --git a/platformio/stima_v3/stimawifi/bin/lolin_c3_mini_v3/firmware.bin b/platformio/stima_v3/stimawifi/bin/lolin_c3_mini_v3/firmware.bin new file mode 100644 index 000000000..fd8708ab3 Binary files /dev/null and b/platformio/stima_v3/stimawifi/bin/lolin_c3_mini_v3/firmware.bin differ diff --git a/platformio/stima_v3/stimawifi/include/stimawifi_config.h b/platformio/stima_v3/stimawifi/include/stimawifi_config.h index a4d26dde4..cc4e33d11 100644 --- a/platformio/stima_v3/stimawifi/include/stimawifi_config.h +++ b/platformio/stima_v3/stimawifi/include/stimawifi_config.h @@ -2,21 +2,28 @@ #define STIMAWIFI_CONFIG_H_ // increment on change -#define SOFTWARE_VERSION "2024-10-17T00:00" // date and time -#define MAJOR_VERSION "20241017" // date YYYYMMDD +#define SOFTWARE_VERSION "2024-10-31T00:00" // date and time +#define MAJOR_VERSION "20241031" // date YYYYMMDD #define MINOR_VERSION "0" // time HHMM without leading 0 +// SSID and password of WiFi for setup #define WIFI_SSED "STIMA-config" #define WIFI_PASSWORD "bellastima" + +// defaul sample time to get measure from sensors #define DEFAULT_SAMPLETIME 30 + +// udp port to use for communicate with androd gps_forwarder app #define UDP_PORT 8888 +// display I2C address #define OLEDI2CADDRESS 0X3C // logging level at compile time // Available levels are: // LOG_LEVEL_SILENT, LOG_LEVEL_FATAL, LOG_LEVEL_ERROR, LOG_LEVEL_WARNING, LOG_LEVEL_NOTICE, LOG_LEVEL_VERBOSE #define LOG_LEVEL LOG_LEVEL_NOTICE + // Length of datetime string %04u-%02u-%02uT%02u:%02u:%02u #define DATE_TIME_STRING_LENGTH (25) @@ -26,14 +33,19 @@ // minimum thread stack size for warning #define STACK_MIN_WARNING 100 +// port for http server #define STIMAHTTP_PORT 80 #define FIRMWARE_TYPE "LOLIN_C3_MINI" + +// set to 1 if we use PMS sensor #define PMS_RESET 0 +// define pins for I2C #define SCL_PIN SCL #define SDA_PIN SDA +// define reset and LED pins #if defined(ARDUINO_LOLIN_C3_MINI) //C3 mini // https://www.wemos.cc/en/latest/_static/files/sch_c3_mini_v2.1.0.pdf @@ -56,13 +68,15 @@ #define LED_PIN 34 // not connected to neopixel #endif -// for sensor_t +// size for sensor_t #define SENSORDRIVER_DRIVER_LEN 5 #define SENSORDRIVER_TYPE_LEN 5 #define SENSORDRIVER_META_LEN 30 -#define CH 8 // character height px for display +// character height px for display +#define CH 8 +// define parameter for queues len and communication //#define DATA_BURST (SENSORS_MAX*VALUES_TO_READ_FROM_SENSOR_COUNT) #define DATA_BURST (15) #define DATA_BURST_RECOVERY (DATA_BURST) @@ -75,8 +89,7 @@ #define MQTT_QUEUE_SPACELEFT_RECOVERY (DATA_BURST*2) // SD card SPI PIN assignment - -//Micro SD Card Shield +// Micro SD Card Shield #define C3SCK 1 #define C3MISO 0 #define C3MOSI 4 @@ -91,18 +104,20 @@ // SPI clock #define SPICLOCK 10000000 -// SD card max number of file +// SD card max number of file opened #define SDMAXFILE 6 - +// SD card file name for archive +#define SDCARD_INFO_FILE_NAME ("/info.dat") #define SDCARD_ARCHIVE_FILE_NAME ("/archive.dat") -// littlefs max number of file +// littlefs max number of file opened on EEPROM #define LFMAXFILE 4 -// time in seconds saved on SD card for archive and recovery +// time in seconds saved on sqlite on SD card for tmp archive and recovery #define SDRECOVERYTIME (3600*24*1) +// sqlite setup /* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/memory-types.html There is 520 KB of available SRAM (320 KB of DRAM and 200 KB of @@ -131,9 +146,9 @@ */ #define MQTT_PACKET_SIZE (220) +// MQTT broker port # define MQTT_SERVER_PORT (1883) - /*! \def CONSTANTDATA_BTABLE_LENGTH \brief Maximum lenght of btable code plus terminator that describe one constant data. diff --git a/platformio/stima_v3/stimawifi/src/db_thread.cpp b/platformio/stima_v3/stimawifi/src/db_thread.cpp index 077c0574e..6da9fc43f 100644 --- a/platformio/stima_v3/stimawifi/src/db_thread.cpp +++ b/platformio/stima_v3/stimawifi/src/db_thread.cpp @@ -163,11 +163,7 @@ bool dbThread::data_purge(const bool flush=false, int nmessages=100){ data->logger->notice(F("db Start purge")); //The values emitted by the RETURNING clause are the values as seen - //by the top-level DELETE, INSERT, or UPDATE statement and do not - //reflect any subsequent value changes made by triggers. Thus, if - //the database includes AFTER triggers that modifies some of the - //values of each row inserted or updated, the RETURNING clause emits - //the original values that are computed before those triggers run + //by the top-level DELETE, INSERT, or UPDATE statement char sql[100]; //char sql[] = "DELETE FROM messages WHERE datetime < strftime('%s',?)"; @@ -264,7 +260,7 @@ bool dbThread::data_purge(const bool flush=false, int nmessages=100){ } -// set data in specific datetime range as unset for recovery +// set data in specific datetime range as unsent for recovery // of messages for retrasmission bool dbThread::data_set_recovery(void){ int rc; @@ -610,7 +606,7 @@ void dbThread::Run() { } // open and write info.dat file - File infoFile = SD.open("/info.dat", FILE_WRITE); + File infoFile = SD.open(SDCARD_INFO_FILE_NAME, FILE_WRITE); if (!infoFile){ data->logger->error(F("db failed to open info file for writing")); } diff --git a/platformio/stima_v3/stimawifi/src/measure_thread.cpp b/platformio/stima_v3/stimawifi/src/measure_thread.cpp index 9b609fa17..aa27d8a26 100644 --- a/platformio/stima_v3/stimawifi/src/measure_thread.cpp +++ b/platformio/stima_v3/stimawifi/src/measure_thread.cpp @@ -83,6 +83,7 @@ void measureThread::get_summary_data_in_progress(uint8_t i) { } +// encode and enqueue in a proper queue one message void measureThread::enqueueMqttMessage(uint8_t i ) { mqttMessage_t mqtt_message; @@ -179,7 +180,7 @@ void measureThread::enqueueMqttMessage(uint8_t i ) { } } -// execute all measure required +// execute all required measure void measureThread::doMeasure() { //LockGuard guard(data->i2cmutex); @@ -308,15 +309,15 @@ void measureThread::Cleanup() void measureThread::Run() { data->logger->notice("Starting Thread %s %d", GetName().c_str(), data->id); for(;;){ - // wait for notification from the main task when we have to do measurements + // wait for notification from the main task; start when we have to do measurements WaitForNotification(); - if (timeStatus() == timeSet) doMeasure(); + if (timeStatus() == timeSet) doMeasure(); // measure il we can use a timestamp // check heap and stack //data->logger->notice(F("HEAP: %l"),esp_get_minimum_free_heap_size()); if( esp_get_minimum_free_heap_size() < HEAP_MIN_WARNING)data->logger->error(F("HEAP: %l"),esp_get_minimum_free_heap_size()); //data->logger->notice("measure stack: %d",uxTaskGetStackHighWaterMark(NULL)); - if (uxTaskGetStackHighWaterMark(NULL) < STACK_MIN_WARNING ) data->logger->error("measure stack"); + if (uxTaskGetStackHighWaterMark(NULL) < STACK_MIN_WARNING ) data->logger->error("measure stack"); //check memory collision } }; diff --git a/platformio/stima_v3/stimawifi/src/measure_thread.h b/platformio/stima_v3/stimawifi/src/measure_thread.h index f8633419f..e7c4b91ae 100644 --- a/platformio/stima_v3/stimawifi/src/measure_thread.h +++ b/platformio/stima_v3/stimawifi/src/measure_thread.h @@ -3,7 +3,7 @@ #ifndef MEASURE_THREAD_H_ #define MEASURE_THREAD_H_ -struct measure_data_t { +struct measure_data_t { // thread communication data int id; frtosLogging* logger; Queue* mqttqueue; diff --git a/platformio/stima_v3/stimawifi/src/publish_thread.cpp b/platformio/stima_v3/stimawifi/src/publish_thread.cpp index edff50dd7..8f50ebca9 100644 --- a/platformio/stima_v3/stimawifi/src/publish_thread.cpp +++ b/platformio/stima_v3/stimawifi/src/publish_thread.cpp @@ -266,7 +266,7 @@ void publishThread::archive() { }else{ data->logger->error(F("publish dequeue mqtt message")); } - } +} // if required connect to the broker, publish maint message, publish constant data messages // try to send message to the broker @@ -401,7 +401,7 @@ void publishThread::Run() { //data->logger->notice(F("HEAP: %l"),esp_get_minimum_free_heap_size()); if( esp_get_minimum_free_heap_size() < HEAP_MIN_WARNING)data->logger->error(F("HEAP: %l"),esp_get_minimum_free_heap_size()); //data->logger->notice("stack publish: %d",uxTaskGetStackHighWaterMark(NULL)); - if( uxTaskGetStackHighWaterMark(NULL) < STACK_MIN_WARNING )data->logger->error(F("publish stack")); + if( uxTaskGetStackHighWaterMark(NULL) < STACK_MIN_WARNING )data->logger->error(F("publish stack")); // check for memory collision } }; diff --git a/platformio/stima_v3/stimawifi/src/publish_thread.h b/platformio/stima_v3/stimawifi/src/publish_thread.h index c98c1388f..ffd46c7cb 100644 --- a/platformio/stima_v3/stimawifi/src/publish_thread.h +++ b/platformio/stima_v3/stimawifi/src/publish_thread.h @@ -6,7 +6,7 @@ #ifndef PUBLISH_THREAD_H_ #define PUBLISH_THREAD_H_ - +// thread exchange data struct struct publish_data_t { int id; frtosLogging* logger; diff --git a/platformio/stima_v3/stimawifi/src/stimawifi.ino b/platformio/stima_v3/stimawifi/src/stimawifi.ino index c2a2d11d3..d1b3765f0 100644 --- a/platformio/stima_v3/stimawifi/src/stimawifi.ino +++ b/platformio/stima_v3/stimawifi/src/stimawifi.ino @@ -19,7 +19,7 @@ along with this program. If not, see . /* TODO -* manage remote procedure call +* manage remote procedure call when it will be required */ /* @@ -38,43 +38,56 @@ ultime misurazioni effettuate L'SD card è opzionale; se presente è utilizzata per memorizzare i dati in sqlite3; la struttura del DB e visibile nel file DB_structure.pdf - +Dopo essere passati dal DB sqlite i file vengono trasferiti in un +archivio, integrato in modalità append. Per poter utilizzare la stazione in modalità "mobile" ossia con -posizione continuamente modificabile ci sono due possibilità: +posizione continuamente aggiornata ci sono due possibilità: * connettere un modulo GPS con Ublox neo6m * utilizzare l'app android GPSD_forwarder - La configurazione è gestita sul server e i thread sono attivati automaticamente. Quando la geolocalizzazione è possibile i dati vengono generati, in caso contrario no. +E' attivo un web server accessibile quando ci si connette allo stesso Wifi +a cui è connessa la stazione, Sono forniti le seguenti URL/servizi: + +http:// Full main page +http:///data.json Data in json format +http:///geo Coordinate of the station + +I dati sono visualizzabile da browser sempre se connessi allo stesso WiFi +autenticandosi sul server RMAP e accedendo alla propria pagina personale, +selezionando la stazione e poi alla voce "Mostra i dettagli stazione" e poi +"Dati locali in tempo reale". +Il reset delle configurazioni è effettuabile a stazione disalimentata +collegando a massa il pin RESET_PIN (4) o premendo il pulsante A della +board del display, alimentare la stazione e dopo 10 secondi scollegare il +RESET_PIN o rilasciare il pulsante. Il frusso dei dati nelle code è il seguente: i dati e metadati sono generati da threadMeasure e accodati nella coda -mqttqueue per la pubblicazione, ricevuti da threadPublish; se non c'è spazio -vanno dirattamente nella coda dbqueue per l'archiviazione su SD card. -threadMeasure è attivato periodicamente. threadPublish prova la -pubblicazione MQTT, in ogni caso dopo un tentativo vengono accodati -per l'archiviazione nella coda dbqueue flaggati relativamente al -risultato della pubblicazione. Dopo il tenativo di invio al broker MQTT -i dati vengono inviati alla coda dbqueue per l'archiviazione. -Il thread threadDb gestisce la scrittura dei dati con eventuale -sovrascrittura nel database. -Il thread threadDb viene attivato periodicamente -per recuperare l'invio dei dati archiviati e non ancora trasmessi -inviando un piccolo blocco di dati a mqttqueue fino a quando avanzi -sufficiente spazio nella coda. Il thread threadDb esegue a priorità -più alta degli altri per garanetire l'archiviazione in tempi utili per -non riempire le code. -I dati vengo continuamente ripuliti eliminando dal database i dati -più vecchi. Se all'avvio i dati presenti nel db risultano essere -tutti vecchi l'intero DB viene rinomitato e ricreato vuoto. +mqttqueue per la pubblicazione, ricevuti da threadPublish per la +pubblicazione sul broker MQTT; se non c'è spazio +vanno direttamente nella coda dbqueue per l'archiviazione su SD card. +threadMeasure è attivato periodicamente. +threadPublish prova la pubblicazione MQTT. +Dopo ogni tentativo di pubblicazione al broker MQTT +i dati vengono accodati per l'archiviazione nella coda dbqueue +etichettati relativamente al risultato della pubblicazione. +Il thread threadDb gestisce due tipi di archiviazione dati. +Il primo (DB) che contiene gli ultimi dati misurati (solitamente 24 ore) +con sovrascrittura nel database e una etichetta a indicare lo stato di +pubblicazione; fino a quando i dati sono presenti in questo +DB i dati possono essere recuperati per la pubblicazione fino al successo della +pubblicazione. +Quando i dati nel DB invecchiano oltre il limite vengono trasferiti nell'archivio +dove potranno essere riletti solo tramite un PC. Ogni thread ha una struttura dati che descrive lo stato di funzionamento. Il thread loop di arduino effettua una sintesi degli stati di tutti i thread e li visualizza tramite i colori del LED e tramite il display opzionale. -Threads +Threads: thread loop arduino @@ -84,7 +97,8 @@ insieme ad alcuni parametri univoci della stazione. Tramite questi ultimi la configurazione stazione viene scaricata dal server. Il thread governa la visualizzazione sul display e la colorazione del LED. Inoltre è possibile visualizzare i dati misurati tramite un -browser. La libreria TimeAlarm gestisce l'attivazione dei segnali ai +browser indirizzandolo sulla pagina personale sul server RMAP. +La libreria TimeAlarm gestisce l'attivazione dei segnali ai thread per attivazioni perioche. threadMeasure @@ -93,7 +107,8 @@ Questo thread si occupa di interrogare i sensori, associare i metadati e accodarli per la pubblicazione e archiviazione. I sensori vengono interrogati in parallelo tramite delle macchine a stati finiti. Inoltre viene prodotta una struttura di dati di riassunto delle misure -effettuate. +effettuate. Insieme alla libreria di driver per sensori viene gestita +la loro inizializzazione e il restart in caso di ripetuti errori. threadPublish @@ -104,19 +119,33 @@ associate le coordinate ai dati. threadDb -Archivia i dati su SD card. Il formato è quello portabile di sqlite3 e +Archivia i dati su SD card. Il formato del DB è quello portabile di sqlite3 e possono essere letti tramite la stessa libreria da PC. Più scritture con gli stessi metadati aggiornano i dati, non creano record -duplicati. +duplicati. L'archivio invece è composto da due file, uno di descrizione +e il secondo con i dati. +Il thread threadDb viene attivato periodicamente +per recuperare l'invio dei dati presenti nel DB e non ancora pubblicati +inviando un piccolo blocco di dati a mqttqueue fino a quando avanzi +sufficiente spazio nella coda di pubblicazione per altri thread. +Il thread threadDb esegue a priorità più alta degli altri per garantire +l'archiviazione senza perdita di dati in tempi utili e non riempire le code. +I dati vengo continuamente traferiti dal DB all'archivio eliminando dal +database i dati più vecchi trasferendoli in un semplice archivio su file +sempre sull'SD card. I dati in archivio possono essere letti e traferiti +sul server RMAP tramite mqtt2bufr, un tool della suite RMAP. +Se all'avvio i dati presenti nel DB risultano essere +tutti vecchi i dati vengono traferiti all'archivio e l'intero DB viene eliminato +e ricreato vuoto per limiti di memoria e performance. threadUdp -Legge i dati UDP inviati dalla app GPSD forwarder riempiendo una -struttura dati con la geolocalizzazione e un timestamp. +Legge i dati UDP inviati dalla app GPSD forwarder di uno smartphone +riempiendo una struttura dati con la geolocalizzazione e un timestamp. threadGps -Legge i dati dal GPS (porta seriale) riempiendo una struttura dati con +Legge i dati dal GPS se presente (porta seriale) riempiendo una struttura dati con la geolocalizzazione e un timestamp. */ @@ -184,6 +213,7 @@ void display_summary_data(char* status) { u8g2.sendBuffer(); } +// Print date and time to loggin system void printLocalTime() { struct tm timeinfo; @@ -214,7 +244,16 @@ void timeavailable(struct timeval *t) } } -// HTTP response +// HTTP response with json data +// data is a fixed selection of total dataset read from sensors and sintetic status +// key are: +// TEMP +// HUMID +// PM2 +// PM10 +// CO2 +// STAT + String Json(){ String str ="{" @@ -239,6 +278,7 @@ String Json(){ return str; } + // HTTP response for browser in smartphone // The browser get a page from server, query the phone for geolocation, // send coordinate with an ajax request to ESP @@ -321,7 +361,8 @@ String Data(){ return str; } -// HTTP response +// function to prepare HTML response main page +// https://lastminuteengineers.com/esp8266-dht11-dht22-web-server-tutorial/ String FullPage(){ String ptr = " \n" "\n" @@ -901,7 +942,8 @@ void setup() { #include "soc/rtc_cntl_reg.h" WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable Brownout detector */ - + + // Initialize GPIO, library and sensors pinMode(RESET_PIN, INPUT_PULLUP); pixels.begin(); //INITIALIZE NeoPixel strip object (REQUIRED) @@ -930,6 +972,8 @@ void setup() { Serial.begin(115200); //Serial.setDebugOutput(true); + // if loggin on SDcard is enabled initialize SDcard + // initialize logging #if (ENABLE_SDCARD_LOGGING) delay(10000); @@ -1023,6 +1067,7 @@ void setup() { u8g2.sendBuffer(); delay(3000); } + // reset configuration on on EEPROM LittleFS.begin(false,"/littlefs",LFMAXFILE); LittleFS.format(); frtosLog.notice(F("Reset wifi configuration")); @@ -1063,6 +1108,7 @@ void setup() { wifiManager.resetSettings(); } + // configure NTP //sntp_init(); //sntp_setoperatingmode(SNTP_OPMODE_POLL); //sntp_setservername(0, station.ntp_server); @@ -1189,7 +1235,8 @@ void setup() { u8g2.sendBuffer(); } } - + + // perform remote configuration String remote_config= rmap_get_remote_config(); if ( remote_config == String() ) { @@ -1198,9 +1245,11 @@ void setup() { }else{ writeconfig_rmap(remote_config); } - + + // check for a firmware upgrade from server firmware_upgrade(); - + + // if here we do not have configuration reboot if (!rmap_config(remote_config) == 0) { frtosLog.error(F("station not configurated")); //frtosLog.notice(F("Reset wifi configuration")); @@ -1222,7 +1271,6 @@ void setup() { reboot(); } - // Set up mDNS responder: // - first argument is the domain name, in this example // the fully-qualified domain name is "stimawifi.local" @@ -1256,6 +1304,7 @@ void setup() { u8g2.sendBuffer(); } + // wait for date and time setup from NTP if (WiFi.status() == WL_CONNECTED) { //if (esp_netif_sntp_sync_wait(pdMS_TO_TICKS(60000)) == ESP_OK) { // alternative starting from idf 5.1 with esp_netif_sntp.h int retry = 0; @@ -1298,8 +1347,8 @@ void setup() { //u8g2.print(F("RESTART")); u8g2.sendBuffer(); - //delay(5000); - //reboot(); //timeout - reset board + // delay(5000); + // reboot(); //timeout - reset board } } else { time_t time=now(); @@ -1314,24 +1363,24 @@ void setup() { frtosLog.notice(F("mqtt server: %s"),station.mqtt_server); - Alarm.timerRepeat(10, dataRecovery); // timer for data recoveru from DB - Alarm.timerRepeat(station.sampletime, measureAndPublish); // timer for every SAMPLETIME seconds + Alarm.timerRepeat(10, dataRecovery); // timer for data recovery from DB + Alarm.timerRepeat(station.sampletime, measureAndPublish); // timer for measure every SAMPLETIME seconds Alarm.timerRepeat(3,displayStatus); // display status every 3 seconds - time_t reboottime; // we reset everythings one time a week + time_t reboottime; if (pmspresent){ - reboottime=3600*24; // pms stall sometime + reboottime=3600*24; // pms stall sometime, we reboot more }else{ - reboottime=3600*24*7; // every week + reboottime=3600*24*7; // we reset everythings one time a week } frtosLog.notice(F("reboot every: %d"),reboottime); - Alarm.timerRepeat(reboottime,reboot); // reboot + Alarm.timerRepeat(reboottime,reboot); // timer for reboot // upgrade firmware - //Alarm.alarmRepeat(4,0,0,firmware_upgrade); // 4:00:00 every day - Alarm.timerRepeat(3600*24,firmware_upgrade); // every day + //Alarm.alarmRepeat(4,0,0,firmware_upgrade); // 4:00:00 every day + Alarm.timerRepeat(3600*24,firmware_upgrade); // check for firmware upgrade every day - // Add service to MDNS-SD + // Add http service to MDNS-SD MDNS.addService("http", "tcp", STIMAHTTP_PORT); // if mobile station start geolocation thread @@ -1358,15 +1407,12 @@ void setup() { void loop() { // set the priority of this thread vTaskPrioritySet(NULL, tskIDLE_PRIORITY); - //disableLoopWDT(); - // set the priority of this thread - //vTaskPrioritySet(NULL, 5); webserver.handleClient(); //MDNS.update(); - Alarm.delay(0); + Alarm.delay(0); // check for alarms delay(1000); //frtosLog.notice("stack loop: %d",uxTaskGetStackHighWaterMark(NULL)); - if(uxTaskGetStackHighWaterMark(NULL)< 100) frtosLog.error("stack loop"); + if(uxTaskGetStackHighWaterMark(NULL)< 100) frtosLog.error("stack loop"); // check memory collision }