Está en la página 1de 40

Manual del Usuario / Desarrollador

Nos complace que haya decidido conocer más sobre la Alarma Vecinal Técnica 1, una alarma
vecinal en curso de alcanzar calidad comercial con el aporte de usuarios y desarrolladores
independientes.
Basado en tecnología abierta, este dispositivo le permite que como usuario se interiorice en su
funcionamiento y pueda construirlo, mejorarlo, repararlo y adaptarlo a sus necesidades sin pedir
permiso ni pagar ningún tipo derecho de propiedad.
Si necesita ayuda puede comenzar leyendo la documentación, luego puede consultar a la comunidad
de desarrolladores, y también puede contactar a los desarrolladores originales. Tenga en cuenta, sin
embargo que es un esfuerzo comunitario y será muy valorado su aporte tanto en mejoras, como en
la solución de problemas. También tenga muy presente que este dispositivo, si bien fue diseñado, y
programado con dedicación y cuidado, ES EXPERIMENTAL, NO TIENE ABSOLUTAMENTE
NINGÚN TIPO DE GARANTÍA, NI SIQUIERA LA DE UTILIDAD O USO PARA UN FIN EN
PARTICULAR, Y USTED USA EL DISPOSITIVO Y/O EL DISEÑO A SU ENTERO RIESGO.

Es un desarrollo escolar original realizado en la Escuela de Educación Secundaria Técnica Nro 1 de


Florencio Varela, en el marco de la materia Prácticas Profesionalizantes correspondiente al 7mo año
de la Especialidad Electrónica.
Sumario
Sobre este manual.................................................................................................................................3
Preparación y consejos de instalación..................................................................................................3
Operación.............................................................................................................................................4
Información para el desarrollador........................................................................................................5
Organización del Software...............................................................................................................5
Funcionamiento del programa.........................................................................................................6
Trama de órdenes.............................................................................................................................6
Comandos........................................................................................................................................7
Lista de comandos.......................................................................................................................8
Organización del código................................................................................................................11
Definición de variables globales y constantes...........................................................................11
Ajuste de parámetros de configuración.....................................................................................14
Bucle principal..........................................................................................................................15
Funciones..................................................................................................................................19
Sobre este manual
El presente manual pretende ser una introducción sobre las características de la Alarma Vecinal y
una guía para que los usuarios/desarrolladores puedan comenzar a trabajar con el diseño. Si bien se
pretende que este sincronizado con la última versión del software y hardware de la alarma, es
posible que esto no ocurra del todo y que describa características que se han eliminado o algunas
que se implementarán más adelante. En tal sentido, la forma más segura de conocer el estado actual
de desarrollo es mirando el código del software y los documentos del hardware.

Preparación y consejos de instalación


El diseño original de la alarma provee una placa base de soporte para los varios módulos que la
componen, una fuente de alimentación para 220 volts y un sistema de pilas de ion de litio como
fuente de energía de respaldo. También ofrece una salida de potencia para conectar una bocina,
sirena o chicharra como dispositivo optativo de aviso que debe alimentarse por separado.

Todo el dispositivo tiene que colocarse en una caja estanca del tipo que se usa en las instalaciones
eléctricas y proveer las conexiones necesarias con el cable correspondiente, sellando los orificios
con silicona de buena calidad, si es que se colocará el dispositivo a la intemperie.

Para su adecuado funcionamiento se requiere una tarjeta SIM activa y se debe garantizar que
siempre esté en condiciones de emitir mensajes SMS. Este es un gasto fijo para la operación de la
alarma. El otro gasto fijo es el consumo eléctrico, pero la potencia consumida es mínima.

Una vez armado se deben revisar todas las conexiones eléctricas,cargar el software y luego verificar
que todo funcione. Esto se puede hacer de varias maneras, pero un camino posible es:

1. Revisar las conexiones eléctricas y asegurarse que llegue la alimentación adecuada a todos
los módulos, junto con el resto de las señales.
2. Insertar una tarjeta telefónica tipo SIM con una línea activa y capaz de emitir mensajes SMS
3. Con una computadora personal y un cable usb, cargar el software de la alarma en la placa
Wemos D1.
4. Cargar la app y conectar mediante Bluetooth. Si se pide un pin al conectar introducir 1234.
5. Verificar la conexión encendiendo y apagando la chicharra desde la app.
6. Dar de alta por lo menos un usuario y vincular por lo menos un llavero.
7. Accionar el llavero y verificar que encienda la chicharra y que llega el SMS de pedido de
ayuda.
8. A partir de este punto se puede instalar la alarma en su lugar definitivo.
9. Si hubo problemas, ver en este manual la sección “Solución de problemas”

Se sugiere colocar la alarma en un lugar despejado y con contacto visual con todos los sitios desde
donde se pretende activarla con los controles remotos.
Si bien las compañías comerciales suelen colocar sus dispositivos en postes telefónicos, de
iluminación, de distribución de energía, etc. se recomienda evitar estos emplazamientos por motivos
tanto legales como de posible riesgo eléctrico u otros. En su lugar será preferible un sitio adecuado
en la casa de un vecino, que además de estar despejado y ofrecer resguardo para el dispositivo, sea
accesible con facilidad ante la eventualidad de una reparación, expansión, etc.

Operación
La alarma debe tener suministro eléctrico permanente, debido a que debe permanecer siempre
encendida. Ante la eventualidad de un corte de energía eléctrica, el equipo cuenta con un paquete de
baterías de respaldo para continuar operativo, pero en tanto no haya electricidad de red, la chicharra
no estará disponible, aunque seguirá funcionando el envío de mensajes SMS para dar aviso de
cualquier evento.

La administración de la alarma estará a cargo del primer usuario ingresado, quien validará su
identidad accionando su llavero cuando desee entrar al modo de administrador. Este usuario será el
que dará de alta, o de baja a los demás usuarios.

Cada control remoto (llavero) habilitado, si es accionado hará que la alarma haga sonar la sirena por
cierto tiempo, y que luego envíe mensajes SMS a todos los demás usuarios solicitando ayuda. Si
esto ocurre, será la comunidad la que debe definir un protocolo de atención de este suceso. La
alarma sigue operativa luego de ser activada y no requiere ninguna atención especial.
Información para el desarrollador
Un usuario interesado puede convertirse en desarrollador si lo desea. De este modo, las
comunidades pueden adaptar este producto a sus necesidades agregando, modificando, quitando, o
mejorando características. Además, pueden corregir cualquier error ni bien se presenta, sin pedir
autorización a nadie.

Nuestra alarma es propiedad de cada una de las comunidades que las usan, y nos complace pensar
que nos enriqueceremos con los aportes de todos para conseguir un producto cada vez más
competitivo y fiable.

A continuación presentamos información para que los desarrolladores puedan comenzar su trabajo
de manera eficaz.

Organización del Software

En principio podemos dividir el software en tres partes: 1) Configuración inicial, 2) bucle principal
y 3) Funciones. Esta organización se muestra en la figura siguiente
La configuración inicial determina los ajustes del hardware y del software necesarios para que el
equipo comience a trabajar.

El Bucle Principal es la parte del programa que se encarga de esperar las órdenes y de procesar los
acontecimientos conforme se van presentando.

Las funciones son procedimientos encargados de tareas específicas que se repiten varias veces a lo
largo del programa, y que por lo tanto, escriben una vez para que se usen cada vez que sea
necesario.

Modificar o corregir el programa puede significar interactuar con una o varias de estas partes.

Funcionamiento del programa


El software de la alarma reacciona a dos cosas

1. Órdenes

2. Eventos

Las órdenes son instrucciones que le da el operador, ya sea por Bluetooth o por medio de una
computadora conectada por USB.

Los eventos implementados hasta ahora son las señales recibidas desde los llaveros.

Trama de órdenes
La trama se definió en etapas tempranas del proyecto y de ahí los nombres y el formato. Si bien
resulta útil y funcional, es posible que se modifique en el futuro. La trama es una secuencia de bytes
que está estructurada en un formato específico tal como se muestra
Los 2 primeros bytes representan la acción que se espera que ejecute el equipo y son obligatorios.

Los 11 bytes siguientes representan un número telefónico y debe completarse obligatoriamente,


aunque sea con caracteres de relleno.

El resto de los bytes no es necesario usarlos todos, pudiendo utilizarse sólo los que hagan falta.

Comandos
Un comando es una secuencia de caracteres que el dispositivo interpreta realizando alguna acción
determinada. Los comandos vienen desde un dispositivo conectado por USB o Bluetoot contenidos
en la trama de órdenes

Los comandos se envían como una trama de bytes desde un programa terminal (como
HyperTerminal en plataforma PC, CuteCom en GNU/Linux, o Bluetooth terminal en Android -solo
modo administrador-) mediante una conexión USB (PC o GNU/Linux) o Bluetooth (Android).

El programa terminal debe configurarse a 9600 bps, 1 bit de stop, sin paridad, sin control de flujo y
para que envíe los caracteres CR/LF (Retorno de carro, línea nueva) al enviar la cadena de
caracteres.
Lista de comandos

0xAA: Envía un comando AT crudo al módulo SIM900 contenido en los bytes de mensaje de la
trama.

Ejemplo:

AA1234567890AT+CREG?

Comentario: Envía el comando AT+CREG? Al SIM900

--o0o--

0xA1: Realiza un test del módulo SIM900 ejecutando varios comandos AT

Ejemplo:

A1

Comentario: Interroga al SIM900 sobre varios parámetros de funcionamiento.

--o0o--

0xA2: Lee cualquier respuesta desde el SIM900

Ejemplo:

A2

Comentario: Lee lo que ha respondido el SIM900

--o0o--

0xA3: Reinicia el módulo SIM900 por software

Ejemplo:

A3

Comentario: Envía el comando AT+CFUN=1,1 que reinicia el módulo SIM900

--o0o--

0xBB: Acciona la tecla powerkey del SIM900 (comando en desuso)

Ejemplo:

BB

Comentario: No implementado en el hardware.


--o0o--

0x1A: Formatea la memoria flash de la tarjeta Wemos D1

Ejemplo:

1A

Comentario: Da formato a la memoria flash. Borra todo el contenido de datos.

--o0o--

0x1B: Crea un archivo si no existe, o lo borra y lo vuelve a crear.

Ejemplo:

1B

Comentario: Crea/Borra el archivo de usuarios. No pide nombre de archivo.

--o0o--

0x1C: Agrega un nuevo número de difusión para solicitar ayuda. El número debe venir contenido
en el campo de número telefónico de la trama. El número de llavero debe venir en los 10 bytes
siguientes. Y en los 40 bytes que siguen al número de llavero, debe venir el nombre y apellido del
usuario. Si en la los bytes de número de llavero se pone AAAAAAAAAA o aaaaaaaaaa, la alarma
pedirá capturar llavero.

Ejemplo 1: Ingreso directo

1C11547028821593578521Juan Perez

Comentario: Ingresa un usuario con un número que posee el número telefónico


1154702882 y con un llavero de código 1593578521; el usuario se llama Juan Perez

