Arduino Home Security GSM Alarm

Buongiorno, ho bisogno di una mano con un programmino che sto cercando di ultimare, l'intento è costruire una piccola centrale d'allarme per controllare la porta basculante del box. A tal proposito non avendo reti wi-fi nelle vicinanze, ho optato per la comunicazione GSM tramite sms. Vi posto la bozza del codice:

// Arduino Home Security GSM Alarm

//Librerie e definizione hardware
#include <SoftwareSerial.h>
#define SIM800_TX_PIN 8                                //SIM800 TX al pin D8 di to Arduino
#define SIM800_RX_PIN 7                                //SIM800 TX al pin D7 di to Arduino
SoftwareSerial mySerial(SIM800_TX_PIN, SIM800_RX_PIN); //Creazione software serial per SIM800L
#define reed 2
#define tumper 3
#define led_1 9
#define led_2 6

//variabili lettura sms in entrata
char stringa[100];
char comando[31] = {0};

// variabili antifurto
bool stato_imp = false;
volatile int stato_reed  = LOW;
volatile int stato_tamper = LOW;
String number = "xxxxxxxxxxx";
//==============================================================
//                           SETUP
//==============================================================
void setup()
{
  Serial.begin(9600);
  mySerial.begin(9600);
  delay(10000);                             // attendo che il GSM abbia agganciato la linea
  mySerial.print("AT+CMGF=1\r\n");          // imposta il formato del messaggio SMS come testo
  delay(1000);
  mySerial.print("AT+CNMI=1,2,0,0,0\r\n");  // specifica come devono essere gestiti i messaggi SMS appena arrivati
  delay(1000);
  pinMode(reed, INPUT);
  attachInterrupt(digitalPinToInterrupt(reed), line_reed, FALLING );    // FALLING = da alto a basso.
  pinMode(tumper, INPUT);
  attachInterrupt(digitalPinToInterrupt(tumper), line_tamper, FALLING );  // FALLING = da alto a basso.
  pinMode(led_1, OUTPUT);
  digitalWrite(led_1, LOW);
  pinMode(led_2, OUTPUT);
  digitalWrite(led_2, LOW);
}
//==============================================================
//                          LOOP
//==============================================================
void loop()
{
  lettura_sms();
  antifurto();
}
//==============================================================
//                     INTERRUPT REED
//==============================================================
void line_reed()
{
  stato_reed = HIGH;
}
//==============================================================
//                     INTERRUPT TAMPER
//==============================================================
void line_tamper()
{
  stato_tamper = HIGH;
}
//==============================================================
//                         ANTIFURTO
//==============================================================
void antifurto() {
  if (stato_imp = true) { // flag impianto inserito

    digitalWrite(led_1, HIGH);  // accende led impianto inserito

    if (stato_reed == HIGH) { // contatto porta aperto
      invio_sms();
      digitalWrite(led_2, HIGH);  // accende led segnalazione allarme
    }

    if (stato_tamper == HIGH) { // linea tamper aperta
      invio_sms();
      digitalWrite(led_2, HIGH); // accende led segnalazione allarme
    }
  }
  else if (stato_imp = false) { // flag impianto disinserito
    digitalWrite(led_1, LOW);  // spengne led a impianto disinserito
  }
}
//==============================================================
//                   INVIO SMS ALLARME
//==============================================================
void invio_sms() { //invio sms
  mySerial.println("AT+CMGF=1"); // Set SMS Mode
  delay(150);
  mySerial.println("AT+CMGS=\"+39" + number + "\"");  // specifica numero di destinazione
  delay(150);
  mySerial.print("Attenzione, la centrale è in allarme!");   // testo di risposta via SMS
  delay(150);
  mySerial.write((byte)0x1A);                         // End of message character 0x1A: equivalente a Ctrl+z
  delay(50);
  mySerial.println();
}
//==============================================================
//      INSERIMENTO/DISINSERIMENTO IMPIANTO VIA SMS
//==============================================================
void lettura_sms() {

  if (mySerial.available()) {

    mySerial.readString().toUpperCase();                         // rendo maiuscola tutta la stringa per evitare errori di sintassi
    mySerial.readString().toCharArray(stringa, 100);              // converto la stringa message nell'array stringa
    
    strncpy(comando, stringa, 30);                                // copio in comando la prima parte del messaggio ricevuto ossia: +CMT "+39numerotelefono"
    Serial.println(comando);
    
    if (strncmp(comando, "+39xxxxxxxxx", 13) == 0) {             // poi controllo se in comando sia presente il numero del mittente autorizzato
      if (strncmp(comando, "ON", 2) == 0) {                       // e inserisco impianto
        stato_imp = true;
      }
      else if (strncmp(comando, "OFF", 3) == 0) {                  // o disinserisco impianto
        stato_imp = false;
      }
    }
    mySerial.print("AT+CMGDA=\"");
    mySerial.println("DEL ALL\"");                                // cancello tutti i messaggi
  }
}

