Бюджетная рассылка СМС

от автора

Приветствую всех хаброжителей!

Конечно, зализанная тема про рассылку смс сообщений, но как говориться: «много — не мало». Как-то так получилось, что именно она меня постоянно преследует: то одни, то другие добрые люди попросят принять участие (советом, например) в реализации бюджетной рассылки сообщений. И поэтому чтобы не пропадать накопленному добру, оставлю здесь, а вдруг кому-то пригодится…


Итак-с… Опускаем все варианты реализации на базе обычного компа и оси семейства NT. А перейдем сразу к «автономным» системам.

Чем может похвастаться arduino в этом направлении? Отвечу сразу, ОНО работает, но есть нюансы, о которых напишу ниже. Вообщем, имеем китайский вариант arduino 2560 (было перепробовано практически вся линейка) и два дополнительных модуля — сеть W5100 (наиболее стабильный вариант) и GSM SIM 900. Выглядит это все дело как-то так.

image

Задача была следующая:
— устройство должно уметь общаться по http
— отправлять сообщение
— выдавать результат в формате json

Гугл делится всей необходимой информацией, и на выходе получаем следующий код:

Скетч

#include <SPI.h> #include <Ethernet.h>  #include <String.h>  #include "SIM900.h" #include <SoftwareSerial.h> #include "sms.h"  #include <LiquidCrystal_I2C.h> #include <Wire.h>  byte mac[] = { 0x90, 0xA2, 0x00, 0x00, 0x00, 0x01 };     IPAddress ip(192,168,34,139);                                EthernetServer server(80);  char char_in = 0;     String HTTP_req;      SMSGSM sms;  boolean started=false; bool power = false;  LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);  void setup() {    Serial.begin(9600);             lcd.begin(16,2);   lcd.setCursor(0,0);   lcd.print("INIT GSM...");   lcd.setCursor(0,1);   lcd.print("WAIT!!!");      //powerUp();   gsm.forceON();      if (gsm.begin(4800)) {     Serial.println("\nstatus=READY");     lcd.clear();     lcd.setCursor(0,0);     lcd.print("READY");         started=true;     }   else {     Serial.println("\nstatus=IDLE");     lcd.clear();     lcd.setCursor(0,0);     lcd.print("IDLE");   }      Ethernet.begin(mac, ip);    server.begin();             }  void software_reset() {   asm volatile ("  jmp 0");   }   void loop() {   EthernetClient client = server.available();    if (client) {     while (client.connected()) {       if (client.available()) {         char_in = client.read();  //         HTTP_req += char_in;                if (char_in == '\n') {                        Serial.println(HTTP_req);                      if(HTTP_req.indexOf("GET /res") >= 0) {             reset_processing(&HTTP_req, &client);             break;           }            if(HTTP_req.indexOf("GET /sms") >= 0) {             sms_processing(&HTTP_req, &client);             break;           }                if(HTTP_req.indexOf("GET /test") >= 0) {             test_processing(&HTTP_req, &client);             break;           }              else {             client_header(&client);               break;           }              }       }     }     HTTP_req = "";         client.stop();   }       if(power) {     delay(1000);     software_reset();   } }  char* string2char(String command) {   if(command.length()!=0){     char *p = const_cast<char*>(command.c_str());     return p;   } }  void parse_data(String *data) {   data->replace("GET /sms/","");   data->replace("GET /test/", "");    int lastPost = data->indexOf("\r");   *data = data->substring(0, lastPost);   data->replace(" HTTP/1.1", "");   data->replace(" HTTP/1.0", "");   data->trim(); }  // explode  String request_value(String *data, char separator, int index) {   int found = 0;   int strIndex[] = {0, -1};   int maxIndex = data->length()-1;    for(int i=0; i<=maxIndex && found<=index; i++) {     if(data->charAt(i)==separator || i==maxIndex) {       found++;       strIndex[0] = strIndex[1]+1;       strIndex[1] = (i == maxIndex) ? i+1 : i;     }   }   return found>index ? data->substring(strIndex[0], strIndex[1]) : ""; }  bool gsm_status() {   bool result = false;   switch(gsm.CheckRegistration()) {     case 1:       result = true;       break;     default:       break;   }   return result; }  bool gsm_send(char *number_str, char *message_str) {   bool result = false;   switch(sms.SendSMS(number_str, message_str)) {     case 1:       result = true;       break;     default:       break;   }    return result;  }  void reset_processing(String *data, EthernetClient *cl) {   client_header(cl);       cl->println("\{\"error\": 0, \"message\": \"restarting...\"\}");      power = true;    }  void test_processing(String *data, EthernetClient *cl) {   parse_data(data);      if(started) {     client_header(cl);     cl->println("\{\"id\":" + request_value(data, '/',0) + ",\"error\":0" + ",\"message\":\"test success\"\}");      } }  void sms_processing(String *data, EthernetClient *cl) {   parse_data(data);    if(started) {     if (gsm_send(string2char(request_value(data, '/', 1)), string2char(request_value(data, '/', 2)))) {       client_header(cl);       cl->println("\{\"id\":" + request_value(data, '/',0) + ",\"error\":0" + ",\"message\":\"success\"\}");     }     else {       if(!gsm_status()) {         client_header(cl);         cl->println("\{\"id\":" + request_value(data, '/',0) + ",\"error\":2" + ",\"message\":\"gsm not registered\"\}");            power = true;       }       else {         client_header(cl);         cl->println("\{\"id\":" + request_value(data, '/',0) + ",\"error\":1" + ",\"message\":\"fail\"\}");            }     }   } }  void client_header(EthernetClient *cl) {   cl->println("HTTP/1.1 200 OK");   cl->println("Content-Type: text/plain");   cl->println("Connection: close");     cl->println(); } 