Ejemplo 2: Ingreso indirecto

1C1154702882AAAAAAAAAAJuan Perez

Comentario: Ingresa un usuario con un número que posee el número telefónico


1154702882 y con un llavero de cuyo código será capturado por el dispositivo para luego grabarlo;
el usuario se llama Juan Perez. Para capturar el llavero, simplemente se debe presionar el botón
correspondiente para que emita el código que será grabado.

--o0o--

0x1D: Lista todos los usuarios almacenados en el archivo de usuarios


Ejemplo:

1D

Comentario: Muestra la lista completa de usuarios, con sus número telefónicos, sus
nombres y código de llavero.

--o0o--

0x1E: Envía sms a todos los usuarios

Ejemplo:

1E

Comentario: Envía un SMS de prueba a todos los usuarios registrados

--o0o--

0x20: Busca si existe el número de llavero entre los guardados en el archivo de usuarios

Ejemplo:

20

Comentario: No implementado

--o0o--

0x21: Captura de llavero y muestra el número en el puerto serie.

Ejemplo:

21

Comentario: Si se presiona un llavero de 433MHz, se detecta su código y lo muestra por el


puerto serie

--o0o--

0x30: Enciende la bocina

Ejemplo:

30

Comentario: Activa el relé que enciende la bocina, sirena u otro medio de aviso de
potencia.
--o0o--

0x31: Apagar bocina

Ejemplo:

31

Comentario: Desactiva el relé que enciende la bocina, sirena u otro medio de aviso de
potencia.

--o0o--

0x40: En desuso

0xDD: Quita un número de a lista difusión

Ejemplo:

DD

Comentario: Aún no implementado completamente.

--o0o--

Organización del código


El software está dividido en cuatro partes

1. Definición de variables globales y constantes

2. Ajuste de parámetros de configuración

3. Bucle principal

4. Funciones que implementan las capacidades del programa

A continuación veremos qué código compone cada parte . Notar que al tratarse de un programa en
desarrollo, puede haber código innecesario o bajo prueba.

Definición de variables globales y constantes

// Centro de mensajes

#define CENTRO_DE_MENSAJES "54079000801"

// Pin donde se conecta el módulo receptor de 433Mhz / 315MHz

#define PIN_RF D3
// Pin donde se conecta el pin pwrkey del SIM900

#define PIN_PWRKEY D10

// Pin donde se conecta el pin netlight del SIM900 (no se usa ahora)

#define PIN_NETLIGHT D11

// Para el caso de las cadenas, recordar que llevan los caracteres útiles

// más un caracter adicional para el terminador nulo.

// Longitud máxima de la cadena de órdenes (trama) desde el PC ó móvil.

#define MAX_TRA 300

#define MAX_SMS 255 // Longitud máxima del sms

// cantidad de dígitos máxima para el número telefónico.

#define MAX_NUM 11

#define MAX_COM 3

#define LLAVE1 12999074 // código del llavero de pruebas

#define RELE D0

/*

* "lista.lst" es el nombre del archivo donde se guardan los usarios de

* la alarma (personas que pueden activarla) y los números de difusión

* (aquellos números a los cuales la alarma envía mensajes). Un usuario

* puede o no ser parte de la difusión, y un número de difusión puede o no

* pertenecer a un usuario

* *lista es el nombre de la variable que contiene el nombre del archivo

* usuarios es el descriptor que hace referencia al archivo contenido en

* *lista.

*/

const char *lista = "/lista.lst";

File usuarios; // Descriptor del archivo

/*

* Campos:

* [14- IIINNNLLLLLLLL];[10 - llavero];[40- Nombre y apellido];

*
* Los campos son los siguientes:

* 14 caracteres de número telefónico: 3 de prefijo internacional, 3 de prefijo


nacional, y 8 de número local

* 10 caracteres para guardar el número de llavero

* 40 caracteres para el nombre y apellido

*/

// Puerto para el módulo SIM900

SoftwareSerial SerialSim (D7, D6); // RX, TX

// Puerto para el módulo bluetooth

SoftwareSerial SerialBT (D5, D4); // RX, TX

// Receptor de los botones de pánico

RCSwitch receptorRF = RCSwitch ();

// 'trama_ord' es la cadena (trama) que se envía desde la PC o el móvil

// con las órdenes para la alarma

char trama_ord[MAX_TRA];

// Una primera decodificación de la trama se hace bajo esta estructura

struct orden {

char comando[MAX_COM + 1];

char numero[MAX_NUM + 1];

char mensaje_sms[MAX_SMS + 1];

};

// Estructura de identificación de usuario

struct usuario {

char telefono [11]; // diez caracteres de 0 a 9 más el terminador nulo

char llavero [10]; // 8 caracteres más el terminador nulo

char nombre [46]; // 44 caracteres más el terminador nulo

int estado = 0; // 1, ayuda solicitada; 0, sin solicitud de ayuda

};
Ajuste de parámetros de configuración
En la plataforma Arduino compatible es usual poner todos los parámetros de inicialización en la
función setup, como una manera de organizar el código. En este caso se inicializan los puertos de
comunicación con el módulo SIM900 (módulo celular) y el módulo Bluetooth. También se
inicializa el sistema de archivos SPIFFS.