nello specifico la difficoltà che incontro è nella gestione deigli sms entranti:

void lettura_sms() {

  if (mySerial.available()) {

    mySerial.readString().toUpperCase();                         // rendo maiuscola tutta la stringa per evitare errori di sintassi
    mySerial.readString().toCharArray(stringa, 100);              // converto la stringa message nell'array stringa
    
    strncpy(comando, stringa, 30);                                // copio in comando la prima parte del messaggio ricevuto ossia: +CMT "+39numerotelefono"
    Serial.println(comando);
    
    if (strncmp(comando, "+39xxxxxxxx", 13) == 0) {             // poi controllo se in comando sia presente il numero del mittente autorizzato
      if (strncmp(comando, "ON", 2) == 0) {                       // e inserisco impianto
        stato_imp = true;
      }
      else if (strncmp(comando, "OFF", 3) == 0) {                  // o disinserisco impianto
        stato_imp = false;
      }
    }
    mySerial.print("AT+CMGDA=\"");
    mySerial.println("DEL ALL\"");                                // cancello tutti i messaggi
  }
}

potete indicarmi il metodo migliore?

Partirei come già detto millemila altre volte sul forum a eliminare l'uso della classe String, che già non andrebbe usata in più tu la usi per poi sparare il risultato in un array di char. Quindi per leggere quello che ti arriva puoi fare una cosa tipo:

byte idxInStr = 0;
const byte lunghezzaMassimaComando = 100;
char stringa[lunghezzaMassimaComando];
void setup
{
   ...
}

void loop
{
...
}

