Termoremoto non è termometro scritto sbagliato

Come sapete col mio inganniere preferito sto facendo un “coso” per leggere le temperature in casa e archiviarle, memorizzarle, scolpirle sulla pietra e passarle ad Excel
stiamo finalmente finalizzando il progetto, abbiamo deciso che sono 7 no 6, magari 8 stazioni nelle varie stanza collegate via cavo, radio, magia wifi canbus rs2323875 e ancora qualcosa, che salva su sd discofisso clood, insomma a parte i dettagli siamo in alto mare
comunque in questi giorni siamo riusciti a definire almeno l’unità centrale, che ssaà collegata via seriale ai satelliti, seriale che sarà … da definire, io voto radio HC12
ma scherzi a parte se la connessione sarà multipuntomultimaster non mi devo preoccupare troppo, sia radio o quello che sia
finito il cappello teorico lasciamo qui il risultato delle nostre fatiche, con un abbondante commento
voi vi chiederete perchè commento tanto e lascio scritto di mia iniziativa tante cose
Semplice: io ho tanto imparato dal forum, leggendo e tentando di capire, quando posso voglio restituire al forum lasciando qualcosa che possa essere di ispirazione a chi magari avrà esigenze simili in futuro
adesso bando alle ciancie e cominciamo
delle varie macchine distribuite una è la “centrale”, che riceve dai satelliti le trasmissioni (parlo sempre dell’ipotesi radi HC12, avrò un tropismo, ma squadra che vince non si cambia)
la centrale, dovrebbe avere la possibilità di collegarsi ad internet in futuro, per un cloud, e scrivere su una scheda SD
quindi abbiamo optato per (non me ne vogliate) una nodemcu
sì, lo so non e’ arduino, ma si programma uguale uguale
serve anche lei per trarre ispirazione
quindi la lista della spesa
Nodemcu
adattatore SD
radio HC12
alimentatore USB
led verde con resistenza
led rosso con resistenza
deviatore (selettore a due vie)
Evvai di codice - impostazioni

//Nelson StandardOil
//progetto stagioneriscaldamento
//termometri distribuiti - unità centrale

// librerie
#include <ESP8266WiFi.h>


// per le radio
#include <SoftwareSerial.h>
SoftwareSerial Radio(D1, D2, false, 64); // RX, TX, inverse logic, buffer lenght
//dimensione massima stringa in ricezione
#define MAXRX 8
// carattere di start trasmissione
#define START '#'
// indirizzo prima e ultima stazione
#define PRIMA 'A'
#define ULTIMA 'F'


//per la parte orologio
#include <NtpClientLib.h>
#include <Time.h>
#include <TimeLib.h>
#include <WiFiUdp.h>
WiFiUDP Udp;
static const char ntpServerName[] = "us.pool.ntp.org";
int timeZone = 1;     // Central European Time
//  per CET ora legale usare 2


unsigned int localPort = 8888;  // porta locale per ricevee pacchetti UDP
//const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets


//Scheda SD
#include <SPI.h>
#include <SD.h>
Sd2Card card;
SdVolume volume;
SdFile root;
const int chipSDSelect = 15;

// inizializzazione dati sensibili letti da file
// Dati connessione wifi contenitori vuoti, riempiti da file
char ssid[] = "                ";
char password[] =   "                        ";
#define PASSWD "pwd.txt"
// nome del file delle password

char logfile[] = "        .csv"; // contenitore per nome file di log

dove fondamentalmente includo le librerie
e inizializzo alcune varibili e macro
in particolare inizializzo alcune stringhe (per me stringa è sempre array di char terminato con un \0)
dicevo le inizializzo a spazi, per pre-allocare lo spazio necessario

dopo di che devo scrivere la setup

void setup()
{
    Serial.begin(9600);

    if (!SDinit())
    {
        Serial.println(F("Errore scheda SD, programma interrotto"));

        while (1)
        {
            delay(1);
        }
    }

    // carico i dati dal file password
    leggipassword(PASSWD, 2, ssid);
    leggipassword(PASSWD, 3, password);
    // su il wifi
    Serial.print(F("Connessione al wifi: "));
    Serial.println(ssid);
    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED)
    {
        Serial.print(".");
        delay(500);
    }

    Serial.print(F("Connesso. IP: "));
    IPAddress ip = WiFi.localIP();
    Serial.println(ip);
    // server NTP
    Serial.println(F("Avvio UDP"));
    Udp.begin(localPort);
    Serial.print(F("Porta locale: "));
    Serial.println(Udp.localPort());
    Serial.println(F("In attesa del sincronismo"));
    setSyncProvider(getNtpTime); // punta alla funzione che restituisce il tempo
    // secondi passati dal primo gennaio 1970
    setSyncInterval(300); // intervallo tra un sincronismo orario e il successivo
}

