sobota, 2 kwietnia 2011

ARDUINO i GSM czyli sterowanie za pomocą SMS-ów.


Autor: Piotr Rzeszut (Piotrva)
Redakcja: Dondu

Artykuł jest częścią cyklu: Kurs Arduino

Niektórzy z Was pewnie znają różne systemy sterowane za pomocą SMS'ów lub wysyłające tą drogą informacje o działaniu jakiegoś systemu.

Przykładem mogą być systemy automatyki przemysłowej (widziałem tego typu rozwiązania np. w małych elektrowniach wodnych, gdzie informują o awariach) czy centrale alarmowe, które mogą wysłać nam wiadomość o włamaniu, wykryciu dymu itp.

Dlaczego więc sami nie mielibyśmy zbudować jakiegoś urządzenia wyposażonego w taką funkcjonalność?



Moduł GSM, czyli telefon bez klawiatury i wyświetlacza...

Na początek rzecz jasna musimy mieć odpowiedni moduł. Tu do wyboru mamy na prawdę wiele układów, na przykład SIM900, czy SIEMENS TC35, który otrzymałem do testów z:




Dokumentacja  SIEMENS TC35: SIEMENS-datasheet-gsm-tc35.pdf

Moduły te komunikują się z innymi układami za pomocą interfejsu UART i komend AT. Zasadniczo moduły GSM to telefony komórkowe, które nie posiadają wyświetlacza i klawiatury a zamiast tego komunikują się z użytkownikiem za pomocą wspomnianego interfejsu.

Jedną przypadłością tych modułów jest spory pobór prądu podczas łączenia się z siecią (pik do 2A) - dlatego mimo dostępności na naszym Arduino napięcia 3,3V dla bezproblemowej pracy musimy zasilić moduł z zewnątrz. Płytka z modułem TC35 posiada złącze do podłączenia zasilania o napięciu 7,5-12V. Ja osobiście testowałem moduł z zasilaczem 9V. Koniecznie musimy pamiętać o połączeniu mas modułu i Arduino (łączymy kabelkiem pin GND z pinem GND w Arduino).

Jeśli już mówimy o połączeniach to do komunikacji z modułem wykorzystamy kolejny raz programowy UART (z biblioteką AltSoftSerial załączoną wraz z kodami), dlatego pin TX modułu podpinamy do pinu 9, a RX do pinu 8. Domyślnie moduł komunikuje się z prędkością 9600bps.

Najpierw wgraj nowy program, a potem podłącz moduł - poprzednio wgrany do Arduino program mógł wykorzystywać te piny do innych czynności co może prowadzić do uszkodzenia sprzętu.



Moduł GSM Siemens TC35


Komendy AT w telegraficznym skrócie.

Teraz pora poznać kilka najpotrzebniejszych komend AT aby sterować naszym modułem GSM. Każdą komendę powinniśmy zakończyć znakiem powrotu karetki ('\r'). Umożliwia to nam np. program Br@y terminalhttps://sites.google.com/site/terminalbpp/

Jego ustawienia przedstawia poniższy obrazek. Zwróćmy uwagę na zaznaczony kwadrat +CR, który powoduje wysłanie po każdej komendzie znaku powrotu karetki.

Polecenia wpisujemy w białym polu obok tego kwadratu, o ile nie zaznaczono inaczej i zatwierdzamy klawiszem enter.

Aby ręcznie wysyłać komendy do modułu możemy wgrać program Test z przykładów dla biblioteki AltSoftSerial. Biblioteka w wersji 1.2: AltSoftSerial-v1.2.zip (kopia)


Ustawienia terminala do testów modułu.


Oczywiście każdy moduł (jak i niektóre telefony - tak, tak, niektóre telefony też można po zastosowaniu odpowiedniego kabla lub jeśli są dostępne odpowiednie sterowniki USB wykorzystać jako moduły GSM obsługiwane komendami AT, ale to bardzo rozległy temat, gdyż co telefon to inna sytuacja) ma swoją listę komend AT i czasem się one różnią. Przykładem może być to że np. moduły G24 od Motoroli wymagają przy nadawaniu ujęcia numeru w cudzysłowy a nasz TC35 musi mieć podany numer bez tych znaków.

Poniżej przedstawiam listę przydatnych przy odbieraniu i wysyłaniu SMS'ów komend według standardów modułu TC35. Bardziej zainteresowanych odsyłam do podręcznika komend AT tego modułu, który jest zaledwie ponad 200-tu stronicową książeczką :-)

Zestaw komend AT: at_commands_tc35.pdf

1. AT

Komenda ta pozwala na sprawdzenie połączenia z modułem - powinniśmy w odpowiedzi otrzymać:
OK