void lettura_sms()
{
  if (mySerial.available())
  {
     idxInStr = 0; ((Azzero l'indice dell'array del comando dove scrivere il carattere letto
     memset(stringa, 0, sizeof(stringa)); //Azzero il contenuto del precedente comando
  }
  while (mySerial.available() && lunghezzaMassimaComando > idxInStr)
  {
     stringa[idxInStr++] = mySerial.read();
  }
  Serial.println(comando);
  if (strncmp(comando, "+39xxxxxxxx", 13) == 0)
  {
    ...
  }
  ...
}

Grazie fanpolli,

ovviamente questo: if (strncmp(comando, "+39xxxxxxxx", 13) == 0)

deve diventare questo: if (strncmp(lunghezzaMassimaComando , "+39xxxxxxxx", 13) == 0)

vero???

come non detto non avevo compreso, rileggendo meglio , ho poi terminato il codice in base alla mia esigenza. Diventa questo:

void lettura_sms()
{
  if (mySerial.available())
  {
    idxInStr = 0;                                         //Azzero l'indice dell'array del comando dove scrivere il carattere letto
    memset(stringa, 0, sizeof(stringa)); //Azzero il contenuto del precedente comando
  }
  while (mySerial.available() && lunghezzaMassimaComando > idxInStr)
  {
    stringa[idxInStr++] = mySerial.read();
  }
  strncpy(comando, stringa, 30);                                // copio in comando la prima parte del messaggio ricevuto ossia: +CMT "+39numerotelefono"

  if (strncmp(comando, "+39xxxxxxxxx", 13) == 0) {             // poi controllo se in comando sia presente il numero del mittente autorizzato
    if (strncmp(comando, "ON", 2) == 0) {                       // e inserisco impianto
      stato_imp = true;
    }
    else if (strncmp(comando, "OFF", 3) == 0) {                  // o disinserisco impianto
      stato_imp = false;
    }
  }
  mySerial.print("AT+CMGDA=\"");
  mySerial.println("DEL ALL\"");                                // cancello tutti i messaggi
}

ma il compilatore non compila. errore:

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino:25:17: warning: missing terminating " character [enabled by default]

String number = "xxxxxxxxxx"

^

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino:25:1: error: missing terminating " character

String number = "xxxxxxxx"

^

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino:28:17: warning: missing terminating " character [enabled by default]

";

^

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino:28:17: error: missing terminating " character

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino:19:37: error: redefinition of ‘char stringa [100]’

char stringa[lunghezzaMassimaComando];

^

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino:14:6: error: ‘char stringa [100]’ previously declared here

char stringa[100];

^

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino:32:1: error: expected primary-expression before ‘void’

void setup()

^

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino: In function ‘void antifurto()’:

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino:76:23: warning: suggest parentheses around assignment used as truth value [-Wparentheses]

if (stato_imp = true) { // flag impianto inserito

^

/tmp/698831843/antifurto_gsm_copy/antifurto_gsm_copy.ino:90:29: warning: suggest parentheses around assignment used as truth value [-Wparentheses]

else if (stato_imp = false) { // flag impianto disinserito

^

exit status 1

No, errore anche mio di copia incolla senza attenzione :slight_smile:

if (strncmp(stringa, "+39xxxxxxxx", 13) == 0)

stringa e l'array di char che contiene il comando ricevuto
lunghezzaMassimaComando è una costante che determina la lunghezza massima dell'array che conterrà i comandi, potrebbe essere anche

#define lunghezzaMassimaComando 100

quando si definisce qualcosa che ha, ad esempio, una lunghezza come un array e che nel programma occorre determinare la sua dimensione è bene usare una define o una costante, in modo tale che se un domani tu ti troverai a dover aumentare o diminuire tale dimensione ti basterà modificare il valore della costante (o define) e tutti i controlli di sicurezza nel programma continueranno a funzionare.
Nell'esempio che ho fatto viene letta la seriale finché ci sono caratteri e i caratteri letti sono un numero inferiore alla dimensione dell'array del comando, senza questo controllo se ti arriva un comando più lungo di 99 caratteri sofreresti la dimnesione dell'array scrivendo in aree di memoria che potenzialmente ospitano altre variabile con conseguenze inpredicibile e quasi sempre catastrofiche

Una sequela di errori :slight_smile:

  • Hai messo il tuo numero in chiaro nel post io editerei e rimetterei le xxxx
  • L'errore dipende dal fatto che non hai chiuso la stringa con i doppi apici
  • L'errore più grande è che continui a usare la classe String, converti tutto ciò che è definito come String in array di caratteri

questa istruzione è innutile

strncpy(comando, stringa, 30);

i test successivi li puoi fare direttamente su stringa anziché comando oppure elimini completamente stringa e sostituisci il tutto usando direttamente l'array comando

Adesso il compilatore non da errori:

void lettura_sms()
{
  if (mySerial.available())
  {
    idxInStr = 0;                                         //Azzero l'indice dell'array del comando dove scrivere il carattere letto
    memset(stringa, 0, sizeof(stringa));                  //Azzero il contenuto del precedente comando
  }
  while (mySerial.available() && lunghezzaMassimaComando > idxInStr)
  {
    stringa[idxInStr++] = mySerial.read();
  }
  //strncpy(comando, stringa, 30);                                // copio in comando la prima parte del messaggio ricevuto ossia: +CMT "+39numerotelefono"
  if (strncmp(stringa, "+39xxxxxxxx", 13) == 0)   {
    // poi controllo se in comando sia presente il numero del mittente autorizzato
    if (strncmp(stringa, "ON", 2) == 0) {                       // e inserisco impianto
      stato_imp = true;
    }
    else if (strncmp(stringa, "OFF", 3) == 0) {                  // o disinserisco impianto
      stato_imp = false;
    }
  }
  mySerial.print("AT+CMGDA=\"");
  mySerial.println("DEL ALL\"");                                // cancello tutti i messaggi
}

che ne pensi? Funzionerà? Sull'hardware potò testare solo stasera...

Non ho mai giocato con il GSM per cui non so dirti come ti arriverà il messaggio ma stando così il codice non funzionerà.
Supponendo che il messaggio sia +CMT +39XXXXXXXXXX ON
se tu verifichi con

strncmp(stringa, "+39xxxxxxxx", 13)

i primi 13 caratteri fallirà poichè +CMT +39XXXXX (primi 13 caratteri) non sarà mai uguale a +39XXXXXXXXXX.
Devi capire come ti arriva il messaggio, ovvero se le varie parti sono in posizione fissa, ad esempio i primi 4 caratteri sono sempre +CMT poi dal carattere 6 al 19 c'è il numero di telefono poi dal 21 all'Nesimo c'è il comando allora puoi pensare di copiare le varie parti da confrontare in un array di char apposito, per copiare il numero di telefono ad esempio puoi fare

strncpy(daConfrontare, stringa+N, 13)

In questo modo copi dal messaggio ricevuto all'array daConfrontare 13 caratteri partendo dal N esimo (escludendo quindi la parte +CMT), poi

strncmp(daConfrontare, "+39xxxxxxxx", 13) == 0

Se il numero è uguale allora copierai il comando

memset(daConfrontare, 0, sizeof(daConfrontare)); //Azzero il contenuto per sicurezza, copiando stringhe più piccole avrei risultati inaspettati
strncpy(daConfrontare, stringa+N, strlen(stringa)-N+1)

In questo modo copierai il comando ricevuto partendo dall'Nesima posizione (ovvero dopo il numero di telefono) fino alla fine del messaggio ricevuto
e poi

strncmp(daConfrontare, "ON", 2)

e via di seguito.
Se invece il messaggio ricevuto non ha posizioni fisse da cui prelevare le varie parti del messaggio allora dovrai individuare un carattere di separazione (Es. lo spazio) e usare la funzione strtok() per suddividere il messaggio originale nelle varie parti da confrontare

capisco....grazie. Infatti per un altro progetto con comunicazione telegram - esp8266 avevo già fatto una cosa simile. Ci riprovo:

void lettura_sms()
{
  if (mySerial.available())
  {
    idxInStr = 0;                                         //Azzero l'indice dell'array del comando dove scrivere il carattere letto
    memset(stringa, 0, sizeof(stringa));                  //Azzero il contenuto del precedente comando
  }
  while (mySerial.available() && lunghezzaMassimaComando > idxInStr) {
    stringa[idxInStr++] = mySerial.read();
  }

  if (strncmp(stringa, "+CMT: "+39xxxxxxxxx"", 20) == 0)   {           // controllo se nella stringa sia presente il numero del mittente autorizzato
    strncpy(tmp, stringa + 2, 3);
    if (strncmp(tmp, "ON", 2) == 0) {                       // e inserisco impianto
      stato_imp = true;
    }
    else if (strncmp(tmp, "OFF", 3) == 0) {                  // o disinserisco impianto
      stato_imp = false;
    }
  }
  mySerial.print("AT+CMGDA=\"");
  mySerial.println("DEL ALL\"");                                // cancello tutti i messaggi
}

che ne pensi?

Però qui so già di avere un problema:

"+CMT: "+39xxxxxxxxxx""

purtroppo nella riga +CMT si sono delle virgolette e quindi la riga di sopra presenta errori, come posso rimediare?

ho seguito le tue indicazioni:

void lettura_sms()
{
  if (mySerial.available())
  {
    idxInStr = 0;                                         //Azzero l'indice dell'array del comando dove scrivere il carattere letto
    memset(stringa, 0, sizeof(stringa));                  //Azzero il contenuto del precedente comando
  }
  while (mySerial.available() && lunghezzaMassimaComando > idxInStr) {
    stringa[idxInStr++] = mySerial.read();                // adesso ho tutto il messaggio ricevuto dal SIM800L nella mia stringa 
    strncpy(tmp, stringa +7, 13);                         // ne copio un pezzo in tmp per il primo confronto con il numero del mittente
  }

  if (strncmp(tmp, "+39xxxxxxxxx", 13) == 0)   {         // adesso posso controllare che se nella stringa tmp sia presente il numero del mittente autorizzato
    memset(tmp, 0, sizeof(tmp));                          // adesso resetto tmp
    strncpy(tmp, stringa+23, strlen(stringa)+3);         // e ci ricopio sopra il testo del comando da usare per attivare o disattivare l'impianto
    if (strncmp(tmp, "ON", 2) == 0) {                       // lo conftronto e se c'è il match proseguo
      stato_imp = true;
    }
    else if (strncmp(tmp, "OFF", 3) == 0) {                  // o disinserisco impianto
      stato_imp = false;
    }
  }
  mySerial.print("AT+CMGDA=\"");
  mySerial.println("DEL ALL\"");                                // cancello tutti i messaggi
}

Mi sembra ok tranne che

strncpy(tmp, stringa +7, 13);

devi metterlo fuori dal while

GRAZIE 1000!