Cómo enviar un SMS desde cero ( y III )

Continuando con la serie de posts, a continuación veremos cómo se envía un SMS.
Suponemos que en el Form tenemos:
- Una lista de números telefónicos almacenados en un grid con varias columnas (en la columna 3 está el número de teléfono donde hay que enviar el SMS), 
- Un TButton con el siguiente procedimiento asociado al evento OnClick, su función es ir leyendo fila a fila el grid obteniendo el número de teléfono y el texto a enviar, que en este caso he puesto un texto fijo, pero que si lo que queréis es enviar un texto diferente a cada número de teléfono, se almacenaría en la columna 1 para leerlo de esta forma TEXTO:= grid3.Cells[0, i];

PROCEDURE TForm1.Button6Click(Sender: TObject);
VAR
  texto, CeldaX, Nombre,telefono: ansistring;
  i: integer;
BEGIN
  TRY
    Application.processmessages;
    Label1.caption := 'ENVIANDO SMS...';
    FOR i := 1 TO grid3.rowcount - 1 DO
    BEGIN
      grid3.SetFocus;
      grid3.Row := i;
      BEGIN
        TEXTO := 'Este es el texto del SMS';
         Telefono:= grid3.Cells[2, i];
        EnviarSMS(telefono,texto, Rdo, CodigoError);
        IF ansipos(chr(13) + chr(10) + '+CMGS:', Rdo) > 0 THEN
        BEGIN
             // Si el envío es correcto pone en la columna 5 del grid un OK+Fecha
          grid3.cells[4, i] := 'OK: ' + DateTostr(now);
        END
        ELSE grid3.cells[4, i] := '===ERROR==='
      END;
    END;
  FINALLY
    Application.processmessages;
    Label1.caption := 'ENVÍO TERMINADO';
  END;
END;



PROCEDURE tForm1.EnviarSMS(Telefono, texto: ansistring; VAR Rdo: ansistring; VAR CodigoError: Integer);
BEGIN
  enviar('AT+CMGS="' + telefono + '"', Rdo, CodigoError);
  //Después del comando at+cmgs el modem devuelve:   >
  IF ansipos('>', Rdo) > 0 THEN
  BEGIN
    enviar('"' + texto + '"' + ^Z, Rdo, CodigoError);
  END;
END;

El procedimiento "enviar" es el mismo que anteriores posts, pero lo pongo para facilitar la comprensión del procedimiento anterior 


PROCEDURE tform1.enviar(at: ANSISTRING; VAR Rdo: ansistring; VAR CodigoError: integer);
BEGIN
  CodigoError := 0;
  Edit1.TExt := '';
  rdo := '';
  Terminado := False;
  Segundos := 0;
  IF (FStatusTrigger = 0) THEN
  BEGIN
    FStatusTrigger := ApdComPort1.AddStatusTrigger(stLine);
    ApdComPort1.SetStatusTrigger(FStatusTrigger,
      lsOverrun OR lsParity OR lsFraming OR lsBreak,
      True);
  END;
  memo1.Lines.add('>>' + at + chr(13));
  timer1.Enabled := true;
  ApdComPort1.OutPut := at + chr(13);
  REPEAT
    delay(2000)  
  UNTIL Terminado;
END;


Procedimiento del objeto ApdComPort1, que se dispara para recoger los caracteres del buffer

PROCEDURE TForm1.ApdComPort1TriggerAvail(CP: TObject; Count: Word);
  {Event OnTriggerAvail - Example how OnTriggerAvail works}
VAR
  I: Word;
  C: Char;
  S, s1: STRING;
BEGIN
  FOR I := 1 TO Count DO BEGIN
    C := ApdComPort1.GetChar;
    s1 := c;
    Rdo := Rdo + s1;
  END;

  memo1.lines.add(Rdo);
  IF ansipos(chr(13) + chr(10) + 'OK' + chr(13) + chr(10), Rdo) > 0
    THEN
  BEGIN
    application.processmessages;
    edit1.Text := Rdo;
  END;