Заливаем, упаковываем в коробочку. Вроде бы выглядит красиво, отдаем добрым людям.

image

Что получилось в коде:
— подняли простенький http
— обрабатываем простые GET
— отправляем полученные данные через SERIAL на SIM 900
— отвечаем с помощью «JSON»

И вот тут есть один большой нюанс, перед умными людьми стоит задача реализовать какой-нибудь сервис, чтобы научиться отправлять через это устройство сразу пачку сообщений, но это уже не мои проблемы. В производстве устройство себя повело удовлетворительно.

Наращиваем мощности… Задача полностью аналогичная: повторение — мать учения. Умные люди уже создали классный сервис для работы с предыдущим устройством: очередь, история и прочие полезности.

Итак, на руках имеем raspberry pi, такой же модуль SIM 900 (был взят только ради экспериментов, потому что линукс прекрасно работает с 3g-модемами через USB) и сам 3g-modem huawei e-линейки

image

Снова задаем гуглу нужные вопросы, читаем результаты, определяемся с языком реализации — python — быстро, просто, надежно…

скрипт

import serial, time from flask import Flask import RPi.GPIO as GPIO  app = Flask(__name__)   def sim900_on():     gsm = serial.Serial('/dev/ttyAMA0', 115200, timeout=1)     gsm.write('ATZ\r')     time.sleep(0.05)      abort_after = 5     start = time.time()     output = ""      while True:         output = output + gsm.readline()         if 'OK' in output:             gsm.close()             return True         delta = time.time() - start         if delta >= abort_after:             gsm.close()             break       #GPIO.setwarnings(False)     GPIO.setmode(GPIO.BOARD)                     GPIO.setup(11, GPIO.OUT)                     GPIO.output(11, True)                      time.sleep(1.2)     GPIO.output(11, False)      return False   def gsm_send(id, port, phone, msg):      delay = False     if 'AMA' in port:         delay = True       msg = msg.replace('\\n', '\n')     msg = msg.replace('\s', ' ')      gsm = serial.Serial('/dev/tty%s' % port, 115200, timeout=1)     gsm.write('ATZ\r')      if delay:         time.sleep(0.05)      gsm.write('AT+CMGF=1\r\n')      if delay:         time.sleep(0.05)      gsm.write('AT+CMGS="%s"\r\n' % phone)      if delay:         time.sleep(0.05)      gsm.write(msg + '\r\n')      if delay:         time.sleep(0.05)      gsm.write(chr(26))      if delay:         time.sleep(0.05)      abort_after = 15     start = time.time()     output = ""      while True:          output = output + gsm.readline()         #print output         if '+CMGS:' in output:             print output             gsm.close()             return '{"id":%s,"error":0,"message":"success", "raw":"%s"}' % (id, output)         if 'ERROR' in output:             print output             gsm.close()             return '{"id":%s,"error":0,"message":"fail", "raw":"%s"}' % (id, output)          delta = time.time() - start         if delta >= abort_after:             gsm.close()             return '{"id":%s,"error":1,"message":"timeout", "raw":"%s"}' % (id, output)  @app.route('/sms/<id>/<port>/<phone>/<msg>',methods=['GET']) def get_data(id, port, phone, msg):     return gsm_send(id, port, phone, msg)  @app.route('/',methods=['GET']) def index():     return "Hello World"  if __name__ == "__main__":     sim900_on()     app.run(host="0.0.0.0", port=8080, threaded=True)