void setup () {

// Defino el pin de salida del relé de potencia

pinMode (D8, OUTPUT);

receptorRF.enableReceive (D3);

// Inicia puerto serie hacia la PC

Serial.begin (9600);

while (!Serial){

Serial.println ("\n SIM900 --- ZS-040");

// Velocidad del puerto conectado al SIM900

SerialSim.begin (115200);

while (!SerialSim) {

Serial.print (".");

if (SerialSim) {

Serial.println ("\n Iniciado puerto serie al SIM900");

delay (10);

// Velocidad del puerto conectado al ZS-040

SerialBT.begin (9600);

while (!SerialBT) {

Serial.print (".");

if (SerialBT) {

Serial.println ("\n Puerto serie al ZS-040 iniciado");

if (SPIFFS.begin ()){

Serial.println ("SPIFFS Configurado");

}
else {

Serial.println ("SPIFFS Error en configuración");

Bucle principal
El bucle principal es la parte del programa que procesa las órdenes y atiende los eventos. En la plataforma
Arduino compatible, no es necesario crear explícitamente un bucle, si no que de eso se ocupa la función
loop que se repite en tanto haya alimentación.

void loop () {
int c; // Caracter
int x = 0;
float llavero = 0;
usuario usuario; // estructura de datos del usuario
orden a; // estructura de la trama de control
//Inicio la estructura de control a valores por defecto
a.comando[0] = '\0';
a.numero[0] = '\0';
a.mensaje_sms[0] = '\0';
trama_ord[0] = '\0';

// Leo un comando desde PC o celular


for (; !leerTrama ();)
{
if (receptorRF.available ())
{
llavero = receptorRF.getReceivedValue ();
receptorRF.resetAvailable ();
// Me fijo si el llavero detectado coincide con el de
// algún usuario habilitado
usuario = buscarLlavero(llavero);
// Si hubo coincidencia, activo la alarma
if (usuario.estado)
{
// Esto es un aviso para ver desde la PC
// no se ve en funcionamiento normal
Serial.println ("######################");
Serial.println ("####### AYUDA! #######");
Serial.println ("######################");

// Envía sms de alarma, enciende / apaga la salida de


// potencia
solicitarAyuda2 (usuario.nombre);
digitalWrite (D8, HIGH);
delay (5000);
digitalWrite (D8, LOW);
delay (1000);

}
}

}
// Borro cualquier lectura previa del llavero
receptorRF.resetAvailable ();
// Si leí una trama, la muestro. Sólo para depuración.
if (*trama_ord)
{
Serial.println (trama_ord);
a = procesarTrama ();
trama_ord[0] = '\0';
Serial.println ("------main------");
Serial.println (a.comando);
Serial.println (a.numero);
Serial.println (a.mensaje_sms);

Serial.print ("Comando::");
Serial.println (aNumero (a.comando, 16));
}

// Esta es la parte del programa que interpreta los comandos y ejecuta


// las instrucciones apropiadas para cumplirlos. Para eso, los dos
// bytes que representan el comando se convierten a un número hexadecimal
// y se procesan. En este ‘switch’ se pueden quitar o agregar comandos
switch (aNumero (a.comando, 16))
{
case 0xAA:
// a.mensaje_sms tiene un comando AT crudo que se manda al sim 900
Serial.println ("Comando AT hacia SIM900");
Serial.println (a.mensaje_sms);
escribirSIM900 (a.mensaje_sms);
delay (200);
leerSIM900 ();
break;
case 0xA1:
// Testeo del SIM900
testSIM900();
break;
case 0xA2:
// lee SIM900
delay (1500);
leerSIM900 ();
delay (1500);
break;
case 0xA3:
// Reinicia el SIM900
delay (1500);
escribirSIM900 ("AT+CFUN=1,1");
delay(1000);
leerSIM900 ();
delay (1500);
break;
case 0xBB:
// Accionar pwrkey para prender o apagar el SIM900
Serial.println ("\npwrkey");
pwrkeySIM900 (PIN_PWRKEY);
delay (1500);
leerSIM900 ();
break;
case 0x1A:
// Formatea la flash
formatearFlash ();
break;
case 0x1B:
// Crea un archivo si no existe, o lo borra y lo vuelve a crear.
crearArchivo ();
break;
case 0x1C:
// Agrega un nuevo número de difusión
altaDifusion (trama_ord);
break;
case 0x1D:
// Listar todo el archivo de usuarios
listarUsuarios ();
break;
case 0x1E:
// Envía sms a todos los usuarios
solicitarAyuda2 ("Este es un mensaje de prueba");
break;
case 0x20:
// Busca si existe el número de llavero entre los guardados en
// el archivo de usuarios
usuario = buscarLlavero(LLAVE1);
if(usuario.estado){
Serial.print("Nom: "); Serial.println(usuario.nombre);
Serial.print("Tel: "); Serial.println(usuario.telefono);
Serial.print("Lla: "); Serial.println(usuario.llavero);
}
break;
case 0x21:
// Captura de llavero.
// CUIDADO: ALARMA DESACTIVADA EN ESTE MODO!!!
Serial.println(capturarLlavero());
delay(1000);
break;

case 0x30:
// Encender bocina
Serial.println ("Bocina encendida...");
digitalWrite (D8, HIGH);
break;

case 0x31:
// Apagar bocina
Serial.println ("Bocina apagada...");
digitalWrite (D8, LOW);
break;

case 0x40:
//aCadena(12345678);
break;

case 0xDD:
// Quita un número de difusión
bajaDifusion (a.numero);
break;
default:
break;
}
usuarios.close (); //Cierro el archivo de usuarios y me voy ;-)
}

Funciones
Las funciones son bloques de código que hacen una tarea específica y por lo general se usan varias veces en
el programa. Se escriben para ordenar y ahorrar código. Las funciones están comentadas lo mejor posible,
de manera que cada una tiene una explicación de qué y como cumple determinada tarea. Sin dudas, la mayor
parte del código reside en las funciones.

/*
* =======================================
* leerTrama()
* =======================================
*/
int leerTrama () {
// Relleno el array de la trama con el flujo de datos que viene desde
// la PC o el celu
int x = 0;
// Leo un comando desde la terminal del PC
while (Serial.available () > 0 && x < MAX_TRA)
{
trama_ord[x] = Serial.read ();
delay (5);
x++;
}
// Agrego terminador de cadena
trama_ord[x] = '\0';
// Si leí caracteres, salgo.
if (x) {
return 1; // verdadero
} else {
//return 0; // falso
}

// Leo un comando desde el móvil


x = 0;
while (SerialBT.available () > 0 && x < MAX_TRA) {
trama_ord[x] = SerialBT.read ();
delay (5);
x++;
}
trama_ord[x] = '\0';
// Si leí caracteres, salgo.
if (x) {
return 1; // verdadero
} else {
//return 0; // falso
}
return 0;
}

/*
* =======================================
* procesarTrama()
* =======================================
*/

orden procesarTrama () {
// Toma la cadena 'trama_ord' que viene desde el móvil o la PC y la usa
// para rellenar los campos de la estructura 'orden'
// La variable 'trama_ord' tiene el siguiente formato (hasta ahora)
// byte 0 y 1: orden. Campo de longitud fija obligatoria
// byte 2 a 15: nro de teléfono. Campo de longitud fija obligatoria.
// byte 16 y siquientes: mensaje. Campo de longitud variable

// Declaro variables
// x es una variable de uso general;
// offset mantiene el desplazamiento sobre la variable trama_ord
// orden guarda los distintos campos de la trama trama_rod
int x = 0, offset = 0;
orden a;
// Comienzo a explorar trama_ord
// Copio los caracteres 0 a 1 a a.comando; en el caracter 2 de a.comando
coloco un '\0'
for (x = 0; x < (MAX_COM - 1) && trama_ord[x]; x++) {
a.comando[x] = trama_ord[x];
}
a.comando[x] = '\0'; // terminador

// guardo el punto en el que estoy sobre trama_ord


offset = x;

// x se desplaza sobre trama_ord: x - offset se desplaza sobre a.numero


// Resto 1 porque los arrays comienzan en cero.
for (x = offset; x < (offset + MAX_NUM - 1) && trama_ord[x]; x++) {
a.numero[x - offset] = trama_ord[x];
}
// en x - offset está el último miembro de a.numero
a.numero[x - offset] = '\0';

offset = x;
for (x = offset; trama_ord[x]; x++) {
a.mensaje_sms[x - offset] = trama_ord[x];
}
a.mensaje_sms[x - offset - 2] = '\0'; // Descuento dos porque parece que
cuenta dos veces el terminado
return a;
}
/*
* =======================================
* escribirSIM900
* =======================================
*/

void escribirSIM900 (char *cadena) {


// Esta función escribe ‘cadena’ al SIM900, así que se espera que sea
// un comando AT, que es lo que espera el módulo
int x = 0;
char c;
//Serial.print("escribirSIM900::");Serial.println(cadena);
// Escribo al SIM900 desde la compu. Hago eco a la PC y al móvil
while (*(cadena + sizeof (char) * x)) {
// Leo el comando de un caracter a la vez.
c = *(cadena + sizeof (char) * x);
// Hago eco a la PC
Serial.write (c);
// Envío al sim900
SerialSim.write (c);
yield ();
// Hago eco al móvil
SerialBT.write (c);
x++;
}
SerialSim.write (0xD);
SerialSim.write (0xA);
Serial.write (0xD);
Serial.write (0xA);
}

/*
* =======================================
* aNumero
* =======================================
*/
long int aNumero (char *cadena, int base) {
// Pasa una cadadena que representa un número de base 'base', a un
// entero decimal. En este momento 'base' sólo puede ser 16 porque sólo
// se ha implementado la función hexaAdecimal(); Habría que implementar
// las funciones decimalAdecimal(), binarioAdecimal() y octalAdecimal().
int longitud = 0; // longitud de la cadena
int pos = 0; // posición de un dígito dentro del número,
// comenzando por la posición cero en la extrema
// derecha y creciendo hacia la izquierda.
long int numero = 0;
// Base 16 y base 10 deberían poder calcularse
// Obtengo la longitud de la cadena contando hasta que encuentro el
// terminador nulo
for (longitud = 0; *(cadena + sizeof (char) * longitud); longitud++){
}
// Recorro la cadena de derecha a izquierda. Es decir suponiendo que la
// cadena representa un número, comienzo por el dígito 0
// (menos significativo), y termino por el que está más
// a la izquierda (el más significativo), 'pos' representa la posición
// de un dígito

for (pos = (longitud - 1); pos >= 0; pos--){


// resto '1' a 'longitud' porque la cadena viene contada desde
// 1 hasta 'longitud' pero el
// 'numero' se cuenta desde 0 hasta (longitud - 1)
numero = numero + hexaAdecimal (*(cadena + sizeof (char) * pos)) * pow
(base,(longitud - 1 - pos));
}
return numero; // Número decimal
}
/*
* =======================================
* hexaAdecimal
* =======================================
*/
int hexaAdecimal (char c) {
// devuelve un valor entre 0 y 15 suponiendo que c es un caracter
// entre '0' y '9', 'A' y 'F' o
// entre 'a' y 'f'. Si no cae dentro de estos valores, entonces el
// caracter no corresponde a un
// número hexadecimal y se devuelve un -1
// Busco números del 0 al 9
if (c >= 48 && c <= 57) {
return (int) c - 48;
}
// Busco numeros entre 'A' y 'F'
if (c >= 65 && c <= 70) {
return (int) c - 55;
}
if (c >= 97 && c <= 102) {
return (int) c - 87;
}
// Fuera de rango
return -1;
}

/*
* =======================================
* leerSIM900()
* =======================================
*/
void leerSIM900 () {
// Leo desde el SIM900 y escribo al celu vía el módulo
// Bluetooth
int x = 0;
char c;
for (;SerialSim.available () && x <100; x++) {
if (x > 100) {
Serial.println ("\nMuchos datos!");
return;
}
delay (5);
c = SerialSim.read ();
delay (5);
//yield();
// Hago eco al móvil
SerialBT.write (c);
// Hago eco a la PC
Serial.write (c);
//yield();
}
}

/*
* =======================================
* escribirSIM900_RAW
* =======================================
*/
void escribirSIM900_RAW (char *cadena) {
// Esta función escribe al SIM900 pero sin los carateres de retorno de
// carro y nueva línea
int x = 0;
char c;
//Serial.print("escribirSIM900::"); Serial.println(cadena);
// Escribo al SIM900 desde la compu. Hago eco a la PC y al móvil
while (*(cadena + sizeof (char) * x))
{
// Leo el comando de un caracter a la vez.
c = *(cadena + sizeof (char) * x);
// Hago eco a la PC
Serial.write (c);
// Envío al sim900
SerialSim.write (c);
yield ();
// Hago eco al móvil
SerialBT.write (c);
x++;
}
}

/*
* =======================================
* pwrkeySIM900
* =======================================
*/
void pwrkeySIM900 (int pwrkey_pin) {
// Actualmente esta función no se usa. Probablemente se elimine en nuevas
// versiones del software
// Lleva el pin pwrkey (pin 1) del SIM900 a BAJO por 1 segundo y luego
// lo deja en ALTO. Esto tiene como efecto conmutar el estado del
// SIM900 de encendido a apagado o viceversa.

// Serial.println("\pwrkey");
pinMode (PIN_PWRKEY, OUTPUT);
digitalWrite (PIN_PWRKEY, LOW);
delay (1200);
digitalWrite (PIN_PWRKEY, HIGH);
pinMode (PIN_PWRKEY, INPUT);
}

/*
* =======================================
* sincronizarSIM900()
* =======================================
*/
// Actualmente esta función no se usa. Probablemente se elimine en nuevas
// versiones del software
void sincronizarSIM900 () {
delay (1000);
pwrkeySIM900 (PIN_PWRKEY); // Apago SIM900
delay (1000);
leerSIM900 ();
pwrkeySIM900 (PIN_PWRKEY); // Prendo SIM900
delay (1000);
escribirSIM900 ("AT+CPOWD=1"); // Apago por software
delay (1000);
pwrkeySIM900 (PIN_PWRKEY); // Prendo SIM900
}

/*
* =======================================
* formatearFlash()
* =======================================
*/

int formatearFlash () {
/*
* Formatea la memoria flash de la Wemos D1
* retorna true para éxito y true para fracaso
*/
Serial.println ("\n formatearFlash()");
if (SPIFFS.format ()) {
Serial.println ("SPIFFS: formateado");
return true;
} else {
Serial.println ("SPIFFS: Error al formatear");
return false;
}
}
/*
* =======================================
* crearArchivo()
* =======================================
*/
int crearArchivo () {
/*
* Crea un archivo nuevo para escribir. El nombre del archivo está codificado en
* el programa para evitar problemas
*/
usuarios = SPIFFS.open (lista, "w");

if (!usuarios) {
Serial.println ("Error: No se pudo crear el archivo");
return false; // Error
} else {
Serial.println ("Archivo creado!");
usuarios.close ();
Serial.println ("Archivo cerrado");
return true;
}
}

/*
* =======================================
* escribirArchivo()
* =======================================
*/
int escribirArchivo (char *cadena) {
// Esta función escribe una cadena que contiene los datos del usuario
// de acuerdo al formato siguiente
/*
* Campos:
* [10- NNLLLLLLLL];[8 - llavero];[45- Nombre y apellido];
*
*/
//Aviso a la PC
Serial.println ("Escribiendo...");

//Escribo en el archivo
usuarios.print (cadena);
usuarios.print ("; ; \0\n");
return 0;
}

/*
* =======================================
* altaDifusion()
* =======================================
*/
void altaDifusion (char *cadena) {
// Esta función ingresa un usuario nuevo. Y si corresponde también vincula
// un llavero.
/*
* Campos:
* [10- NNLLLLLLLL];[10 - llavero];[40- Nombre y apellido];
*
* Los campos son los siguientes:
* 10 caracteres para el número de teléfono: dos de código de área y 8
* del número local
*
* 10 caracteres para guardar el número de llavero
*
* 43 caracteres para el nombre y apellido u otros datos futuro
*
* 1 caracter para el terminador nulo.
*
* En esta función se descarta todo lo que hay después del primer punto y coma
*/
int x = 0, z=0;
char c, llavero[11];
File usuarios = SPIFFS.open (lista, "a");

if (!usuarios) {
Serial.println ("Error al abrir archivo");
SerialBT.println("Error al abrir archivo");
} else {
Serial.println ("Escribiendo...");
SerialBT.println("Escribiendo...");
//Escribo en el archivo
//Descarto los dos bytes del comando de la trama de entrada
cadena++;
cadena++;

// Escribo a la flash
for (x = 0; x < 64 && (*cadena); x++){
// Tomo un valor de la trama de entrada
c = (char) (*cadena);
if((c=='A' || c=='a') && (x>=10 && x<=17)){
// Estoy en la parte del número de llavero
// Si en la trama viene una 'a' o una 'A' pido llavero
aCadena(capturarLlavero(), llavero );
for(;x<=17;x++){
usuarios.write(llavero[x-10]);
Serial.write(llavero[x-10]);
SerialBT.write(llavero[x-10]);
cadena++;
}
}else{
Serial.write (c);
SerialBT.write(c);
usuarios.write (c);
cadena++;
}
}
// Si la cadena se termina antes de ocupar los 63 bytes, completo
// con ceros hasta los 64
for (; x <= 64; x++){
usuarios.write (0x0);
}
usuarios.close ();
Serial.println ("Archivo cerrado");
}
}

/*
* =======================================
* bajaDifusion()
* =======================================
*/
void bajaDifusion (char *cadena) {
// Todavía no esta completamente implementada
/*
* En 'cadena' viene el n2úmero que se quiere borrar. Este número
* se busca entre los archivados. Si se encuentra, se sobreescribe con
* ceros, lo cual hará que esa entrada no sea tenida en cuenta por las
* funciones que envían los sms de ayuda. Sin embargo,
* una entrada con número puesto a cero, estará libre para ser
* sobre escrita para almacenar una nueva entrada.
*/
File usuarios = SPIFFS.open (lista, "r");
int x;
char c, *p;
int long_cadena = 0;
p = cadena;
if (!usuarios) {
Serial.println ("No se pudo abrir el archivo");
} else {
Serial.println ("Leyendo...");
Serial.println (cadena);
Serial.println ("--Loop--");

for (x = 0; x < usuarios.size (); x++) {


c = ((char) usuarios.read ());
if (c == *p && (*p)) {
Serial.write (*p);
p++;
long_cadena++;
} else {
if (!(*p)) {
// cadena encontrada
Serial.print ("Cadena encontrada. Long: ");
Serial.println (long_cadena);
Serial.print ("Origen: ");
Serial.println (usuarios.position () - long_cadena);
break;
} else {
// Reinicio la cadena para comenzar una nueva búsqueda
Serial.write ('R');
p = cadena;
long_cadena = 0;
}
}
}
usuarios.close ();
Serial.println ("Archivo cerrado");
}
}

/*
* =======================================
* listarUsuarios()
* =======================================
*/
void listarUsuarios () {
// Lista los usuarios grabados en el archivo de usuarios
File usuarios = SPIFFS.open (lista, "r");
int x;
char c;
if (!usuarios) {
Serial.println ("No se pudo abrir el archivo");
} else {
Serial.println ("Leyendo...");
for (x = 0; x < usuarios.size (); x++) {
c = (char) usuarios.read ();
if (c) {
Serial.write (c);
};
}
usuarios.close ();
Serial.println ("Archivo cerrado");
}
}

/*
* =======================================
* buscarLlavero()
* =======================================
*/
struct usuario buscarLlavero (long int llavero) {
// Busca si un llavero está en la lista de usuarios
File usuarios = SPIFFS.open (lista, "r");
struct usuario usuario;
int x, n = 1;
char a;
Serial.println("===buscarLlavero()===");
if (!usuarios) {
Serial.println ("No se pudo abrir el archivo");
} else {
Serial.println ("Leyendo...");
// Exploro el archivo completo
for (n = 1; (n * 64) <= usuarios.size (); n++){
for (x = 0; x <= 64; x++){
a = (char) usuarios.read ();
// guardo el teléfono
if (x >= 0 && x<10){
usuario.telefono[x] = a;
usuario.telefono[10] = '\0'; // terminador
}

// guardo el número de llavero


if (x >= 10 && x < 18){
usuario.llavero[x - 10] = a;
usuario.llavero[8] = '\0';
}

// guardo el nombre del usuario


if (x >= 18 && x < 64){
usuario.nombre[x - 18] = a;
}
// No hace falta un terminador explícito porque el campo
// de nombre viene relleno con ceros al final.
}
Serial.print("Tel: "); Serial.println(usuario.telefono);
Serial.print("Lla: "); Serial.println(usuario.llavero);
Serial.print("Nom: "); Serial.println(usuario.nombre);
if (llavero == aNumero (usuario.llavero,10)) {
usuarios.close ();
Serial.println ("Archivo cerrado");
Serial.println ("¡Coincide...!");
usuario.estado = 1; // indica coincidencia
return usuario; // retorna toda la información
}
}
usuarios.close ();
Serial.println ("Archivo cerrado");
usuario.estado = 0; // No hubo coincidencia
return usuario;
}
}

/*
* =======================================
* solicitarAyuda()
* =======================================
*/
void solicitarAyuda (char *mensaje_de_ayuda) {
/* Versión vieja. En desuso. Se eliminará en nuevas ediciones.
* Envía un SMS de solicitud de ayuda a todos los usuarios
*/
File usuarios = SPIFFS.open (lista, "r");
int x, n = 0;
char numero[20], c = 0;
if (!usuarios)
{
Serial.println ("No se pudo abrir el archivo");
}
else
{
Serial.println ("Leyendo...");
for (x = 0; x < usuarios.size (); x++) // Recorro todo el archivo byte
a byte
{
c = (char) usuarios.read (); // Leo un byte del archivo
numero[n] = c; // Guardo los caracteres de a uno en un vector
n++; // Incremento el índce del vector para guardar
el caracter siguiente
if (c == ';' || n == 10)
{ // Si encuentro un punto y coma, se terminó el
// número
numero[n] = 0x0; //Agrego terminador nulo
n = 0;
Serial.print("Numero::");
Serial.println (numero);

// acá el número está disponile para mandar un SMS


enviarSMS (numero, CENTRO_DE_MENSAJES, mensaje_de_ayuda);

for (; c && x < usuarios.size (); x++)


{
// Recorro la cadena hasta el final para llegar al próximo
número
// que está al comienzo de la siguiente. El final de la cadena
ocurre cuando
// c == 0 o cuando se termina el archivo
c = ((char) usuarios.read ());
}

}
usuarios.close (); // Cierro el archivo y me voy
Serial.println ("Archivo cerrado");
}
}

/*
* =======================================
* solicitarAyuda2()
* =======================================
*/
void solicitarAyuda2 (char *mensaje_de_ayuda) {
/* Versión nueva
* Envía un SMS de solicitud de ayuda a todos los usuarios
*/
int x, n = 0;
char numero[10], a = 0;

//Esta variable se debe declara luego de las demás, para


//evitar problemas de cuelgue del sistema.
File usuarios = SPIFFS.open (lista, "r");

if (!usuarios){
Serial.println ("No se pudo abrir el archivo");
} else {
Serial.println ("Leyendo...");
for (n = 1; (n * 64) <= usuarios.size (); n++){
for (x = 0; x <= 64; x++){
a = (char) usuarios.read ();
// guardo el teléfono
if (x >= 0 && x<10){
numero[x] = a;
}

if(x==10){
//Solicitar ayuda
numero[10] = 0x0; // terminador
delay(200);
enviarSMS (numero, CENTRO_DE_MENSAJES,
mensaje_de_ayuda);
}
}
}

}
usuarios.close (); // Cierro el archivo y me voy
Serial.println ("Archivo cerrado");
return;
}
/*
* =======================================
* enviarSMS()
* =======================================
*/
void enviarSMS (char *numero, char *centro_de_mensajes, char *texto) {
// Envía un SMS compuesto por *texto, al *numero indicado, y con el
// *centro_de_mensajes

Serial.println ("enviarSMS::Inicio"); // Sólo para depuración


escribirSIM900 ("AT+IFC=1,1;+CMGF=1;+CREG=1;+CSCS=\"GSM\";+CMEE=2");
delay (1000);
leerSIM900 ();
escribirSIM900_RAW ("AT+CSCA=\"+");
escribirSIM900_RAW (centro_de_mensajes);
escribirSIM900 ("\",145");
delay (1000);
leerSIM900 ();
escribirSIM900 ("AT+CSMP=17,167,0,240;+CNMI=3,1;+CLIP=1");
delay (1000);
//leerSIM900 ();
escribirSIM900_RAW ("AT+CMGS=\"");
escribirSIM900_RAW (numero);
escribirSIM900_RAW ("\"\x0D");
delay (100);
leerSIM900 ();
escribirSIM900_RAW (texto);
escribirSIM900_RAW (" solicita ayuda!");
escribirSIM900_RAW ("\x1A");
//delay (200);
leerSIM900 ();
Serial.println ("enviarSMS()::fin"); // Sólo para depuración
return;

}
/*
* =======================================
* testSIM900()
* =======================================
*/
void testSIM900(){
// Interroga y muestra varios datos del SIM900
Serial.println("Probando SIM900...");
Serial.println ("Estado del PIN");
escribirSIM900 ("AT+CPIN?");
delay (200);
leerSIM900 ();

Serial.println ("Calidad de la señal");


escribirSIM900 ("AT+CSQ");
delay (100);
leerSIM900 ();

Serial.println ("Versión:");
escribirSIM900 ("AT+CGMR");
delay (100);
leerSIM900 ();

Serial.println ("Registración en la red");


escribirSIM900 ("AT+CGREG?");
delay (100);
leerSIM900 ();
}

/*
* =======================================
* capturarLlavero()
* =======================================
*/
unsigned long capturarLlavero(){
//Entro en un loop hasta que aparece un llavero
//La alarma queda desactivada mientras no se salga de esta función
Serial.println("CUIDADO: ALARMA DESACTIVADA HASTA TERMINAR CAPTURA");
SerialBT.println("CUIDADO: ALARMA DESACTIVADA HASTA TERMINAR CAPTURA");
unsigned long valor;
for(; !receptorRF.available ();){
delay(10);
}
valor = receptorRF.getReceivedValue ();
receptorRF.resetAvailable ();
return valor;

/*
* =======================================
* aCadena()
* =======================================
*/
void aCadena(unsigned long n, char (&s)[11]){

/*
* Esta función toma un entero largo y lo convierte en una cadena de
* 8 dígitos. Se usa para convertir el número del código del llavero
* en una cadena y guardarlo en un archivo
*/

float a;
int c, x;
//static char s[11];
a = (float)n;
for(x = 0;x<8; x++){
c = ((int)(n/pow(10,x)))*pow(10,x) - ((int)(n/pow(10,x+1)))*pow(10,x+1);
c = c/(pow(10,x));

switch (c) {
case 0:
s[7-x]='0';
break;
case 1:
s[7-x]='1';
break;
case 2:
s[7-x]='2';
break;
case 3:
s[7-x]='3';
break;
case 4:
s[7-x]='4';
break;
case 5:
s[7-x]='5';
break;
case 6:
s[7-x]='6';
break;
case 7:
s[7-x]='7';
break;
case 8:
s[7-x]='8';
break;
case 9:
s[7-x]='9';
break;
}
}
s[x]='\0';
Serial.println(s);
return;
}

También podría gustarte