Ciao a tutti,
posto con orgoglio questo messaggio perchè sono finalmente arrivato a fare quello che volevo e cerco di spiegare l'ultimo scoglio che mi affligge sperando in qualche idea o workaround che mi possa essere d'aiuto.
Specifiche:
ARDUINO UNO R4 WIFI
BME280 (Termometro e misuratore di pressione atmosferica)
DS3231 (Real time clock)
MemoryCard SPI per Arduino
Obiettivo del progetto:
Rilevare la temperatura della casetta degli attrezzi che ho in giardino poichè ci metterò delle piante molto sensibili alle basse temperature.
Salvare i valori su file in un file giornaliero con un tracciato da me definito.
Quando è disponibile una rete WIFI di casa (ne ho 5 ma le accendo a singhiozzo per vari motivi ed esigenze che qui sono off topic) deve:
- effettuare l'upload dei file con i dati su internet via FTP su un mio spazio Altervitsa.
- scaricare dallo stesso spazio Altervista i file di configurazione T_Max e T_Min assieme ad altri così che possa comandare Arduino, anche se in differita, cambiando i valori a mio piacere (p.es a seconda della stagione).
Quando la temperatura scende sotto la soglia T_Min manda una notifica telegram (così posso ritirare le piante in casa per evitare le gelate) oppure quando la temperatura sale sopra la soglia T_Max da me impostata manda comunque una notifica telegram.
Poter usare Google Charts per elaborare il file di testo (che è di fatto un csv) e creare il grafico che mi mostra i dati raccolti.
Accendere un led quando devo attivare la stufa a mano, accendere un led quando devo accedere a mano la luce apposta per le piante.
Usare la matrice a led per farmi dire cosa sta facendo la macchina o eventuali errori con un messaggio quanto più parlante possibile.
Questo il tracciato del file alimentato da parete :
2024;08;04;02;17;49;26.45;98363.62;5480;26.00;26.50;28.00;0;0
2024;08;04;02;18;24;26.45;98360.73;5480;26.ÿ00;26.50;28.00;0;0
2024;08;04;02;18;50;26.43;98363.44;5480;26.00;26.50;28.00;0;0
2024;08;04;02;19;20;26.44;98359.27;5480;26.00;26.50;28.00;0;0
2024;08;04;02;20;50;26.44;98361.76;5480;26.00;26.50;28.00;0;0
2024;08;04;02;21;20;26.44;98365.40;5480;26.00;26.50;28.00;0;0
2024;08;04;02;21;51;26.47;98361.79;5480;26.00;26.50;28.00;0;0
2024;08;04;02;22;21;26.49;98360.07;5480;26.00;26.50;28.00;0;0
2024;08;04;02;22;51;26.51;98359.79;5480;26.00;26.50;28.00;0;0
2024;08;04;02;23;21;26.53;98364.31;5480;26.00;26.50ÿ;28.00;0;0
2024;08;04;02;23;51;26.56;98359.80;5480;26.00;26.50;28.00;0;0
Si vedono le ÿ di troppo che mi disallineano il file e mi creano problemi col grafico.
A volte il risultato è nettamente peggiore con tantissimi caratteri che non esistono nel file sulla SD (tolta da Arduino aperta da PC), come ad esmepio questo:
2024;08;08;00;27;47;25.71;98628.42;5356;21.00;21.50;30.00;0;0
2024;08;08;00;28;17;25.70;98628.84;5356;21.00;21.50;30.0ے0;0;0
2024;08;08;00;28;47;25.70;98626.29;5356;21.00;21.50;30.00;0;0
2024;08;08;00;29;17;25.68;98632.45;5356;21.00;21.50;30.00;0;0
2024;08;08;00;29;47;25.66;98632.33;5356;21.00;21.50;30.00;0;0
2024;08;08;00;30;17;25.65;98633.77;5356;21.00;21®50;َ0>?ےû?؟ےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےےے024;08;08;00;32;47;25.68;98632.53;5356;21.00;21.50;30.00;0;0
2024;08;08;00;33;17;25.67;98631.60;5356;21.00;21.50;30.00;0;0
2024;08;08;00;33;47;25.67;98630.39;5356;21.00;21.50;30.00;0;0
Ottengo questo pessimo risultato solo cambiando l'alimentazione :
- da PC va sempre tutto bene. Un test di 13 giorni di fila non ha rilevato alcun problema.
- con alimentazione alla parete arrivano le ÿ nel file destinazione.
Specifico che la zona della misura è sempre la stessa, nel raggio di pochi centimetri. Lo dico perchè ho pensato ad interferenze elettromagnetiche.
Il cavo che uso in entrambi i casi è sempre lo stesso.
Il codice che uso, è ovvio ma lo scrivo, è sempre lo stesso. Alla fine del post il codice completo.
La rete WIFI che ho usato per le prove è sempre la stessa , nonostante le 5 alternabili, e l'access point WIFI è sempre uguale ed è sempre nello stesso punto.
Ecco il codice:
#include <Arduino_LED_Matrix.h>
#include <WiFiS3.h>
#include <SD.h>
#include <Wire.h> //Avvio comunicazione I2C
#include <BMx280I2C.h>
#include <uRTCLib.h>
#include <SPI.h>
#include <AsyncTelegram2.h>
// ****************
// Custom include
// ****************
#include "arduino_secrets.h"
#include "coda_led.h"
#include "ftp_led.h"
#include "sd_led.h"
#include "err_led.h"
// ***********
// Definizioni
// ***********
#define HEADER_SIZE 1024 // Buffer size definitions per il download dei file
#define BODY_SIZE 256 // Buffer size definitions per il download dei file
#define WEB_SERVER "www.<mio_sito>.altervista.org" // Web server per il download dei file
#define I2C_bmx280_ADDRESS 0x76
#define I2C_RTC_ADDRESS 0x68
#define IMPLEMENTATION FIFO // Gestione della pila per la coda Telegram : First In First Out
// **********
// Costanti
// **********
//int intervallo_ciclo_secondi = 30; // CICLO PRINCIPALE (misura temperatura)
int intervallo_rilevazione_temp_secondi = 60; // CICLO PRINCIPALE (misura temperatura)
int intervallo_download_file_config_minuti = 14;
int intervallo_di_rilevazione_wifi_minuti = 1;
int intervallo_di_upload_FTP_minuti = 9; // Minuti tra due upload FTP del file del giorno corrente
int intervallo_di_notifica_temperatura_telegram_minuti = 30 ; // secondi minimi tra due notifiche telegram successive
// Flag
bool serial_avail = false;
bool ConnessoInPassato = false; // Vale true appena si connette in modo da tenere traccia di eventuali (dis/ri)connesisoni
// Parametri per gestione notifiche e luce
double TEMP_MIN_ALERT = 26 ;
double TEMP_DELTA = 0.5;
double TEMP_STOP = TEMP_MIN_ALERT + TEMP_DELTA ;
double TEMP_MAX_ALERT = 28 ;
int ORA_LUCE_ON = 7;
int ORA_LUCE_OFF = 17;
float intVREF = 1.100; // Variabili per misurare la tensione della batteria
int PIN_LUCE = 3;
int flag_luce = 0;
int PIN_STUFA = 4;
int flag_stufa = 0;
int old_flag_stufa = 0;
char* nome_file_stufa = "f_stufa.txt";
char messaggioHTML[200];
// Array dei nomi dei file di configurazione da scaricare
const char* fileNames[] = {"T_min.txt", "T_max.txt", "T_delta.txt", "t_on.txt", "t_off.txt"};
// Array dei puntatori alle variabili (di tipo void* per gestire tipi diversi)
void* variables[] = {&TEMP_MIN_ALERT, &TEMP_MAX_ALERT, &TEMP_DELTA, &ORA_LUCE_ON, &ORA_LUCE_OFF};
// Array dei tipi delle variabili (1 = double, 2 = int)
int varTypes[] = {1, 1, 1, 2, 2};
int numFiles = sizeof(fileNames) / sizeof(fileNames[0]);
// **********
// Variabili
// **********
//unsigned int millis_ultimo_ciclo;
unsigned int millis_ultimo_download_file_config;
unsigned int millis_ultima_rilevazione_wifi;
unsigned int millis_ultima_rilevazione_temp;
unsigned int millis_ultimo_upload_FTP; // Soglia di intervallo per upload FTP
unsigned int millis_ultima_notifica_temperatura; // Soglia di intervallo per notifiche Telegram
ArduinoLEDMatrix matrix; // Inizializzo la matrice a LED
WiFiClient DLclient;
// SD e files
char filename[20];
char fileNote[20] = "note.txt";
char fileDel[20] = "file_del.txt";
char lineaMessaggio[250];
char nomeFileDaCancellare[10];
char msg[200];
File logfile;
File root;
// Telegram
WiFiSSLClient client;
//cppQueue q_telegram(150, 60, IMPLEMENTATION); // Istanzio la coda dei mesaggi Telegram -- 60 mesaggi da 150 caratteri ?
AsyncTelegram2 myBot(client);
const char* token = TELEGRAM_TOKEN; // Recupero ID Bot di Telegram
const char* channel = TELEGRAM_CHATID; // Recupero ID Chat di Telegram
char* nome_rete;
// FTP variables & c.
char server[40] = FTP_SERVER;
char user[40] = FTP_USER;
char pasw[40] = FTP_PASSWORD;
char fold[40] = FTP_FOLDER;
char outBuf[128];
File fh;
char outCount;
WiFiClient client_FTP;
WiFiClient dclient_FTP;
//create a BMx280I2C object using the I2C interface with I2C Address 0x76
BMx280I2C bmx280(I2C_bmx280_ADDRESS);
// uRTCLib rtc;
uRTCLib rtc(I2C_RTC_ADDRESS);
void setup() {
Serial.begin(115200);
delay(5000);
// Attendo 5 secondi se non c'è la seriale (ossia se non è attaccato al PC)
while (!Serial && (millis() < 5000)) {}
if(Serial){
serial_avail=true;
Serial.println("SERIAL OK!!!");
}
matrix.begin();
URTCLIB_WIRE.begin();
// Comment out below line once you set the date & time.
// Following line sets the RTC with an explicit date & time
// for example to set January 13 2022 at 12:56 you would call:
// rtc.set(second, minute, hour, dayOfWeek, dayOfMonth, month, year)
/*
rtc.set(0, 30, 17, 7, 10, 8, 24);
messaggioHTML[0]='\0';
rtc.refresh();
sprintf(messaggioHTML, "DEBUG: Controllare data ed ora. %02d:%02d:%02d ⏲️ del %02d/%02d/%04d 📅", rtc.hour(),rtc.minute(),rtc.second(), rtc.day(), rtc.month(), (rtc.year()+2000));
Serial.println(messaggioHTML);
while(1){};
*/
// set day of week (1=Sunday, 7=Saturday)
rtc.refresh();
if (rtc.year()==0) {
Serial.println("RTC initialization failed!");
matrix.loadFrame(err_led[0]);
while(1){}
}
//Check/Set SD
matrix.loadFrame(coda_led[0]);
if (!SD.begin(4)) {
Serial.println("SD initialization failed!");
matrix.loadFrame(sd_led[0]);
while(1){}
//NVIC_SystemReset();
}
Serial.println("SD initialization done.");
//Check/Set termometro
matrix.loadFrame(coda_led[1]);
if (!bmx280.begin())
{
Serial.println("begin() failed. check your BMx280 Interface and I2C Address.");
matrix.loadFrame(coda_led[2]);
while (1);
}
bmx280.resetToDefaults();
bmx280.writeOversamplingPressure(BMx280MI::OSRS_P_x16);
bmx280.writeOversamplingTemperature(BMx280MI::OSRS_T_x16);
// Set the Telegram bot properies
myBot.setUpdateTime(2000);
myBot.setTelegramToken(token);
// Arretro volontariamente l'ultima rilevazione wifi così da farla già al primo ciclo senza aspettare
millis_ultima_rilevazione_wifi= millis() - intervallo_di_rilevazione_wifi_minuti*1000*60;
// Arretro volontariamente l'ultimo download dei file di configurazione così da farlo già al primo ciclo senza aspettare
millis_ultimo_download_file_config = millis() - intervallo_download_file_config_minuti*60*1000;
// Arretro volontariamente l'ultimo upload FTP del file corrente così da farlo già al primo ciclo senza aspettare
millis_ultimo_upload_FTP = millis() - intervallo_di_upload_FTP_minuti*60*1000;
// Arretro volontariamente l'ultima rilevazione temperatura così da farla già al primo ciclo senza aspettare
millis_ultima_rilevazione_temp = millis() - intervallo_rilevazione_temp_secondi *1000;
// Arretro volontariamente l'ultima notifica telegram così da farla già al primo ciclo senza aspettare
millis_ultima_notifica_temperatura= millis() - intervallo_di_notifica_temperatura_telegram_minuti*60*1000;
ReadConfigFiles();
// Sovrascrivi TEMP_STOP: è la temperatura di arresto della stufa, non il delta contenuto nel file
TEMP_STOP = TEMP_MIN_ALERT + TEMP_DELTA;
Serial.print("Nuovo TEMP_DELTA : " ); Serial.println(TEMP_DELTA);
ReadFromFile(nome_file_stufa, 2, &flag_stufa);
Serial.print("Nuovo flag_stufa : " ); Serial.println(flag_stufa);
old_flag_stufa = flag_stufa;
}
void loop() {
// Se non è connesso ma lo era in passato riavvia Arduino
if (WiFi.status()!=WL_CONNECTED && ConnessoInPassato){ // 0 = Disconnected ? 2 = Looking for ?
// NOTA: Inutile mandare messaggi via Telegra: tanto è disconnesso.
/*
Serial.println(" ====> RIAVVIO ARDUINO !");
if(serial_avail){delay(1000);}
NVIC_SystemReset();
*/
WiFi.disconnect();
WiFi.end();
ConnessoInPassato = false;
Serial.println(" ====> DISCONNETTO WIFI E END !");
if(serial_avail){delay(1000);}
}
//Check/Set wifi
if (WiFi.status()!=WL_CONNECTED){
if (millis() - millis_ultima_rilevazione_wifi > intervallo_di_rilevazione_wifi_minuti*1000*60 ){
matrix.loadFrame(coda_led[3]);
Serial.print("Cerco la rete Wifi...");
listNetworks();
// Se la rete risulta connessa non cercarla più
if (WiFi.status()==WL_CONNECTED){
Serial.println("... COLLEGATO A WIFI !!");
delay(1000);
matrix.loadFrame(coda_led[4]);
}
millis_ultima_rilevazione_wifi = millis();
}else{
//Serial.print("Mancano "); Serial.print((intervallo_di_rilevazione_wifi_minuti*1000*60 - (millis() - millis_ultima_rilevazione_wifi) )/1000 ); Serial.println(" secondi prima di cercare una rete wifi.");
Serial.print("Tempo passato dall'ultima ricerca di una rete WIFI : "); Serial.print((millis() - millis_ultima_rilevazione_wifi) / 1000 ); Serial.print(" secondi su "); Serial.print(intervallo_di_rilevazione_wifi_minuti *60 ); Serial.println(" secondi");
}
}/*else{
Serial.println("Già connessa a Wifi");
} */
// Primo messaggio di benvenuto in Telegram
//Serial.print("ConnessoInPassato : ");Serial.println(ConnessoInPassato);
if (!ConnessoInPassato && WiFi.status()==WL_CONNECTED){
messaggioHTML[0]='\0';
sprintf(messaggioHTML, "Connesso alla rete 🔗 <b>%s</b> ", nome_rete);
myBot.sendToChannel(channel, messaggioHTML, true);
ConnessoInPassato = true;
}
// Scarica i file di configurazione se superata la soglia in minuti dall'ultimo download...
if (millis() - millis_ultimo_download_file_config > intervallo_download_file_config_minuti *60 *1000 )
{
if (WiFi.status()==WL_CONNECTED){
Serial.println("INIZIO SCARICAMENTO FILE DI CONFIGURAZIONE");
int n_file_da_aggiornare = numFiles;
int n_file_aggiornati =0;
// Buffer per header e body
char header[HEADER_SIZE] = {0};
char body[BODY_SIZE] = {0};
for (int i = 0; i < numFiles; i++) {
Serial.print("Scaricando file: "); Serial.println(fileNames[i]);
bool success = DownloadFile(fileNames[i], header, body);
if (success && strlen(body) > 0) {
n_file_aggiornati +=1;
Serial.print("Contenuto del file "); Serial.print(fileNames[i]); Serial.print(":");Serial.println(body);
// Assegna il contenuto del body alla variabile appropriata
if (varTypes[i] == 1) { // double
*(double*)variables[i] = atof(body);
} else if (varTypes[i] == 2) { // int
*(int*)variables[i] = atoi(body);
}
} else {
Serial.print("Errore nel download del file: "); Serial.println(fileNames[i]);
}
// Pulizia dei buffer
memset(header, 0, sizeof(header));
memset(body, 0, sizeof(body));
}
// Stampa i valori per verifica
Serial.print("TEMP_MIN_ALERT: "); Serial.println(TEMP_MIN_ALERT);
Serial.print("TEMP_MAX_ALERT: "); Serial.println(TEMP_MAX_ALERT);
Serial.print("TEMP_DELTA: "); Serial.println(TEMP_DELTA);
Serial.print("ORA_LUCE_ON: "); Serial.println(ORA_LUCE_ON);
Serial.print("ORA_LUCE_OFF: "); Serial.println(ORA_LUCE_OFF);
TEMP_STOP = TEMP_MIN_ALERT + TEMP_DELTA;
// Solo se ho aggiornato tutti i files allora aggiorna il tempo
if (n_file_aggiornati==n_file_da_aggiornare){
// Sovrascrivi TEMP_STOP: è la temperatura di arresto della stufa, non il delta contenuto nel file
Serial.print("Nuovo TEMP_STOP : " ); Serial.println(TEMP_STOP);
messaggioHTML[0]='\0';
rtc.refresh();
sprintf(messaggioHTML, "Scaricati i file di configurazione ⚙️ alle ore %02d:%02d:%02d ⏲️ del %02d/%02d/%d 📅", rtc.hour(),rtc.minute(),rtc.second(), rtc.day(), rtc.month(), (rtc.year()+2000));
myBot.sendToChannel(channel, messaggioHTML, true);
millis_ultimo_download_file_config = millis();
}else{
Serial.println("NON AGGIORNO IL millis() PER IL DOWNLOAD DEI FILE DI CONFIGURAZIONE");
}
}
}else{
Serial.print("Tempo passato dall'ultimo download : "); Serial.print((millis() - millis_ultimo_download_file_config) / 1000 ); Serial.print(" secondi su "); Serial.print(intervallo_download_file_config_minuti *60 ); Serial.println(" secondi");
}
// Fa la misura della temperatura se superata la soglia in minuti dall'ultima misura...
if (millis() - millis_ultima_rilevazione_temp > intervallo_rilevazione_temp_secondi *1000 )
{
//start a measurement
matrix.loadFrame(coda_led[5]);
if (!bmx280.measure())
{
Serial.println("could not start measurement, is a measurement already running?");
return;
}
//wait for the measurement to finish
do
{
delay(50);
} while (!bmx280.hasValue());
matrix.loadFrame(coda_led[6]);
// Costruisci il nome del file: dopo mezzanotte cambia
rtc.refresh();
filename[0] = '\0';
sprintf(filename, "D_%02d%02d%d.TXT", rtc.day() ,rtc.month(),rtc.year());
matrix.loadFrame(coda_led[7]);
// ******** Gestisci temperatura e luce ********
// Flag luce
if (rtc.hour()>=ORA_LUCE_ON && rtc.hour()< ORA_LUCE_OFF){
digitalWrite(PIN_LUCE,HIGH); //Luce sul PIN 3
flag_luce = 1;
}else{
digitalWrite(PIN_LUCE,LOW); //Luce sul PIN 3
flag_luce = 0;
}
// Flag stufa
if(bmx280.getTemperature() <= TEMP_MIN_ALERT ){
digitalWrite(PIN_STUFA,HIGH); //Stufa sul PIN 4
flag_stufa = 1;
}
if(bmx280.getTemperature() >= TEMP_STOP ){
digitalWrite(PIN_STUFA,LOW); //Stufa sul PIN 4
flag_stufa = 0;
}
// Salva il file con il flag se è cambiato
if (flag_stufa!= old_flag_stufa){
//Serial.print("Vecchio flag_stufa : " ); Serial.println(old_flag_stufa);
//Serial.print("Nuovo flag_stufa : " ); Serial.println(flag_stufa);
// Cancello il file vecchio se esiste
if (SD.exists(nome_file_stufa)) {
SD.remove(nome_file_stufa);
}
File file_stufa = SD.open(nome_file_stufa, FILE_WRITE);
if (file_stufa) {
file_stufa.println(flag_stufa);
file_stufa.close();
} else {
Serial.print("Errore apertura file: "); Serial.println(nome_file_stufa);
}
// Verifico la scrittura su file
//ReadFromFile(nome_file_stufa, 2, &flag_stufa);
//Serial.print(" flag_stufa letto da file: " ); Serial.println(flag_stufa);
}
if (WiFi.status()==WL_CONNECTED){
messaggioHTML[0]='\0';
rtc.refresh();
if (old_flag_stufa==0 && flag_stufa==1){
sprintf(messaggioHTML, "La stufa si accende 🔥 alle ore %02d:%02d:%02d ⏲️ del %02d/%02d/%d ", rtc.hour(),rtc.minute(),rtc.second(), rtc.day(), rtc.month(), (rtc.year()+2000));
}
if (old_flag_stufa==1 && flag_stufa==0){
sprintf(messaggioHTML, "La stufa si spegne 🪵 alle ore %02d:%02d:%02d ⏲️ del %02d/%02d/%d ", rtc.hour(),rtc.minute(),rtc.second(), rtc.day(), rtc.month(), (rtc.year()+2000));
}
if(messaggioHTML[0]!='\0'){
Serial.print("Messaggio telegram stufa: "); Serial.println(messaggioHTML);
if (WiFi.status()==WL_CONNECTED){ // se sei connesso
myBot.sendToChannel(channel, messaggioHTML, true); // mandala subito
}else{
File NoteFile = SD.open(fileNote, FILE_WRITE); // altrimenti scrivla nel file "note.txt"
if (NoteFile) {
NoteFile.println(messaggioHTML);
NoteFile.close();
}
}
}
}
old_flag_stufa = flag_stufa;
// Costruisci la riga di log e scrivila
rtc.refresh();
msg[0] = '\0';
sprintf(msg, "%d;%02d;%02d;%02d;%02d;%02d;%.2f;%.2f;%d;%.2f;%.2f;%.2f;%d;%d", (rtc.year()+2000) ,rtc.month(),rtc.day(),rtc.hour(),rtc.minute(),rtc.second(),bmx280.getTemperature(),bmx280.getPressure64(),readVcc(),TEMP_MIN_ALERT,TEMP_STOP,TEMP_MAX_ALERT,flag_luce,flag_stufa );
// Scrivo solo se non contiene 'nan'
if (strstr(msg, "nan") == nullptr){
logfile = SD.open(filename, FILE_WRITE);
if (logfile){
logfile.println(F(msg));
logfile.close();
}
matrix.loadFrame(coda_led[8]);
}else{
matrix.loadFrame(coda_led[10]);
}
// DEBUG: Check sui valori
Serial.print(" "); Serial.print("Nome file : ");Serial.print(filename);
Serial.print(" ");Serial.print(' '); Serial.print(rtc.hour()); Serial.print(':'); Serial.print(rtc.minute()); Serial.print(':'); Serial.print(rtc.second());
Serial.print(" ");Serial.print("Riga log : ");Serial.print(msg);
//Serial.print(" ");Serial.print("Pressure:"); Serial.print(bmx280.getPressure64());Serial.print(" Temperature: ");Serial.println(bmx280.getTemperature());
//Serial.print(" ");Serial.print("WiFi Status: "); Serial.println(WiFi.status());
rtc.refresh();
Serial.print(" ");
messaggioHTML[0]='\0';
sprintf(messaggioHTML, "Sono le ore %02d:%02d:%02d del %02d/%02d/%04d", TEMP_MIN_ALERT, rtc.hour(),rtc.minute(),rtc.second(), rtc.day(), rtc.month(), (rtc.year()+2000), bmx280.getTemperature());
Serial.print(messaggioHTML);
//Serial.print(" ");Serial.print(" - Temp RTC: "); Serial.print(rtc.temp() / 100);
Serial.println("");
if(serial_avail){delay(2000);}
//millis_ultimo_ciclo = millis();
// Calcola quanto tempo è passato dall'ultima rilevazione e aspetta la differenza
/*Serial.println(intervallo_rilevazione_temp_secondi*1000);
Serial.println(millis() - millis_ultima_rilevazione_temp);
Serial.println((millis() - millis_ultima_rilevazione_temp) - intervallo_rilevazione_temp_secondi*1000 );
Serial.println("Ora fa la misura");
while(1){};
*/
millis_ultima_rilevazione_temp = millis();
}else{
Serial.print("Tempo passato dall'ultima rilevazione : "); Serial.print((millis() - millis_ultima_rilevazione_temp) / 1000 ); Serial.print(" secondi su "); Serial.print(intervallo_rilevazione_temp_secondi ); Serial.println(" secondi");
//delay(1000);
}
// ******** Gestisci CREAZIONE di notifiche fuori soglia ********
if (millis() - millis_ultima_notifica_temperatura > intervallo_di_notifica_temperatura_telegram_minuti *60 *1000 )
{
// ... e se la temperatura rilevata è esterna alle soglie min e max
rtc.refresh();
messaggioHTML[0]='\0';
if (bmx280.getTemperature()< TEMP_MIN_ALERT){
sprintf(messaggioHTML, "La temperatura 🌡️ è scesa 🥶 sotto la soglia di %.02f gradi alle ore %02d:%02d:%02d 🕰 del %02d/%02d/%d ed è pari a <b>%.02f</b> gradi", TEMP_MIN_ALERT, rtc.hour(),rtc.minute(),rtc.second(), rtc.day(), rtc.month(), (rtc.year()+2000), bmx280.getTemperature());
}
if (bmx280.getTemperature()> TEMP_MAX_ALERT){
sprintf(messaggioHTML, "La temperatura 🌡️ è salita 🥵 sopra la soglia di %.02f gradi alle ore %02d:%02d:%02d 🕰 del %02d/%02d/%d ed è pari a <b>%.02f</b> gradi", TEMP_MAX_ALERT, rtc.hour(),rtc.minute(),rtc.second(), rtc.day(), rtc.month(), (rtc.year()+2000), bmx280.getTemperature());
}
if (messaggioHTML[0]!='\0'){ // se c'è una notifica
if (WiFi.status()==WL_CONNECTED){ // se sei connesso
myBot.sendToChannel(channel, messaggioHTML, true); // mandala subito
}else{
File NoteFile = SD.open(fileNote, FILE_WRITE); // altrimenti scrivla nel file "note.txt"
if (NoteFile) {
NoteFile.println(messaggioHTML);
NoteFile.close();
Serial.print("Messaggio "); Serial.println(messaggioHTML);
Serial.println("Scritto nel file");
} else {
Serial.println("Errore nell'apertura del file delle notifiche.");
}
if(serial_avail){delay(2000);}
}
millis_ultima_notifica_temperatura = millis();
}
}else{
Serial.print("Tempo passato dall'ultima notifica della temperatura fuori soglia : "); Serial.print((millis() - millis_ultima_notifica_temperatura) / 1000 ); Serial.print(" secondi su "); Serial.print(intervallo_di_notifica_temperatura_telegram_minuti*60 ); Serial.println(" secondi");
}
// Fa upload del file se superata la soglia in minuti dall'ultimo upload...
if (millis() - millis_ultimo_upload_FTP > intervallo_di_upload_FTP_minuti * 60 *1000 )
{
if (WiFi.status()==WL_CONNECTED){
matrix.loadFrame(coda_led[9]);
if (SD.exists(filename)) {
// Fai FTP del file del giorno corrente
char annoStr[3]; // 2 caratteri + 1 per il terminatore
char meseStr[3]; // 2 caratteri + 1 per il terminatore
// Copia i caratteri da 6 a 8 (24)
strncpy(annoStr, filename + 6, 2); // 6 è l'indice del 6° carattere
annoStr[2] = '\0'; // Terminatore di stringa
// Copia i caratteri da 4 a 6 (07)
strncpy(meseStr, filename + 4, 2); // 4 è l'indice del 4° carattere
meseStr[2] = '\0'; // Terminatore di stringa
// Converti le sottostringhe in int
int anno_da_nomefile = atoi(annoStr);
int mese_da_nomefile = atoi(meseStr);
// Aggiungi 2000 all'anno per ottenere 2024
anno_da_nomefile += 2000;
matrix.loadFrame(ftp_led[0]);
byte Upload_OK = doFTP(filename, anno_da_nomefile, mese_da_nomefile );
matrix.loadFrame(ftp_led[1]);
if (Upload_OK){
// Notifica Telegram per FTP file del giorno corrente
rtc.refresh();
messaggioHTML[0]='\0';
sprintf(messaggioHTML, "Il file <b>%s</b> è stato caricato 🆙 alle ore %02d:%02d:%02d del %02d/%02d/%d", filename , rtc.hour(),rtc.minute(),rtc.second(), rtc.day(), rtc.month(), (rtc.year()+2000));
Serial.println("Messaggio telegram: ");
Serial.println(messaggioHTML);
myBot.sendToChannel(channel, messaggioHTML, true);
millis_ultimo_upload_FTP = millis();
}
}else{
Serial.print("Tempo per fare l'upload FTP è passato, sei connesso ma il file '"); Serial.print(filename);Serial.println("' non esiste!");
}
}else{
Serial.println("Tempo per fare l'upload FTP è passato ma non sei connesso!");
}
}else{
Serial.print("Tempo passato dall'ultimo upload FTP : "); Serial.print((millis() - millis_ultimo_upload_FTP) / 1000 ); Serial.print(" secondi su "); Serial.print(intervallo_di_upload_FTP_minuti *60 ); Serial.println(" secondi");
}
// Ultima cosa da fare sempre: se esiste il file di notifiche 'note.txt' e sono connesso invio un messaggio alla volta
if (SD.exists(fileNote)) {
if(WiFi.status()==WL_CONNECTED){
File file = SD.open(fileNote);
while (file.available()) {
int index = 0;
// Leggi la riga corrente
while (file.available() && index < sizeof(lineaMessaggio) - 1) {
char c = file.read();
if (c == '\n') {
break;
}
lineaMessaggio[index++] = c;
}
lineaMessaggio[index] = '\0'; // Termina la stringa
// Stampa la riga letta
myBot.sendToChannel(channel, lineaMessaggio, true);
delay(100);
//Serial.println(lineaMessaggio);
//if(serial_avail){delay(3000);}
}
file.close(); // Chiudi il file dopo aver terminato la lettura
SD.remove(fileNote); // Rimuovi il file suponendo che abbia mandato tutte le notifiche indietro
}
}
}
// *********************************************
// UTILITIES FUNCTIONS
// *********************************************
bool DownloadFile(const char* FileName, char* header, char* body) {
WiFiClient DLclient;
char msg_file[200];
memset(header, 0, HEADER_SIZE);
memset(body, 0, BODY_SIZE);
if (DLclient.connect(WEB_SERVER, 80)) {
char get_req[200];
sprintf(get_req, "GET /termometro/config/%s HTTP/1.1", FileName);
DLclient.println(get_req);
DLclient.println("Host: <mio_sito>.altervista.org");
DLclient.println("Connection: close");
DLclient.println("");
unsigned long t1 = millis();
while (DLclient.available() == 0) {
if ((millis() - t1) > 15000) {
Serial.println("Timeout");
DLclient.stop();
return false;
}
}
// Ricezione dati
char line[256];
bool headerComplete = false;
while (DLclient.connected() || DLclient.available()) {
memset(line, 0, sizeof(line));
DLclient.readBytesUntil('\n', line, 255);
line[strcspn(line, "\r")] = 0; // Rimuove i caratteri di ritorno a capo
/*
msg_file[0] = '\0';
sprintf(msg_file, "==> Linea letta : %s", line);
Serial.println(msg_file);
*/
if (!headerComplete) {
if (strlen(line) == 0) {
headerComplete = true;
} else {
strncat(header, line, HEADER_SIZE - strlen(header) - 1);
strncat(header, "\n", HEADER_SIZE - strlen(header) - 1);
}
} else {
strncat(body, line, BODY_SIZE - strlen(body) - 1);
strncat(body, "\n", BODY_SIZE - strlen(body) - 1);
}
}
DLclient.stop();
// Cancello il file vecchio se esiste
if (SD.exists(FileName)) {
SD.remove(FileName);
msg_file[0] = '\0';
if (!SD.exists(FileName)) {
sprintf(msg_file, "File vecchio %s rimosso con successo. ", FileName);
} else {
sprintf(msg_file, "File vecchio %s NON rimosso !! ", FileName);
}
Serial.print(msg_file);
}
File DLfile = SD.open(FileName, FILE_WRITE);
if (DLfile) {
DLfile.println(body);
DLfile.close();
} else {
Serial.println("Errore nell'apertura del file per la scrittura.");
return false;
}
msg_file[0] = '\0';
if (SD.exists(FileName)) {
sprintf(msg_file, "Il file %s è stato (ri)creato con successo", FileName);
} else {
sprintf(msg_file, "Il file %s NON è stato creato", FileName);
return false;
}
Serial.println(msg_file);
return true;
} else {
Serial.println("Errore nel download del file - NON Connesso");
return false;
}
}
void ReadConfigFiles(){
//const char* FileName, char* header, char* body
int numFiles = sizeof(fileNames) / sizeof(fileNames[0]);
for (int i = 0; i < numFiles; i++) {
ReadFromFile(fileNames[i], varTypes[i], variables[i]);
}
// Stampa i valori per verifica
Serial.println("*** VALORI LETTI DAI FILE DI CONFIGURAZIONE ****");
Serial.print("TEMP_MIN_ALERT: "); Serial.print(TEMP_MIN_ALERT); Serial.print(" ");
Serial.print("TEMP_MAX_ALERT: "); Serial.print(TEMP_MAX_ALERT);Serial.print(" ");
Serial.print("TEMP_STOP: "); Serial.print(TEMP_STOP);Serial.print(" ");
Serial.print("ORA_LUCE_ON: "); Serial.print(ORA_LUCE_ON);Serial.print(" ");
Serial.print("ORA_LUCE_OFF: "); Serial.print(ORA_LUCE_OFF);Serial.print(" ");
Serial.println("");
}
bool ReadFromFile(const char* FileName, int varType, void* variable) {
if (SD.exists(FileName)) {
File file = SD.open(FileName);
if (file) {
// Assicurati che il buffer sia abbastanza grande per contenere il contenuto del file
char buffer[16]; // Modifica la dimensione in base alle tue necessità
int index = 0;
// Leggi il file fino al carattere di nuova linea o fine del file
while (file.available() && index < sizeof(buffer) - 1) {
char c = file.read();
if (c == '\n') {
break;
}
buffer[index++] = c;
}
buffer[index] = '\0'; // Termina la stringa
file.close();
// Assegna il contenuto del file alla variabile appropriata
if (varType == 1) {
*(double*)variable = atof(buffer);
} else if (varType == 2) {
*(int*)variable = atoi(buffer);
} else {
return false;
}
return true;
} else {
Serial.print("Errore nell'apertura del file: ");
Serial.println(FileName);
return false;
}
} else {
Serial.print("Il file ");
Serial.print(FileName);
Serial.println(" non esiste.");
return false;
}
}
void listNetworks() {
// scan for nearby networks:
Serial.println("** Scan Networks **");
int numSsid = WiFi.scanNetworks();
if (numSsid != -1) {
// print the list of networks seen:
Serial.print("number of available networks:");
Serial.println(numSsid);
// print the network number and name for each network found:
for (int thisNet = 0; thisNet < numSsid; thisNet++) {
Serial.print(thisNet); Serial.print(") "); Serial.print(WiFi.SSID(thisNet)); Serial.print(" Signal: "); Serial.print(WiFi.RSSI(thisNet)); Serial.print (" dBm ");
if (strcmp(WiFi.SSID(thisNet), SECRET_SSID1) == 0){
Serial.print (" **** => TROVATA : "); Serial.println (SECRET_SSID1);
char pass[] = SECRET_PASS1;
if(connetti_rete(SECRET_SSID1,pass)){
thisNet = numSsid;
}
}
else if (strcmp(WiFi.SSID(thisNet), SECRET_SSID2) == 0){
Serial.print (" **** => TROVATA : ");Serial.println (SECRET_SSID2);
char pass[] = SECRET_PASS2;
if(connetti_rete(SECRET_SSID2,pass)){
thisNet = numSsid;
}
}
else if (strcmp(WiFi.SSID(thisNet), SECRET_SSID3) == 0){
Serial.print (" **** => TROVATA : ");Serial.println (SECRET_SSID3);
char pass[] = SECRET_PASS3;
if(connetti_rete(SECRET_SSID3,pass)){
thisNet = numSsid;
}
}
else if (strcmp(WiFi.SSID(thisNet), SECRET_SSID4) == 0){
Serial.print (" **** => TROVATA : ");Serial.println (SECRET_SSID4);
char pass[] = SECRET_PASS4;
if(connetti_rete(SECRET_SSID4,pass)){
thisNet = numSsid;
}
}
else if (strcmp(WiFi.SSID(thisNet), SECRET_SSID5) == 0){
Serial.print (" **** => TROVATA : ");Serial.println (SECRET_SSID5);
char pass[] = SECRET_PASS5;
if(connetti_rete(SECRET_SSID5,pass)){
thisNet = numSsid;
}
}
}
}
}
bool connetti_rete(char NetName[], char NetPwd[]){
bool res = false;
unsigned int SOGLIA_MS_RICERCA_RETE = 200;
Serial.print("Try to connect to '"); Serial.print(NetName); Serial.println("' ...");
WiFi.begin(NetName,NetPwd);
unsigned int t1 = millis();
while (WiFi.status()!=WL_CONNECTED){
delay(50);
Serial.print(".");
// Se è passato troppo tempo smetti di cercare questa rete
if(millis() - t1 > SOGLIA_MS_RICERCA_RETE && WiFi.status()!=WL_CONNECTED ){
Serial.println("Esco per troppo tempo passato");
return false;
}
}
if (WiFi.status()==WL_CONNECTED){
Serial.println("==> CONNESSA !!");
nome_rete = NetName;
res = true;
}
return res;
}
long readVcc()
{ // function to read actual supply voltage Vcc (in mV) of Arduino - adapted to also work with new UNO R4
long result;
float Vcc = analogReference(); // analogReference() reads Vcc, based on internal reference, and returns value as a float
result = Vcc * intVREF * 1000; // intVREF * 1000; // result is corrected for incorrect internal reference (fixed factor) and converted to mV
return result; // long result (Vcc in mV) is returned as a result of readVccUnoR4 function
}
byte doFTP(String filename, int anno_da_nomefile,int mese_da_nomefile)
{
fh = SD.open(filename,FILE_READ);
if(!fh)
{
Serial.println(F("doFTP: SD open fail"));
return 0;
}
Serial.println(F("doFTP: SD opened"));
if (client_FTP.connect(server,21)) {
Serial.println(F("doFTP: Command connected"));
}
else {
fh.close();
Serial.println(F("doFTP: Command connection failed"));
return 0;
}
if(!eRcv()) return 0;
client_FTP.print(F("USER "));
client_FTP.println((String)user);
if(!eRcv()) return 0;
client_FTP.print(F("PASS "));
client_FTP.println((String)pasw);
if(!eRcv()) return 0;
client_FTP.println(F("SYST"));
if(!eRcv()) return 0;
client_FTP.println(F("Type I"));
if(!eRcv()) return 0;
client_FTP.println(F("PASV"));
if(!eRcv()) return 0;
char *tStr = strtok(outBuf,"(,");
int array_pasv[6];
for ( int i = 0; i < 6; i++) {
tStr = strtok(NULL,"(,");
array_pasv[i] = atoi(tStr);
if(tStr == NULL)
{
Serial.println(F("doFTP: Bad PASV Answer"));
}
}
client_FTP.println(F("PWD"));
if(!eRcv()) return 0;
client_FTP.print(F("CWD "));
client_FTP.println(fold);
if(!eRcv()) return 0;
client_FTP.print(F("CWD "));
client_FTP.println(anno_da_nomefile);
if(!eRcv()) return 0;
client_FTP.print(F("CWD "));
client_FTP.println(mese_da_nomefile);
if(!eRcv()) return 0;
unsigned int hiPort,loPort;
hiPort = array_pasv[4] << 8;
loPort = array_pasv[5] & 255;
Serial.print(F("Data port: "));
hiPort = hiPort | loPort;
Serial.println(hiPort);
if (dclient_FTP.connect(server,hiPort)) {
Serial.println(F("doFTP: Data connected"));
}
else {
Serial.println(F("doFTP: Data connection failed"));
client_FTP.stop();
fh.close();
return 0;
}
client_FTP.print(F("STOR "));
client_FTP.println(filename);
if(!eRcv())
{
dclient_FTP.stop();
return 0;
}
Serial.println(F("doFTP: Writing"));
byte clientBuf[64];
int clientCount = 0;
while(fh.available())
{
clientBuf[clientCount] = fh.read();
clientCount++;
if(clientCount > 63)
{
dclient_FTP.write(clientBuf,64);
clientCount = 0;
}
}
if(clientCount > 0) dclient_FTP.write(clientBuf,clientCount);
dclient_FTP.stop();
Serial.println(F("doFTP: Data disconnected"));
if(!eRcv()) return 0;
client_FTP.println(F("QUIT"));
if(!eRcv()) return 0;
client_FTP.stop();
Serial.println(F("doFTP: Command disconnected"));
fh.close();
Serial.println(F("doFTP: SD closed"));
return 1;
}
byte eRcv()
{
byte respCode;
byte thisByte;
while(!client_FTP.available()) delay(1);
respCode = client_FTP.peek();
outCount = 0;
while(client_FTP.available())
{
thisByte = client_FTP.read();
Serial.write(thisByte);
if(outCount < 127)
{
outBuf[outCount] = thisByte;
outCount++;
outBuf[outCount] = 0;
}
}
if(respCode >= '4')
{
efail();
return 0;
}
return 1;
}
void efail()
{
byte thisByte = 0;
client_FTP.println(F("QUIT"));
while(!client_FTP.available()) delay(1);
while(client_FTP.available())
{
thisByte = client_FTP.read();
Serial.write(thisByte);
}
client_FTP.stop();
Serial.println(F("Command disconnected"));
fh.close();
Serial.println(F("SD closed"));
}