2. AT+CPIN?

Komenda sprawdza czy została przeprowadzona weryfikacja PIN. Powinniśmy otrzymać odpowiedź:
+CPIN: READY
OK


3. AT+CPIN=xxxx

Komenda służy do wprowadzania kodu pin. Polecam jednak wyłączyć ochronę kodem PIN wkładając kartę do telefonu i wybierając odpowiednie opcje. Wiele kart SIM (w szczególności startery na kartę) ma domyślnie wyłączoną ochronę PIN.

4. ATE0 i ATE1

Komenda ATE0 wyłącza echo, czyli wysyłanie przez moduł z powrotem każdej wysłanej komendy.
Po poprawnym wykonaniu otrzymamy odpowiedź:
OK
ATE1 włącza echo

5. AT+CMGF=1

Komenda włącza tryb tekstowy wysyłania i odbierania wiadomości. Drugi dostępny tryb to PDU ale jest on bardziej skomplikowany w obsłudze.
Zwraca odpowiedź:
OK


6. AT+CMGS=+48xxxxxxxxx

Komenda wysyła SMS pod podany numer telefonu.
Po wysłaniu tej komendy pojawia się znak "zachęty": >
Teraz musimy wpisać wiadomość do wysłania (160 znaków max )
A następnie wysyłamy znak CTRL+Z (kod 26 w zapisie dziesiętnym w ASCII) aby wysłać wiadomość.
Korzystając z Br@y terminal po wysłaniu AT+CMGS=(numer) klikamy w szarym polu (czerwona strzałka), tam piszemy tekst wiadomości i wciskamy CTRL+Z - tekst zniknie z szarego okienka i możemy czekać na odpowiedź +CMGS opisaną poniżej.

UWAGA! Nie możemy wysłać żadnego znaku po CTRL+Z do momentu otrzymania odpowiedzi - inaczej wiadomość nie zostanie wysłana (xx - numer identyfikacyjny wiadomości):
+CMGS: xx
OK


7. AT+CMGL i AT+CMGL=ALL

Wyświetla listę wszystkich (lub tylko nieodczytanych przy zastosowaniu wersji bez =ALL) wiadomości z pamięci urządzenia.

Odpowiedź to ramki postaci:
+CMGL: 1,"REC UNREAD","+480000000005",,"14/07/26,21:43:15+08"
Test
1 - numer wiadomości w pmięci
REC UNREAD lub REC READ - oznacza czy wiadomość była już wyświetlana czy nie
+480000000005 - numer telefonu nadawcy
14/07/26,21:43:15+08 - data i czas
Test - treść wiadomości - może mieć wiele linijek
Po wyświetlaniu wszystkich wiadomości z pamięci (może być ich wiele) otrzymujemy odpowiedź:
OK


8. AT+CMGR=xx

Odczytuje z pamięci wiadomość o numerze xx. Otrzymujemy w odpowiedzi:
+CMGR: "REC READ","+480000000005",,"14/07/26,21:43:15+08"
Test

OK
Znaczenie pól jak poprzednio

9. AT+CMGD=xx

Usuwa z pamięci wiadomość o numerze xx
Odpowiedź:
OK


10. AT+CNMI=1,1,0,0,1

Ustawia powiadamianie o nadchodzącym SMS'ie - nie wnikajmy w szczegóły...
Odpowiedź:
OK
Każdy nadchodzący SMS zostanie zakomunikowany ramką:
+CMTI: ”SM”,xx
SM - typ pamięci w której przechowywany jest SMS; SM oznacza kartę SIM
xx - numer wiadomości

11. AT+CSQ

Sprawdza jakość sygnału
Odpowiedź:
+CSQ: 13,99

OK
13 - moc sygnału - liczba z zakresu 0-31 (im wyższa tym lepszy zasięg) lub 99 gdy nie można określić
99 - bitowa stopa błędów - nas ta wartość nie interesuje (liczba 0-7 lub 99 gdy nie można określić)

Skrócone "procedury" obsługi telefonu przez komendy AT

Teraz gdy znamy potrzebne nam komendy możemy powiedzieć jaką sekwencję czynności należy wykonać, by odebrać i nadać wiadomość.

Aktywacja PIN

Oczywiście na początek musimy przeprowadzić aktywację kodu PIN. Dokonujemy tego komendą AT+CPIN=..., jednak osobiście polecam wyłączenie ochrony PIN i jedynie sprawdzenie aktywacji karty komendą AT+CPIN?

Nadajemy wiadomość

  1. Nadajemy komendę AT+CMGF=1
  2. Wysyłamy SMS komendą AT+CMGS=...
  3. Czekamy na odpowiedź +CMGS... OK

