Developers Club geek daily blog

1 year, 4 months ago
I welcome all habrozhitel!

Of course, the licked subject about mailing of SMS messages, but how to be told: "more is better than less". Somehow so it turned out that it constantly pursues me: one, other kind people will ask to take part (council, for example) in implementation of budget mailing of messages. And therefore not to vanish to the saved-up good, I will leave here, and suddenly it is useful to someone …


So - with … We lower all options of implementation based on a normal computer and an axis of NT family. And we will pass to "autonomous" systems at once.

What arduino in this direction can brag of? I will answer immediately, IT works, but there are nuances about which I will write below. Generally, we have the Chinese arduino 2560 option (it was tried practically all line) and two additional modules — the W5100 network (the stablest option) and GSM SIM 900. All this business somehow so looks.

image

The task was following:
— the device has to be able to communicate on http
— to send the message
— to issue result in the json format

Google shares all necessary information, and on an output we receive the following code:

Sketch
#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();
}



We fill in, we pack into a box., It seems, looks beautifully, we give to kind people.

image

What turned out in a code:
— lifted unpretentious http
— we process simple GET
— we send data retrieveds through SERIAL to SIM 900
— we answer by means of "JSON"

And there is one big nuance, smart people are faced by a task to implement some service to learn to send via this device at once a pack of messages, but it is not my problems. In production the device behaved well.

We increase capacities … The task is completely similar: repetition — mother of the doctrine. Smart people already created cool service for work with the previous device: queue, history and other usefulness.

So, we hold raspberry pi, the same SIM 900 module (it was taken only for the sake of experiments because Linux perfectly works with 3g-modems through USB) itself also 3g-modem huawei e-line

image

Again we ask Google the necessary questions, we read results, we decide on implementation language — python — quickly, simply, reliably …

script
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)



We feed to a python, we start by means of "start-stop-daemon", we give a friendly type, we give to kind people …

image

It turned out nearly one in one, only at the expense of the bus USB the system can be expanded. In production of claims it did not appear at all — all were VERY happy.

The device turned out so successful that there was a desire to use this matter in "personal" interests, namely to implement this device in a monitoring system. But it was necessary to get rid of the main nuance — absence of message queue. I took the principle of implementation from one known vendor (he offered hardware-software komleks which part lifted the smtp-server for processing of notifications and sending it on the gsm-device). Such scheme is built in any monitoring system.

So, the necessary knowledge is gained for a long time, we start implementation.

SMTP demon
#!/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()



The demonizing occurs as I already wrote above, by means of "start-stop-daemon", and smtp a script starts subprocess for work with base of messages.

gsm script
#!/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)



Together with my monitoring system the device behaves adequately though it works not so long ago. I hope, material will be useful to somebody.

This article is a translation of the original post at habrahabr.ru/post/261387/
If you have any questions regarding the material covered in the article above, please, contact the original author of the post.
If you have any complaints about this article or you want this article to be deleted, please, drop an email here: sysmagazine.com@gmail.com.

We believe that the knowledge, which is available at the most popular Russian IT blog habrahabr.ru, should be accessed by everyone, even though it is poorly translated.
Shared knowledge makes the world better.
Best wishes.

comments powered by Disqus