ok,ok la ho copiata brutalmente, poi ho cambiato due o tre cose:
l'intera gestione della SD la ho chiusa nella bool SDinit(), che indovinate, restituisce true quando la SD è OK

bool SDinit()
{
    Serial.print(F("Inizializzazione scheda SD "));

    if (!card.init(SPI_HALF_SPEED, chipSDSelect))
    {
        Serial.println(F("fallita"));
        return LOW;
    }
    else
    {
        Serial.print(F("completata, scheda tipo: "));
    }

    // print the type of card

    switch (card.type())
    {
        case SD_CARD_TYPE_SD1:
            Serial.println(F("SD1"));
            break;

        case SD_CARD_TYPE_SD2:
            Serial.println(F("SD2"));
            break;

        case SD_CARD_TYPE_SDHC:
            Serial.println(F("SDHC"));
            break;

        default:
            Serial.println(F("Sconosciuto"));
            return LOW;
    }

    if (!volume.init(card))
    {
        Serial.println(F("Non trovo la partizione FAT16/FAT32.\nControlla la formattazione della scheda SD"));
        return LOW;
    }
    else
    {
        Serial.println(F("Partizione FAT16/FAT32 trovata"));

        if (!SD.begin(chipSDSelect))
        {
            Serial.println(F("Non pronta all'uso"));
            return LOW;
        }

        Serial.println(F("Pronta all'uso."));
        return HIGH;
    }
}

le credenziali di rete le faccio leggere, dentro in stringhe già inizializzate da una funzione ad hoc, che legge dal file specificato, la riga specificata dentro nella stringa specificata, fino al fine riga o al fine file o alla fine della stringa
eccola:

void leggipassword(char filename[], byte rigaoggetto, char stringa[])
{
    // legge dal file fliename il contenuto della riga rigaoggetto e lo copia nella stringa stringa
    // se la stringa è più corta tronca la lettura
    // se la stringa è piu' lunga sposta il terminatore di stringa dopo la fine della lettura
    // la stringa in ingresso deve esistere ed essere correttamente terminata da un \0
    // la lunghezza della stringa deve essere sufficente
    // conta le righe contando il carattere \n
    // ignora il carattere \r (dovrebbe essere quindi windows compatibile)
    // meno di 255 righe stringhe piu' corte di 253

    // apro il file
    if (!SD.exists(filename))
    {
        Serial.print(F("File inesistente: "));
        Serial.println(filename);
        return;
    }
    else
    {
        byte index = 0;
        byte riga = 1; // si parte dalla prima riga, sarebbe la 0, ma umanamente e' 1
        File dataFile = SD.open(filename, FILE_READ);

        while (dataFile.available())
        {
            char cx = dataFile.read();

            if (cx == '\n')
            {
                riga++;
                continue;
                // riga contata, lavoro fatto, avanti un carattere
            }

            if (cx == '\r')
            {
                continue;
                // terminatore di riga di windows, ignorato
            }

            if (riga == rigaoggetto)
            {
                if (stringa[index])
                {
                    // la stringa non è terminata
                    stringa[index++] = cx;
                }
            }

            if (riga > rigaoggetto)
            {
                // abbiamo passato
                break;
                // fuori dal ciclo
            }
        }

        // finito file
        dataFile.close();
        // adesso index punta alla fine della stringa +1
        // ci metto un \0 per sicurezza
        stringa[index] = '\0';
    }
}

dopo aver letto le credenziali di rete fa partire il wifi, quando partito mi sincronizzo con l'ora su un server NTP

avanti con un’altra puntata, la loop
che è semplicissima
ogni minuto stampa l’ora, solo per vedere che va, in effetti non serve a nulla e si puo’ togliere
di continuo controlla la presenza di un carattere dalla radio

void loop()
{
    static byte minuto = 0;

    // operazioni pianificate al minuto
    if (minuto != minute())
    {
        minuto = minute();
        Serial.print("ora attuale: ");

        if (hour() < 10)
        {
            Serial.print('0');
        }

        Serial.print(hour());
        Serial.print(':');

        if (minute() < 10)
        {
            Serial.print('0');
        }

        Serial.println(minute());
    }

    // un controllo se abbiamo qualcosa da leggere dalla radio
    if (Radio.available())
    {
        // la radioricevi riceve e tratta i dati dalle radio
        Radioricevi();
    }
}

naturalmente è la radioricevi() che fa il lavoro sporco
prima acqusisce il carattere in ingresso
poi controlla se è un carattere di start

void Radioricevi(void)
{
    // riceve e tratta i dati dalla radio
    static char stringa[MAXRX]; // il buffer di appoggio della trasmissione
    static byte index = 0;
    char rx = Radio.read(); // sono sicuro che esista un carattere dal leggere, altrimenti non sarebbe stata chiamata

    if (rx == START)
    {
        index = 1;
        stringa[0] = rx;
        return;
        // sempre un carattere trattato, fuori dalla funzione
    }

    if (index > 0 && index < MAXRX + 2)
    {
        // se abbiamo sentito un carattere di start
        // e non abbiamo indice fuori range
        stringa[index++] = rx;

        if (index == MAXRX - 1)
        {
            // fine stringa
            stringa[MAXRX - 1] = '\0';
            valida(stringa);
            index = MAXRX + 3; // metto l'indice fuori range
        }

        return;
    }
}

se sì comincia a caricare i dati un un array di char (anzi, una stringa)
arrivato a riempire, ovvero aver latto il giusto numero di caratteri, manda in validazione la stringa stessa
se supera in numero di caratteri non fa nulla, se trova un nuovo start ricomincia
ed acco la valida()

void valida(char stringa[])
{
    // valida la stringa ricevuta, e se supera il check la manda in esecuzione
    char controllo = stringa[MAXRX - 2]; // maxrx è \0 meno due è il carattere di controllo
    stringa[MAXRX - 2] = '\0'; // stringa ora termina prima del carattere di controllo

    if (checkchar(stringa) == controllo)
    {
        // il carattere di controllo corrisponde a quello calcolato
        esegui(stringa); // la stringa è ancora monca del carattere di controllo
    }
}

semplicemente toglie il carattere di check e lo fa ricalcolare, se OK passa alla esegui
altrimenti nisba
la esegui effettua un controllo sul fatto che la stazione mittente sia una stazione valida, e poi salva il dato acquisito

void esegui(char stringa[])
{
    // il formato stringa e':
    // carattere di start, non salvato
    // 1° carattere indirizzo di stazione
    // dal 2° carattere la temperatura
    // controllo che la stazione sia una di quelle autorizzate
    if (stringa[1] >= PRIMA && stringa[1] <= ULTIMA)
    {
        // ok stazione autorizzata
        // adesso dovrei leggere la teperatura e convertirla in numero
        // ma a pensarci bene è gia' scritta come mi serve di metterla nel file
        // la passo dritta nel file, solo separando la stazione
        nomefilelog(logfile);// definito nome del file
        scrivi(logfile, stringa[1], &stringa[2]); // se va è una magata, passare l'indirizzo del secondo carattere per escludere il primo
    }

    // ok, scritto
    //adesso diciamo al satellite di andare pure a dormire
    Radio.print(START);
    Radio.print(stringa[1]);
    Radio.print("OK");
}

al termine della scrittura sulla SD manda conferma alla stazione mittente, leggendo il mittente dal messaggio ricevuto la stazione mittente, quando riceve conferma si addormente dormendo il sonno el giusto, sapendo che è andato tutto bene
il formato è semplice:
start
una lettera maiuscola per la stazione (lo so, sono monomaniaco, io dico invece squadra che vince non si cambia)
il dato da trasmettere
un carattere di controllo, preso pari pari dai suggermineti che mi avete dato per il progetto pinremoti (ancora grazie)
il carattere di controllo:

