Control remoto GSM por mensajes de texto


Resulta ser que un amigo tiene un salón de fiestas infantiles el cual por supuesto dispone de conexión a internet con WiFi para todos los asistentes, ya que van a ver a sus seres queridos en general infantes tanto le importan los chicos a la gente que prefieren pavear con el celular! En fin, mi amigo quien se llama Jorge comparte dicha conexión a internet con los vecinos cuando no la ocupa y como es normal cada tanto el módem de banda ancha se termina colgando y es necesario apagarlo y volverlo a encender. Normalmente esto lo hace en persona pero ahora con este tema de la cuarentena obligatoria el no puede ir al salón cada vez que se cuelga el módem y por ende los vecinos se quejan. Buscamos varias soluciones y terminamos por hacer este control remoto que por medio de mensajes de texto permiten al usuario comandar hasta ocho relés ya sean electromecánicos o de estado sólido los cuales pueden interrumpir y devolver la alimentación AC a los aparatos.

Decidimos emplear un Arduino debido a su bajísimo costo comparado con un microcontrolador por separado, si bien un Arduino no es mas que un microcontrolador Atmega328P con sus componentes anexos necesarios para su alimentación, programación, temporización y demás aspectos eléctricos necesarios lo cierto es que a la escala abrumadora que los Chinos producen estos módulos hoy usar un Arduino cuesta una cuarta parte o menos que emplear por separado cualquier microcontrolador y por ello es que decidí hacerlo con uno. Nótese que no puse en el circuito UNO o Nano puesto que como son idénticos en cuanto a lo electrónico y su única diferencia es el factor de formato se puede implementar el mismo programa para ambas placas sin problemas. Claro está que para meterlo dentro de una plaqueta universal junto con el módulo GSM es mas práctico la versión Nano. Ya que lo he mencionado al pasar el módulo GSM que decidí emplear es el Simcom SIM340DZ del cual pueden ver una foto a continuación

Si bien no es el módulo mas pequeño del mercado ni es una versión moderna lo cierto es que en sitios de venta en línea se consiguen por muy poco dinero y no dejan de ser muy funcionales sin nada que envidiarle a módulos mas modernos como el SIM800L. Las conexiones de este módulo hacia el Arduino son sólo tres, una línea que comanda el encendido y apagado del SIM340, al ponerse a masa el módulo se enciende y al liberar la masa se apaga; una línea de transmisión o Tx a través de la cual el módulo SIM340 le envía datos seriados al microcontrolador en nuestro circuito un Atmega328P dentro de un Arduino y una tercera y última línea por la cual recibe datos o Rx. Esta comunicación serie se efectúa en forma asincrónica (sin líneas de reloj o clock) a 19.200 bits por segundo. El módulo SIM340DZ propiamente dicho, no la placa del módulo se alimenta de 3.7V tensión que se consigue con los reguladores conmutados en serie dentro de la plaqueta que contiene a dicho módulo por ello basta con darle 12V por el terminal 7 del conector y tener presente una corriente de al menos 1A en 12V. Si bien el módulo dispone de una salida regulada de 5V que bien podría alimentar al Arduino teniendo presente que éste ya dispone de su propio regulador de tensión AMS1117-5.0 en su interior no vale la pena cargar con este consumo al regulador del módulo GSM por tanto el pin 9 del conector lo dejaremos desconectado. Hay que tener presente que la comunicación sólo está permitida con el terminal RTS a masa.  En cuanto al sketch a cargar en el Arduino el mismo lo pueden bajar haciendo clic aquí.

