Ciao a tutti,
Sto tentando di creare una stazione meteo casalinga composta da un raspberry sul quale gira un server per la raccolta e la consultazione dei dati ed una centralina esterna composta da un nodemcu al quale sono connessi un sensore DHT22, un sensore di luce ed un rele per l’accensione delle luci esterne. Il nodemcu è in modalità station e comunica via protocollo TCP con un server sul raspberry usando il router Wi-Fi come ponte di comunicazione. Il formato dati scambiato è composto da una stringa di circa 20 caratteri contenenti le info dei sensori. Purtroppo quello che sto riscontrando è che, dopo alcune ore di funzionamento, la stazione esterna va in stallo e sono costretto a ravviarla. Non capisco se si blocca la connessione tcp oppure quella Wi-Fi. Il server tcp interroga ogni 5 minuti la centralina esterna la quale non fa altro che aggiornare le letture dei sensori ogni 15 secondi (non accumula, ma salva su delle variabili locali l’ultimo valore disponibile). Ho cercato un po’ in giro, ma non ho trovato soluzioni efficienti. Qualcuno scriveva di non usare variabili di tipo String, così ho convertito tutte in array di char. Ma il risultato è sempre lo stresso. Dovrei far ravviare la stazione ogni ora, ma mi sembra un po’ eccessivo, quindi vorrei capire se è possibile fare qualcosa su questo nodemcu o se ci sono problemi noti irrisolvibili.
Grazie in anticipo... è da una settimana che provo a rendere il firmware più robusto possibile, ma nulla.
Buongiorno e benvenuto,
essendo il tuo primo post, nel rispetto del regolamento della sezione Italiana del forum (… punto 13, primo capoverso), ti chiedo cortesemente di presentarti IN QUESTO THREAD (spiegando bene quali conoscenze hai di elettronica e di programmazione ... possibilmente evitando di scrivere solo una riga di saluto) e di leggere con molta attenzione tutto il su citato REGOLAMENTO ... Grazie.
Guglielmo
P.S.: Ti ricordo che, purtroppo, fino a quando non sarà fatta la presentazione nell’apposito thread, nessuno ti potrà rispondere, quindi ti consiglio di farla al più presto.
Ciao gpb10,
lieto di conoscerti e di partecipare al forum. Presentazione fatta, spero che qualcuno riesca ad illuminarmi sul problema.
Ciao
La classe String crea problemi quando viene usata "a cazzum", cosa che purtroppo è troppo facile fare, anche se è evidente che non è questo il caso visto che dici di non usarla.
L'ESP8266 è noto soffrire problemi di "memory leak" in alcune condizioni e l'utilizzo del WiFiClient spesso causa questi problemi. Troverai decine di issues aperte in giro per il web.
Se aggiungi alle informazioni inviate al server, anche lo stato della memoria, hai qualche elemento in più per analizzare il problema.
Se poi metti lo sketch completo che gira sul nodeMCU, vediamo se si riesce a trovare il modo di evitare questo fastidioso bug.
Ciao,
grazie per la risposta. Eccoti lo sketch: ho dovuto mantenere una variabile di tipo String per una conversione necessaria.
Ps. aggiungo che da stamani alle 9:00 sto tenendo la stazione esterna all'interno della casa (internamente ci sono circa 20 gradi, mentre fuori circa 10) e fino ad ora non si è disconnessa... non vorrei che ci fosse qualche problema con la temperatura e la breadborda che uso per alloggiare i vari componenti (anche se l'alimentazione è presa dalla microUsb sul nodemcu e non da un pin sulla breadboard).
#include "DHT.h"
#include <ESP8266WiFi.h>
#define LED D0
#define SENS D1
#define RELE D8
IPAddress tcpServer(192,168,1,50);
DHT dht(SENS, DHT22);
WiFiClient client;
unsigned long nextSensorReading = 0;
unsigned long nextReset = 0;
char lightPercent[6];
char temperature[6];
char humidity[6];
byte releStatus = 0;
char client_id[5] = "EXT1";
void setup() {
dht.begin();
pinMode(RELE, OUTPUT);
pinMode(LED, OUTPUT);
digitalWrite(RELE, LOW);
digitalWrite(LED, HIGH);
WiFi.begin("xxxxxxxxx", "xxxxxxxxxxxxxxxxxx");
delay(2000);
nextReset = 90000000; //25 hours
}
void loop() {
scheduleSensor();
wiFiCheck();
manageTcpConnection();
}
void wiFiCheck() {
if (WiFi.status() != WL_CONNECTED) {
client.stop();
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
}
for(int i=0; i<5; i++) {
delay(100);
digitalWrite(LED, LOW);
delay(100);
digitalWrite(LED, HIGH);
}
}
}
void manageTcpConnection() {
if (!client.connected()) {
if(client.connect(tcpServer, 8787)){
delay(500);
}
else{
client.stop();
delay(2000);
}
}
else if (client.available()){
char ch = client.read();
char cmd[10];
int i = 0;
while(ch != '\n') {
cmd[i] = ch;
ch = client.read();
i++;
}
digitalWrite(LED, LOW);
delay(20);
digitalWrite(LED, HIGH);
char res[26];
i=0;
int j=0;
res[i] = 'O';
res[++i] = 'K';
res[++i] = ' ';
if(cmd[0] == 'S' && cmd[1] == 'T' && cmd[2] == 'A' &&
cmd[3] == 'T' && cmd[4] == 'U' && cmd[5] == 'S') {
// T20.8|U78.7|L27.4|R10
res[++i] = 'T';
ch = temperature[j];
while(ch != '\0') {
res[++i] = ch;
ch = temperature[++j];
}
res[++i] = '|';
res[++i] = 'U';
j=0;
ch = humidity[j];
while(ch != '\0') {
res[++i] = ch;
ch = humidity[++j];
}
res[++i] = '|';
res[++i] = 'L';
j=0;
ch = lightPercent[0];
while(ch != '\0') {
res[++i] = ch;
ch = lightPercent[++j];
}
res[++i] = '|';
res[++i] = 'R';
res[++i] = '1';
res[++i] = releStatus == 1 ? '1' : '0';
res[++i] = '\n';
}
else if(cmd[0] == 'I' && cmd[1] == 'D') {
j=0;
ch = client_id[j];
while(ch != '\0') {
res[++i] = ch;
ch = client_id[++j];
}
res[++i] = '\n';
}
else if(cmd[0] == 'R' && cmd[1] == '1') {
if (cmd[2] == '1') {
digitalWrite(RELE, HIGH);
releStatus = 1;
}
else {
digitalWrite(RELE, LOW);
releStatus = 0;
}
res[++i] = 'R';
res[++i] = '1';
res[++i] = releStatus == 1 ? '1' : '0';
res[++i] = '\n';
}
else {
i=0;
res[i] = 'E';
//...rimosso porzione troppo lunga
}
i=0;
ch = res[i];
while(ch != '\n') {
ch = res[++i];
}
i++;
client.write(res, i);
if(cmd[0] == 'S' && cmd[1] == 'T' &&
cmd[2] == 'A' && cmd[3] == 'T' && millis() > nextReset
) {
ESP.restart();
}
}
}
void scheduleSensor() {
if (millis() > nextSensorReading) {
readSensor();
nextSensorReading = millis() + 15000;
}
}
void readSensor() {
int dac = analogRead(A0);
float percent = dac/10.24;
int i=-1;
int j=0;
String s = String(percent, 1);
char ch = s[j];
while(ch != '\0') {
lightPercent[++i] = ch;
ch = s[++j];
}
lightPercent[++i] = '\0';
float temp = dht.readTemperature();
float hum = dht.readHumidity();
i=-1;
j=0;
s = String(temp, 1);
ch = s[j];
while(ch != '\0') {
temperature[++i] = ch;
ch = s[++j];
}
temperature[++i] = '\0';
i=-1;
j=0;
s = String(hum, 1);
ch = s[j];
while(ch != '\0') {
humidity[++i] = ch;
ch = s[++j];
}
humidity[++i] = '\0';
}
La breadboard è una pessima idea ...
... t'assicuro che o varie WeMos D1 mini (8266) che leggono la temperatura da dei sensori TMP3x e me la trasmettono in HTTP e ... funzionano da anni, all'aperto, senza problemi, anche alcuni gradi sotto zero !
Guglielmo
>andrea-5: occhio a NON lasciare mai le password in chiaro nei codici che pubblichi ... questa volta ho corretto io su segnalazione di un utente
Guglielmo
Ciao,
grazie per l'accorgimento sulla pswd. Attualmente sono 7 ore che funziona e non si è disconnessa (tenendola all'interno della casa, il massimo che è durata all'esterno sono state 48 ore, ma negli ultimi giorni durava max 3 ore). Quello che non mi torna è il fatto che l'alimentazione la prende dalla microUsb collegata sulla scheda, mentre la breadboard viene usata per collegare i sensori.
Il codice è OK ?
Non credo sia la temperatura, quanto, se usi una breadboard, l'umidità.
La breadboard usala a casa, poi se vuoi testare il tuo circuito all'esterno usa una millefori quantomeno, e magari proteggi il tutto dentro con una qualche scatoletta in plastica.
E comunque la String levala di torno, non mi pare che tu non possa sostituirla con una char array, fai solo una conversione (magari usa una sprintf, visto che se ho capito bene ti basta un decimale non è difficile "convertire" un float in una stringhetta con la parte intera ed un decimale...).
andrea-5, ho provato il tuo sketch con un Wemos D1 aggiungendo un println delle informazioni sullo stato dell'heap (ESP8266 Community versione 2.7.4)
Il codice in se non frammenta in alcun modo la memoria ne tantomeno si perde in giro byte, dai un'occhiata al printscreen del plotter seriale: linea blu - memoria libra totale, linea rossa - dimensione massima blocco di memoria contiguo.
I buchi corrispondono all'invio da parte del server del messaggio STATUS e quindi l'esecuzione della funzione manageTcpConnection() che usa circa 750byte di RAM.
C'è però un punto di vulnerabilità non indifferente: se il comando inviato dal server TCP non è esattamente uno di quelli atteso tipo "STATUS\n", il microcontrollore solleva una "Fatal exception".
Noi tendiamo a dare per scontato che i messaggi inviati attraverso un qualsiasi mezzo di trasmissione arrivino dall'altra parte esattamente come "progettato", ma non è sempre cosi, anzi!
Gli errori di trasmissione sono sempre in agguato e nel tuo caso non sono gestiti.
Normalmente il micro si riavvia e ricomincia il ciclo, ma ogni tanto a me capita che al riavvio rimane bloccato ed è necessario resettare manualmente.
Magari anche nel tuo caso hai un comportamento simile.
Per quanto riguarda il codice, probabilmente funziona come l'hai scritto ma mi chiedevo perché tu abbia fatto tutte queste cose:
char res[26];
i=0;
int j=0;
res[i] = 'O';
res[++i] = 'K';
res[++i] = ' ';
if(cmd[0] == 'S' && cmd[1] == 'T' && cmd[2] == 'A' &&
cmd[3] == 'T' && cmd[4] == 'U' && cmd[5] == 'S') {
// T20.8|U78.7|L27.4|R10
res[++i] = 'T';
ch = temperature[j];
while(ch != '\0') {
res[++i] = ch;
ch = temperature[++j];
}
res[++i] = '|';
res[++i] = 'U';
Immagino che tu abbia provato a "tradurre" le istruzioni per togliere delle "String", ma ti consiglio di studiare un pochino le varie funzioni standard per la stringhe C, come strcpy(), strcmp(), strcat() e la sprintf() per cominciare, vedrai che quelle istruzioni che hai inserito diventeranno molto più semplici, ad esempio:
char res[26] = "OK ";
int j=0;
if( strcmp(cmd, "STATUS") == 0 ) {
// T20.8|U78.7|L27.4|R10
...eccetera
cotestatnt:
C'è però un punto di vulnerabilità non indifferente
Se nella parte rimossa perché troppo lunga c'è l'istruzione res[++i] = '\n'; lascia perdere quello che ho scritto, in caso contrario potrebbe essere un bug da sistemare.
Comunque quoto docdoc!
L'hai reso bello complicato
Ciao a tutti,
grazie per le risposte. Non pensavo di ricevere subito tutti questi messaggi sull'argomento, mi fa veramente piacere, essendo un forum italiano. Vi confermo che il dispositivo ha trasmesso dalle 10:00 alle 16:00, la lettura delle 17:00 è stata persa, quindi mi aspetto che non si riconnetta più: di fatto ogni 5 min il server TCP invia un ID\n per verificare l'attivazione del socket ed ogni ora manda uno STATUS\n.
Purtroppo il problema persiste :(.
Cmq avete inteso perfettamente la "complicazione" del codice, inizialmente usavo le Stringhe e non conoscevo i metodi come "strcmp". Grazie per il suggerimento.
Per quanto riguarda il suggerimento di cotestant sulle eccezioni, sull'else finale (dove ho messo un commento per non allungare troppo) io rispondo con un "error bad request". Potrebbe bastare?
Ecco l'else che ho rimosso,
devo dire che è brutto il codice scritto così, se avete un modo più efficiente per popolare una sottoparte dell'array sarò ben lieto di usarlo:
else {
i=0;
res[i] = 'E';
res[++i] = 'R';
res[++i] = 'R';
res[++i] = 'O';
res[++i] = 'R';
res[++i] = ' ';
res[++i] = 'B';
res[++i] = 'A';
res[++i] = 'D';
res[++i] = ' ';
res[++i] = 'C';
res[++i] = 'O';
res[++i] = 'M';
res[++i] = 'M';
res[++i] = 'A';
res[++i] = 'N';
res[++i] = 'D';
res[++i] = '\n';
}
andrea-5:
se avete un modo più efficiente per popolare una sottoparte dell'array sarò ben lieto di usarlo:
Più efficiente non lo so (anche se mi aspetto di si). Di sicuro più leggibile, ma occhio al terminatore
strcpy(res, "ERROR BAD COMMAND\n");
Grazie per la risposta,
ho aggiornato lo scketch sulle varie parti. Qualche consiglio per risolvere il problema della disconnessione invece?
Postaci lo sketch "corretto" con le ultime cose, così lo possiamo "leggere" meglio. E mi raccomando, prima di postarlo accertati che l'indentazione sia corretta (Ctrl-T dentro all'IDE te lo fa lui).
Comunque oltre alla strcpy() vediti pure la sprintf()... Ricorda che nella printf() su Arduino non è supportato il float quindi ti do' un aiutino basato sulla dtostrf() che ha questo formato:
dtostrf(val, totcaratteri, decimali, buffer);
Converte un float riportandolo nel buffer (char*) secondo il formato indicato ossia il numero totale di caratteri (fissi, quindi con padding di spazi a sinistra) ed in questi il numero di decimali specificato dal terzo parametro.
Ad esempio volendo mostrare una temperatura di 4 caratteri in totale (2 interi, una virgola ed un decimale):
char sTemp[20];
float Temp = 23.354;
...
dtostrf(Temp, 4, 1, sTemp);
lcd.print(sTemp); // "23.3"
...
Ciao,
ecco lo sketch. Le logiche sono rimaste invariate e non ho provato a caricarlo sulla scheda, ma la compilazione va a buon fine.
#include "DHT.h"
#include <ESP8266WiFi.h>
#define LED D0
#define SENS D1
#define RELE D8
IPAddress tcpServer(192, 168, 1, 50);
DHT dht(SENS, DHT22);
WiFiClient client;
unsigned long nextSensorReading = 0;
unsigned long nextReset = 0;
char lightPercent[6];
char temperature[6];
char humidity[6];
byte releStatus = 0;
char client_id[5] = "EXT1";
void setup() {
dht.begin();
pinMode(RELE, OUTPUT);
pinMode(LED, OUTPUT);
digitalWrite(RELE, LOW);
digitalWrite(LED, HIGH);
WiFi.begin("xxxxxx", "xxxxxx");
delay(2000);
nextReset = 90000000; //25 hours
}
void loop() {
scheduleSensor();
wiFiCheck();
manageTcpConnection();
}
void wiFiCheck() {
if (WiFi.status() != WL_CONNECTED) {
client.stop();
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
}
for (int i = 0; i < 5; i++) {
delay(100);
digitalWrite(LED, LOW);
delay(100);
digitalWrite(LED, HIGH);
}
}
}
void manageTcpConnection() {
if (!client.connected()) {
if (client.connect(tcpServer, 8787)) {
delay(500);
}
else {
client.stop();
delay(2000);
}
}
else if (client.available()) {
char ch = client.read();
char cmd[10];
int i = 0;
while (ch != '\n') {
cmd[i] = ch;
ch = client.read();
i++;
}
digitalWrite(LED, LOW);
delay(20);
digitalWrite(LED, HIGH);
char res[26];
i = 0;
int j = 0;
res[i] = 'O';
res[++i] = 'K';
res[++i] = ' ';
if (strncmp(cmd, "STATUS", 6) == 0) {
// T20.8|U78.7|L27.4|R10
res[++i] = 'T';
ch = temperature[j];
while (ch != '\0') {
res[++i] = ch;
ch = temperature[++j];
}
res[++i] = '|';
res[++i] = 'U';
j = 0;
ch = humidity[j];
while (ch != '\0') {
res[++i] = ch;
ch = humidity[++j];
}
res[++i] = '|';
res[++i] = 'L';
j = 0;
ch = lightPercent[0];
while (ch != '\0') {
res[++i] = ch;
ch = lightPercent[++j];
}
res[++i] = '|';
res[++i] = 'R';
res[++i] = '1';
res[++i] = releStatus == 1 ? '1' : '0';
res[++i] = '\n';
}
else if (strncmp(cmd, "ID", 2) == 0) {
j = 0;
ch = client_id[j];
while (ch != '\0') {
res[++i] = ch;
ch = client_id[++j];
}
res[++i] = '\n';
}
else if (strncmp(cmd, "R1", 2) == 0) {
if (cmd[2] == '1') {
digitalWrite(RELE, HIGH);
releStatus = 1;
}
else {
digitalWrite(RELE, LOW);
releStatus = 0;
}
res[++i] = 'R';
res[++i] = '1';
res[++i] = releStatus == 1 ? '1' : '0';
res[++i] = '\n';
}
else {
strcpy(res, "ERROR BAD COMMAND\n");
}
i = 0;
ch = res[i];
while (ch != '\n') {
ch = res[++i];
}
i++;
client.write(res, i);
if (strncmp(cmd, "STATUS", 6) == 0 && millis() > nextReset) {
ESP.restart();
}
}
}
void scheduleSensor() {
if (millis() > nextSensorReading) {
readSensor();
nextSensorReading = millis() + 15000;
}
}
void readSensor() {
int dac = analogRead(A0);
float percent = dac / 10.24;
sprintf(lightPercent, "%.1f", percent);
float temp = dht.readTemperature();
float hum = dht.readHumidity();
sprintf(temperature, "%.1f", temp);
sprintf(humidity, "%.1f", hum);
}
Per la disconnessione, oltre a tutti gli ottimi suggerimenti sul codice....
ma... dal punto di vista hardware (non sono un elettronico altri ti risponderanno meglio) ti pongo molte domande:
- Sei sicuro che il segnale arriva bene all'esterno ?
- All'esterno il tutto è ancora su breadboard ? senza protezione da intemperie ?
- Non è che all'esterno usi una fonte di alimentazione non molto stabile e diversa da quella interna ?
Oltre al problema della breadboard, altro interessante "sintomo" risulta che in casa funziona tutto.
Quanto dista, dentro casa, il router wifi?