Odbieramy wiadomość - metoda z odpytywaniem modułu

  1. Jeśli nie nadajemy (między krokiem 2 i 3 sekwencji nadawania) wiadomości to co jakiś czas wysyłamy komendę AT+CMGF=1 i potem AT+CMGL lub AT+CMGL=ALL
  2. Dekodujemy odpowiedzi typu +CMGL...
  3. Aż do otrzymania OK
  4. Usuwamy wiadomości z pamięci komendą AT+CMGD...

Odbieramy wiadomość - metoda z powiadamianiem o wiadomości przez moduł

  1. Nadajemy AT+CMGF=1 i potem AT+CNMI=1,1,0,0,1
  2. Oczekujemy na ramkę +CMTI:...
  3. Odczytujemy wiadomość komendą AT+CMGR...
  4. Usuwamy wiadomość z pamięci komendą AT+CMGD...

Na samym początku możemy przećwiczyć te komendy ręcznie korzystając ze wspomnianego zestawu oprogramowania dla Arduino i na PC.

Automatyzacja, czyli niech Arduino wysyła SMS'y

Poniżej przedstawiam dwa programy, realizujące z różnymi metodami odbierania SMS'ów (z odpytywaniem i sygnalizacją) bardzo proste zadanie:

Po wysłaniu z określonego numeru komendy ON lub OFF SMS'em ma zostać  odpowiednio zapalona lub zgaszona diodka podpięta do pinu 13 płytki. Po wysłaniu SMS'a o treści ? moduł ma odpowiedzieć na numer podany w kodzie (w innym miejscu niż na początku - druga definicja!!!) informacją o stanie diody.
Dodatkowo na ekranie w terminalu szeregowym Arduino możemy obserwować jak pracuje program i w jakich etapach dekoduje wiadomości.


Wymiana wiadomości SMS z urządzeniem

Myślę, że nie potrzeba dodatkowych komentarzy poza powyższym tekstem i komentarzami w kodzie - mam nadzieję, ze zainspiruje to Was do stworzenia własnych systemów z modułami GSM, czemu by nie stworzyć centralki alarmowej czy zdalnego sterowania urządzeniami w domu?

Z powodu różnych błędów oprogramowania Arduino moduł może wysyłać duże ilości wiadomości SMS, co może narazić nas na koszty ze strony Operatora.

W związku z tym Autor programów i tego poradnika nie ponosi żadnej odpowiedzialności za wszelkie straty wynikłe z nieprawidłowego działania, zarówno w formie opłat naliczonych przez Operatora jak i innych strat spowodowanych przez nieprawidłowe funkcjonowanie urządzeń budowanych z wykorzystaniem tego poradnika.

gsm_sterowanie_online.c

/*
 * GSM TC35 SIMPLE DEMO
 * Autor: Piotr Rzeszut (http://piotr94.net21.pl)
 * Data: 26.07.2014
 * Artykuł pobrany z blogu: http://mikrokontrolery.blogspot.com
*/
#include <AltSoftSerial.h>

// AltGSM always uses these pins:
//
// Board          Transmit  Receive   PWM Unusable
// -----          --------  -------   ------------
// Arduino Uno        9         8         10
// Arduino Leonardo   5        13       (none)
// Arduino Mega      46        48       44, 45

AltSoftSerial GSM;

void setup() {
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor to open
  Serial.println("GSM testing");
  GSM.begin(9600);
  
  //GSM.print("ATE1\r");//ATE1 powoduje, że wszystko co wyślemy DO modułu będzie przez niego wysyłane do nas (echo)
  GSM.print("ATE0\r");//ATE0 wyłącza powyższą funkcjonalność
  delay(550);//czekamy nieco
  pinMode(13,OUTPUT);//ustawiamy pin dla diody led jako wyjście
}

//definiujemy zmienne pomocne przy odbiorze danych
char buffer[256];
byte field_begin[14];
int num=0;

//oraz zmienne kontrolujące odbiór danych, stałe do porównywania
const char SMS_REC[]="+CMGL: ";
const char OK[]="OK";
const char ERROR[]="ERROR";
const char accepted_nr[]="\"+48000000005\"";
byte nr_correct=0;

enum states{IDLE,NEXT_TXT};
states stan=IDLE;

byte msg_id=0;

byte ok;
int j;
byte send_resp,wait_for_sending;

unsigned long old_millis=millis();