El sketch inicialmente incluye la librería SoftwareSerial.h la cual permite hacer una comunicación serie asincrónica virtual o simulada por software empleando para ello pines de E/S tradicionales sin ocupar la UART del chip la cual está vinculada al conversor USB CDC para la programación del sketch y para el uso con la terminal serial. Utilizando esta librería podemos comunicarnos vía serie con el módulo SIM340. Seguidamente incluye la librería EEPROM.h la cual nos permitirá almacenar datos no volátiles en la memoria FLASH interna del uC Atmega328P. Luego definimos nombres "entendibles" a los diferentes pines de E/S del Arduino entre ellos el LED incorporado en la misma placa y que nos indica cuando está inicializando el sistema, las ocho salidas y el control de encendido del módulo GSM. Seguidamente se crea una instancia de la librería SoftwareSerial a la cual llamamos SIM340 para su fácil identificación y le indicamos que recibirá datos por el pin 2 y los enviará por el pin 3 ambos digitales del Arduino. Se declaran ocho posiciones de memoria de 32 bits sin signo destinadas a contener un número representativo de la cantidad de milisegundos a los cuales se debe encender cada una de las ocho salidas, se emplea este tipo de variables puesto compararemos dicho número con el devuelto por la función millis() del Arduino y esta función retorna un entero largo de 32 bits sin signo por ende necesitamos contrastar dicho valor con el almacenado en las variables declaradas. Mas adelante se explicará con mayor detalle. Un arreglo de cien caracteres se destina para almacenar los datos provenientes del módulo SIM340, otro arreglo de caracteres al que llamé mensaje también de cien caracteres es el encargado de almacenar el mensaje que el sistema enviará al módulo SIM340 y finalmente éste al usuario por medio de un SMS, un arreglo de caracteres de 30 posiciones almacena el número celular al cual enviar las respuestas por SMS, mas adelante es explicará en detalle como se obtiene dicho celular. El registro posicion es el encargado de apuntar en qué posición del arreglo de caracteres llamado cadena se debe meter cada caracter que se va recibiendo desde el SIM340 y el booleano modemListo se pone en alto cuando concluye la fase de inicialización del SIM340 tras su encendido.

La función setup como en todo programa Arduino contiene las sentencias e instrucciones necesarias para la puesta en funcionamiento y correcta inicialización de los dispositivos y pines de E/S. Se definen como salidas los pines destinados al LED on board y a las ocho salidas y se llama a la función encargada de leer la EEPROM interna del uC y esa función pone en 1 o 0 las correspondientes salidas del equipo conforme a como estaban antes de apagarse. Esto para evitar que ante un corte de energía todo arranque apagado y sea necesario volver a encenderlo. Se enciende el LED indicando que el proceso de inicialización del módulo SIM340 está en marcha y se enciende el módulo. Una vez alimentado el módulo y aterrizado el pin On/Off y conforme se van dando los procesos internos de inicialización enviará a través de su pin Tx llegando al arduino a través del pin Rx del SoftwareSerial (pin D2) los siguientes mensajes: RDY, luego en otra línea +CFUN: 1, luego en otra nueva línea +CPIN: READY, luego en otra línea CALL READY que en verdad lo envía en minúsculas pero la función de recepción está armada de tal manera que lo convierte en mayúsculas y seguidamente queda a la espera de recibir comandos. RDY representa la primer indicación de buen funcionamiento del módulo y debemos esperar las siguientes indicaciones. El mensaje +CFUN: 1 hace alusión a las funcionalidades telefónicas al ver un 1 en dicho parámetro indicador estamos con funcionalidad total, un 0 en dicho parámetro es funcionalidad básica (sólo llamadas de emergencia) y un 4 indica que están deshabilitadas todas las funcionalidades del SIMcard. Deberíamos ver este parámetro detenidamente (algo que el código en este momento no hace) y en caso de no ser un 1 dejar el LED OnBoard destellando como señal de problemas con la comunicación. Un típic problema es la imposibilidad de registrar el SIMcard en el operador celular ya sea por vencido o no habilitado. La indicación +CPIN: READY nos indica que no es necesario enviar al SIM340 el código PIN provisto por el emisor de la SIMcard o chip celular dado que el mismo no esta bloqueado. En caso de estarlo en este parámetro nos retornaría SIM PIN o SIM PUK según sea el código programado. Claramente es otro parámetro que deberíamos observar y pedir al usuario de alguna forma que ingrese PIN o PUK de ser necesario para lo cual requeriríamos de algún método de entrada como podría ser el uso del terminal serial. Es bueno guardar el cartón de soporte de las tarjetas SIM porque en dichos cartones están esos números. Habiendo llegado el CALL READY estamos en condiciones de enviar algunos comandos que establecerán parámetros para un correcto uso del módulo, estos son:

ATE0 el cual le indica al módulo SIM340 que no devuelva los caracteres que le enviamos vía serie, en otras palabras que desactive el eco (o echo en inglés) y de ahí la E en el nombre del comando y el 0 para desactivar. Si enviáramos ATE1 activaríamos el retorno o eco de caracteres que a nosotros no nos sirve de nada mas que para tener mas caracteres que procesar sin razón.

ATV0 establece que las respuestas a partir de este momento serán numéricas y no mas alfanuméricas ayudando así a reducir al mínimo el tráfico de datos innecesarios desde el SIM340 hacia el Arduino. Estando este parámetro en 0 cuando el módulo nos quiere decir OK manda 0, cuando nos quiere decir CONNECT nos dice 1, RING (timbre sonando) es 2, NO CARRIER (no se logro conectar) 3, ERROR lo representa enviando 4, NO DIALTONE (no pudo marcar al número especificado) 6, BUSY (o línea ocupada) 7 y NO ANSWER (al que llamamos no nos atiende) 8. Nótese que 5 no es ocupado!

Estos dos parámetros ATE0 y ATV0 no son estrictamente necesarios, para la forma en la que armé este programa vienen mejor puestos así pero no por no ponerlos el módulo no va a funcionar correctamente. Si estan conformes con los ecos y no les molesta verlos pasar por la terminal repetidamente, pueden obvir ATE0 y si prefieren tener respuestas con palabras en vez de simples resultados numéricos pueden dejar ATV1 o no poner ATV0 y listo. A mi para el uso que se le está dando me parece mejor como lo he planteado.

ATS0=0 establece en cero la cantidad de veces que debe dejar sonar el teléfono antes de atender, estando este parámetro en cero se deja deshabilitada la función de atender automáticamente una llamada. Esta línea es importante porque de fábrica viene seteado para atender al segundo ring que suena y no tiene sentido que así sea. Convengamos que el módulo no incorpora físicamente micrófono ni parlante por lo que no se oye ni transmite nada pero no tiene lógica que se auto establezca una comunicación telefónica sin utilidad.

AT+CLIP=1 habilita el sistema de identificación de llamadas o Caller ID permitiendo al Arduino por medio de algunas maniobras que ya veremos "saber" quien le envía los mensajes y por ende a quien debe responderle. Sin este comando no podríamos enviar respuestas sin tener el número telefónico previamente memorizado de alguna forma.

Luego de cada uno de estos comando he puesto una pausa de medio segundo, por ahí con 100 milisegundos también funcionaba pero como no hay prisa en esta etapa inicial mejor que sobre y no que falte! Terminado este proceso se apaga el LED de la placa Arduino quedando el sistema listo para recibir comandos. Nótese en la terminal serie que al haberse activado el método de respuestas numéricas obtenemos cuatro ceros, un OK por cada una de estas instrucciones enviadas.

Nuestra función principal o loop() es muy compacta y fácil de entender. Primero mira si hay algún dato proveniente del módulo SIM340 y en caso de haberlo se dirige a la función leeRX(). Seguidamente se comprueba si al menos 14 dígitos fueron recibidos desde el SIM340 y en caso afirmativo se procede a ver si el último caracter recibido fue un salto de línea mas comúnmente conocido como ENTER.  Para entender todo lo que sigue será necesario ver que es lo que el SIM340 nos proporciona por cada mensaje de texto:

Recordemos que los cuatro ceros nos los devolvió el módulo tras la inicialización por lo que ahora vemos como nuevo la línea que comienza con +CMT: y seguidamente el número de teléfono desde el cual recibimos el mensaje entre comillas, una coma separadora de datos, entre comillas el nombre porque el número telefónico se encuentra almacenado en la SIMcard con ese nombre entre comillas y luego de una nueva coma de separación y también entre comillas la fecha y hora del mensaje. En una línea inferior recibimos el mensaje en si, pero no tal cual lo enviamos desde el celular sino convertido en mayúsculas para facilitar la tarea de comparación de caracteres a la hora de determinar que hacer. Entonces la función chequeaRemitente() es llamada primero para ver si tiene que recibir el número telefónico del cual le están enviando el mensaje para que sea a ese número al cual envíe la respuesta correspondiente. Ya veremos mas adelante como funciona dicha función. El siguiente condicional verifica si la clave recibida es correcta, en este programa y haciendo honor al nombre de mi amigo que me pidió esto le puse JORGITO. Si la primer palabra no es JORGITO (sin importar mayúsculas) lo que sigue NO se ejecuta. Superada la etapa de clave se determina la función recibida, la segunda palabra y esta puede ser ENCIENDE, APAGA o RESETEA. cualquier otra cosa no es válida ni tomada en cuenta. Dependiendo de que función se haya recibido es la función a la que se llama por medio de los condicionales. Luego se vacía el arreglo de caracteres y se vuelve a guardar desde la posición 0 y se controla si ha pasado el tiempo de reseteo de cada una de las salidas siempre y cuando se esté en proceso de reseteado (que el valor almacenado sea superior a 0).  Hasta aquí todo lo lo que hace la función principal. Ahora veamos que hacen las distintas funciones llamadas antes.

La función encender() determina primeramente que canal debe accionar y para ello mira la posición 17 de la cadena (tengamos presente que se cuenta de cero en adelante por lo que si el primer caracter es el de posición 0 en el array el 17 tiene como número de índice el 16). Luego se usan ocho bloques if para determinar si hay que encender la salida 1, la 2, la 3 y así hasta la 8 pero cada bloque hace lo mismo solo que sobre su salida. Antes que nada pone en cero el registro de demora de re encendido usado para los reseteos esto por si le damos encender mientras estaba ejecutando una función de reset aún no terminada, luego encendemos la salida en sí y luego avisamos al usuario por medio de un mensaje de texto que hemos encendido dicha salida. Fuera de los bloques if grabamos la memoria del microcontrolador para que ante un corte de energía vuelva en la nueva configuración de encendidos y apagados y regresa a de donde fue llamado.

La función apagar() hace exactamente lo mismo que la anterior sólo que para el apagado de cada salida.

La función resetear() al igual que las dos anteriores determina primero que salida el usuario quiere resetear para lo cual mira el caracter recibido en la posición 17 del mensaje. Cual sea este canal a resetear primeramente se apaga dicha salida, se envía al usuario un mensaje que se ha apagado dicha salida, se carga en el registro de temporización correspondiente a dicha salida un valor igual al valor actual de millis() mas 10.000. Para quienes no lo conocen millis() es una función del Arduino que devuelve la cantidad de milisegundos trascurridos desde que se alimentó el uC, es un contador entorno a un registro entero sin signo de 32 bits o sea que puede contener un valor comprendido entre 0 y 4.294.967.295 (casi cuatro mil trescientos millones!) lo que en teoría equivale a una vuelta completa en algo asi como 50 días de funcionamiento. Claro está que en algún momento todo se termina, el registro se llena y por supuesto vuelve a empezar desde cero! Por lo que debemos contemplar dicha condición. Que sucedería si justo, justito cargamos el registro demora1 cuando millis() tiene un valor acumulado de 4.294.957.295 mas 10.000 ??? Sin dudas que el valor cargado en demora1 será 0 por lo que quedará en teoría desactivado el sistema de reseteo automático, recordemos que en las últimas líneas de la función loop() se verificaba si las variables demora eran mayor a 0... Pero aquí 0 sería el momento preciso en donde terminaría la temporización, para evitar este muy poco probable pero posible al fin problema es que la siguiente línea verifica si el registro queda en 0 tras adicionarle 10.000 a millis(), que sea un milisegundo mas entonces! O alguien lo notará ? Y eso es todo lo que sucede al reiniciar una salida, en verdad sólo se apaga y se anota en qué momento de la vida del microcontrolador la misma debe ser vuelva a encender.

