jueves, 12 de mayo de 2016

Bloqueador de llamadas con conexion bluetooth

Tras ver que una conexión por cable con el modem de 56k era propensa a dar errores de comunicación y a fallos de recepción de los datos de CallerID, amen de que suponía la existencia de mucho cableado de conexión propenso a fallos, me plantee usar una conexión inalambrica por bluetooth entre el modem serie 56k y el arduino. Usar un modulo serie bluetooth en arduíno es sencillo, hay muchos tutoriales para ello. Pero un tutorial para convertir un modem 56k en un modem por bluetooth no es tan fácil de encontrar por internet. Para realizar todo esto he recurrido al empleo de dos módulos de comunicaciones bluetooth serie, uno como master tipo HC-05 conectado a arduino y otro como slave tipo HC-06 conectado al modem 56k. Los módulos son muy fáciles de configurar y tras establecer los parámetros de comunicaciones a 38400bps, el emparejamiento entre ellos es automático. La configuración del módulo HC-05 como master se hace mediante el uso de un modulo usb serie o incluso usando a arduino y un sketch específico para ello. Yo usé un modulo usb serie y el Hyperterminal de Windows. El HC-05 se coloca en modo Master con AT+ROLE=1 y en modo conexión con cualquier HC-06 presente con AT+CMODE=0, el cambiar el protocolo a 38400,0,0 se hace con AT+UART 38400,0,0  El HC-06 se coloca en modo 38400,0,0 con AT+BAUD6 . Tras esto, quitamos la alimentación para que vuelvan al modo conexión por bluetooth y tras volver a dar alimentación ambos módulos se conectan sin problemas. El módulo HC-05 se conecta a Arduino en la forma RX-TX y TX-RX. En su alimentación por parte de arduino en el shield que utilicé, he tenido la precaución de colocar un interruptor de alimentación de los 5v del módulo bluetooth, ya que con el módulo conectado a arduino y alimentado, la carga de los sketch da error de averdude, por interferencias en el RX/TX. Con lo que para cargar los sketch sólo tengo que poner el interruptor en apagado y listo. El módulo HC-06 se conecta al modem de 56k mediante el uso de un modulo serie y un cable nulo, igual que se describió en el sistema del discriminador inicial. La alimentación de 5v para el modulo serie y el HC-06 es fácil de obtener de cualquier punto de 5v del interior del modem de 56K.

También consideré el cambio de las pantallas LCD16x02 por unas Nokia LCD bastante mas curiosas, por la cantidad de lineas que presentan para poder presentar más datos. El integrar el módulo Nokia LCD 5110 es sencillo siguiendo el siguiente esquema:


Una vez conseguido esto, me he decidido también por colocar un buzzer que emita una señal de llamada para sustituir a los RING de los teléfonos, ya que es imposible evitar que al menos suenen dos veces antes de que el CallerID funcione. Así que he insertado código para que el sistema reconozca los RING recibidos, y haga sonar el buzzer como una llamada de teléfono, si el numero no está bloqueado, a partir del segundo RING. Los teléfonos deberían estar en mute o en ring muy bajo, para que no molestaran las llamadas bloqueadas. Realmente dichas llamadas se pueden bloquear para que no suenen haciendo que el modem coja la llamada entrante (hay una parte del sketch que contiene el código para hacerlo, en modo desactivado con slash, para activar si se desea), pero como en las pruebas usando el móvil vi que era factible el dejar las llamadas sin coger y a la vez impedir que el teléfono suene, pues me he decidido por esto último.
El sistema ya montado tiene el siguiente aspecto:


El resultado final del sketch es el siguiente:

/* Proyecto de discriminador de llamadas telefonicas no deseadas, tras la llamada se identifica el
numero llamante y se compara con los que están anotados en un fichero almacenado en una tarjeta SD. Si
coincide con alguno, la llamada es bloqueada. Ademas graba automáticamente los números que han llamado
y que posteriormente pueden seleccionarse con la botonadura para ser bloqueados. Al necesitar dos RING para
poder leer el CALLERID lleva un buzzer incorporado que suena a partir del tercer RING, para evitar
que suenen los telefonos siempre un par de veces (hacer mute en los telefonos para evitar el ring).
El hardware necesario es un Arduino Uno, un modem con CallerID (en mi caso US Robotics 56k o un Conceptronics
C55E de 56k), un modulo SD YL-30, una tarjeta SD, un modulo Serie HC05, un modulo LCD de 16x2 y
una botonadura tipo Keypad DIY de resistencias (una botonadura simple con tres resistencias de 3K en serie
conectando GND a Pin A0, p.e.).
Se usa un modulo HC05 master bluetooth para comunicarse con un modem bluetooth hc06 conectado al modem serie, que
a su vez está conectado a la linea de telefono.
La cadena del Caller ID detectada por el modem es siempre del tipo siguiente:
DATE = 0422 //MMDD
TIME = 1945 //HHMM
NMBR = 659278358 //Numero llamante

Conexion modulo hc05 a Arduino es : RX-TX(Pin digital 1) y TX-RX(Pin digital 0)

Conexion pines modulo SD a Arduino:
modulo: GND 3.3v  5v. CS  MOSI SCK MISO  GND
Arduino:GND 3.3V 5V. 10   11       13      12      GND

La botonadura es del tipo resistencias en serie unidas a pin A0 y GND, y boton uniendo PIN RESET a GND.

El buzzer está conectado al Pin A1 - GND - 5v


*/
// LCD5110_NumberFonts
// Copyright (C)2015 Rinky-Dink Electronics, Henning Karlsen. All right reserved
// web: http://www.RinkyDinkElectronics.com/
//
// This program is a demo of the included number-fonts,
// and how to use them.
//
// This program requires a Nokia 5110 LCD module.
//
// It is assumed that the LCD module is connected to
// the following pins using a levelshifter to get the
// correct voltage to the module.
// CLK/ SCK  - Pin 7
//      MOSI - Pin 6
//      DC   - Pin 5
//      RST  - Pin 3
//   CE/CS   - Pin 4
//
#include <LCD5110_Basic.h>

LCD5110 myGLCD(7,6,5,3,4);
extern uint8_t SmallFont[];
extern uint8_t MediumNumbers[];
extern uint8_t BigNumbers[];


#include <SdFat.h>//CS en pin 10
SdFat sd;
SdFile myFile;



// define some values used by the panel and buttons
int lcd_key     = 0;
int adc_key_in  = 0;
#define btn3      0
#define btnUP     1
#define btn2      2
#define btnLEFT   3
#define btn1      4
#define btnNONE   5


/// Guardamos en que entrada de arduino esta conectado el pin CS del modulo.
const int chipSelect = 10;
char nmbrstring[ ] = "NMBR";
char ringstring[ ] = "RING";
char numerodete[10];
char numerotst[10];
char numllam[10];

char charBuf[10];
char charBuf2[10];
char charThree[10];

int numblock = 50;//cantidad de lineas de numeros del fichero
int i = 0;
int i2 = 0;
int llamadas = 0;// Contador de llamadas, debe ser 0. Otro valor es para pruebas.
int ringer = 2;// Contador de ring, los 2 primeros no se detectan
int pin = A1;//pin conexion buzzer
String stringThree;

byte Bin2;

unsigned long tAntes = 0;
unsigned long tAntes2 = 0;
unsigned long tAntes3 = 0;
unsigned long tAntes4 = 0;
unsigned long tAntes5 = 0;
unsigned long tAntes6 = 0;
long time_now = 0;

//Sonido del buzzer
void playNote(int noteInt, long length, long breath = 20) {
  length = length - breath;
  buzz(pin, 2000 , length); // Valor 2000 es agudo y fuerte, 50 es grave y flojo
  if(breath > 0) { //take a short pause or 'breath' if specified
    delay(breath);
  }
}


void setup()
{
 myGLCD.InitLCD();

 //myGLCD.clrScr();

 pinMode(A0, INPUT_PULLUP); // sets analog pin for input

 //digitalWrite(pin, HIGH);
 pinMode(pin, OUTPUT); // set a pin for buzzer output

  myGLCD.setFont(SmallFont);
  myGLCD.print("LCD Activado", 0, 0); // print a simple messag
 delay (2000);

  // Configuramos el puerto serie (esta version NO usa Serie Virtual)
  Serial.begin(38400);
  while (!Serial) {
; // Espera para conexion puerto serie, solo necesario para leonardo
}
 

  myGLCD.clrScr();

  myGLCD.print("Setup is Ready", 0, 0);
  delay(2000);

  //Iniciamos la SD
  //Serial.print("Iniciando SD...");
  //Serial.print("\n");
  myGLCD.clrScr();

  myGLCD.print("Iniciando SD...", 0, 0);
  // El pin CS por defecto de la placa arduino debe ser configurado como salida
  // aunque no se use (10 en la mayoria de las placas, 53 en Arduino Mega).
  pinMode(10, OUTPUT);
  // Si ha habido error al leer la tarjeta informamos por el puerto serie.
  if (!sd.begin(chipSelect, SPI_HALF_SPEED))
{
  //Serial.println("Error inicializar SD!");

  myGLCD.print("Error SD...", 0, 20);
  sd.initErrorHalt();

  delay(2000);
  myGLCD.clrScr();

  myGLCD.print("PULSE RESET", 0, 40);
  pinMode(10, INPUT);
  delay(1000);

  return;
 }



 myGLCD.print("SD ini...OK", 0, 20);
 delay(2000);

//REcreando fichero RECIB.TXT

if (!myFile.open("RECIB.TXT", O_RDWR)) {

    myGLCD.print("Error SD write", 0, 20);
    delay(2000);
    }
    myFile.println("111111111");
    delay(200);
    myFile.close();// close the file:
 
myGLCD.clrScr();

myGLCD.print("RECIB.TXT ini", 0, 10);
delay(2000);

myGLCD.print("OK", 0, 30);
delay(2000);

myGLCD.clrScr();

myGLCD.print("Start Modem....", 0, 0);

  // Iniciamos los comandos Hayes para configurar el modem
  Serial.print("ATZ\r\n");
  delay(4000);

  myGLCD.print("4.", 0, 20);
  Serial.print("AT+VCID=1\r\n");//Conceptronics CID ON
  //Serial.print("AT#CID=1\r\n");//USR Robotics CID ON
  delay(2000);

  myGLCD.print("3.", 10, 20);
  Serial.print("ATS7=5\r\n");//USR Robotics Answer Wait 7 seg
  delay(2000);

  myGLCD.print("2.", 20, 20);
  Serial.print("ATS0=0\r\n");//USR Robotics Auto Answer OFF
  delay(2000);

  myGLCD.print("1.", 30, 20);
  Serial.print("AT+FAE=1\r\n");//USR Robotics Adaptative Answer/Silent Answer
  delay(2000);

  myGLCD.print("OK", 40, 20);

  delay(2000);
  digitalWrite(pin, HIGH);
  playNote(1,100);
  digitalWrite(pin, LOW);
 }



void loop()
{
static char buffer[20];

  buttonselect();// Selector botones

  myGLCD.setFont(SmallFont);

  if (readline(Serial.read(), buffer, 80) > 0)
  {
    //Deteccion del numero llamante leido por puerto serie
    char* numero1 = strchr(buffer, 'NMBR =');
    char* prefinmbr = strtok(buffer, " =");
    char* numero2 = strchr(numero1, ' ');
    if(strcmp(prefinmbr, nmbrstring) == 0)//Detectamos la cadena NMBR que contiene el numero llamante
    {
     //Lector de numero
     char* numerodete = numero2;//detecta numero con 1 espacio al inicio
     String stringOne = numerodete;//convierte numero char a string
     stringOne.trim();//elimina el espacio del inicio del número
   
     myGLCD.clrScr();

     myGLCD.print("Num.llamante:", 0, 0);
   
     myGLCD.print("*********", 0, 20);
     //myGLCD.print(charBuf, 0, 20);
   
     stringOne.toCharArray(charBuf, 10);//convierte el numero string a char para strcmp
       
     //ringer = 2;//Pone a 0 el contador de rings
   
     myGLCD.print(stringOne, 0, 20);//Presenta en numero detectado
       
     //Inicio codigo escritura llamadas recibidas  
     // open the file. note that only one file can be open at a time,
  // so you have to close this one before opening another.

  // if the file opened okay, write to it:
  if (!myFile.open("RECIB.TXT", O_RDWR | O_CREAT | O_AT_END)) {
   
     myGLCD.print("Error SD write", 0, 30);
   }
 
    myFile.println(stringOne);
    delay (200);
    myFile.close();// close the file:
 
    llamadas++;// acumulado de numero de llamadas

    myGLCD.print("Llamadas.....", 0, 40);

    myGLCD.printNumI(llamadas, 60, 40);
 

    myGLCD.print("WCOK", 60, 20);//Presenta el OK de grabacion numero detectado
 
    //Mantiene el tiempo a 0 mientras se pulsa boton lectura
    long time_now = millis();
    tAntes = time_now;
    tAntes2 = time_now;
    tAntes3 = time_now;
    tAntes4 = time_now;
 

  // fin codigo escritura llamadas recibidas
   
    //Inicio seccion lectura fichero numeros bloqueados.
 
    char Ruta[9] = {'B', 'L', 'O', 'Q', '.', 'T', 'X', 'T', '\0'};//establece nombre fichero tlfs bloqueados
   
    for(int i = 0; i < numblock; i++)// secuencialmente se lee cada una de los numeros del fichero
    {
    //Leemos linea de archivo en la SD
    String numerotst = ReadFile(i,Ruta);
    String stringTwo = numerotst;
    stringTwo.toCharArray(charBuf2, 10);// convertimos string to char para strcmp
 
    if(strcmp(charBuf, charBuf2) == 0)// comparamos el numero detectado con el leido en el fichero
    {
   
      myGLCD.clrScr();

      myGLCD.print("Numero detec.:", 0, 0);
   
      myGLCD.print(stringTwo, 0, 20); // Muestra el numero bloqueado
      myGLCD.print("**BLOQUEADO**", 0, 30);//Presenta señal numero bloqueado
   
      ringer = 20;//Impide que suene el buzzer
     /* //Codigo activacion del modem para contestar y colgar
     Serial.print("ATA\r\n");//Coge el tlf
     delay(2000);
     Serial.print("ATH1\r\n");//Cuelga el tlf
     */
       delay (4000);
     
     }// Fin deteccion numero bloqueado
   }//Fin bucle lectura numeros bloqueados SD
  }//Fin deteccion de llamada NMBR

  //Deteccion de los RING para activar el buzzer
     if(strcmp(buffer, ringstring) == 0)//Detectamos la cadena RING
    {
      /* Test cadena detectada
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print(buffer);
      lcd.print(ringer);
      *///Fin test
      ringer++;
      //myGLCD.printNumI(ringer, 0, 35);//Print num rings
      if(ringer > 3 && ringer < 15)//detector de rings para hacer sonar el buzzer
      {
        digitalWrite(pin, HIGH);
        playNote(1,100);
        playNote(1,200);
        playNote(1,100);
        playNote(1,100);
        playNote(1,200);
        playNote(1,100);
        playNote(1,100);
        playNote(1,200);
        playNote(1,100);
        digitalWrite(pin, LOW);
      }
   
   }
  //Fin deteccion RING
 }//Fin lectura buffer serie

//Timer Reponedor a 0 de rings
 long time_now = millis();
      if ((long)(time_now - tAntes6) > 80000)//Temporizador 80 seg para poner rings a 0
    {
    tAntes6 = time_now;
    ringer = 2;
    }
   /*Test rings status
      lcd.setCursor(0,15);
      lcd.print(ringer);
      *///Fin test
 
}// Fin LOOP



// Codigo de lectura de los telefonos bloqueados en la SD
String ReadFile(int Linea,char Ruta[]){
int Lin=0;
String Resultado;
byte Bin;
if (!myFile.open(Ruta, O_RDWR)) {

     myGLCD.print("SD Read Error", 0, 0);
    delay (2000);
    myFile.close();return Resultado;
    }
while (myFile.available()){
Bin=myFile.read();
if (Bin==13){Lin++;myFile.read();}
else
{
if (Lin==Linea){Resultado=Resultado+(char(Bin));}
if (Lin>Linea){myFile.close();return Resultado;}
}
}
myFile.close();return Resultado;
}

 
/// AÑADIDO CONTROL BOTONADURA Y GRABADO NUMEROS BLOQUEADOS SD

void buttonselect()
{

 lcd_key = read_LCD_buttons();  // read the buttons

 switch (lcd_key)               // depending on which button was pushed, we perform an action
 {
   case btn3:
     {
     myGLCD.clrScr();
     //LcdCharacter(adc_key_in);
     myGLCD.print("Numero Block", 0, 0);
     bloquear();
     delay(500);
     break;
     }
   case btn2:
     {
     myGLCD.clrScr();
     //gotoXY(0,1);
     //LcdCharacter(adc_key_in);
     myGLCD.print("Bloquear Numero?", 0, 0);
     myGLCD.print("NO", 0, 40);
     myGLCD.print("X", 30, 40);
     myGLCD.print("SI", 60, 40);
     delay(500);
     break;
     }
   case btn1:
     {
     //gotoXY(7,0);
     //myGLCD.print("Bloquear");
     leer();
     /*myGLCD.clrScr();
     gotoXY(0,1);
     LcdCharacter(adc_key_in);
     myGLCD.print("SELECT");*/
     break;
     }
     case btnNONE:
     {
      long time_now = millis();
   
    if ((long)(time_now - tAntes) > 10000) // regulador de tiempo medida datos y presentacion lcd
    {
     myGLCD.clrScr();

     myGLCD.print("Waiting call...", 0, 0);

     myGLCD.print("Llamadas..", 0, 40);

     myGLCD.printNumI(llamadas, 60, 40);
     tAntes = time_now;
    }
     if ((long)(time_now - tAntes2) > 1000)//Blink de * para indicar funcionamiento normal
    {

    myGLCD.print("****************", 0, 10);
    tAntes2 = time_now;
    }
    if ((long)(time_now - tAntes3) > 1500)
    {

    myGLCD.print("* * * * * * * * ", 0, 10);
    tAntes3 = time_now;
    }
    //Reponedor a 0 de lectura llamadas si inactividad en botones
    if ((long)(time_now - tAntes4) > 20000)
    {
      i2 = 0;
      tAntes4 = time_now;
    }
    if ((long)(time_now - tAntes5) > 1800000) //RESETEO MODEM CADA 30 MINUTOS
    {
      modemreset();
      tAntes5 = time_now;
      ringer = 2;
    }
 
      break;
     }
 }

}


void leer()
{
    char Ruta[10] = {'R', 'E', 'C', 'I', 'B', '.', 'T', 'X', 'T', '\0'};//establece nombre fichero tlfs bloqueados
     
    String numllam = ReadFile(i2,Ruta);//Leemos linea de archivo como char en la SD
 
    //Proceso conversion de string to char para poder grabar los datos leidos
    String stringThree = numllam; //convertir char to string
    stringThree.toCharArray(charThree, 10);// convertimos string to char
 
    delay(500);
    myGLCD.clrScr();

    myGLCD.print("Numero Leido.....", 0, 0);
    myGLCD.print(".................", 0, 10);
    myGLCD.printNumI((llamadas + 5)- i2, 40, 10);
    //gotoXY(0,1);
    //myGLCD.print("         "); // Muestra el numero leido

    myGLCD.print(charThree, 0, 30); // Muestra el numero leido

    //myGLCD.print("..", 75, 0);
    delay (500);
 
    i2++;
 
    //Mantiene el tiempo a 0 mientras se pulsa boton lectura
    long time_now = millis();
    tAntes = time_now;
    tAntes2 = time_now;
    tAntes3 = time_now;
    tAntes4 = time_now;
 
    if (i2 > (llamadas + 5))
    {
    i2 = 0;
    llamadas = 0;
    delay (1000);
    myGLCD.clrScr();

    myGLCD.print("Borrando las", 0, 0);

    myGLCD.print("LLamadas...", 0, 10);
 
   if (!myFile.open("RECIB.TXT", O_RDWR | O_CREAT)) {
    myGLCD.clrScr();

    myGLCD.print("Error SD write", 0, 10);
    delay(2000);

    myGLCD.print("PULSE RESET", 0, 20);
    delay(2000);
    }
 
    //Borrando fichero RECIB.TXT
    myFile.remove();//borra fichero
    delay(2000);
 
    myFile.close();
 
    //REcreando fichero RECIB.TXT

if (!myFile.open("RECIB.TXT", O_RDWR | O_CREAT)) {

    myGLCD.print("Error SD write", 0, 10);
    delay(2000);
    myGLCD.clrScr();

    myGLCD.print("PULSE RESET", 0, 20);
    delay(2000);
    }

  myFile.println("111111111");
    delay(200);  
    myFile.close();// close the file:
 
    myGLCD.clrScr();

    myGLCD.print("RECIB.TXT ini", 0, 0);
    delay(2000);

    myGLCD.print("OK", 0, 10);
    delay(2000);
   }
}
//funcion para escribir el número leido de RECIB.TXT y grabarlo en
//el fichero de numeros bloqueados BLOQ.TXT
void bloquear()
{
  if (!myFile.open("BLOQ.TXT", O_RDWR | O_CREAT)){

  myGLCD.print("Error SD write", 0, 0);

  sd.initErrorHalt();

  delay(2000);

  myGLCD.print("PULSE RESET", 0, 10);
   pinMode(10, INPUT);
  delay(1000);
   }
  myFile.println(charThree);
  delay(200);
  myFile.close();

  myGLCD.clrScr();

  myGLCD.print("Numero bloqueado", 0, 0);

  myGLCD.print("*********", 0, 20); // Limpia espacio numero

  myGLCD.print(charThree, 0, 20); // Muestra el numero bloqueado

  myGLCD.print("**BLOQUEADO**", 0, 30);
  delay(2000);
}

//Sistema de lectura del puerto serie
int readline(int readch, char *buffer, int len)
{
  static int pos = 0;
  int rpos;

  if (readch > 0) {
    switch (readch) {
      case '\n': // Ignore new-lines
        break;
      case '\r': // Return on CR
        rpos = pos;
        pos = 0;  // Reset position index ready for next time
        return rpos;
      default:
        if (pos < len-1) {
          buffer[pos++] = readch;
          buffer[pos] = 0;
        }
    }
  }
  // No end of line has been found, so return -1.
  return -1;
}
// fin programa lectura puerto serie

// read the buttons
int read_LCD_buttons()
{
 adc_key_in = analogRead(0);      // read the value from the sensor

//Valores para botonadura simple de 3 resistencias de 3k Ohm en serie

 if (adc_key_in > 1000) return btnNONE; // Ningun boton pulsado

 if (adc_key_in < 95 && adc_key_in > 90)   return btn3;
 //if (adc_key_in < 250)  return btnUP;
 if (adc_key_in < 200 && adc_key_in > 100)  return btn2;
 //if (adc_key_in < 650)  return btnLEFT;
 if (adc_key_in > 200 && adc_key_in < 300)  return btn1;

 return btnNONE;  // when all others fail, return this...
}
// FIN READ BUTTONS

// Reseteo modem
void modemreset()
{
  myGLCD.clrScr();

  myGLCD.print("Reset Modem....", 0, 0);

  // Iniciamos los comandos Hayes para configurar el modem
  Serial.print("ATZ\r\n");
  delay(4000);

  myGLCD.print("4.", 0, 20);
  Serial.print("AT+VCID=1\r\n");//Conceptronics CID ON
  //Serial.print("AT#CID=1\r\n");//USR Robotics CID ON
  delay(2000);

  myGLCD.print("3.", 10, 20);
  Serial.print("ATS7=5\r\n");//USR Robotics Answer Wait 7 seg
  delay(2000);

  myGLCD.print("2.", 20, 20);
  Serial.print("ATS0=0\r\n");//USR Robotics Auto Answer OFF
  delay(2000);

  myGLCD.print("1.", 30, 20);
  Serial.print("AT+FAE=1\r\n");//USR Robotics Adaptative Answer/Silent Answer
  delay(2000);

  myGLCD.print("OK", 40, 20);

  delay(2000);
}
// sonido del buzzer

void buzz(int targetPin, long frequency, long length) {
  long delayValue = 1000000/frequency/2; // calculate the delay value between transitions
  //// 1 second's worth of microseconds, divided by the frequency, then split in half since
  //// there are two phases to each cycle
  long numCycles = frequency * length/ 1000; // calculate the number of cycles for proper timing
  //// multiply frequency, which is really cycles per second, by the number of seconds to
  //// get the total number of cycles to produce
  for (long i=0; i < numCycles; i++){ // for the calculated length of time...
    digitalWrite(targetPin,HIGH); // write the buzzer pin high to push out the diaphram
    delayMicroseconds(delayValue); // wait for the calculated delay value
    digitalWrite(targetPin,LOW); // write the buzzer pin low to pull back the diaphram
    delayMicroseconds(delayValue); // wait againf or the calculated delay value
  }
}



No hay comentarios:

Publicar un comentario