//pętla główna programu
void loop() {
  char c;//zmienna pomocznicza
  //ten blok pozwala nam przesyłać komendy do telefonu z terminala komputera
  /*if(Serial.available()){
    c=Serial.read();
    GSM.print(c);
  }*/
  
  //ten blok co 5 sekund ustawia format wiadomości na tekstowy a następnie sprawdza listę wiadomości odebranych przez moduł
  //i romimy to tylko wtedy gdy nie czekamy na nadanie wiadomości SMS
  if(millis()-old_millis>5000&&wait_for_sending==0){
    GSM.print("AT+CMGF=1\r");//ustaw format wiadomości na tesktowy
    delay(550);
    GSM.print("AT+CMGL=ALL\r");//wyślij zapytanie o wyświetlenie wszystkich wiadomości 
    old_millis=millis();
  }
  
  if (GSM.available()) {//jeśli czeka na odbiór jakiś znak
    c = GSM.read();//to odczytujemy go
    //Serial.print(c);//a tu możemy do celów debugowania wyświetlić na ekranie to co wysyła nam moduł - nie tylko nasze komunikaty
    if(c=='\n'){//jeśli to znak zakończenia linii (koniec ramki) to przystępujemy do jej dekodowania
      switch(stan){
        case IDLE://normalny stan - sprawdzamy ramki pod kątem tego czego dotyczą
        //-----------------------------------------------------------------------------------------------------------------------------------
        //SMS przychodzący po poleceniu AT+CMGL... - dekodujemy początek wpisu
        //-----------------------------------------------------------------------------------------------------------------------------------
        ok=1;//zakładamy, ze to interesująca nas ramka
        for(int i=0;i<6;i++){//w pętli porównujemy pierwsze znaki ramki ze wzorcem informacji o sms'ie
          if(buffer[i]!=SMS_REC[i])ok=0;//jeśli jakiś znak się różni to to nie jest interesująca nas ramka
        }
        j=0;
        if(ok){//jeśli mamy interesującą nas ramkę to możemy ją dekodować
          for(int i=0;i<num;i++){//przeglądamy ją znak po znaku
            if(buffer[i]==','){//jeśli trafiliśmy na przecinek
              buffer[i]=0;//to zastępujemy go znakiem 0 - kończy on ciąg znaków
              field_begin[j++]=i+1;//kolejny znak po przecinku to początek nowego pola - zapisujemy jego pozycję
              if(j>5){//maksymalnie w tej ramce moze być 5 pól
                j=0;
                ok=0;
              }
            }else if(buffer[i]=='\r'||buffer[i]=='\n')buffer[i]=0;//znaki kończące ramkę też zastąpimy znakami zerowymi
          }
          
        }
        if(ok){
          //tu odczytuję z tekstu ID wiadomości
          msg_id=atoi(&buffer[7]);
          Serial.print("Wiadomosc nr: ");
          Serial.println(msg_id);
          Serial.print("Od numeru: ");
          //następnie z pól odczytujemy numer telefonu wraz z "" w których jest zawarty
          Serial.println(&buffer[field_begin[1]]);
          //tu porównujemy ten ciąg z zapisanym w stałej na początku programu - nie chcemy by reklamy czy inne osoby coś mogły zrobić
          if(strcmp(&buffer[field_begin[1]],accepted_nr)==0){
            nr_correct=1;
            Serial.println("NUMER ZGODNY");
          }else{
            nr_correct=0;
            Serial.println("NUMER NIEOBSLUGIWANY");
          }
          stan=NEXT_TXT;//po tej ramce na pewno otrzymamy tekst sms'a, więc informujemy program, że kolejna linijka musi być dekodowana jako tekst a nie komenda
        }
        
        //-----------------------------------------------------------------------------------------------------------------------------------
        //OK - komunikat poprawnego zakońvczenia wykonywania komendy
        //-----------------------------------------------------------------------------------------------------------------------------------
        ok=1;//zakładamy, ze to interesująca nas ramka
        for(int i=0;i<2;i++){//w pętli porównujemy pierwsze znaki ramki ze wzorcem OK
          if(buffer[i]!=OK[i])ok=0;//jeśli jakiś znak się różni to to nie jest interesująca nas ramka
        }
        if(ok){
          wait_for_sending=0;//kasujemy blokadę wysyłania na czas nadawania sms'a
          //jeśli odebrano jakąś wiadomość
          if(msg_id!=0){
            //to ją usuniemy
            GSM.print("AT+CMGD=");
            GSM.print(msg_id);
            GSM.print("\r");
            msg_id=0;
          }else if(send_resp){//jeśli wiadomość zostałą usunięta i mamy nadać odpowiedź 
            send_resp=0;
            GSM.print("AT+CMGS=+48000000005\r");//to wysyłamy ją na ten numer
            delay(550);
            GSM.print("Led is ");//tu wpisujemy treść wiadomości (do 160 znaków)
            if(digitalRead(13)){
              GSM.print("ENABLED");
            }else{
              GSM.print("DISABLED");
            }
            GSM.print((char)26);//a następnie zatwierdzamy wysłanie 
            Serial.println("Info wyslane");
            wait_for_sending=1;//musimy zadbać, by zanim telefon potwierdzi nadanie wiadomości nie nadać nic do niego
          }
        }
        
        //-----------------------------------------------------------------------------------------------------------------------------------
        //ERROR
        //-----------------------------------------------------------------------------------------------------------------------------------
        ok=1;//zakładamy, ze to interesująca nas ramka
        for(int i=0;i<5;i++){//w pętli porównujemy pierwsze znaki ramki ze wzorcem OK
          if(buffer[i]!=ERROR[i])ok=0;//jeśli jakiś znak się różni to to nie jest interesująca nas ramka
        }
        if(ok){
          wait_for_sending=0;//kasujemy blokadę wysyłania na czas nadawania sms'a
          Serial.println("Huston, mamy problem...");
        }
        break;
        //-----------------------------------------------------------------------------------------------------------------------------------
        //Tekst wiadomości SMS
        //-----------------------------------------------------------------------------------------------------------------------------------
        case NEXT_TXT:
          for(int i=0;i<num;i++){//przeglądamy ją znak po znaku
            if(buffer[i]=='\r'||buffer[i]=='\n')buffer[i]=0;//znaki kończące ramkę też zastąpimy znakami zerowymi
          }
          Serial.print("Wiadomosc: ");
          Serial.println(buffer);
          //sprawdzamy czy to któreś z interesujących nas poleceń - możemy tu dodać własne polecenie, przesyłanie np. nastawy dla PWM itp.
          if(nr_correct){//ale najpierw sprawdzamy czy numer był naszym numerem - nie chcemy, zeby irytująca reklama włączyła nam oświetlenie w domu :D
            if(strcmp(buffer,"ON")==0){
              digitalWrite(13,HIGH);
            }
            if(strcmp(buffer,"OFF")==0){
              digitalWrite(13,LOW);
            }
            if(strcmp(buffer,"?")==0){
              send_resp=1;//poinformujemy, aby po zakończeniu nadawania wysłać informację do użytkownika
            }
          }
          Serial.println();
          stan=IDLE;
        break;
      }
      
      
      num=0;
    }else{//jeśli to inny znak niż zakończenie ramki to dopisujemy go do bufora
      buffer[num++]=c;
      buffer[num]=0;//ponadto zawsze kończymy zawartośc bufora znakiem 0 - koniec ciągu znaków
      if(num>255)num=0;//i dodatkowo dbamy o to, żeby nie przepełnić bufora
    }
  }
}

Do pobrania: gsm_sterowanie_online.c (kopia)


gsm_wo_polling_online.c

/*
 * GSM TC35 DEMO
 * Autor: Piotr Rzeszut (http://piotr94.net21.pl)
 * Data: 26.07.2014
 * Artykuł pobrany z blogu: http://mikrokontrolery.blogspot.com
*/
#include <AltSoftSerial.h>

// AltGSM always uses these pins:
//
// Board          Transmit  Receive   PWM Unusable
// -----          --------  -------   ------------
// Arduino Uno        9         8         10
// Arduino Leonardo   5        13       (none)
// Arduino Mega      46        48       44, 45

AltSoftSerial GSM;

void setup() {
  Serial.begin(9600);
  while (!Serial) ; // wait for Arduino Serial Monitor to open
  Serial.println("GSM testing");
  GSM.begin(9600);
  
  //GSM.print("ATE1\r");//ATE1 powoduje, że wszystko co wyślemy DO modułu będzie przez niego wysyłane do nas (echo)
  GSM.print("ATE0\r");//ATE0 wyłącza powyższą funkcjonalność
  delay(550);//czekamy nieco
  GSM.print("AT+CMGF=1\r");//pformat wiadomości tekstowy
  delay(550);//czekamy nieco
  GSM.print("AT+CNMI=1,1,0,0,1\r");//konfigurujemy automatycznie powiadamianie o nadchodzących SMS'ach
  delay(550);//czekamy nieco
  pinMode(13,OUTPUT);//ustawiamy pin dla diody led jako wyjście
}

//definiujemy zmienne pomocne przy odbiorze danych
char buffer[256];
byte field_begin[14];
int num=0;