De esos menesteres precisamente se encargan las funciones chequearReset1() a chequearReset8() respectivamente para cada una de las salidas. Recordemos que entramos aquí desde el loop() sí y solo sí tenemos algún valor en el registro demora correspondiente. El registro demora siempre tendrá un valor mayor a millis() en tanto reste tiempo por transcurrir y recién cuando dicho tiempo haya sido alcanzado o incluso superado es que demora será igual o incluso menor a millis(), en ese momento es que se vuelve a encender la salida correspondiente, se pone en 0 el registro para evitar volver a entrar sin corresponder y como era de esperarse se avisa al usuario por medio de un mensaje de texto.

Superadas las ocho rutinas anteriores la función leeRX() se encarga de obtener un caracter del buffer virtual de recepción de la instancia SIM340 de SoftwareSerial y almacenarlo en el array de caracteres destinado para tal fin en la posición apropiada no sin antes convertirlo en mayúscula de ser necesario. Como método de depuración y control dicho caracter es enviado a la PC a través del USB CDC puerto serie. Cuando el programa está recién arrancado o cuando se ha terminado de leer toda una línea el registro llamado posicion se encuentra en 0 y el array de caracteres vacío (en realidad lleno de espacios). Al recibirse un caracter por SIM32.read() el mismo es sometido a un condicional que indaga si su número ASCII se encuentra entre el 97 (que corresponde a la letra a) y el 122 (que corresponde a la z) de ser así sea cual fuese el valor le resta 32 quedando ahora dentro de la parte de la tabla ASCII que comprende las mayúsculas. Luego se guarda en el array de caracteres llamado cadena en la ubicación indicada por posicion y se avanza en 1 unidad dicho contador. Se envía el dato a PC vía puerto serie y aquí termina la rutina de recepción.

La función chequeaRemitente() se encarga de identificar en que parte de la cadena recibida aparecen los caracteres +CMT: (incluyendo el espacio). Esto se hace porque he notado que no siempre el + arranca desde la posición 0 del array y por ende si utilizo posiciones fijas (valores constantes) si se desplaza hacia posiciones mas adelantadas no se podría procesar. De encontrar la posición en la cadena que se encuentra el primer signo + se ocupa el primer bucle for. Porqué es necesario ubicar sólo el primero ? Porque los números telefónicos globales o GSM incluyen un + en vez del clásico doble cero del sistema de telediscado anterior. Por cuanto si no dejáramos de buscar luego de encontrar el primer + correspondiente al comienzo de la línea seguro el del número telefónico nos complicaría el resto del código. Encontrado el + sabemos que estamos ante la línea que trae dentro el número celular del remitente y por ende tenemos que obtenerlo. Para ello ahora partiendo desde siete posiciones mas adelante que donde encontramos el signo + buscamos una coma, que es la que divide entre el fin del número telefónico y el comienzo del nombre almacenado en la SIMcard. Encontrada la coma ahora sabemos desde que posición y hasta qué posición tenemos que hacer el for para "copiar" desde cadena el número telefónico incluyendo las comillas porque nos serán de utilidad. Terminada la copia del número entre comillas ponemos un fin de cadena o caracter nulo al final y esta función no hace mas nada!