END;


Si queremos leer todos los mensajes almacenados en la SIM y copiarlos en el Grid1  tendremos que utilizar el comando AT+CMGL=all  de la siguiente forma:

PROCEDURE TForm1.Button2Click(Sender: TObject);
VAR
  linea1, linea2: ansistring;
  contador, contadorfila: integer;
  HayParametros: boolean;
BEGIN
  contador := 2; contadorfila := 1; HayParametros := false;
//Lee todos los mensajes de la SIM
  enviar('AT+CMGL=all', Rdo, CodigoError);
  REPEAT
        //captura el token nº “contador” de la cadena RDO, separada por el carácter chr(13))
    linea1 := gettoken(Rdo, chr(13), contador);
    IF ansipos('+CMGL:', linea1) > 0 THEN
    BEGIN
      inc(contadorFila);
      Grid1.Cells[0, contadorFila] := gettoken(linea1, ',', 1);
      Grid1.Cells[1, contadorFila] := gettoken(linea1, ',', 2);
      Grid1.Cells[2, contadorFila] := gettoken(linea1, ',', 3);
      Grid1.Cells[4, contadorFila] := gettoken(linea1, ',', 4);
      Grid1.Cells[5, contadorFila] := gettoken(linea1, ',', 5);
      Grid1.Cells[6, contadorFila] := gettoken(linea1, ',', 6);
      Grid1.Cells[7, contadorFila] := gettoken(linea1, ',', 7);
      Grid1.Cells[8, contadorFila] := gettoken(linea1, ',', 8);
      HayParametros := true;
    END
    ELSE
    BEGIN
      IF HayParametros THEN
      BEGIN
        Grid1.Cells[9, contadorFila] := linea1;
      END;
      HayParametros := false;
    END;
    inc(contador);
  UNTIL (ansipos('OK', LINEA1) > 0) OR (ansipos('OK', LINEA2) > 0) OR (contador > 100);
END;


Para borrar todos los mensajes de la SIM tendremos que usar el comando AT+CMGD=numero del mensaje

PROCEDURE TForm1.Button1Click(Sender: TObject);
VAR
  I: INTEGER;
BEGIN
  FOR I := 0 TO Grid1.rowcount - 1 DO
  BEGIN
    application.processmessages;
    label7.caption := 'Borrando mensaje: ' + inttostr(i);
    application.processmessages;
    enviar('AT+CMGD=' + inttoStr(i), Rdo, CodigoError);
  END;
END;

Una vez que se muestran todos los mensajes en la rejilla podremos borrar un mensaje cualquiera haciendo doble clic sobre una fila , utilizando el siguiente procedimiento:

PROCEDURE TForm1.Grid1DblClick(Sender: TObject);
VAR
  NumeroDeMensaje: ansistring;
  i: integer;
BEGIN
  IF MessageDlg('Eliminar mensaje ?' + grid1.Cells[0, FilaSeleccionada],
    mtConfirmation, [mbYes, mbNo], 0) = mryes THEN
  BEGIN
    NumeroDeMensaje := gettoken(grid1.Cells[0, FilaSeleccionada], ':', 2);
    enviar('AT+CMGD=' + NumeroDeMensaje, Rdo, CodigoError);
    IF CodigoError = 0 THEN
    BEGIN
      FOR i := 0 TO grid1.ColCount - 1 DO grid1.Cells[i, FilaSeleccionada] := '';
    END;
  END;
END;


Relacionados: 


Cómo enviar un SMS desde cero  ( I )
Cómo enviar un SMS desde cero  ( II )

Cómo enviar un SMS desde cero ( II )

Continuado con el post anterior aquí tenéis cómo se inicializa el módem.



Para ello he puesto un Botón llamado "Conectar" dentro del Form con el siguiente procedimiento que lo que hace es:
Enviar un ATE0 para quitar el "eco" de los caracteres, después le decimos que queremos enviar un texto AT+CMGF=1,  y por último leemos varios parámetros:

- AT+CSQ (Potencia de la señal), posibles respuestas son:

0 113 dBm or menos
1 111 dBm
2...30 109... 53 dBm
31 51 dBm o más
99 desconocido o no detectable


ComandoPosibles respuestas
+CSQ+CSQ: , +CME ERROR: 
+CSQ=?+CSQ: (list of supporteds),(list of supporteds)

- AT+CMGF: Pone el módem en modo texto (=1)  o en modo PDU (=0), en nuestro caso será en texto.
- AT+CNMI : queremos preguntar al módem cómo serán tratados los nuevos SMS recibidos Su respuesta es: AT+CNMI=,,,,
=1 Descarga los códigos de resultado no solicitados cuando el link TA-TE está reservado.

=1 Devuelve los SMS.

=0 no hay indicaciones CBM para rutar al TE.
=0 no SMS-STATUS-REPORTs son rutados.
=0 El buffer del TA de códigos de resultado no solicitados definidos con este comando son borrados del TE.
- AT+CPMS Almacenamiento preferido del mensaje.
- AT+CSCA  Informa del número del Centro de Servicio


PROCEDURE TForm1.BitBtn1Click(Sender: TObject);
VAR i: integer;
BEGIN
  enviar('ATE0', Rdo, CodigoError);
  IF CodigoError <> 0 THEN
  BEGIN
    showmessage('Se ha producido el error: ' + Rdo)
  END
  ELSE
  BEGIN
    enviar('AT+CMGF=1', rdo, CodigoError);
  END;
  //muestra en un Grid, fila a fila, distintos parámetros del modem
  RellenarGrid(1, 'Calidad de señal', 'AT+CSQ');
  RellenarGrid(2, 'PDU/TEXTO', 'AT+CMGF=?');
  RellenarGrid(3, 'PDU/TEXTO Modo Activo', 'AT+CMGF?');
  RellenarGrid(4, 'CNMI', 'AT+CNMI?');
  RellenarGrid(5, 'CPMS', 'AT+CPMS?');
  //El Centro de Servicio viene dado por el operador de telefonía
  //Es el que se encargar de gestionar los envíos
  RellenarGrid(6, 'Centro de servicio', 'AT+CSCA?');
  Application.processmessages;
  label8.visible := false;
END;

PROCEDURE tform1.RellenarGrid(Fila: integer; Texto, ComandoAT: ansistring);
BEGIN
  Grid2.Cells[0, Fila] := Texto;
  enviar(ComandoAT, Rdo, CodigoError);
  IF CodigoError = 0 THEN
  BEGIN
    IF Ansipos(':', Rdo) > 0 THEN Rdo := Gettoken(Rdo, ':', 2);
    Rdo := stringReplace(Rdo, 'AT+', '', [rfreplaceAll]);
    Rdo := stringReplace(Rdo, OK, '', [rfreplaceAll]);
    Rdo := stringReplace(Rdo, chr(13) + chr(10) + 'OK', '', [rfreplaceAll]);
    Rdo := stringReplace(Rdo, chr(13), '', [rfreplaceAll]);
    Rdo := stringReplace(Rdo, chr(10), '', [rfreplaceAll]);
    Grid2.Cells[1, Fila] := Rdo;
  END
  ELSE Grid2.Cells[1, Fila] := 'Error';
END;

Procedimiento que envía el comando AT al módem

PROCEDURE tform1.enviar(at: ANSISTRING; VAR Rdo: ansistring; VAR CodigoError: integer);
BEGIN
  CodigoError := 0;
  Edit1.TExt := '';
  rdo := '';
  Terminado := False;
  Segundos := 0;
  IF (FStatusTrigger = 0) THEN
  BEGIN
    FStatusTrigger := ApdComPort1.AddStatusTrigger(stLine);
    ApdComPort1.SetStatusTrigger(FStatusTrigger,
      lsOverrun OR lsParity OR lsFraming OR lsBreak,
      True);
  END;
  memo1.Lines.add('>>' + at + chr(13));
  timer1.Enabled := true;
  ApdComPort1.OutPut := at + chr(13);
  REPEAT
    delay(2000)  
  UNTIL Terminado;