//oraz zmienne kontrolujące odbiór danych, stałe do porównywania
const char SMS_REC[]="+CMGR:";
const char SMS_CMTI[]="+CMTI:";
const char OK[]="OK";
const char ERROR[]="ERROR";
const char accepted_nr[]="\"+48000000005\"";
byte nr_correct=0;

enum states{IDLE,READ_TXT,NEXT_TXT,OK_WAIT_DELETE};
states stan=IDLE;

byte msg_id=0;

byte ok;
int j;
byte send_resp;

unsigned long old_millis=millis();

//pętla główna programu
void loop() {
  char c;//zmienna pomocznicza
  //ten blok pozwala nam przesyłać komendy do telefonu z terminala komputera
  if(Serial.available()){
    c=Serial.read();
    GSM.print(c);
  }
  
  if (GSM.available()) {//jeśli czeka na odbiór jakiś znak
    c = GSM.read();//to odczytujemy go
    Serial.print(c);//a tu możemy do celów debugowania wyświetlić na ekranie to co wysyła nam moduł - nie tylko nasze komunikaty
    if(c=='\n'){//jeśli to znak zakończenia linii (koniec ramki) to przystępujemy do jej dekodowania
      switch(stan){
        case IDLE://normalny stan - sprawdzamy ramki pod kątem tego czego dotycz
        //-----------------------------------------------------------------------------------------------------------------------------------
        //SMS przychodzący sygnalizowany automatycznie ramką +CMTI... - dekodujemy początek wpisu
        //-----------------------------------------------------------------------------------------------------------------------------------
        ok=1;//zakładamy, ze to interesująca nas ramka
        for(int i=0;i<5;i++){//w pętli porównujemy pierwsze znaki ramki ze wzorcem informacji o sms'ie
          if(buffer[i]!=SMS_CMTI[i])ok=0;//jeśli jakiś znak się różni to to nie jest interesująca nas ramka
        }
        j=0;
        if(ok){//jeśli mamy interesującą nas ramkę to możemy ją dekodować
          for(int i=0;i<num;i++){//przeglądamy ją znak po znaku
            if(buffer[i]==','){//jeśli trafiliśmy na przecinek
              buffer[i]=0;//to zastępujemy go znakiem 0 - kończy on ciąg znaków
              field_begin[j++]=i+1;//kolejny znak po przecinku to początek nowego pola - zapisujemy jego pozycję
              if(j>5){//maksymalnie w tej ramce moze być 5 pól
                j=0;
                ok=0;
              }
            }else if(buffer[i]=='\r'||buffer[i]=='\n')buffer[i]=0;//znaki kończące ramkę też zastąpimy znakami zerowymi
          }
          
        }
        if(ok){
          //tu odczytuję z tekstu ID wiadomości
          msg_id=atoi(&buffer[field_begin[0]]);
          Serial.print("Wiadomosc nr: ");
          Serial.println(msg_id);
          GSM.print("AT+CMGR=");//wysyłam polecenie do odczytania szczegółów i tekstu wiadomości
          GSM.print(msg_id);
          GSM.print("\r");
          stan=READ_TXT;//po tej ramce na pewno otrzymamy tekst sms'a, więc informujemy program, że kolejna linijka musi być dekodowana jako tekst a nie komenda
        }
        break;
        case READ_TXT:
         //-----------------------------------------------------------------------------------------------------------------------------------
        //SMS przychodzący po poleceniu AT+CMGR... - dekodujemy początek wpisu
        //-----------------------------------------------------------------------------------------------------------------------------------
        ok=1;//zakładamy, ze to interesująca nas ramka
        for(int i=0;i<5;i++){//w pętli porównujemy pierwsze znaki ramki ze wzorcem informacji o sms'ie
          if(buffer[i]!=SMS_REC[i])ok=0;//jeśli jakiś znak się różni to to nie jest interesująca nas ramka
        }
        j=0;
        if(ok){//jeśli mamy interesującą nas ramkę to możemy ją dekodować
          for(int i=0;i<num;i++){//przeglądamy ją znak po znaku
            if(buffer[i]==','){//jeśli trafiliśmy na przecinek
              buffer[i]=0;//to zastępujemy go znakiem 0 - kończy on ciąg znaków
              field_begin[j++]=i+1;//kolejny znak po przecinku to początek nowego pola - zapisujemy jego pozycję
              if(j>5){//maksymalnie w tej ramce moze być 5 pól
                j=0;
                ok=0;
              }
            }else if(buffer[i]=='\r'||buffer[i]=='\n')buffer[i]=0;//znaki kończące ramkę też zastąpimy znakami zerowymi
          }
          
        }
        if(ok){
          Serial.print("Wiadomosc od numeru: ");
          //następnie z pól odczytujemy numer telefonu wraz z "" w których jest zawarty
          Serial.println(&buffer[field_begin[0]]);
          //tu porównujemy ten ciąg z zapisanym w stałej na początku programu - nie chcemy by reklamy czy inne osoby coś mogły zrobić
          if(strcmp(&buffer[field_begin[0]],accepted_nr)==0){
            nr_correct=1;
            Serial.println("NUMER ZGODNY");
          }else{
            nr_correct=0;
            Serial.println("NUMER NIEOBSLUGIWANY");
          }
          stan=NEXT_TXT;//po tej ramce na pewno otrzymamy tekst sms'a, więc informujemy program, że kolejna linijka musi być dekodowana jako tekst a nie komenda
        }
        break;
        //-----------------------------------------------------------------------------------------------------------------------------------
        //Tekst wiadomości SMS
        //-----------------------------------------------------------------------------------------------------------------------------------
        case NEXT_TXT:
          for(int i=0;i<num;i++){//przeglądamy ją znak po znaku
            if(buffer[i]=='\r'||buffer[i]=='\n')buffer[i]=0;//znaki kończące ramkę też zastąpimy znakami zerowymi
          }
          Serial.print("Wiadomosc: ");
          Serial.println(buffer);
          //sprawdzamy czy to któreś z interesujących nas poleceń - możemy tu dodać własne polecenie, przesyłanie np. nastawy dla PWM itp.
          if(nr_correct){//ale najpierw sprawdzamy czy numer był naszym numerem - nie chcemy, zeby irytująca reklama włączyła nam oświetlenie w domu :D
            if(strcmp(buffer,"ON")==0){
              digitalWrite(13,HIGH);
            }
            if(strcmp(buffer,"OFF")==0){
              digitalWrite(13,LOW);
            }
            if(strcmp(buffer,"?")==0){
              send_resp=1;//poinformujemy, aby po zakończeniu nadawania wysłać informację do użytkownika
            }
          }
          Serial.println();
          stan=OK_WAIT_DELETE;//następnie po komuniiacie OK usuniemy wiadomosć i ewentualnie wyślemy odpowiedź
        break;
        case OK_WAIT_DELETE:
        //-----------------------------------------------------------------------------------------------------------------------------------
        //OK - komunikat poprawnego zakończenia wykonywania komendy
        //-----------------------------------------------------------------------------------------------------------------------------------
        ok=1;//zakładamy, ze to interesująca nas ramka
        for(int i=0;i<2;i++){//w pętli porównujemy pierwsze znaki ramki ze wzorcem OK
          if(buffer[i]!=OK[i])ok=0;//jeśli jakiś znak się różni to to nie jest interesująca nas ramka
        }
        if(ok){
          //jeśli odebrano jakąś wiadomość
          if(msg_id!=0){
            //to ją usuniemy
            GSM.print("AT+CMGD=");
            GSM.print(msg_id);
            GSM.print("\r");
            msg_id=0;
          }else if(send_resp){//jeśli wiadomość została usunięta i mamy nadać odpowiedź 
            send_resp=0;//to już to zaraz zrobimy
            GSM.print("AT+CMGS=+48000000005\r");//wysyłamy ją na ten numer
            delay(550);
            GSM.print("Led is ");//tu wpisujemy treść wiadomości (do 160 znaków)
            if(digitalRead(13)){
              GSM.print("ENABLED");
            }else{
              GSM.print("DISABLED");
            }
            GSM.print((char)26);//a następnie zatwierdzamy wysłanie 
            Serial.println("Info wyslane");
          }
          stan=IDLE;
          if(send_resp)stan=OK_WAIT_DELETE;//jeśli mamy nadać a zrealizowaliśmy tylko usunięcie wiadomości to musimy powtórtzyć ten etap i wysłać odpowiedź
        }     
        //-----------------------------------------------------------------------------------------------------------------------------------
        //ERROR
        //-----------------------------------------------------------------------------------------------------------------------------------
        ok=1;//zakładamy, ze to interesująca nas ramka
        for(int i=0;i<5;i++){//w pętli porównujemy pierwsze znaki ramki ze wzorcem OK
          if(buffer[i]!=ERROR[i])ok=0;//jeśli jakiś znak się różni to to nie jest interesująca nas ramka
        }
        if(ok){
          Serial.println("Huston, mamy problem...");
          stan=IDLE;
        }
        break;
      }
      
      
      num=0;
    }else{//jeśli to inny znak niż zakończenie ramki to dopisujemy go do bufora
      buffer[num++]=c;
      buffer[num]=0;//ponadto zawsze kończymy zawartośc bufora znakiem 0 - koniec ciągu znaków
      if(num>255)num=0;//i dodatkowo dbamy o to, żeby nie przepełnić bufora
    }
  }
}

