Arduino + sensor de movimiento + Ruby

El principio de este post es una interpretación (no me atrevo a llamarlo traducción) del artículo sobre sensores PIR de la fabulosa gente de Adafruit.

Que es un sensor PIR ?
un sensor PIR es un sensor capaz de detectar movimiento dentro de su rango de acción.
La denominación PIR viene de “Passive Infrared”, de “Pyrolectric” o de “IR motion”.
El componente principal es un sensor piroeléctrico que detecta niveles de radiación infraroja. Cualquier cosa emite pequeños niveles de radiación y cuanto más caliente es algo, más radiación emite.
El sensor de movimiento está separado en dos mitades, esto es porque necesitamos detectar movimiento, es decir cambios en los valores de IR y no simplemente su valor. Las dos mitades están conectadas de manera que una anula a la otra, si una mitad ve más o menos radiación que la otra, la salida del sensor cambiará activándose o desactivándose. Resumiendo mucho mucho, cuando el sensor está inactivo, es porque las dos mitades están detectando el mismo valor de IR, la del ambiente o la habitación. Cuando un cuerpo caliente como una persona o un animal entran en la habitación, una mitad del sensor lo registra y se produce una diferencia positiva con la otra mitad, cuando la persona abandona la habitación ocurre lo mismo, en este caso la diferencia entre las dos mitades del sensor es negativa.
Además del sensor pyrolectrico el circuito se completa con un montón de otros componentes (resistencias, condensadores, etc …). Al parecer la mayoría de sensores “de juguete” usan el chip BISS0001 (http://www.ladyada.net/media/sensors/BISS0001.pdf). Este chip se encarga de tomar la señal analógica del sensor y procesarla para convertirla en la señal digital de salida del sensor.
Para proyectos en los que necesitas saber cuando una persona a entrado o se ha ido de una habitación o de una zona, los sensores PIR son geniales, son muy baratos, necesitan muy poca potencia, son bastante resistentes, tienen bastante rango de detección y son muy fáciles de conectar.

Estas son las especificaciones de nuestro sensor
Voltaje de entrada: 4,5V – 20V DC
Señal de salida: 0,3V (cuando se detecta movimiento)
Distancia y ángulo de detección: máximo 7 metros y 110º


 

 

Conexion
la mayoria de los PIR tienen 3 pin, un pin es tierra (-) otro será la señal que emite (OUT) y el otro la potencia (+).
Aqui mostramos el esquema para conectarlo a un Arduino, además añadimos un LED que se activa cuando el sensor detecta movimiento
El Pin – del sensor a ground, el + a 5V y la salida, el pin OUT a a un pin digital del Arduino

 

Existe un jumper en el PIR que tiene dos posiciones, L y H,
En posición L (non-retriggering = no-reactivar) podemos ver como el LED no queda encendido cuando se detecta movimiento sino que se enciende y se apaga más o menos cada segundo.
En posición H (retriggering = reactivar), el LED permanece encendido todo el tiempo que algo se está moviendo

Que es un Arduino ? , si no sabes lo que es en este punto pensarás que hablo en chino, viva Arduino !!

 

El codigo para probar el PIR es muy sencillo, aquí lo tienes, súbelo al Arduino y abre el monitor Serial para ver los mensajes que aparecen.

/*
 * PIR sensor tester
 */
 
int ledPin = 13;                // pin para el LED
int inputPin = 2;               // pin de entrada (for PIR sensor)
int pirState = LOW;             // de inicio no hay movimiento
int val = 0;                    // estado del pin
 
void setup() {
  pinMode(ledPin, OUTPUT);      // declare LED as output
  pinMode(inputPin, INPUT);     // declare sensor as input
 
  Serial.begin(9600);
}
 
void loop(){
  val = digitalRead(inputPin);  // read input value
  if (val == HIGH) {            // check if the input is HIGH
    digitalWrite(ledPin, HIGH);  // turn LED ON
    if (pirState == LOW) {
      // we have just turned on
      Serial.println("Motion detected!");
      // We only want to print on the output change, not state
      pirState = HIGH;
    }
  } else {
    digitalWrite(ledPin, LOW); // turn LED OFF
    if (pirState == HIGH){
      // we have just turned of
      Serial.println("Motion ended!");
      // We only want to print on the output change, not state
      pirState = LOW;
    }
  }
}

Cuando se detecta movimiento, incluso cuando este no ha parado, el sensor PIR tiene unas fases LOW, son pequeños intervalos de tiempo en los que el sensor indicaría que no hay movimiento, aunque lo haya. Para corregirlo, en el siguiente código se considera que el movimiento ha parado cuando no se detecta movimiento durante más de un cierto tiempo (valor de pause).

/* 
 * @ Autor: Kristian Gohlke / krigoo (_) gmail (_) com / http://krx.at
 * Publicado bajo Creative Commons "Reconocimiento-No comercial-Compartir bajo la misma de 2,0" de licencia
 * http://creativecommons.org/licenses/by-nc-sa/2.0/de/
 * http://www.ladyada.net/learn/sensors/pir.html
 *
 * la salida del PIR es HIGH cuando se detecta movimiento, pero a veces va a LOW incluso cuando hay movimiento,
 * para arreglar esto, no le hacemo caso a fases LOW mas cortas que un tiempo dado PAUSE y suponemos que sigue habiendo movimiento
 * Solo me creo que no hay moviemiento cuando no lo detecto mas de PAUSE segundos (5 secs default)
 * 
 */
/////////////////////////////
//VARS
//El tiempo que nos da el sensor a calibrar (10-60 segundos de acuerdo con la hoja de datos)
int calibrationTime = 10;               
// si no hay movimiento en este tiempo me creo de verdad que no lo hay
long unsigned int pause = 4000;  

long unsigned int lowIn; // el sensor detecta que no hay movimiento
boolean lockLow = true;  // mientras sea false estoy decidiendo si ha parado el movimiento de verdad o no
boolean takeLowTime;  


int pirPin = 2;    //pin digital conectado a la salida del sensor PIR
int ledPin = 13;

/////////////////////////////
//SETUP
void setup(){
  Serial.begin(9600);
  pinMode(pirPin, INPUT);
  pinMode(ledPin, OUTPUT);
  digitalWrite(pirPin, LOW);

  //Dar el sensor de un cierto tiempo para calibrar
  Serial.print(" calibrando sensor ");
    for(int i = 0; i < calibrationTime; i++){
      Serial.print(".");
      delay(1000);
      }
    Serial.println("SENSOR ACTIVADO");
    delay(50);
  }

////////////////////////////
//LOOP
void loop(){

     if(digitalRead(pirPin) == HIGH){
         digitalWrite(ledPin, HIGH);   //LED visualiza el estado de la salida del sensor
           if(lockLow){  
             // solo paso por aqui cuando la deteccion es nueva, es decir cuando lockLow es TRUE, y solo es TRUE cuando pasa por la deteccion de NO movimiento
             lockLow = false;            
             Serial.println("---");
             Serial.print("movimiento encontrado a los ");
             Serial.print(millis()/1000);
             Serial.println(" segundos "); 
             delay(50);
           }         
           takeLowTime = true; // Si lockLow no esta bloqueado
      }
      else{       
           digitalWrite(ledPin, LOW);  //LED visualiza el estado de la salida del sensor
    
           if(takeLowTime){      // solo paso por aqui despues de haber detectado movimiento
                                 //en las siguientes vueltas si sigue sin haber movimiento ya no necesito guardar el timepo en el que el movimiento par´
            lowIn = millis();          // guardar el momento de la transición de alta a baja
            takeLowTime = false;       //  asegurarse de que esto sólo se hace al comienzo de una fase de baja
           }
         
           //Si el sensor esta LOW mas tiempo que PAUSE, es que el movimiento ha parado de verdad       
           if(!lockLow && millis() - lowIn > pause){  
               // lockLow solo es FALSE cuando se ha detectado movimiento de nuevo, es decir solo paso por aqui cuando hay nueva deteccion        
               lockLow = true;                        
               Serial.print("movimiento detenido a los ");      
               Serial.print((millis() - pause)/1000);
               Serial.println(" segundos ");
               delay(50);
           }
       }
       
}

El proposito de todo esto es conseguir que ocurra algo cuando el PIR detecte movimiento, que se encienda una luz, que suene una alarma, … para conseguir esto hemos escrito un script en Ruby que lee el estado del sensor y realiza una acción cuando se detecta movimiento.
Gracias a la GEM serialport, podemos leer con nuestro script Ruby la salida del puerto Serie del Arduino igual que la vemos en el monitor serial de Arduino, así podemos lanzar acciones cada vez que el estado sea el indicado,
en el ejemplo hacemos que suene una canción mediante la interfaz de linea de comandos de VLC.

Y en este ejemplo siguiente hemos conseguido que el script ruby envie un email a través de una cuenta de Gmail cada vez que el sensor detecte que alguien ha entrado en la habitación.
Por cada acción que lanzamos desde el script Ruby, creamos un nuevo hilo, sino lo hacemos así, no podemos seguir leyendo la salida del Arduino mientras no termine la acción, si hacemos sonar una canción,no podríamos hacer nada mientras no termine la canción, de hecho, no podríamos hacer nada mientras no cerremos el VLC porque éste se queda abierto esperando una nueva orden.

#simplest ruby program to read from arduino serial, 
#using the SerialPort gem
#(http://rubygems.org/gems/serialport)

require "rubygems"
require "serialport"

    require 'net/smtp'

	def send_email(txt)
		    from_email = "from@prueba.es"
		    from_name = "desde"
		    pass = "12345"
		    
		    to_email = "parati@ejemplo.es"
		    to_name = "tunombre"
		    
		    from = "From: #{from_name}<#{from_email}>"
		    to = "To: #{to_name}<#{to_email}>"
		    headers = "MIME-Version: 1.0\r\nContent-Type: text/html; charset=utf-8"
		    subject = "Subject: #{txt}"
		    body = "<html>\n<body>\n<div style='font-size:20px;color:#cc6600;'>alarma</div>texto<br>firmado, yo\n</body>\n</html>"  
		
			msg = "#{from}\n#{to}\n#{headers}\n#{subject}\n\n#{body}"
		    
		    smtp = Net::SMTP.new 'smtp.gmail.com', 587
		    smtp.enable_starttls
		    smtp.start('gmail.com', from_email, pass, :login) do
		      smtp.send_message(msg, from_email, to_email)
		    end  
	end




#params for serial port
port_str = "/dev/tty.usbmodem1d11"  #may be different for you
baud_rate = 9600
data_bits = 8
stop_bits = 1
parity = SerialPort::NONE

sp = SerialPort.new(port_str, baud_rate, data_bits, stop_bits, parity)
play = "/Applications/VLC.app/Contents/MacOS/VLC --intf=rc 'ruta de la cancion'"

puts "empezamos, alarma armada"
playing = 'no'

# recieve part
Thread.new do
  while TRUE do
    while (i = sp.gets.chomp) do # viene con salto de linea al final, chomp lo elimina	
    	#puts i.class #String
	      	if i == 'salta'
	      		if playing == 'si'
	      			puts 'alarma sonando'
	      		else
	      			playing = 'si'
	      			send_email('salto la alarma')
		    		Thread.new do
		    			puts 'hay alguien guey !!'
		    			system(play)
		    		end
	      		end	      	
	      	else
	      		if playing == 'si'
	      			puts 'alarma sonando'
	      		else
	      			puts "todo en orden"
	      			playing = 'no'
	      		end
	      	end
    end
  end 
end

# send part
begin
  while TRUE do
    sp.print STDIN.gets.chomp
  end
rescue Interrupt
  sp.close
  puts      #insert a newline character after ^C
end

Fácil y bonito, un sensor manda señales al Arduino, nosotros leemos la salida a través del puerto serie y ejecutamos una acción cada vez que pase algo “serio”, las posibilidades de esto, son infinitas, los tipos de sensor son miles, yo quiero intentar enviar mensajes a Twitter o mensajes de móvil, no sé hasta dónde se puede pero creo que hasta dónde yo quiera.

Si no lo has sospechado ya, lo que quiero es hacerme una alarma y no tener que pagar una cuota mensual a ninguna compañía, no por el hecho de no pagarlo sino porque realmente ellos no le dan valor, pagas por nada. Cuando contratas una alarma, cada vez que salta, la compañía te llama por teléfono para comprobar que no ha sido un error, si tú confirmas que no debiera haber nadie en tu local, entonces llaman a la policía. Pero y si mi sistema de alarma me avisa directamente a mí y si veo que hay algo raro soy yo el que llamo a la policía ? no hay ninguna diferencia verdad ? bueno sí la hay, había una cuota mensual que ya no está.

Seguiremos informando

6 comentarios »

  1. hamilton dice:

    Buen día, Felipe, estoy intentando correr este ultimo pero no pedo configurar mi puerto serial, soy novato, este es el error al depurar:

    sketch_may13b.ino:2:2: error: invalid preprocessing directive #simplest
    sketch_may13b.ino:4:2: error: invalid preprocessing directive #using
    sketch_may13b.ino:6:2: error: invalid preprocessing directive #(
    sketch_may13b.ino:72:2: error: invalid preprocessing directive #params
    sketch_may13b:74: error: stray '#' in program
    sketch_may13b.ino:98:3: error: invalid preprocessing directive #recieve
    sketch_may13b:104: error: stray '#' in program
    sketch_may13b.ino:106:10: error: invalid preprocessing directive #puts
    sketch_may13b.ino:154:3: error: invalid preprocessing directive #send
    sketch_may13b:168: error: stray '#' in program
    sketch_may13b:1: error: expected unqualified-id before numeric constant

    • elquiltro dice:

      Hola Hamilton,
      ten en cuenta que este post es un poco antiguo, está probado con la versión 1.0.4 del Software de Arduino,
      tal vez por eso no te funciona …
      podrías tratar de instalar esa misma versión para ver si ese es el problema … o sino tendrías que adaptar el código, el mismo mensaje de error te está diciendo en qué lineas están los fallos …

      En cuanto pueda voy a actualizar esta entrada para que funcione con la ultima versión.
      Saludos !!

  2. rodrigo dice:

    Hola me podrias decir como ejecuto el script

    • Felipe Rojas dice:

      Hola Rodrigo,
      los scripts de Arduino solo tienes que copiarlos en el Arduino respetando los pines, en ambos casos el pin 2 es donde conectas la salida del sensor, qué Arduino tienes ? esto está probado en un UNO con la versión 1.04 del Software…
      SI te refieres al script Ruby, está probado con Ruby 1.9.1, prueba a reducirlo hasta que sólo estés leyendo el puerto Serial de Arduino, a partir de ahí añádele más cosas.
      Cuál es el script que quieres ejecutar ?
      Que errores tienes ?
      saludos !!

  3. nalle dice:

    jajajaj, grasias por la informacionn, pero tengo una duda, en lugar de hacer que algo funcone se podria hacer que algo se detenga?
    como en una banda del super?

    • Felipe Rojas dice:

      Hola,
      claro que sí, en realidad se puede hacer lo que quieras, sólo necesitas el sensor adecuado y la programación. Para hacer como una banda del súper, estaría la banda en movimiento y al final un sensor midiendo la distancia a los objetos sobre la banda, cuando esa distancia sea 5cm, la banda se detiene.
      AQUÍ hay un ejemplo de uso de sensor de ultrasonidos para medir distancias,
      y AQUI ejemplos de varios tipos de sensores (en inglés)
      Saludos y suerte

RSS feed para los comentarios de esta entrada. / TrackBack URI

Deja un comentario