END;

Es un objeto tTimer que se encarga de comprobar si el módem ha respondido con un OK o con un ERROR


PROCEDURE TForm1.Timer1Timer(Sender: TObject);
BEGIN
  IF (ansipos(chr(13) + chr(10) + 'OK' + chr(13) + chr(10), Rdo) > 0)
    OR (Segundos > 15)
    OR (ansipos(chr(13) + chr(10) + 'ERROR' + chr(13) + chr(10), Rdo) > 0)
    OR (ansipos(chr(13) + chr(10) + '>', Rdo) > 0)
    THEN
  BEGIN
    IF Segundos > 15 THEN CodigoError := 1; //Timeout
    IF (ansipos(chr(13) + chr(10) + 'ERROR' + chr(13) + chr(10), Rdo) > 0)
      THEN CodigoError := 2; //Se ha producido un error
    terminado := true;
    timer1.Enabled := false;
  END
  ELSE inc(Segundos);

END;


Procedimiento del objeto ApdComPort1 que se dispara para recoger los caracteres del buffer del puerto serie

PROCEDURE TForm1.ApdComPort1TriggerAvail(CP: TObject; Count: Word);
  
VAR
  I: Word;
  C: Char;
  S, s1: STRING;
BEGIN
  FOR I := 1 TO Count DO BEGIN
    C := ApdComPort1.GetChar;
    s1 := c;
    Rdo := Rdo + s1;
  END;

  memo1.lines.add(Rdo);
  IF ansipos(chr(13) + chr(10) + 'OK' + chr(13) + chr(10), Rdo) > 0
    THEN
  BEGIN
    application.processmessages;
    edit1.Text := Rdo;
  END;
END;


Seguiremos con más procedimientos en próximos posts

Relacionados:
Cómo enviar un SMS desde cero ( I )

Cómo enviar un SMS desde cero ( I )

En esta serie de posts os enseñaré cómo se envía un SMS desde cero. 
Para empezar tenemos que contar un hardware que en mi caso es un módem modelo MC35i, tiene una salida para una antena con un imán en la base para fijarla firmemente a un soporte, en la parte de atrás tiene un conector DB9 que es el puerto COM del equipo, que tendremos que unir a la salida del puerto serie del PC, una ranura para insertar la tarjeta SIM que nos la proporcionará nuestro operador de telefonía favorito y por último una salida para poder conectar un teléfono.
Aquí tenéis algunas fotos:










Pues una vez que hemos conectado el módem al PC por el puerto serie y le hemos encendido (parpadeará un led verde), procedemos a ver cómo lo configuramos:


En este caso he utilizado Delphi 7 y Turbo Power Async Pro (o cualquier otra utilidad que os permita enviar y recibir texto por un puerto serie)


Instalamos Turbo Power y ponemos el objeto ApdComPort1 en el Form de nuestra aplicación, con las siguientes propiedades:

AutoOpen=TRUE, para que se active cuando iniciamos el programa.
Baud=9600 se podría poner a 19200 con este módem, pero he visto que trabaja mejor a esta velocidad.
ComNumber=8 es el número de puerto COM
DataBits=8, StopBits=1, SWFlowOption=swfNone



El Form contendrá un botón (Caption=INICIO) y un Grid, donde mostraremos fila por fila los parámetros de inicialización del módem





La inicialización del módem se hace utilizando comandos AT .

Existen cuatro formatos de ejecución para cada  comando AT. Cada formato se determina generalmente por la sintaxis cuyas diferencias se presentan en la siguiente tabla:


Comandos de prueba
AT+CXXX=?
Devuelve una lista de los posibles parámetros que se pueden introducir con el correspondiente parámetro de escritura.

Comandos de lectura
AT+CXXX?
Devuelve el parámetro o conjunto de parámetros actualmente establecidos.

Comandos de escritura
AT+CXXX=<...>
Establece el valor a los parámetros introducido por el usuario.

Comandos de ejecución
AT+CXXX
Este comando utiliza parámetros predefinidos en caso de que no hayan sido modificados con el correspondiente comando de escritura.


La respuesta dada por el módem a cada comando es distinta dependiendo del formato, incluso a pesar de que el comando sea el mismo.


Al final de cada comando se debe incluir un retorno de carro.
Los comandos para el  MC35i son los siguientes:


 Comandos generales.
Comando
Sintaxis / Respuesta
Descripción
AT
AT
OK
Verifica que la comunicación se ha establecido pero no realiza ninguna acción. La respuesta del terminal es OK. De ahora en adelante se marcara en rojo la respuesta del terminal.
AT+CPIN
AT+CPIN=
OK
CPIN=?
READY
Permite introducir el código PIN para poder utilizar la tarjeta SIM.
Una vez lo hemos introducido se puede comprobar si es correcto ejecutando el correspondiente comando de lectura.
AT+CGMI
AT+CGMI
Identifica al fabricante del terminal.
AT+CGMM
AT+CGMM
Identifica el modelo del terminal.
AT+CVIB
AT+CVIB=1

OK
ERROR
Activa el modo vibración. Sólo está disponible para terminales que tengan vibrador incorporado.
Si el modo vibrador se ha activado correctamente
Si el modo vibrador no esta disponible

Comandos para control de llamadas.
Comando
Sintaxis / Respuesta
Descripción
ATD
Ó
ATDT
ATD [;]


OK
NO DIALTONE
BUSY
Realiza una llamada de teléfono al número dado por n. Es importante enviar ; al terminar, en caso contrario la llamada será una llamada de datos. debe ser una cadena de caracteres.
Siempre se recibe esta respuesta.
Se recibe además de la anterior sino hay tono.
Si el interlocutor esta ocupado.
ATA
ATA
OK
CONNECT
NO CARRIER
Responder a una llamada.
En el caso de llamada de voz.
En el caso de llamada de datos.
En el caso de que lo haya fallo de conexión.
ATH
ATH
OK
Finalizar la llamada en curso.
Llamada finalizada con éxito.
ATDL
ATDL
ERROR
Rellamada.
No hay ningún número como último número marcado.

                    Comandos para gestión de la memoria.
Comando
Sintaxis / Respuesta
Descripción
AT+CPBS
AT+CPBS=
OK
AT+CPBS=?

+CPBS: lista posible
OK
Selecciona la memoria activa dada por storage. Storage es una cadena de caracteres.
Consulta la lista de las posibles unidades de almacenamiento.
Devuelve dicha lista.
AT+CPBR
AT+CPBR=
Lee de la memoria activa la entrada en dicha agenda situada en la posición dada por el entero location. Si dicho entero supera el límite de almacenamiento se devolverá un error.
AT^SDLD
AT^SDLD
OK
ERROR
Borra el número establecido como último número marcado.

Comandos para el envío y recepción de SMS.


Comando
Sintaxis / Respuesta
Descripción
AT+CMGF
AT+CMGF
OK
Activa/Desactiva el modo texto.
Con el correspondiente comando de prueba se puede consultar el estado: 1, activado, 0, desactivado
AT+CMGR
AT+CMGR=
OK
Lee el mensaje almacenado en la posición de la memoria activa, dada por el entero n.
AT+CMGD
AT+CMGR=
OK
Borra el mensaje almacenado en la posición de la memoria activa, dada por el entero n.
AT+CMSS
AT+CMSS=
OK
Reenvía el mensaje, anteriormente enviado, almacenado en la posición de la memoria activa dada por el entero n.
AT+CMGS
AT+CMGS=””[,]



> ^z
+CMGS: 149
OK
Envia un mensaje SMS al numero de teléfono indicado en n. n es una cadena de caracteres. Si n incluye el prefijo internacional el tipo de destino puede dejarse en blanco. es un retorno de carro.
El modem responde con > y enviamos el cuerpo del mensaje, texto, seguido de ^z (Ctrl z)



En siguientes posts veremos cómo preparar al módem para enviar y recibir SMS

como enviar un sms con Delphi con zenvia

Zenvia es la empresa líder en movilidad corporativa de Brasil y ofrece a sus clientes la posibilidad de gestionar los envíos SMS desde aplicaciones externas. El método que presento es la forma de gestionar esos envíos desde una aplicación realizada en Delphi.


Ajuste el envío

Primer paso
Haga la  descarga de la biblioteca de integración  en ella se encuentran los  
siguientes archivos:. HumanApiClient 
   Indy _9_00_17_ src. Zip.

Paso 2
Añadir a la  ruta de búsqueda  de  Delphi  el directorio donde están las fuentes del paquete
Indy  y el paquete  HumanClient.

Paso 3
Para cualquier operación de  acceso a enviar  SMS, es necesario identificarse con su
cuenta y código de acceso. Vamos a trabajar con instancias de clases TSimpleSending   TMultipleSending  para el manejo de  SMS  (envío y consulta) 
a través de sus parámetros del constructor de su cuenta y código de acceso.  
Estos datos son transferidos por 
 Zenvia  junto con la negociación del contrato  
firmado con la empresa.

Ejemplo:

sms: = TMultipleSending.create ('Cuenta', 'contraseña');
Envío de  SMS  de forma individual
Primer paso
Utilizar una instancia de la clase  TSimpleSending con su cuenta y contraseña.
Ejemplo:

var
    ret: string;
    sms: TSimpleSending;
comenzar  
    sms: = TSimpleSending.create ('Cuenta', 'contraseña');

Paso 2
Agregue la información de  Para  (requerido)  Mensaje  (requerido)  De  (opcional), Id  (opcional) Horario  (opcional) y devolución de llamada  (opcional)
 
Ejemplo:

sms.From: = 'YourCo';
sms.ToNumber: = 'NumeroDeDestino';
sms.Msg: = 'SuaMensagem';


Paso 3
Llame al método  TSimpleSending.send

Ejemplo:
ret: sms.send = ();

Paso 4

El método de envío de retorno es una  cadena;el  texto que se puede ver en el código y la descripción de la solicitud y el estado 
 de SMS  enviados.
Ejemplo:

StatusBar1.Panels [0] .Text: = ret;    

Envío múltiple  de SMS
Primer paso
Instancia de la clase  TMultipleSending con su cuenta y contraseña.
Ejemplo:

var
    ret: TStrings;
    sms: TMultipleSending;
    Fecha: string;
    typeLayout: string;
comenzar
    sms: = TMultipleSending.create ('Cuenta', 'contraseña');
   
Paso 2

Llame al método  TMultipleSending.LoadList, para llenar una  cadena 
con la lista de  SMS  para ser enviados y el  tipo de diseño de la lista.
En nuestro ejemplo, vamos a elegir la 
 disposición  A, que consiste en
 
"numeroDeTelefone; textoDaMensagem".

Ejemplo:

fecha: = '555199990101; testen555199990102 mensaje; Mensaje de prueba ";
typeLayout: = 'A';
sms.LoadList (fecha, typeLayout);

Paso 3
Llame al método  TMultipleSending.send, a través de clases de parámetros ListResource.
Ejemplo:

ret: sms.send = ();

Paso 4
El método de envío de retorno es un  TStrings; texto que se puede incluir en el código junto con la descripción de la solicitud y el estado del  SMS  enviado a través  de devolución de llamada.
Ejemplo:

ShowMessage (ret.Text);


Fuente: 
programadordelphiatual.blogspot.com.es