La función ChequeaClave() devuelve un booleano que bien puede ser true o false o 1 o 0 dependiendo del contenido de los primeros ocho caracteres de la cadena recibida. Si se ha recibido JORGITO (incluyendo el espacio final) devuelve un true o 1 mientras que de cualquier otra forma devuelve 0 o false.

La función chequeaComando() revisa la cadena recibida en busca de las palabras RESETEA, ENCENDE ó APAGA y devuelve 1, 2 o 3 según corresponda. En cualquier otra circunstancia devuelve 0.

Las dos funciones siguientes, avisaApagado y avisaEncendido reciben un char sin signo (valor entre 0 y 255) correspondiente al número de canal que deben notificar y envían un mensaje de texto diciendo SALIDA x APAGADA ó SALIDA x ENCENDIDA respectivamente donde x depende del valor recibido al llamar a la función. Nótese que al valor recibido se le suma 48 para que lo transmitido sea el caracter ASCII correspondiente al número de salida.

La función enviarMensaje() se ocupa de enviar al módulo SIM340 los comandos necesarios para una correcta operación de dicha tarea. Lo primero que hace es enviar el comando AT+CMGF=1 el cual le indica al SIM340 que quiere enviar un mensaje en modo texto. Luego envía el comando AT+CSCS="GSM" con lo que se le indica al módulo SIM340 que se utilizará el set de caracteres por default del sistema GSM. Luego enviamos el comando AT+CMGS="nro. telefonico" con lo que le indicamos que el mensaje queremos que se envíe al número telefónico almacenado en el array telefono. Nótese que primero enviamos la instruccion AT+CMGS= y en un segundo envío mandamos el teléfono que previamente estaba almacenado en el char array telefono. Entre cada una de estas instrucciones he decidido darle un tiempo de 200 mili segundos para que el módulo "se acomode" pero en ninguna parte del PDF del mismo dice que sea realmente necesario. Luego se envía el mensaje en sí y para terminar se envía un caracter de control Z el cual es representado por el hexadecimal 1A. De esta forma se envía un mensaje de texto.

La función leeMemoria() se encarga de levantar el dato presente en la posición 0 de la memoria EEPROM o no volátil interna del ATmega328P que puede ser cualquier valor entre 0 y 255 dado que es un entero de ocho bits sin signo. Valiéndome de las instrucciones de lectura de bits acomodo cada uno de estos bits en cada una de las ocho salidas del módulo de reles y de esta forma el dato guardado se convierte en estado de salidas.

La función grabaMemoria() hace exactamente lo opuesto a la anterior, mirando cada una de las ocho salidas acomoda cada uno de los ocho bits de una posición de memoria que luego será almacenada en la EEPROM.

Y hasta aquí todo el sketch. Por supuesto que se puede mejorar y mucho, por empezar la clave podría ser modificable vía mensaje de texto, mandando JORGITO CLAVE PABLITO podríamos cambiar la clave actual (JORGITO) por PABLITO y por supuesto que habría que guardarla en EEPROM interna. Como habrán notado no emplee el objeto String de Arduino y con ello me la compliqué bastante haciendo en un nivel mucho mas bajo la búsqueda y comparación de caracteres. Lo cierto es que todavía no me hallo demasiado con este objeto ni sus funciones y como mi amigo necesitaba el programa hice tripa corazón y me mandé a la vieja usanza. También se le podría agregar como parámetro de canal la T de todo entonces poniendo CLAVE RESETEA TODO o CLAVE RESETEA T (sería lo mismo) voltea todo de un saque y no hay que hacerlo canal por canal. En fin, mejorable siempre es mejorable y dejo abierto este canal para que quien tenga ganas de enviar su versión lo haga y con gusto la publico.

Por último pero no menos importante para quien quiera el pdf con los comandos del SIM340 aquí puede bajarlo.

Pablo Canello, 28/05/2020