char checkchar(char messaggio[])
{
    // restituisce il carattere di controllo per il messaggio in ingresso
    // una cosa semplice, non troppo elaborata
    byte appoggio = 0;
    byte index = 0;

    while (messaggio[index])
    {
        appoggio = ((appoggio >> 1) | (appoggio << 7)) ^ messaggio[index++];
    }

    return (char)(appoggio % 93) + 33;
}

naturalmente la scrivi(logfile…) scrive i dati giusti in un file di nome logfile

void scrivi(char logfile[], char indirizzo, char stringa[])
{
    // scrive su un file la stringa
    // Data, ora, indirizzo, valore
    File datafile;
    datafile = SD.open(logfile, FILE_WRITE);

    if (datafile)
    {
        // ok file aperto
        datafile.print(year());

        if (month() < 10)
        {
            datafile.print('0');
        }

        datafile.print(month());

        if (day() < 10)
        {
            datafile.print('0');
        }

        datafile.print(day());
        datafile.print(',');

        if (hour() < 10)
        {
            datafile.print('0');
        }

        datafile.print(hour());
        datafile.print(',');

        if (minute() < 10)
        {
            datafile.print('0');
        }

        datafile.print(minute());
        datafile.print(',');
        datafile.print(indirizzo);
        datafile.print(',');
        datafile.println(stringa);
        datafile.close();
    }
    else
    {
        Serial.println(F("Errore apertura file di log"));
    }
}

e il nome del file viene generato giorno per giorno dalla nomefilelog()

void nomefilelog(char stringa[])
{
    // restituisce nella stringa il nome del file attuale
    int locale = year();

    for (int i = 3; i >= 0; i--)
    {
        stringa[i] = locale % 10 + '0';
        locale = locale / 10;
    }

    locale = month();
    stringa[5] = locale % 10 + '0';
    locale = locale / 10;
    stringa[4] = locale % 10 + '0';
    locale = day();
    stringa[7] = locale % 10 + '0';
    locale = locale / 10;
    stringa[6] = locale % 10 + '0';
}

Mi mancano solo due funzione non mie, che ho brutalmente copiato da altri e che non ho ben capito, ma vanno e tanto mi basta (il bello delle funzioni…)
quelle che gestiscono la ricezione dei tempi dal servere NTP

time_t getNtpTime()
// restituisce il tempo locale in secondi
{
    IPAddress ntpServerIP; // indirizzo del server NTP

    while (Udp.parsePacket() > 0) ; // scarto tutti i pacchetti vecchi

    Serial.println(F("Trasmetto richiesta al server NTP: "));
    WiFi.hostByName(ntpServerName, ntpServerIP);
    Serial.print(ntpServerName);
    Serial.print(F(": "));
    Serial.println(ntpServerIP);
    sendNTPpacket(ntpServerIP);
    unsigned long int beginWait = millis();

    while (millis() - beginWait < 1500)
    {
        int size = Udp.parsePacket();

        if (size >= NTP_PACKET_SIZE)
        {
            Serial.println(F("Risposta NTP ricevuta"));
            Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
            unsigned long int secsSince1900;
            // convert four bytes starting at location 40 to a long integer
            secsSince1900 = (unsigned long int)packetBuffer[40] << 24;
            secsSince1900 |= (unsigned long int)packetBuffer[41] << 16;
            secsSince1900 |= (unsigned long int)packetBuffer[42] << 8;
            secsSince1900 |= (unsigned long int)packetBuffer[43];
            return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
        }
    }

    Serial.println(F("Nessuna risposta dal server NTP"));
    return 0; // return 0 if unable to get the time
}



// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress & address)
{
    // set all bytes in the buffer to 0
    memset(packetBuffer, 0, NTP_PACKET_SIZE);
    // Inizializzo ivalori necessari pwr una richiesta NTP
    packetBuffer[0] = 0b11100011;   // LI, Version, Mode
    packetBuffer[1] = 0;     // Stratum, or type of clock
    packetBuffer[2] = 6;     // Polling Interval
    packetBuffer[3] = 0xEC;  // Peer Clock Precision
    // 8 bytes of zero for Root Delay & Root Dispersion
    packetBuffer[12] = 49;
    packetBuffer[13] = 0x4E;
    packetBuffer[14] = 49;
    packetBuffer[15] = 52;
    // Fatto
    // possiamo lanciare la richiesta
    Udp.beginPacket(address, 123); //NTP risponde alla porta 123
    Udp.write(packetBuffer, NTP_PACKET_SIZE);
    Udp.endPacket();
}

siccome non abbiamo ancora definito la parte di trasmissione dai satelliti (o stazioni mittenti) la abbiamo provata sostituenda alla Radio la Serial
cambiando questa riga:

SoftwareSerial Radio(D1, D2, false, 64); // RX, TX, inverse logic, buffer lenght

in queste due

//SoftwareSerial Radio(D1, D2, false, 64); // RX, TX, inverse logic, buffer lenght
#define Radio Serial

sicchè invece di usare le radio HC12, cha ancora non abbiamo, usiamo la seriale USB, scrivendo a mano i valori

e per stasera è tutto, se avete domande io qui sono
buonasera a tutti

Non vorrei fare il guastafeste.
Io ho implementato una cosa simile con una nodemcu e anche con un modulo ESP-01. Al primo è collegato un BME280 (temperatura, pressione e umidità), al secondo un BMP180 (solo temperatura e pressione)
Sparano i dati su Thingspeak, quindi in cloud ma dovrei farmi il server in casa, sempre con Thingspeak, se non ricordo male entra su un RaspberryPI.

Dopodiché ho anche pensato a sensori esterni dove non arriva il WiFi o da far andare a pile.
Ho preso un sensore esterno Digoo DG-R8S (433MHz) a meno di 5$ su BangGood e quindi ho adattato una libreria per decodificarlo
http://stm32duino.com/viewtopic.php?f=50&t=2198&hilit=digoo&start=10#p40663
Vorrei provare a replicarlo con un Arduino pro micro a 8MHz, un trasmettitore e il deepsleep e vedere se riesco a battere i consumi del Digoo.

Per l'NTP all'inizio avevo implementato un codice ma mi sto orientando verso le funzioni già contenute nel core
come Tecno500 ha scritto qui

Invece dell'SD, se hai la nodeMCU potresti usare la flash con SPIFFS che sarebbe il file system ideato per la flash degli ESP8266.

Lo sapevo che avevi fatto buon lavoro
Infatti tempo fa abbiamo parlato di questo, mi ricordo
Per me, non col mio collega, seguiro le tue orme, sto aspettando dalla cina i dgr
Ma col mio collega abbiamo deciso attiny e ds18b20 per i satelliti
Non abbiamo scelto spiffs, perché non abbiamo capito come riportare i file sul pc, abbiamo trovato solo come scriverli su nodemcui, non come leggerli.
E via ftp arrivavano bacati, riproveremo in futuro

zoomx:
Per l'NTP all'inizio avevo implementato un codice ma mi sto orientando verso le funzioni già contenute nel core
come Tecno500 ha scritto qui
http://forum.arduino.cc/index.php?topic=466867.msg3686997#msg3686997

questa me la spieghi?
Voglio dire: quando ci sei riuscito me la spieghi anche a me?
Che il mio inglese zoppica molto, non ci capisco uno Jota

Dopo aver realizzato alcuni progettini di orologi con Arduino e DS3231 mi ero accorto cher ad ogni passaggio da ora estiva ad invernale e viceversa avrei dovuto cambiare l’ora a questi moduli oppure implementare un sistema che lo facesse alla pressione di un pulsante oppure usare la libreria TimeZone. Però in ogni caso avrei avuto degli orologi che lentamente perdevano il sincronismo.
Per cui ho pensato ai moduli ESP8266 e al protocollo NTP.
Ho quindi incluso la libreria NTP, la libreria TimeLib e la Timezione e adattato un codice che faceva richiesta del pacchetto con l’ora.

Invece sul forum ESP8266 l’utente Tecno500 ha ricevuto il suggerimento di usare funzioni già incluse nell’ESP8266 e sono arrivato allo sketch accluso che ha già gli indirizzi di server italiani e il fuso CET che è quello che usiamo.

La parte adesso che fa tutto è praticamente questa

  configTime(0, 0, NTP0, NTP1);
  Serial.print("wait for first valid timestamp ");

  while (time(nullptr) < 100000ul)
  {
    Serial.print(".");
    delay(10);
  }
  Serial.println(" time synced");

ESP8266_Time_Sync.ino (3.24 KB)

che ' dire, a parte che grazie?