Скармливаем питону, запускаем с помощью «start-stop-daemon», придаем дружелюбный вид, отдаем добрым людям…

image

Получилось практически один в один, только за счет шины USB систему можно расширять. В производстве претензий вообще не оказалось — все были ОЧЕНЬ довольны.

Устройство получилось настолько удачным, что появилось желание использовать это дело в «личных» интересах, а именно внедрить в систему мониторинга данный аппарат. Но надо было избавиться от главного нюанса — отсутствие очереди сообщений. Принцип реализации я взял у одного известного вендора (он предлагал программно-аппаратный комлекс, часть которого поднимала smtp-сервер для обработки уведомлений и отправки ее на gsm-устройство). Такая схема встраивается в любую систему мониторинга.

Итак, нужные знания уже давно получены, приступаем к реализации.

SMTP-демон

#!/usr/bin/env python2.7 # -*- coding: utf-8 -*-  import smtpd import asyncore import email import MySQLdb import subprocess  def InsertNewMessage(phone, msg):     conn = MySQLdb.connect(host="localhost", # your host, usually localhost                      user="sms", # your username                       passwd="sms", # your password                       db="sms") # name of the data base     c = conn.cursor()     c.execute('insert into message_queue (phone, message) values ("%s", "%s")' % (phone, msg))     conn.commit()     conn.close()  class CustomSMTPServer(smtpd.SMTPServer):      def process_message(self, peer, mailfrom, rcpttos, data):          msg = email.message_from_string(data)         phone = rcpttos[0].split('@',1)[0]         addr = mailfrom          for part in msg.walk():             if part.get_content_type() == "text/plain": # ignore attachments/html                 body = part.get_payload(decode=True)           InsertNewMessage(phone, str(body))  subprocess.Popen("/home/pi/daemons/sms/pygsmd.py", shell=True)  server = CustomSMTPServer(('0.0.0.0', 25), None)  asyncore.loop()

Демонизация происходит, как я уже писал выше, с помощью «start-stop-daemon», а сам smtp скрипт запускает подпроцесс для работы с базой сообщений.

gsm скрипт

#!/usr/bin/env python2.7 # -*- coding: utf-8 -*-  import serial import time import MySQLdb import commands   def gsm_send(port, phone, msg):      print 'Sending message: %s to: %s' % (msg, phone)     gsm = serial.Serial('/dev/tty%s' % port,                         460800,                         timeout=5,                         xonxoff = False,                         rtscts = False,                         bytesize = serial.EIGHTBITS,                         parity = serial.PARITY_NONE,                         stopbits = serial.STOPBITS_ONE )     gsm.write('ATZ\r\n')     time.sleep(0.05)     gsm.write('AT+CMGF=1\r\n')     time.sleep(0.05)     gsm.write('''AT+CMGS="''' + phone + '''"\r''')     time.sleep(0.05)     gsm.write(msg + '\r\n')     time.sleep(0.05)     gsm.write(chr(26))     time.sleep(0.05)      abort_after = 15     start = time.time()     output = ""       while True:          output = output + gsm.readline()         #print output         if '+CMGS:' in output:             #print output             gsm.close()             return 0         if 'ERROR' in output:             #print output             gsm.close()             return 1          delta = time.time() - start         if delta >= abort_after:             gsm.close()             return 1  def msg_delete(list):     conn = MySQLdb.connect(host="localhost",                      user="sms",                       passwd="sms",                       db="sms")     c = conn.cursor()     c.execute("delete from  message_queue where id in %s;" % list)     conn.commit()     conn.close()  def msg_hadle():     list = tuple()     conn = MySQLdb.connect(host="localhost",                      user="sms",                       passwd="sms",                       db="sms")     c = conn.cursor()     c.execute("select * from message_queue")      numrows = int(c.rowcount)     if numrows > 0:         for row in c.fetchall():             result = gsm_send('USB0', ('+' +  row[1]),  row[2])             if result == 0:                 list +=(str(row[0]),)      conn.close()      if len(list) == 1:         qlist = str(list).replace(',','')      if len(list) > 1:         qlist = str(list)      if len(list) > 0:         msg_delete(qlist)      del list  while True:     try:         msg_hadle()     except:         print "mysql error"      time.sleep(10)

В связке с моей системой мониторинга устройство ведет себя адекватно, хотя работает не так давно. Надеюсь, материал будет полезен кому-нибудь.

ссылка на оригинал статьи http://habrahabr.ru/post/261387/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *