Zapraszam do zapoznania się z moim projektem sterowania przekaźnikiem przy użyciu Arduino oraz modemu SIM800L.
Urządzenie powstało, ponieważ chciałem mieć możliwość otwierania bramy na osiedlu, bez konieczności używania pilota.
Idea była taka, żeby wyzwolić przekaźnik znajdujący się w mieszkaniu, który zwierając odpowiedni styk w domofonie, otworzy bramę.
Założenia:
- urządzenie powinno obsługiwać tylko wskazane numery
- numery nie powinny być hardkodowane w kodzie programu
- urządzenie powinno móc pracować bez zasilacza
- urządzenie powinno oszczędzać baterię
- urządzenie powinno w pełni obsługiwać przychodzące wiadomości SMS
- urządzenie powinno umożliwiać zdalne uruchomienie komend AT
- urządzenie powinno umożliwiać zdalne wywołanie kodów USSD
Jak to działa?
Jest wskazane aby przy pierwszym użyciu, na karcie SIM nie znajdowały się kontakty. Wynika to z faktu, że numer telefonu kontaktu na pierwszej pozycji karty SIM, jest traktowany jako numer zarządzający. Tylko ten konkretny numer będzie mógł wykonywać operacje za pomocą komend SMS.
Jak sprawdzić kontakty na karcie SIM?
Poniżej znajduje się kod prostego szkicu, do komunikacji Arduino via Serial port z modemem podłączonym do pinów 11/12
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#include <SoftwareSerial.h> SoftwareSerial gsm(11, 12); String atCommand; void setup() { Serial.begin(9600); while (!Serial) {} gsm.begin(9600); while (!gsm) {} } void loop() { if (gsm.available()) { Serial.write(gsm.read()); } while (Serial.available()) { delay(10); if (Serial.available() > 0) { char c = Serial.read(); atCommand += c; } } if (atCommand.length() > 0) { gsm.println(atCommand); atCommand = ""; } } |
Po wgraniu szkicu do Arduino, należy w Serial Monitorze wywołać komendę listującą wszystkie kontakty karty SIM
AT+CPBR=1,250
1 |
AT+CPBR=1,250 |
Jeżeli na pierwszej pozycji mamy jakiś wpis, to kasujemy go przez wywołanie
AT+CPBW=1
1 |
AT+CPBW=1 |
Wracamy do kodu głównego.
Szkic wykorzystuje zmodyfikowaną przez mnie bibliotekę GSM-GPRS-GPS-Shield-GSMSHIELD którą należy pobrać i dodać do IDE
Modyfikacja umożliwia m.in. komunikację z modułem SIM800L, czego nie oferował oryginał
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
#include "SIM900.h" #include <avr/interrupt.h> #include <avr/power.h> #include <avr/sleep.h> #include <EEPROM.h> #include "sms.h" #include "call.h" CallGSM call; SMSGSM sms; const int RELAY1 = A0; const int RELAY2 = A1; const int callsAddr = 0; const int openingAddr = 1; //I`m counting calls and relay opening //Liczę przychodzące rozmowy oraz uruchomienie przekaźnika int calls = 1; int opening = 1; char spos; char number[13]; char sim_number[13]; char reply[210]; char stat; char message[159]; //Comment to disable debug with Serial. //Zakomentowanie wyłączy konsolę Serial #define LOCAL_DEBUG //Comment line below to disable sms sent (when testing/debuging) //Zakomentowanie wyłączy wysyłanie wiadomości SMS np podczas debugowania #define SMS_ON |
Jak widać powyżej, definiujemy dwa przekaźniki RELAY1, RELAY2 sterowane przez wyjścia A1, A2. callsAddroraz openingAddr są adresami Eprom, gdzie przechowuję wartości calls oraz opening, które zliczają ile połączeń odebrał modem oraz ile razy został uruchomiony przekaźnik (otwarta brama).
W setupie nie dzieje się nic szczególnego, włączamy rozszerzone opisy błędów i sprawdzamy czy na karcie znajdują się jakieś wiadomości SMS. To na wypadek, gdyby podczas wyłączenia modemu, lub przy niewgranym szkicu, przyszły do nas nowe wiadomości. Normalnie sprawdzenie wiadomości następuje po wybudzeniu Arduino przez modem.
Pętla
Skoro jesteśmy w pętli, to znaczy że modem uruchomił Arduino. Możliwości są dwie:
- połączenie głosowe
- wiadomość SMS
Modem wybudza Arduino przez podanie sygnału z pinu RING w modemie na pin D1. attachInterrupt(1, pinInterrupt, LOW)
Sprawdzamy to za pomocą call.CallStatus(). Jeżeli zwraca CALL_INCOM_VOICE, to wybudziło połączenie głosowe . W przeciwnym razie – wiadomość SMS.
Przypadek 1 połączenie głosowe
Warunek (call.CallStatusWithAuth(number, 1, 100) == CALL_INCOM_VOICE_AUTH) sprawdza czy dzwoniący numer numberznajduje się na karcie SIM w przedziale 1-100.
Jeżeli tak, to “podnoszę słuchawkę” czekam po czym “rozłączam się” i wyzwalam przekaźnik. Czemu tak? Oczywiście można wysłać kod zajętości ale wtedy osoba dzwoniąca trafi na pocztę lub wysłucha komunikat o zajętości – po czym będzie musiała się rozłączyć a do nas (modem) przyjdzie SMS o próbie połączenia. Odebranie i szybkie rozłączenie załatwia problem w niecałe 2 sekundy.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
void loop() { byte b = call.CallStatus(); if (b == CALL_INCOM_VOICE) { #ifdef LOCAL_DEBUG Serial.print(F("> Incomming phone call from ")); #endif if (call.CallStatusWithAuth(number, 1, 100) == CALL_INCOM_VOICE_AUTH) { #ifdef LOCAL_DEBUG Serial.println(F(" Allowed <")); #endif //Pick up, wait a second, hang up and close/open relay, increase opening counter //Odbierz rozmowę, poczekaj chwilę, rozłącz rozmowę i zamknij/otwórz przekaźnik, inkrementuj licznik otwarć call.PickUp(); delay(100); call.HangUp(); digitalWrite(RELAY1, LOW); delay(1000); digitalWrite(RELAY1, HIGH); opening = EEPROM.read(openingAddr) + 1; EEPROM.update(openingAddr, opening); } else { #ifdef LOCAL_DEBUG Serial.println(F(" Forbidden <")); #endif call.PickUp(); delay(100); call.HangUp(); } calls = EEPROM.read(callsAddr) + 1; EEPROM.update(callsAddr, calls); } else { checkSMS(); } }; |
Po zakończeniu rozmowy wracamy do pętli i wywoływana jest procedura checkSMS()
checkSMS()
Sprawdzamy czy na karcie są jakiekolwiek SMSy
1 |
spos = sms.IsSMSPresent(SMS_ALL); |
Sprawdzamy czy jest zdefiniowany numer Administratora
1 |
(1 == gsm.GetPhoneNumber(1, number)) |
Sprawdzamy czy nadawcą wiadomości jest Administrator
1 |
sms.GetAuthorizedSMS((int)spos, number, 13, message, 160, 1, 1); |
Rozdzielamy wiadomość SMS na słowa rozdzielone średnikiem
1 |
char delimiters[] = ";"; |
Lista zdefiniowanych keywordów
1 2 3 4 5 6 7 8 |
C;A;xxxxxxxxx;John Doe;N - will add (if not exist) John Doe contact and send him a message C;A;xxxxxxxxx;John Doe - will add (if not exist) John Doe contact C;D;xxxxxxxxx - will delete contact with number xxxxxxxxx (if exist) C;D;A - will delete ALL contacts (Administrator too) C;F;xxxxxxxxx - will find if number xxxxxxxxx exist on phonebook A;A;AT+CBC - will execute AT command (e.g. AT+CBC) and send modem answer A;U;someCode - will execute USSD code A;I - will send replay message with some details |
- Dodawanie kontaktu – komenda np C;A;123456789;Janek Michalczewski;N spowoduje sprawdzenie czy podany numer już nie istnieje na karcie SIM, jeżeli nie – to dodanie na pierwszej wolnej pozycji. Ponieważ numer z przykładu ma tylko 9 cyfr, dodany zostanie do niego prefix 48. Poza tym nazwa zostanie przycięta do 14 znaków, co wynika z ograniczeń karty SIM. Litera N na końcu jest opcjonalna, jeżeli zostanie użyta, to po dodaniu do książki, na numer rejestrowanego kontaktu, zostanie wysłany SMS z potwierdzeniem rejestracji.
- Usuwanie kontaktu – komenda np C;D;123456789 spowoduje odszukanie i usunięcie kontaktu z tym numerem
- Usuwanie wszystkich kontaktów C;D;A usuwa wszystkie kontakty łącznie z Administratorem
- Wyszukiwanie kontaktu – np C;F;123456789 sprawdzi czy podany numer znajduje się na karcie SIM
- Wywołanie komendy AT -np A;A;AT+CSQ sprawdzi poziom sygnału odbieranego, informacja zwrotna (jeżeli nie dłuższa niż 160 znaków) zostanie odesłana przez SMS
- Wywołanie kodu USSD -np A;U;1271# sprawdzi stan środków w nju mobile
- Na koniec komenda którą sprawdzam statystyki połączeń oraz ilość otwarć bramy oraz stan baterii A;I
Przy okazji komend należy pamiętać że wysłanie pierwszej, nie spowoduje jej wykonania! Wynika to z faktu, że na początku nie mamy wpisanego numeru Administratora i komenda nie przechodzi weryfikacji. Pierwszy SMS skutkuje tylko przypisaniem nadawcy do roli Administratora.
Kolejną rzeczą są odpowiedzi na komendy AT oraz kody USSD. Jeżeli ich długość przekroczy 160 znaków, nie zostaną odesłane do Administratora.
Jeżeli program odbierze nieautoryzowaną wiadomość SMS prześle ją do Administratora.
Pełny kod
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 |
/* For the purposes of this program I have modified GSM-GPRS-GPS-Shield library from https://github.com/open-electronics/GSM-GPRS-GPS-Shield. This code is runnig on Arduino Nano clone and SIM800L modem. For proper communication with SIM800L parameters need to be set: -GSM.cpp #define _GSM_TXPIN_ #define _GSM_RXPIN_ -GSM.h #define GSM_ON If your SIM card require PIN code, open GSM.cpp, find SendATCmdWaitResp(F("AT+CPIN=XXXX"), 500, 50, "READY", 5); uncomment and fill XXXX http://theveel.com/ Kamil Kaleta */ #include "SIM900.h" #include <avr/interrupt.h> #include <avr/power.h> #include <avr/sleep.h> #include <EEPROM.h> #include "sms.h" #include "call.h" CallGSM call; SMSGSM sms; const int RELAY1 = A0; const int RELAY2 = A1; const int callsAddr = 0; const int openingAddr = 1; //I`m counting calls and relay opening //Liczę przychodzące rozmowy oraz uruchomienie przekaźnika int calls = 1; int opening = 1; char spos; char number[13]; char sim_number[13]; char reply[210]; char stat; char message[159]; //Comment to disable debug with Serial. //Zakomentowanie wyłączy konsolę Serial #define LOCAL_DEBUG //Comment line below to disable sms sent (when testing/debuging) //Zakomentowanie wyłączy wysyłanie wiadomości SMS np podczas debugowania #define SMS_ON void setup() { spos = -1; Serial.begin(9600); Serial.println(F("> GSM Shield testing")); if (gsm.begin(2400)) { Serial.println(F("> Status READY")); } else Serial.println(F("> Status IDLE")); pinMode(RELAY1, OUTPUT); pinMode(RELAY2, OUTPUT); digitalWrite(RELAY1, HIGH); digitalWrite(RELAY2, HIGH); gsm.SimpleWriteln(F("AT+CMEE=2")); checkSMS(); }; void loop() { byte b = call.CallStatus(); if (b == CALL_INCOM_VOICE) { #ifdef LOCAL_DEBUG Serial.print(F("> Incomming phone call from ")); #endif if (call.CallStatusWithAuth(number, 1, 100) == CALL_INCOM_VOICE_AUTH) { #ifdef LOCAL_DEBUG Serial.println(F(" Allowed <")); #endif //Pick up, wait a second, hang up and close/open relay, increase opening counter //Odbierz rozmowę, poczekaj chwilę, rozłącz rozmowę i zamknij/otwórz przekaźnik, inkrementuj licznik otwarć call.PickUp(); delay(100); call.HangUp(); digitalWrite(RELAY1, LOW); delay(1000); digitalWrite(RELAY1, HIGH); opening = EEPROM.read(openingAddr) + 1; EEPROM.update(openingAddr, opening); } else { #ifdef LOCAL_DEBUG Serial.println(F(" Forbidden <")); #endif call.PickUp(); delay(100); call.HangUp(); } calls = EEPROM.read(callsAddr) + 1; EEPROM.update(callsAddr, calls); } else { checkSMS(); } }; void pinInterrupt(void) { detachInterrupt(1); } void sleepNow(void) { //Arduino is awake by SIM800L. Check data from modem, starts loop and go sleep again. //Modem RING pin is connected to D1 pin (interrupt1) //Arduino jest wybudzane przez modem SIM800L, uruchamia pętlę i ponownie zasypia //Pin RING modemu podłączony jest do pinu D1 Arduino (przerwanie1) //Without this modem will continue sending +creg reply which will wake up arduino for no reason //Bez tej komendy modem będzie cyklicznie wybudzał arduino gsm.SendATCmdWaitResp("AT+CREG=0", 2000, 50, "OK", 2, reply ); #ifdef LOCAL_DEBUG Serial.println(F("< Fell asleep >")); #endif attachInterrupt(1, pinInterrupt, LOW); delay(100); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); sleep_mode(); sleep_disable(); #ifdef LOCAL_DEBUG Serial.println(F("n< Woke up >")); #endif //Important delay //Ważne opóźnienie delay(2000); } void checkSMS() { #ifdef LOCAL_DEBUG Serial.println(F("> Check text Messages")); #endif gsm.SendATCmdWaitResp("AT+CREG=2", 2000, 50, "OK", 2, reply ); delay(1000); spos = sms.IsSMSPresent(SMS_ALL); Serial.println(spos); message[0] = ''; number[0] = ''; sim_number[0] = ''; reply[0] = ''; byte admNrValid = 0; if ((int)spos > 0) { //Check if there is a contact on position 1 simcard, it`s where it keeps Administrator phone number //Sprawdź czy na 1 pozycji karty SIM znajduje się kontakt - na pierwszej pozycji przechowywany jest numed administratora if (1 == gsm.GetPhoneNumber(1, number)) { #ifdef LOCAL_DEBUG Serial.print(F("n@ Administrator phone numbern@ ")); Serial.println(number); #endif admNrValid = 1; } else { #ifdef LOCAL_DEBUG Serial.print(F("> No Administrator phone number!")); #endif } //Ckeck if Authorized SMS (from Admin, whitch is positin 1 on sim card) //Sprawdź czy SMS autoryzacyjny - czy nadawcą jest Administrator stat = sms.GetAuthorizedSMS((int)spos, number, 13, message, 160, 1, 1); #ifdef LOCAL_DEBUG Serial.print(F("n########## SMS ############n# Position on list:")); Serial.println((int)spos); Serial.print(F("# From:")); Serial.println((char *)number); Serial.print(F("# Message:")); Serial.println(message); #endif if (stat == GETSMS_AUTH_SMS) { //Split sms body to keywords //Podziel wiadomość SMS na komendy char delimiters[] = ";"; char* valPosition; valPosition = strtok(message, delimiters); char* smsWords[] = {0, 0, 0}; int i; while (valPosition != NULL) { smsWords[i] = valPosition; #ifdef LOCAL_DEBUG Serial.print(F("# smsWords[")); Serial.print(i); Serial.print(F("] = ")); Serial.println(smsWords[i]); #endif valPosition = strtok(NULL, delimiters); i++; } /* SMS keywords Wyciągniete słowa kluczowe z SMSa smsWords[0]=[C]ontact, [A]dmin smsWords[1]=[A]add, [D]elete, [F]ind,[A]tt,[U]ssd smsWords[2]=phoneNumber,[A]ll,atCommand, ussdCode smsWords[3]=contatName smsWords[4]=[N]otify Available sms commands Dostępne komendy SMS C;A;xxxxxxxxx;John Doe;N - will add (if not exist) John Doe contact and send him a message C;A;xxxxxxxxx;John Doe - will add (if not exist) John Doe contact C;D;xxxxxxxxx - will delete contact with number xxxxxxxxx (if exist) C;D;A - will delete ALL contacts (Administrator too) C;F;xxxxxxxxx - will find if number xxxxxxxxx exist on phonebook A;A;AT+CBC - will execute AT command (e.g. AT+CBC) and send modem answer A;U;someCode - will execute USSD code A;I - will send replay message with some details */ //Parse incomming message //Dekodowanie wiadomości SMS if (strcmp(smsWords[0], "C") == 0) { #ifdef LOCAL_DEBUG Serial.println(F("[C]ontact")); #endif if (strcmp(smsWords[1], "A") == 0) //[C]ontact-[A]dd { #ifdef LOCAL_DEBUG Serial.println(F("[C]ontact-[A]dd")); #endif byte p = isPhoneNumber(smsWords[2]); if (p > 0) { char smsBody[160] = {0}; strcat(smsBody, "Phone number "); strcat(smsBody, smsWords[2]); strcat(smsBody, " ALREADY found on position "); char buffer [20];//iteger as char itoa ( p, buffer, 10); strcat(smsBody, buffer); #ifdef SMS_ON //Send SMS with smsBody to Administrator (1 simcard contact position) //Wyślij SMS do Administratora (kontakt na 1 pozycji karty SIM) sms.SendSMS(1, smsBody); #endif #ifdef LOCAL_DEBUG Serial.print("nSMS "); Serial.println(smsBody); #endif } else { #ifdef LOCAL_DEBUG Serial.print(F("Have NOT found given number.I will try to add ")); Serial.println(smsWords[2]); #endif if (1 == gsm.WritePhoneNumber(0, smsWords[2], smsWords[3])) { byte b = isPhoneNumber(smsWords[2]); if (strcmp(smsWords[4], "N") == 0) //NOTIFY USER { #ifdef SMS_ON //Send SMS to b, which is new contact simcard index //Wyślij SMS do b, gdzie b to index dodawanego kontaktu sms.SendSMS(b, "Your phone number has been registered"); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println(F("Your phone number has been registered.")); #endif } char smsBody[160] = {0}; strcat(smsBody, "Phone number "); strcat(smsBody, smsWords[2]); strcat(smsBody, " has been registered on position "); char buffer [20];//iteger as char itoa ( b, buffer, 10); strcat(smsBody, buffer); #ifdef SMS_ON sms.SendSMS(1, smsBody); #endif #ifdef LOCAL_DEBUG Serial.print(F("n[SMS] ")); Serial.println(smsBody); #endif } } } else if (strcmp(smsWords[1], "D") == 0) //[C]ontact-[D]elete { #ifdef LOCAL_DEBUG Serial.println(F("[C]ontact-[D]elete")); #endif if (strcmp(smsWords[2], "A") == 0) { //[C]ontact-[D]elete-[A]ll #ifdef LOCAL_DEBUG Serial.println(F("[C]ontact-[D]elete-[A]ll")); #endif for (int i = 1; i < 251; i++) { gsm.DelPhoneNumber(i); } gsm.SendATCmdWaitResp("AT+CPBR=1,250", 20000, 50, "OK", 5, reply ); #ifdef SMS_ON sms.SendSMS(number, (char *)reply); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println((char *)reply); #endif } else { //Will delete single contact but is number on phonebook? //Usuwa kontakt jeżlei znajdzie na karcie SIM byte p = isPhoneNumber(smsWords[2]); if (p > 0) { if (1 == gsm.DelPhoneNumber(p)) { char smsBody[160] = {0}; strcat(smsBody, "Number "); strcat(smsBody, sim_number); strcat(smsBody, " found on position "); char buffer[4]; sprintf(buffer, "%d", p); //p to char strcat(smsBody, buffer); strcat(smsBody, ", and deleted"); #ifdef SMS_ON sms.SendSMS(1, smsBody); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println(smsBody); #endif } else { char smsBody[160] = {0}; strcat(smsBody, "Found number "); strcat(smsBody, sim_number); strcat(smsBody, " on position "); strcat(smsBody, p); strcat(smsBody, ", but couldn't delete"); #ifdef SMS_ON sms.SendSMS(1, smsBody); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println(smsBody); #endif } } } } else if (strcmp(smsWords[1], "F") == 0) //[C]ontact-[F]ind { #ifdef LOCAL_DEBUG Serial.println(F("[C]ontact-[F]ind")); #endif byte p = isPhoneNumber(smsWords[2]); if (p > 0) { char smsBody[160] = {0}; strcat(smsBody, "Phone number "); strcat(smsBody, smsWords[2]); strcat(smsBody, " found on position "); char buffer [20]; itoa ( p, buffer, 10); strcat(smsBody, buffer); #ifdef SMS_ON sms.SendSMS(1, smsBody); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println(smsBody); #endif } else { char smsBody[160] = {0}; strcat(smsBody, "Phone number "); strcat(smsBody, smsWords[2]); strcat(smsBody, " has not been found"); #ifdef SMS_ON sms.SendSMS(1, smsBody); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println(smsBody); #endif } } } else if (strcmp(smsWords[0], "A") == 0) { #ifdef LOCAL_DEBUG Serial.println(F("[A]dministrator")); #endif if (strcmp(smsWords[1], "A") == 0) //[A]dministrator-[A]T { #ifdef LOCAL_DEBUG Serial.println(F("[A]dministrator-[A]T")); #endif gsm.SendATCmdWaitResp(smsWords[2], 5000, 50, "OK", 5, reply ); #ifdef SMS_ON if (strlen((char *)reply) > 159) sms.SendSMS(1, "AT Response too long for message"); else sms.SendSMS(1, (char *)reply); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println((char *)reply); #endif } else if (strcmp(smsWords[1], "U") == 0) { //[A]dministrator-[U]SSD #ifdef LOCAL_DEBUG Serial.println(F("[A]dministrator-[U]SSD")); #endif char atussd[20] = {0}; strcat(atussd, "AT+CUSD=1,""); strcat(atussd, smsWords[2]); strcat(atussd, """); char st = (gsm.SendATCmdWaitResp(atussd, 5000, 100, "CUSD", 3, reply)); if (2 == gsm.WaitResp(10000, 100, "CUSD")) { char b[160]; memcpy(b, gsm.comm_buf + 2, strlen(gsm.comm_buf)); b[160] = ''; #ifdef SMS_ON if (strlen(gsm.comm_buf) < 160) sms.SendSMS(1, b); else sms.SendSMS(1, "USSD response to long for SMS message"); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println((char *)(gsm.comm_buf)); #endif } else { Serial.print(F("Something wrong with USSD command")); Serial.println(reply); #ifdef SMS_ON sms.SendSMS(1, reply); #endif } } else if (strcmp(smsWords[1], "I") == 0) { /////[A]dministrator-[I]nfo char st[1]; st[0] = ''; char str_perc[3]; str_perc[0] = ''; char str_vol[6]; //4 str_vol[0] = ''; #ifdef LOCAL_DEBUG Serial.println(F("[A]dministrator-[I]nfo")); #endif reply[0] = ''; opening = EEPROM.read(openingAddr); calls = EEPROM.read(callsAddr); char calls_; char smsBody[160] = {0}; strcat(smsBody, "Calls: "); char buffer[10]; sprintf(buffer, "%d", calls); strcat(smsBody, buffer); strcat(smsBody, ", Opening: "); sprintf(buffer, "%d", opening); strcat(smsBody, buffer); if (1 == gsm.getBattInf(st, str_perc, str_vol)) { strcat(smsBody, "n"); strcat(smsBody, "Batt "); strcat(smsBody, str_perc); strcat(smsBody, ", "); strcat(smsBody, str_vol); } if (1 == (gsm.readCellTimeDate(reply))) { strcat(smsBody, "n"); strcat(smsBody, reply); } #ifdef SMS_ON sms.SendSMS(1, smsBody); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println(smsBody); #endif } } #ifdef LOCAL_DEBUG Serial.print(F("####### Authorized ########n")); #endif } else { #ifdef LOCAL_DEBUG Serial.print(F("###### NOT Authorized #####n")); #endif if (!admNrValid) { //Very first SMS (1 simcard position was empty, so I will set sender as Administrator) //Pierwszy SMS (pozycja 1 na karcie SIM jest pusta, więc ustawiam nadawcę jako Administratora) #ifdef LOCAL_DEBUG Serial.print(F(">> ")); Serial.print(number); Serial.println(F(", set as Administrator number")); Serial.print(F("###########################n")); #endif gsm.WritePhoneNumber(1, number, "Administrator"); #ifdef SMS_ON sms.SendSMS(1, "You are Administrator now"); #endif } else { //This SMS is NOT Authorized SMS and the Administrator is set. I`m FFD it to the Administrator. //SMS nieautoryzacyjny i już wpisany numer Administratora. Przesyłam do administratora, niech czyta. char smsBody[160] = {0}; strcat(smsBody, "From: "); strcat(smsBody, (char *)number); strcat(smsBody, ", "); strcat(smsBody, message); #ifdef SMS_ON sms.SendSMS(1, smsBody); #endif #ifdef LOCAL_DEBUG Serial.print(F("[SMS] ")); Serial.println(smsBody); #endif } } //Now message must be removed //Na koniec usuwam wiadomość SMS #ifdef LOCAL_DEBUG Serial.print(F("n!!!!!!!!!!!!!!!!!!!!!!!!!!!n! deleting messages n! on position ")); Serial.println((int)spos); int d = 1; for (int i = 0; i < 5; i++) { delay(1000); if (sms.DeleteSMS((int)spos) == 1) { Serial.println(F("! successfully completed")); d = 0; break; } else Serial.println(F("try delete message")); } if (d) { Serial.println(F("! !failed n! try kill em all")); //This should not happen, but just in case delate all messages //Nie powinno mieć miejsca, na wszelki wypadek usuwam wszystkie wiadomości SMS gsm.SimpleWriteln(F("AT+CMGDA = "DEL ALL"")); } Serial.print(F("!!!!!!!!!!!!!!!!!!!!!!!!!!!nn")); #endif } else { #ifdef LOCAL_DEBUG Serial.println(F("> No SMS")); #endif sleepNow(); } } byte isPhoneNumber(char *what_nr) { int found = 0; //250 max amount contacts on my SIM card phonebook, this loop below costs you 40 seconds //250 to maksymalna ilość kontaktów na mojej karcie SMS, ta pętla poniżej kosztuje 40 sekund for (byte i = 1; i < 251; i++) { // Serial.print("*"); // if ((i % 25) == 0) // Serial.print("n"); if (1 == gsm.GetPhoneNumber(i, sim_number)) { if (strstr(sim_number, what_nr)) { found = i; break; } } } return found; } |
O sofcie było sporo, teraz trochę o sprzęcie.
Urządzenie jest zasilane przez akumulator Li-Po 3.7V (odzysk z baterii do laptopa). Akumulator oprócz uniezależnienia od gniazdka, zapobiega niespodziewanym restartom modemu. Mogą one wynikać z chwilowego zapotrzebowania na energię (są komentarze że modem SIM800L może potrzebować nawet 2A podczas logowania do sieci).
Akumulator ładowany jest przez moduł 03962a. Na zdjęciu widoczny jest (zielony) zacisk. Są do niego wyprowadzone inputy ładowarki. Był pomysł żeby zasilać urządzenie nie przez port USB ale z instalacji domofonowej (u mnie wychodzi 6V). Niestety to źródło miało za mały amperaż.
Poniżej na zdjęciu mamy DC booster podnoszący napięcie z wyjścia ładowarki na 5V. Pracując nad kolejnym projektem, dochodzą do wniosku że ten booster nie jest prawdopodobnie potrzebny. Napięcie z naładowanego akumulatora LiPo jest wystarczające do zasilanie Arduino 5V. Myślę że modem i przekaźniki też dały by radę. Z drugiej strony taki booster to grosze. Jedyny minus to drobna strata przy podnoszeniu napięcia i zasilenia LEDa.
Kolejny moduł to konwerter stanów logicznych. Jest on wpięty w TX/RX pomiędzy modemem a pinami Arduino. Spotkałem się z opinią i nawet sprawdziłem że SIM800L gada z Arduino bez konwersji stanów logicznych. Ale nie polecam takiego podejścia bo w końcu modem to najdroższa część całego urządzenia (konwerter chyba najtańszy).
Na koniec przekaźnik. Chociaż w kodzie korzystam tylko z jednego, zamontowałem podwójny. Bo się komponował, daje możliwości rozbudowy i.. nie miałem pojedynczego ;)
I jeszcze koszty
- moduł ładowania 1,45PLN
- przekaźnik 3,46PLN
- arduino 8,40PLN
- konwerter stanów logicznych 1,18PLN
- SIM800L 15,47PLN
- koszyk baterii 1,46PLN
- DC boost converter 3,01PLN
- obudowa około 4PLN
- akumulator z odzysku
Całość poniżej 40PLN
Bardzo fajny projekt, przydatna rzecz.
Dzięki Leszek. Co ważne działa i żyje swoim życiem. Moje sąsiadki korzystają regularnie-bo maja tylko jednego pilota do bramy. Ja się wdzwaniam minutę przed dojechaniem do bramy i czeka na mnie otwarta :)
Niestety za cholerę nie potrafię tego uruchomić… Mozna jakis kontakt telefoniczny ? Chętnie bym kupił takie działające urządzonko… Pozdrawiam.
Jakaś lista podzespołów, schemat?
Listę sprzętu dopisałem dziś rano. Na schemat nie mam zwyczajnie czasu. Trzeba pracować żeby zarobić na przesyłki z Aliexpress :) Nie ukrywam że część softwareowa zajęła mi bardzo dużo czasu i to na niej skupiam opis. Podpięcia wynikają bezpośrednio z kodu. Oczywiście jak ktoś nie znajdzie to zapraszam do komentarzy.
Świetny projekt, zasłużone 5! Zachęcam do zobaczenia moich zmagań z komunikacją gsm TU LINK :)
Dzięki, widziałem już Twój artykuł i nawet oceniłem ;)
Myślałem o czymś podobnym. Ale zorganizowałem to inaczej. Przekaźnik podobnie zwiera styki w domofonie, ale mam inną komunikację. Mózgiem jest raspberry. Mam na nim inne rzezczy, i do tego też go wykorzystałem. Plus jest taki, że już go mam i nie potrzebuję nic nowego. Ale mam go zasilanego z gniazdka. Na raspi stoi serwer www (właśnie on jest używany do tych ‘innych rzeczy’), widoczny jest z zewnątrz. Zrobiłem prostą aplikację na smartfona, w której klikam ‘otwórz’ albo ‘zamknij’. Każde kliknięcie zmienia stan pinu połączonego z przekaźnikiem. I na tej podstawie przekaźnik zwiera albo rozwiera domofon.
Plus mam taki, że sprzęt praktycznie miałem już w domu. Rozbudowałem tylko istniejący system.
Minus, muszę mieć w smartfonie internet włączony.
Kolejny projekt typu “chwalimy się – nie pokazujemy szczegółów”.
Pomysł fajny, projekt fajny ale sorry, dla majsterkowicza, który chciałby skorzystać albo czegoś się nauczyć jest prawie bezwartościowy :(
Andy, jakich szczegółów nie pokazuję? Omawiam praktycznie cały kod. Niemało, prawie 600 linijek. Mam pisać że puls ładowarki łączę z plusem arduino? Twój komentarz też jest dla mnie ‘prawie bezwartościowy’, bo nie wiem co mam poprawić. Jeżeli jest faktycznie zapotrzebowanie na schemat, to proszę pisać w komentarzach lub założyć temat na forum. Pewnie znajdę godzinę żeby go narysować.
Projekt jest ciekawy, ale nie przydatny dla użytkowników, którzy chcieliby go powtórzyć (brak schematu połączeń). Założenie oszczędzania energii zostało sprowadzone tylko do usypiania arduino. Dlaczego nie jest usypiany najbardziej prądożerny element układu jakim jest sim800l? Sim800l posiada różne tryby oszczędzania energii, które należało umiejętnie zaaplikować w kodzie programu. Proszę poczytać o poleceniu “CSCLK”. Jeżeli jednym z złożeń projektu była oszczędność energii to należało: zastosować przekaźniki bistabilne i arduino 3.3V bez usb, wywalić moduł ładowania i DC boost converter. Gdyby autor pomyślał wcześniej i zamiast sim800l kupił sim800c (koszt na ali około 5$) z pinem VMCU to konwerter stanów logicznych też byłby nie potrzebny. Wprowadzenie poprawek, które wymieniłem powyżej pozwoliłaby układowi na kilkutygodniową pracę przy zasilaniu z jednego ogniwa 18650. Przy opisywaniu projektu zabrakło też podstawowej informacji jaki jest pobór prądu (proszę podać taką informację).
Pisałem o oszczędzaniu baterii nie o pracy przez kilka tygodni. Jesteś pewien że SIM800L poprawnie implementuje CSCLK? Jak wybudzić modem przy połączeniu przychodzącym? Żeby to zrobić trzeba podać DTR z… czego? Nie sądzę żeby modem w trybie uśpienia potrafił sam się wybudzić. O schemacie już pisałem w komentarzu, szkoda że nie czytałeś. O DC boosterze i konieczności jego użycia też jest w tekście. Argument że mogłem użyć innych części – jak dla mnie kulą w płot. Moje projekty powstają spontanicznie i składam z tego co mam pod ręką. A części zamawiam w Chinach, wiec czas oczekiwania to koło miesiąca. Szkoda że nie znalazłeś w moim projekcie nic pozytywnego. Poza tym, nie_przydatny piszemy razem. Pozdrawiam.
1. Kilka komentarzy wyżej napisałeś, że cyt. “Jeżeli jest faktycznie zapotrzebowanie na schemat, to proszę pisać w komentarzach” więc napisałem, że w opisie projektu zabrakło schematu połączeń. 2. Napisałeś też, że cyt. “są komentarze że modem SIM800L może potrzebować nawet 2A podczas logowania do sieci”. Komentarze w tym przypadku mówią prawdę, ale takie rzeczy sprawdza się specyfikacji. 3. Kolejny cytat ” Jesteś pewien że SIM800L poprawnie implementuje CSCLK? Jak wybudzić modem przy połączeniu przychodzącym? Żeby to zrobić trzeba podać DTR z… czego?” Tak jestem pewien, że sim800l poprawnie obsługuje tryby oszczędzające energię, ponieważ sam z nich korzystam. Komentarze pod projektem to nie jest najlepsze miejsce na wykład, a szczególnie jak piszę się ze smartfona. Tak jak napisałem wcześniej sim800l posiada kilka trybów oszczędzających energię. Jeszcze raz polecam zapoznać się ze specyfikacją sim800l, a o ewentualne niejasności zapytać tutaj. Tak w skrócie. Sim800l możemy wprowadzić w Sleep Mode 1 i Sleep Mode 2. Zarówno przy sleep mode 1, jak i przy sleep mode 2 sim800l wybudza się automatycznie kiedy odbiera sms-a lub przychodzi połączenie głosowe. W tym czasie na pinie RI przez około 1 sekundę pojawia się stan niski. RI możemy użyć jako przerwanie, aby wybudzić arduino. Jak zapewne się domyśliłeś ustawienie trybu sleep mode 1 lub 2 nie odcina sim800l od sieci, ale różnice w oszczędności energii są bardzo duże. W sleep mode 2 zużycie energii w czasie monitorowania sieci wynosi tylko do 3 mA, a bez włączonego sleep mode minimum 13 mA. Przy Twoim projekcie oszczędzanie energii powinno być priorytetem, które można rozwiązać małym kosztem.
Na balkonie marznie już czujnik temperatury, (Arduino PM 3,3V 8Mhz) do stacji pogody. Ten to będzie miał szansę na kilka jak nie kilkadziesiąt tygodni pracy bez ładowania :) O trybach pracy uśpienia SIM800L czytałem kilka artykułów które opisywały wyłącznie kłopoty z tym związane. Więc na razie nie podejmuję tematu- przynajmniej do czasu zamówienia kolejnego modemu. Nie chcę demontować sterownika, bo po prostu już działa i co tu nie mówiąc – robi robotę.
Problem pojawi się gdy korzystając z SDR (np bladeRF x40) postawimy niedaleko własnego BTSa i podszyjemy się pod numer administratora :)
A pod jaki numer dokładnie chcesz się podszyć? Znasz ten z pozycji 1 mojej karty SIM? Poza tym skąd gwarancja że modem zaloguje się do twojego fałszywego BTSa?
Bo SIM800L domyślnie loguje się do BTSa z najwyższym RSSI, którego ID sieci się zgadza z tym na karcie SIM. Załatwia to antena kierunkowa wycelowana w moduł. Numer można poznać poprzez nasłuch ruchu sieciowego dla danego IMEI. IMEI mam na zdjęciu w artykule :) Ogólnie projekt bardzo mi się podoba, ale nie polegałbym na rzekomym bezpieczeństwie sieci 2G. Takowe praktycznie nie istnieje. Sensowniej byłoby nawiązać komunikację przez GPRS i dane przesyłać szyfrowane. Chociażby z użyciem MQTT. Niemniej muszę przyznać, że wdzwanianie się na numer modemu jest nieporównywalnie wygodniejsze.
Moduł wykorzystuje Arduino, można zatem zaimplementować szyfrowanie RSA i podpis cyfrowy (funkcja skrótu). Komunikacja jest tylko w jedną stronę. Klucz prywatny wgrywasz osobiście. Są odpowiednie algorytmy do generowania haseł jednorazowych (nadawca) i ich jednorazowej weryfikacji (odbiorca). Można też wykorzystywać rzeczy zmienne, ale znane dla obu stron (np. data, godzina jeśli do układu dodać by zegar RTC). Jest sporo sposobów by bardzo skutecznie zabezpieczyć transmisję :)
Oczywiście że nie. Nie do najmocniejszego RSSI ale najwyższej technologii. Lte zawsze wygra z 3G, z którym przegra 2G.
U jakiego operatora najtaniej wychodzi utrzymywać kartę SIM aktywną na odbieranie SMS-ów?
Play “rok ważności konta”
SMS 9gr.
W KLUCZ MOBILE po każdym zasileniu konto ważne jest rok, a sms-y w sieci KLUCZ MOBILE są za darmo! SMS-y wysyłane poza sieć są po 9 gr.
Najtaniej wychodzi w sieci KLUCZ MOBILE. Każde zasilenie przedłuża ważność konta o rok. SMS-y w sieci KLUCZ MOBILE są za darmo!
Ja korzystam z njumobile.
Projekt świetny – myślałem kiedyś nad czymś podobnym, tylko zamiast modułu GSM stara nokia, no ale to gra nie warta świeczki, jeżeli moduł kosztuje 15zł :D
Powiedz mi proszę czy wiesz, czy ciągłe ładowanie tym modułem nie wpływa negatywnie na pojemność baterii? Czy bez problemu można korzystać z niego cały czas, czy lepiej programowo załączać go np. mosfetem gdy napięcie aku spadnie poniżej jakiegoś poziomu?
Widzę, że autorowi projektu ciężko jest odpowiedzieć na Twoje pytanie. Przypomnę, że obiecał też umieszczenie schematu połączeń, ale go nie wstawił :)
Nie jest ciężko. Po prostu nie zaglądam do tematu co dziennie. Nie ma też maili o nowym komentarzu. woysz akumulatory powinny pracować w trybie buforowania, czyli być zasilane określonym napięciem konserwującym. W projekcie można się pokusić o mocniejszy zasilacz i całkowicie pozbyć się akumulatora. @nowy, mogę się jedynie usprawiedliwić, że pracuję nad fajną stacją pogodową i już powoli kończę. Przy okazji zrobię dwa schematy ;)
woysz masz rację. Nie wiem, jak ciągłe ładownie ogniw wpłynie na jego żywotność, ale wiem, że takie rozwiązanie nie jest dobrym pomysłem. Włączanie ładowania ogniwa powinno być sterowne programowo. W sim800l wartość napięcia zasilania można sprawdzić poleceniem AT+CBC.
Używając laptopa wyciągasz z niego akumulator? Jest cała masa urządzeń które dokujemy i przez cały czas ładują się w nich akumulatory.
intrygujące !, a skąd wiozłeś SIM800 za 15 zł no chyba popłynąłeś trochę za bardzo
Gdzie kupić SIM800L za 15,47zł? Wszędzie, gdzie szukałem to po ~30zł
Najlepiej podaj link do sklepu w którym się zaopatrzyłeś w komponenty. :)
AliExpress € 3,80
Wczoraj zamawiałem ;)
W jaki sposob (elektronicznie i programowo) zrealizowany jest pomiar naladowania baterii? Cos nie moge znalezc…
Hej, Bardzo fajny ten projekt ale po amatorskiej próbie jego sprawdzenia jak działa, pojawił się problem z wgraniem szkicu a własciwie biblioteki GSM-GPRS-GPS-Shield-GSMSHIELD którą należy pobrać i dodać do IDE. Czy mógłbyś podpowiedzieć w jaki sposób należy dodać tą bibliotekę? Za każdym razem kiedy próbuję dodać ją w formie ZIP, program krzyczy że jest to niepoprawna biblioteka. Dzięki z góry za szybką odpowiedź,
Pozdrawiam,
Około linii 464 gsm.getBattInf
Napięcie jest raportowane przez modem.
Witam ponownie. Udało się finalnie wgrać i bibliotekę i kod. Ważne aby przy wgrywaniu Arduino nie było podłączone do żadnego układu i w moim przypadku musiałem nacisnąć przycisk reset na płytce tuż przed wgraniem kodu. Po wgraniu pokazuje się informacja że jest mało pamięci ale całość działa bardzo fajnie.
DZIĘKI i kciuk w górę!
Witam,
próbuję skompilować powyższy kod ale w efekcie otrzymuję komunikat:
exit status 1
unterminated argument list invoking macro “F”
Rozwiązałeś problem?
Zrobiłem podobne sterowanie, ale uprościłem układ do minimum. Użyłem tylko modułu gsm Air200 i przekaźnika + zasilanie 5V z ładowarki. Włączanie i wyłączanie odbywa się za pomocą wiadomości sms. Dodałem także przywracanie wcześniej ustawionego stanu przekaźnika po wyłączeniu prądu.
Fajny projekt (:
Ale takie zamazywanie numerów nie wcale nie uniemożliwia ich odczytania. Trochę pracy w gimpie i można się domyślić co to za numer. Rozmycie też nie działa za dobrze.
Najlepiej jest zamazać je jednolitym kolorem. (:
Nie mój numer, nie mój problem ;) Ale pewnie masz rację. Zastanawiam się czy mój egzemplarz SIM800 MA jakiś feler. Średnio raz w miesiącu przestaje się wybudzać. Muszę restarować całość. Poza tym działa nieprzerwanie.
Problemem może być zasilanie, te moduły potrafią pobierać impulsowo do 2A prądu i są wrażliwe na zakłócenia zasilania.
Buforem jest ogniwo 18650. A po restarcie wszytko działa. Być może SIM800 kończy żywot. Jaki moduł GSM jest teraz na topie? Muszę przyznać że porzuciłem Arduino na rzecz ESP8266 i nie jestem w temacie. Jakieś Air200?
Dla zainteresowanych sterowanie przekaźnikiem za pomocą modułu gsm Air200. Skrypt umożliwia nie tylko sterowanie włącz/wyłącz, ale także sterowanie czasowe. Dodane jest zabezpieczenie przed niepowołanym włączeniem/wyłączeniem z innego numeru. Jest też możliwość sprawdzenia aktualnego stanu pinu oraz przywracanie wcześniej ustawionego stanu pinu po zaniku zasilania. O szczegółach można przeczytać na tej stronie https://www.elektroda.pl/rtvforum/viewtopic.php?p=17008021#17008021
Witam, czy biblioteki są jeszcze aktualne? po ściągnięciu i załadowaniu ukazują się inne nazwy i brak na przykład EEPROM