C'è un Telegram nel tuo futuro....

Vabbe', allora
complice un cambio di programma e una piccola serie di fortunate coincidenze sono rientrato 12 ore prima
e quindi posso anticipare la presentazione della mia "libreria" per telegram
Ora, dalla documentazione di telegram sappiamo che per trasmettere un messaggio di testo basta inviare al server di telegram un "messaggio" così composto:
GET /botValoredelToken/sendMessage?chat_id=Account&text=TestodaTrasmettere&parse_mode=TipodiParsemode
dove si deve sostituire a
ValeredelToken la sequenza di caratteri che rappresenta il token del bot
TestodaTrasmettere con la sequenza di caratteri che rappresenta il messaggio, con gli eventuali a capo sostituiti dalla sequenza %0A
La parte &parse_mode è opzionale e si usa per cambiare certe caratteristiche grafiche di Telegram
Questa sequenza viene terminata da un '/n'
invece di "costruire" un unica gigantesca stringa, magari prorpio oggetto stringa, come ho visto fare nelle librerie che ho trovato ho preferito dividere l'operazione in tre fasi:

  1. aprire la comunicazione col bot
  2. con una serie di print (e mai nessuna println) "spedire" i vari pezzi successivi del messaggio
  3. chiudere il messaggio con una sola println, e magari la parte parsemode opzionale
    ecco quindi le funzioni dedicate
bool botopen(unsigned long int destinatario)
{
    digitalWrite(LED, 1); // led di trasmissione in corso
    // inizia una trasmissione al bot
    // non chiude la connessione, lascia aggiungere
    // per Telegram il messaggio viene chiuso da un fine riga (a capo)

    if (telegrambot.connect(telegram, PORTA))
    {
        telegrambot.print(F("GET /bot"));
        telegrambot.print(token);
        telegrambot.print(F("/sendMessage?chat_id="));
        telegrambot.print(destinatario);
        telegrambot.print(F("&text="));
        return true;
    // si commenta da solo
    }
    else
    {
        Serial.println(F("Errore trasmissione telegram"));
        digitalWrite(LED, 0); // led di trasmissione in corso
        return false;
    }
}

che iniza la comunicazione col bot e per soprammercato accende anche un LED di segnalazione "trasmissione in corso"

void botclose(int parsemode)
{// chiude la connessione
    // parse=0 per no parse
    // parse 1 per parse= Markdown
    if (parsemode == 1)
    {
        telegrambot.print(F("&parse_mode=Markdown"));
    }
// il parametro opzionale parsemode permette di chiudere il messaggio 
// dicendo a Telegram di seguire una differente convenzione grafica

    telegrambot.println(); // il println chiude il messaggio
    digitalWrite(LED, 0);
    delay(350); // sembra che serva un delay minimale, anche se non ne ho trovato traccia nella documentazione
}

Che chiude la comunicazione ed eventualmente cambia il parsemode di Telegram
Per il corpo del messaggio è sufficente eseguire delle

telegrambot.print(quello che devo trasmettere);

anche più di una, usando "%0A" come ritorno a capo e solo alla fine della composizione del messaggio chiudere con la botclose()
per pura comodità ho anche creato una botsend(), che trasmette direttamente una stringa di 'C' (un array di caratteri), aprendo e chiudendo il bot come serve

void botsend(unsigned long int destinatario, char messaggio[])
{
    // trasmette al bot il messaggio
    // chiudendo il bot  e non lasciando aggiungere
    if (botopen(destinatario))
    {
        telegrambot.print(messaggio);
        botclose();
        // si commenta da solo
    }
    else
    {
        Serial.println(F("Errore apertura bot"));
    }
}

naturalmente l'argomento destinatario è il "numero" dello account di Telegram, che non è il numero di telefono, e non è una stringa, ma un numero a 32 bit senza segno
parimenti l'argomento messaggio è l'array di char che contiene un messaggio completo
nella prossima puntata: Come ricevere

Come ricevere:
quì l'argomento è più complicato
per due motivi

  1. Telegram mantine una coda di messaggio, che potrebbero anche essere molti
  2. Telegram restituisce il messaggio assieme ad una serie di informazioni in formato Json, che non mi piace da decodificare
  3. a me piace decodificare e/o gestire il flusso di testo come flusso in arrivo, non come stringhe "finite e pronte", che ci volete fare, è una mia malattia....
    e quindi, argomento complesso: affrontiamolo in teoria:
    spedire al server di Telegram una sequenza del tipo:
    GET /bot/getUpdates?offset=&limit=1
    fa rispondere a telegram con (se c'è), una sequenza Json corrispondente al primo dei messaggi in coda per essere letti
    le parti importanti di questa sequenza Json sono 3
    Numero del messaggio ricevuto, un numero a 32 bit che "identifica" il messaggio che Telegram ci ha mandato
    è garantito che il prossimo ha un numero maggiore di 1 rispetto a questo, nella stringa Json è preceduto da "update_id"
    Mittente: un numero a 32bit senza segno, che identifica il mittente del messaggio, nella stringa Json è preceduto da "chat_id"
    Il testo del messaggio, una stringa di lunghezza variabile preceduta da "text"
    queste tre informazioni, non sapendo, e non volendo, io programmare in C++ devono per forza essere contenute in tre variabili globali
    Variabili che verranno, per così dire, riempite durante la ricezione del messaggio vero e proprio
    Ecco quindi come ricevere il messaggio:
    per prima cosa controllo se c'è un messaggio da ricevere
void botread(void)
{
    // connessione con telegram
    if (telegrambot.connect(telegram, PORTA))
    {
   
        telegrambot.print(F("GET /bot"));
        telegrambot.print(token);
        telegrambot.print(F("/getUpdates?offset="));
       
        telegrambot.print(ultimo + 1); // Telegram considera letto e quindi scarta ogni messaggio di numero < offset
        // in questa maniera avanzo di uno nella coda dei messaggi
        telegrambot.println(F("&limit=1")); // uno alla volta uno alla volta per carita.... (da: il barbiere di Siviglia)
        // resetto la variabile di appoggio che mi serve per conteggiare il valore di ultimo update ricevuto
        appoggio = 0;
        // resetto la variabile di ultimo mittente
        mittente = 0;
        // svuoto il campo testo
        testo[0] = 0; // basta il primo carattere, le stringhe sono terminate da 0
        // il contatore di lunghezza del testo
        lentext = 0;
        unsigned long int ora = millis();

        while (millis() - ora < 1500) // un timeout
        {
            if (telegrambot.available())
            {
                // passo alla funzione di trattamento il carattere ricevuto
                char c=telegrambot.read();
                if (!elabora(c))
                {
                    break;
                }
            }
        }
    }
}

che si limita a "chiedere" a Telegram se esiste il messaggio successivo all'ultimo ricevuto (per Telegram quiesto significa anche dare per letti e scartare tutti i messaggi precedenti)
Poi, se telegram risponde antro un certo tempo, carattere per carattere faccio elaborare la risposta
la funzione elabora restituisce 0 quando il messaggio è finito (stringa Json terminata con la chiusura di tutte le graffe)
ecco la funzione che carattere per carattere elabora la risposta

int elabora(char c)
{
    // elabora char by char l'intero messaggio ricevuto da telegram
    // riconosce le coppie di parentesi graffe
    // riconosce alcuni campi
    // restituisce zero per fermare, che poi significa chiusa ultima graffa del messaggio
    static int livello = 0; // il livello di rientro delle parentesi graffe
    static int oggetto = 0; // in quale nome di oggetto siamo
#define NUMSTRINGHE  4  // e' una define solo per comodità di trovarla
    static char * oggetti[NUMSTRINGHE] = {"niente", "\"update_id\":", "chat\":\"id\":", "\"text\":\""};
    // vettore di puntatori a carattere
    // siccome un puntatore a carattere è una stringa di 'C'
    // è come fare un array di stringhe ('C' non esiste un tipo specifico stringa, quindi non sarebbe possibile un array di stringhe semplice semlice)
    // siccome restituisce l'indice devo tenere ciccato il primo elemento, altrimenti me lo riconosce e fa oggetto=0
    // che si confonde con fuori dall'oggetto
    static byte actual[NUMSTRINGHE];
    // un vettore di posizioni correnti nelle stringhe puntate dal primo vettore
  

    if (c == '{')
    {
      // conto i livelli
        livello++;

        return livello;

    }

    if (c == '}')
    {
        livello--;
        // oggetto=0;
        return livello;
        // se siamo a livello 0 il messaggio è finito
    }

    // ne graffa aperta ne graffa chiusa, nel pieno del messaggio
    // devo riconoscere se stiamo leggendo il nome di un oggetto conosciuto
    if (oggetto)
    {
        // abbiamo trovato un nome di oggetto (: compreso)
        // adesso fino alla virgola o virgoletta
        // se si tratta di una virgola o virgoletta
        if (c == ',' || c=='\"')
        {
            oggetto = 0;
            // fuori dall'oggetto
            return 1;
        }

       
    }

    // ok non è una virgola, ne una virgoletta
    // siamo quindi in un oggetto che voglio riconoscere

    // campi numerici, purtroppo devo saperlo io (hard coded)
    if (oggetto == 1)
    {
        // updateid = ultimo messaggio
        appoggio = appoggio * 10 + c - '0';
        // converto in intero
    }

    if (oggetto == 2)
    {
        // mittente
        mittente = mittente * 10 + c - '0';
        // traduco in intero (gli utenti sono sempre unsigned long
    }

    // campi alfabetici, che per ora è solo text, corpo del messaggio
    if (oggetto == 3)

        // siamo nel testo
        if (lentext < LENTEXT)
        {
            testo[lentext] = c;
            lentext++;
            testo[lentext] = 0; // terminatore
        }
        else
        {
            oggetto = 0;
            // evito di uscire dalla stringa
        }
    else
    {
        // non abbiamo ancora trovato il nome di un oggetto
        // vado avanti a cercarlo
        for (byte i = 0; i < NUMSTRINGHE; i++)
        {
            //per ogni oggetto, vedo se coincide il carattere
            if (c != oggetti[i][actual[i]])
            {
                // non coincide
                actual[i] = 0;
            }
            else
            {
                // coincide
                actual[i]++;

                //avanti
                if (oggetti[i][actual[i]] == 0)
                {
                    // oggetto finito stringa trovata
                    actual[i] = 0; //torno indietro
                    oggetto = i;

                }
            }
        }
    }

    return 1; // OK in lettura
}

che come si può vedere cerca delle corrispondenze (in particolare la corrispondenza su tre stringhe)
e se le trova, copia la parte di testo successiva nella variabile globale corrispondente
se vi ricordate il lavoro fatto 2 mesi fa per il Linbus, vedrete che la minestra è quella
contemporaneamente conta le aprentesi graffe aperte e quelle chiuse, e quando il conto torna a zero restitiosce 0, che significa messaggio Telegram completato
nella loop a questo punto basterà chiamare la botread() e poi vedere se il contatore di ultimo messaggio si è incrementato
se sì, significa che abbiamo un ultimo messaggio arrivato e nella variabile testo[] abbiamo il testo, fino a un massimo di LENTEXT caratteri
purtroppo non possiamo gestire messaggi di lunghezza arbitraria, io mi sono trovato bene con 100
ecco la parte che metto nella loop()

    botread(); // lancio la ricezione
    // controllo che sia arrivato un nuovo messaggio

    if (appoggio > ultimo)
    {
    // ricevuto
        // aggiorno l'ultimo messaggio ricevuto
        ultimo = appoggio;

    }
    else
    {
    // non ricevuto
    }

assì, usare come ultimo messaggio il valore 0 fa comunque arrivare l'eventuale primo messaggio in coda da leggere

rimane da mostrare le varie inizializzazioni e dichiarazioni
in particolare servirà di creare le variabili globali, dichiarare l'oggetto telegrambot e fare le varie define

void botclose(int a = 0);   // protoripo della botclose, che ha argomento di default
unsigned long int ultimo;   // il numero dell'ultimo messaggio ricevuto
unsigned long int appoggio; // una variabile di appoggio
unsigned long int mittente; // il numero del mittente ultimo messaggio
unsigned long int io;       // tutti gli account di Telegram sono interi a 32 bit senza segno
#define LENTEXT 100
char testo[LENTEXT];        // il testo appena ricevuto, massimo LENTEXT caratteri
byte lentext = 0;           // la lunghezza del testo trovato
IPAddress telegram(149, 154, 167, 198); // ho fatto un NSlookup
#define PORTA 443 
// indirizzo del server e sua porta
#define LED D0 // il led di trasmissione
// istanza del client
WiFiClientSecure telegrambot; 
char token[]="                                                 "; // scrivere qui il token

per pura comodità ho riunito tutto questo in un file puntoacca che si può includere all'inizio del proprio programma, lo trovate allegato qui sotto
per ogni domanda, io qui sono
comunque, per essere molto chiari:
NON venitemi a dire che non va, lo uso da circa 2 mesi, se non vi funziona a voi, avete fatto qualche errore, chiedete e vi sarà detto
NON venitemi a fare le pulci, io lo so di non essere il più bravo, e magari sono anche tra i più scalcinati, ma metto a disposizione il mio sapere volentieri e con spirito positivo, chi mi fa le pulci evidentemente non ha spirito positivo, lo mando dritto a espletare funzioni corporali

telegrambot.h (7.16 KB)

Proverò sicuramente il tutto ... appena ritorno dalla "Microchip Masters Conference", ovvero non prima di una settimana :smiley:

Intanto, grazie per la condivisione.

Guglielmo

Grazie a te

Una Prece

oggi dopo 121 giorni ininterrotti di funzionamento dall'ultima mancanza di energia la mia nodeMCU ha reso i bit allo spirito del silicio

a parte un trasloco e qualche black out funzionava da poco dopo il primo post di questa discussione.......

domanda: con cosa la sostituisco?