Do pobrania: gsm_wo_polling_online.c (kopia)


Cały projekt można pobrać tutaj: gsm_arduino.zip (kopia)

Życzę udanej zabawy!!! :-)


13 komentarzy:

  1. Arduino.
    Czyli nie ma jakichś przeciwskazań, by uruchomić takie sterowanie na AVR z UARTEM, np. jakaś ATmega8 czy 16?

    OdpowiedzUsuń
  2. Absolutnie nie ma, robiłem na ATmega88 z sim900. Zabawa z Uartem jest tak prosta jak obliczenia w Excelu. Tak naprawdę arduino to taka " prostsza biblioteka"dla uC.

    OdpowiedzUsuń
  3. Witam, zakupiłem modem TC35 prosto z chin. Modem podpięty do komputera przez przejściówkę USB-UART oraz zasilony z 9,5V. Niestety w terminalu cisza. Istnieje jakaś metoda na sprawdzenie w inny sposób sprawności modemu ?

    OdpowiedzUsuń
  4. Możesz próbować zadzwonić na numer karty włożonej do modemu, ale cisza w terminalu źle wróży - moze jakiś bład w połączeniach?

    OdpowiedzUsuń
  5. Witam. Super program ale nie rozumiem dwóch rzeczy.(Zaznaczę że nie jestem jakimś zaawansowanym programistą. Raczej hobbysta wiec proszę o wyjaśnienie)
    1. if(buffer[i]!=SMS_CMTI[i]) - tz. nie widzę w programie żeby do buffer[] cokolwiek było przekazywane wiec jak on porównuje jego zawartość? Co się tam znajduje i jak tam dotarło.
    2. for(int i=0;i<num;i++) na początku programu. jak wykonuje pętle sokoro num =0;
    Mógłby ktoś wytłumaczyć to w łatwy i zrozumiały sposób?

    OdpowiedzUsuń
  6. 1. Do zmiennej bufor dane wpisywane są na samym końcu programu (linijki 191. i 216 w pierwszym i drugim programie odpowiednio) - jest tam też komentarz kiedy te dane są dopisywane.
    2. Zmienna num jest inkrementowana (zwiększana o 1) także w okolicach tychże linijek, czyli zapisu do bufora (to nic innego jak zmienna pamiętająca ilość znaków zapisanych w buforze)

    OdpowiedzUsuń
  7. Ten komentarz został usunięty przez autora.

    OdpowiedzUsuń
  8. Witam
    Posiadam płytkę Leonardo. Jaki byście polecali moduł GSM do tego (w rozsądnej cenie) Potrzebuję zrobić powiadomienie (zwarcie pinu do masy wysyła SMS o jakieś treści, 3 lub 4 wyjścia) oraz za pomocą wysłania komendy (SMS) aktywować jkieś wyjście (podanie napięcia). Kupiłem chińczyka z SIM00A (taka biała płytka z pinami), ale za żadne skarby nie mogę tego zflashować do ustawień europejskich. Proszę o jakieś info na adres markp11@wp.pl Z gry dzięki. PS jakiś gotowy program mile widziany (pod arduino oczywiście) ;-)

    OdpowiedzUsuń
  9. Poprawka SIM900A

    OdpowiedzUsuń
    Odpowiedzi
    1. Najlepiej sflashować używając sprzętowego portu COM.

      Usuń
  10. zmiany w gsm_sterowanie_online.c dla Arduino i SIM800L:

    linia 68: z GSM.print("AT+CMGL=ALL\r"); na GSM.print("AT+CMGL=\"ALL\"\r");
    linia 136: x GSM.print("AT+CMGS=+48000000005\r"); na GSM.print("AT+CMGS=\"+48000000005\"\r");

    OdpowiedzUsuń

  11. W tej chwili jest możliwość kupienia na aliexpress air200. Jest to ciekawy moduł gsm bo posiada możliwość wgrania do niego własnego programu. Nie potrzebujemy wówczas dodatkowego mikrokontrolera. Air200 ma wyprowadzone piny gipo, które można oprogramować. Air200t można obsługiwać za pomocą poleceń AT. Natomiast wersję air200 za pomocą oprogramowania luat.

    [img]https://i.imgur.com/dvmBJdo.png[/img]

    OdpowiedzUsuń
  12. Jak już wspomniano o dostępnych modułach GSM, to są:

    Air200 cena około $4
    SIM800L $3,4
    M590 $1,24

    Ceny różne i dostępne funkcje różne. Do sterowania przez SMS wszystkie się nadają.

    OdpowiedzUsuń