martes, 29 de mayo de 2012

Proyecto de Simulación - Air Hockey

Descripción
El hockey de aire (también llamado hockey de mesa, aerohockey o tejo) es un deporte en el cual dos personas compiten para anotar puntos en la portería contraria.


En particular pensamos en el proyecto para simulación de colisiones, la velocidad del disco de hockey disminuyendo con la poca fricción de la mesa, y otros efectos físicos que mencionaremos más adelante.

Por lo tanto las reglas del juego y el gameplay en sí no son en realidad importantes, aunque implementamos unas ligeras funcionalidades para incrementar la puntuación y anotar puntos.

Teoría(Física)
En este proyecto, tenemos tres efectos de física importantes que deseamos simular:
  • Colisiones. El disco choca con una pared-limitante, o con el mazo.
  • Fricción. El disco y los mazos(ambos pueden ser controlados con el mouse) deben de disminuir su velocidad una cantidad considerable al dejarlos moverse.
  • Fuerza. La velocidad y dirección del disco debe ser resultado de una "fuerza" aplicada con el mazo. 
Para comprender esto, necesitamos saber como funcionan en mecánica todos estos fenómenos.
Colisiones

En cuanto a las colisiones, nos referimos a un tipo específico, las colisiones elásticas. En física, estas son colisiones entre dos o más cuerpos en las que éstos no sufren deformaciones permanentes durante el impacto. En una colisión elástica se conservan tanto el momento lineal como la energía cinética del sistema, y no hay intercambio de masa entre los cuerpos, que se separan después del choque.




Ahora en nuestro caso, dos cuerpos de dos dimensiones chocaran(el disco y el mazo), y la velocidad total de cada uno debe ser separada en dos velocidades perpendiculares: una tangente a la normal común de la superficie de los cuerpos colisionando al punto de contacto, y la otra junto a la linea de colisión. Este gif obtenido de Wikipedia, explica perfectamente este fenómeno:
Fricción


Fricción es la fuerza resistiva generada por el movimiento de dos superficies que hacen contacto una con la otra. Es el producto indirecto de una de las cuatro fuerzas fundamentales conocidas. La fricción de un sistema es imposible de predeterminar estríctamente desde principios teóricos. Matemáticamente, la expresión para la fricción incluye una sola constante que incorpora todos los factores que puedan causarla, el coeficiente de fricción (COF). La ecuación es simplemente escrita  fx = μxF, donde fdefine la forma y la medida de la fricción, y F es la fuerza normal ejercida por ambas superficies, una sobre la otra.

Todos los coeficientes de fricción son magnitudes escalares sin dimensiones. La fuerza fundamental responsable por la mayoría de la fricción es también la que permite la formación de los enlaces químicos, la fuerza electroestática. En una consideración inicial, podría decirse que la gravedad es la causa inicial de la fricción, debido a la fuerza que ejerce la gravedad hacia la tierra es la fuente de la variable F.




En el hockey de mesa, es importante señalar que la fricción que estamos considerando, no es la del disco con la mesa, o el mazo con la mesa. Debido a que prácticamente el disco flota de un lado a otro con la mínima fuerza, la única fricción que estamos tomando en cuenta es la del aire que las mesas de hockey utilizan normalmente. Esta fuerza en contra del movimiento es bastante pequeña, pero existe, de otra forma el disco continuaría moviéndose indefinidamente.


Herramientas
  • Python-Pygame
    • Pygame es un set de módulos de Python diseñado para escribir juegos. Pygame permite crear juegos y programas de multimedia en el lenguaje python. Es bastante portable y corre en prácticamente cualquier plataforma y sistema operativo.
  • ¿Por qué Pygame?
    • Al inicio no considerábamos Pygame como una opción, debido a que como la mayoría de personas, solamente por el nombre o por ver algunos ejemplos, podemos darnos a la idea de que Pygame es una librería para hacer juegos, y nada más. Pero buscando en Internet ,encontramos ejemplos bastantes interesantes de como aplicar fenómenos físicos a objetos dibujados por Pygame, lo cual prácticamente cumplía nuestras expectativas, lo que hizo que lo tomáramos en serio.
Física en Pygame


Para entender un poco el potencial de Pygame, se explicará a continuación algunas de las funciones que utilizamos para poder añadir un poco de realidad a la simulación.


Limites y Rebotes


Creando una simulación como la nuestra, deseamos que tenga un limite, en este caso sería la mesa, para evitar simular una región infinita. El ejemplo que utilizamos para esto, es una pared virtual en la cual las partículas rebotan.


Lo primero que nuestra función de rebote debe tener sería verificar si una partícula paso un cierto límite. Los cuatro limites en una ventana serían entonces:
  • x = 0 (pared izquierda)
  • x = ancho (pared derecha)
  • x = 0 (parte superior)
  • x = alto (parte inferior)
Ya que la simulación tiene pasos de tiempo discretos, es difícil verificar una partícula en el punto exacto en el que toca el límite, pero es posible verificar si ha viajado un poco después del límite. Si la velocidad de la partícula es baja, entonces la partícula tiene poca probabilidad de haber pasado muy lejos del limite(la distancia máxima que habrá excedido después del límite es entonces, igual a la velocidad. En este caso ignoraremos éste fenómeno, y nos concentraremos en simplemente reflectar el ángulo de la partícula lo más precisamente posible.

Entonces cuando una partícula excede el límite, calculamos cuanto fue lo que excedió. Por ejemplo, sí el valor de x de la partícula es mayor al ancho de la ventana menos el tamaño de la partícula, ha superado el límite derecho, entonces calculamos el excedente:


d = self.x - (width - self.size)

Y reflejamos la posición en el límite (rebotando) poniendo las coordenadas x de la siguiente manera:


self.x = (width - self.size) - d

Esto puede ser simplificado para que no necesitemos la variable d, de la siguiente manera:


self.x = 2*(width - self.size) - self.x


La parte más importante de rebotar una partícula, es reflejar el ángulo en el límite, que es donde nuestros vectores empiezan a ser útiles(aunque es más útil cuando los límites no son lineares). Un limite vertical tiene un ángulo de 0, mientras que uno horizontal tiene un ángulo de pi. Entonces reflejamos verticalmente partículas que reboten, restando su ángulo actual de 0, y por pi en el caso de rebotes horizontales.

La función de rebote entonces debe quedarnos así:


def bounce(self):

    if self.x > width - self.size:
       self.x = 2*(width - self.size) - self.x
       self.angle = - self.angle

   elif self.x < self.size:
        self.x = 2*self.size - self.x
        self.angle = - self.angle

    if self.y > height - self.size:
        self.y = 2*(height - self.size) - self.y
        self.angle = math.pi - self.angle

    elif self.y < self.size:
        self.y = 2*self.size - self.y
        self.angle = math.pi - self.angle



Ahora podemos llamar a la función rebote, después de cada movimiento, pero antes de mostrarlo en pantalla para poder comprobar si hay un rebote en las partículas.


Movimiento


Casi todas las simulaciones tienen valores que cambian con el tiempo. Un valor que cambia bastante seguido en nuestra simulación, es la posición de un objeto(el disco y el mazo), en el caso de que este en movimiento.
Para nuestra simulación hicimos 2 cosas a estos objetos:
  1. Dar a los objetos velocidad y dirección
  2. Usar trigonometría básica para convertir los vectores en movimiento.
Como se mencionó anteriormente, nuestra simulación es en tiempo discreto, lo que significa que dividimos el tiempo en pasos individuales. En cada paso actualizamos la simulación un poco para después mostrar la nueva situación. Entonces seguimos actualizando la simulación hasta que el usuario salga.

La forma más simple de representar el movimiento es crear dos atributos, dx y dy. Entonces cada paso por el ciclo principal del programa, añadir dx a x y dy a y. Entonces, por ejemplo, si una particula tiene dx = 2 y dy = 1, seguira una diagonal desde la izquierda a la derecha, y desde arriba hacia abajo. Éste es un método simple y correcto para la mayoría de las situaciones. Pero cuando necesitamos trabajar en la interacción entre dos objetos, las cosas se vuelven más complejas.

El otro método es entonces crear atributos para representar la rapidez y dirección (velocidad). Esto requiere un poco más de trabajo inicial, pero hace todo más simple. El método es bueno también para crear objetos con velocidad constante pero dirección variada. Entonces para darle velocidad a nuestra partícula, necesitamos darle dos atributos:

self.speed = 0.01
self.angle = 0

Ya que haremos cálculos de trigonometría, haremos uso del módulo math de python, para el uso de funciones como sin y cos para calcular senos y cosenos.
Ahora debemos agregar una función move a nuestro objeto. La función debería entonces cambiar las coordenadas x, y de la partícula basada en la velocidad y el ángulo de movimiento. Entonces podemos calcular el cambio en x y y, como muestra el diagrama:

The mathematics of movement

Es mas simple considerar un ángulo de 0 cuando apunta hacia arriba, a pesar del hecho de que el eje y realmente apunta hacia abajo en gráficos de computadora.

Para calcular el cambio en x y y, usamos algunas funciones trigonométricas de nivel de secundaria como se puede observar:

def move(self):

self.x += math.sin(self.angle) * self.speed

self.y -= math.cos(self.angle) * self.speed

Otro punto a tomar en cuenta es que los ángulos están todos en radiantes. Entonces debemos de tomar en cuenta que 1 radian es 180°/pi. Entonces si queremos que una partícula se mueva hacia adelante(izquierda o derecha), el ángulo debería ser pi/2 = 90°. En python podemos hacer eso usando math también, de la siguiente manera:

self.angle = math.pi/2
De esta forma podemos llamar la función move dentro del ciclo principal del programa, antes de llamar la función que dibuja los elementos
Colisiones


Crear partículas con movimiento, y que tengan un limite es bastante interesante, pero no es suficiente. En el hockey de mesa un punto importante son las colisiones entre el disco y el mazo, por lo cual es imprescindible agregar ese tipo de interacción al programa. Entonces lo que necesitábamos hacer era:
  • Checar si dos partículas han chocado
  • Hacer que dichas partículas reboten
Las cosas se empiezan a poner difíciles cuando tenemos múltiples partículas interactuando unas con otras. De hecho, es matemáticamente imposible resolver las ecuaciones que describen tres o más objetos que interactuan. En nuestra simulación se hicieron simplificaciones, que aunque son un tanto imprecisas, representan una razonable aproximación a la realidad.

Primero para probar si dos partículas se sobreponen unas a otras, necesitamos comparar cada partícula con las demás. Podemos hacer éso con un simple for anidado, justo después de moverlos y checar si han tocado un límite, pero antes de dibujarlos en la pantalla:

for i, particle in enumerate(my_particles):
    particle.move()
    particle.bounce()
    for particle2 in my_particles[i+1:]:
        collide(particle, particle2)
    particle.display()

El segundo for anidado no es un completo, es decir no iteramos todas las partículas. Si tuviéramos dos ciclos completos, compararíamos cada partícula con las demás dos veces cada una(además de compararla consigo mismo). En cambio, comparamos cada partícula con cada partícula con un índice superior que ella en el arreglo. Entonces necesitamos saber el índice de la partícula actual, el cual podemos conseguir usando enumerate. Usamos el valor de i para cortar el arreglo desde i+1 hasta el final para construir el segundo for.
Ahora podemos definir la función colisión. La primera cosa que la función debe hacer es checar si dos partículas han colisionado. Esto es muy simple debido a que las partículas son ambos círculos en el caso del hockey de mesa, pero contienen un sprite superpuesto. Entonces para verificar la colisión, medimos la distancia entre ellas, utilizando math.hypot(), y probamos si el valor es menor que sus radios combinados.
def collide(p1, p2):
    dx = p1.x - p2.x
    dy = p1.y - p2.y
    distance = math.hypot(dx, dy)
    if distance < p1.size + p2.size:
        print 'Chocaron!'

Hasta esta parte sería suficiente con imprimir algo en caso de que esto sea cierto.
Como mencionamos en la teoría, cuando dos partículas circulares chocan, hacen contacto en un punto infinitamente pequeño. El ángulo de este punto es la tangente de la partícula en este punto. Entonces el ángulo de salida es perpendicular a la linea que se une el centro de las dos partículas.
Entonces podemos tratar la colisión como si las partículas estuvieran rebotando con una superficie plana con el ángulo de la tangente. Podemos encontrar dicho ángulo con la siguiente fórmula:
tangent = math.atan2(dy, dx)
Para reflejar el ángulo de las partículas en la superficie, restamos el ángulo actual menos dos veces la tangente, para lo cual necesitamos conocer el ángulo en el que nuestra partícula viajaba, el mismo que usamos en el movimiento.


p1.angle = 2*tangent - p1.angle
p2.angle = 2*tangent - p2.angle



Entonces necesitamos cambiar las velocidades de las dos partículas, debido a que transfieren las energías uno a otro. Podemos hacer esto con una simple tupla.


(p1.speed, p2.speed) = (p2.speed, p1.speed)


Entonces podemos agregar una elasticidad para reducir la energía de ambas partículas después de la colisión.


p1.speed *= elasticity
p2.speed *= elasticity
Código


#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Referencias:
# Imagen de fondo obtenida de https://engineering.purdue.edu/477grp6/images/lrprice/air_hockey_concept.png
# 
# Código basado en el tutorial http://razonartificial.com/tutoriales-pygame/
# Código basado en el tutorial http://www.petercollingridge.co.uk/book/export/html/6
# Código basado en el tutorial http://www.petercollingridge.co.uk/pygame-physics-simulation/mouse-interactions

#modulos
import sys, pygame, random, math
from pygame.locals import *

#Constantes
width = 800
height = 600
handles = []
pucks = []
nHandles = 2
npocks = 1
puntuacion = [0,0]#jugador A, jugador B

#Clases
class Puck(pygame.sprite.Sprite):
        def __init__(self, x, y, id_):
            self.inicial = [x,y]
     self.anterior = [0, 0]
     self.cambio = [0.0, 0.0, 0.0]#X, Y y Contador
     self.id_ = id_
            pygame.sprite.Sprite.__init__(self)
            self.image = load_image("images/ball.png", True)
            self.diametro = width*0.07
            self.image = pygame.transform.scale(self.image, (int(self.diametro) ,int(self.diametro) ))
            self.rect = self.image.get_rect()
            self.rect.centerx = x
            self.rect.centery = y
            self.speed = [0.8, 0.8]
     self.coeficiente_arrastre = 0.001
            self.acceleration =  [0, 0]
     self.velocidad_juego = 4

        def actualizar(self, time, bandera):
            if bandera == self.id_:
  if self.cambio[2] == 15: 
      self.cambio[2] = self.cambio[1] = self.cambio[0] = 0

                pos = pygame.mouse.get_pos() 
                self.rect.centerx = pos[0]
                self.rect.centery = pos[1]
  
  self.cambio[2] = self.cambio[2]+1
  self.speed[0] = (self.cambio[0] + (float(pos[0] - self.anterior[0]))/(time*16) )
  self.speed[0]/self.cambio[2]
  self.speed[1] = (self.cambio[1] + (float(pos[1] - self.anterior[1]))/(time*16) )
  self.speed[1]/self.cambio[2]

                self.anterior[0] += self.speed[0] * time
                self.anterior[1] += self.speed[1] * time
  
            else:
                self.cambio[2] = self.cambio[1] = self.cambio[0] = 0
                self.anterior[0] = self.rect.centerx
                self.anterior[1] = self.rect.centery

  self.speed[0] = self.speed[0]*(1-self.coeficiente_arrastre)
  self.speed[1] = self.speed[1]*(1-self.coeficiente_arrastre)
                self.rect.centerx += self.speed[0] * time
                self.rect.centery += self.speed[1] * time

                if (self.rect.left <= 0 or self.rect.right >= width) and (self.rect.bottom <= height*(1.0/3) or self.rect.top > height*(2.0/3) ):
                    self.speed[0] = - self.speed[0]
                    self.rect.centerx += self.speed[0] * time
  elif (self.rect.right < 0 or self.rect.left > width) and not (self.rect.bottom <= height*(1.0/3) or self.rect.top > height*(2.0/3) ):

                    if self.rect.centerx <= 0:
          puntuacion[0] = puntuacion[0] + 1
                    if self.rect.centerx > width:
          puntuacion[1] = puntuacion[1] + 1

      self.rect.centerx = self.inicial[0]
      self.rect.centery = self.inicial[1]
      self.speed[0] = self.speed[1] = 0

                if self.rect.top <= 0 or self.rect.bottom >= height:
                    self.speed[1] = -self.speed[1]
                    self.rect.centery += self.speed[1] * time

                for i in range(len(handles)):
                    if  math.hypot((handles[i].rect.centerx - self.rect.centerx), (handles[i].rect.centery - self.rect.centery) ) <= (self.diametro):

                        angulo = math.atan2(self.rect.centery - handles[i].rect.centery, self.rect.centerx-handles[i].rect.centerx )
   angulo_invertido = math.atan2(-self.speed[1], -self.speed[0])
   angulo_reflexion = angulo_invertido + (angulo * angulo_invertido)*2
   self.speed[1] = math.sin(angulo_reflexion)
   self.speed[0] = math.cos(angulo_reflexion)

                for i in range(len(pucks)):
                    if math.hypot((pucks[i].rect.centerx - self.rect.centerx), (pucks[i].rect.centery - self.rect.centery) ) <= (self.diametro) and pucks[i].id_ != self.id_ :
                        angulo = math.atan2(self.rect.centery - pucks[i].rect.centery, self.rect.centerx-pucks[i].rect.centerx )
   angulo_invertido = math.atan2(-self.speed[1],-self.speed[0])
   angulo_reflexion = angulo_invertido + (angulo * angulo_invertido)*2
   self.speed[1] = math.sin(angulo_reflexion)
   self.speed[0] = math.cos(angulo_reflexion)


class Handle(pygame.sprite.Sprite):
        def __init__(self, x, y, id_):
            self.inicial = [float(x), float(y)]
     self.anterior = [0.0, 0.0]
     self.cambio = [0.0, 0.0, 0.0]#X, Y y Contador
            self.id_ = id_
            pygame.sprite.Sprite.__init__(self)
            self.image = load_image("images/handle.png", True)
            self.diametro = width*0.15
            self.image = pygame.transform.scale(self.image, (int(self.diametro) ,int(self.diametro) ))
            self.rect = self.image.get_rect()
            self.rect.centerx = float(x)
            self.rect.centery = float(y)
            self.speed = [0.2, 0.2]
     self.coeficiente_arrastre = 0.012
            self.acceleration =  [0, 0]

        def inteligencia(self, time):
            if pucks[0].rect.centerx <= width:
                if pucks[0].rect.centerx < self.rect.centerx :
                    self.rect.centerx += 5
  else:
                    self.rect.centerx -= 5

                if pucks[0].rect.centery < self.rect.centery :
                    self.rect.centery += 5
  else:
                    self.rect.centery -= 5      
     else:
                if pucks[0].rect.centerx < self.rect.centerx :
                    self.rect.centerx += 5
  else:
                    self.rect.centerx -= 5

                if pucks[0].rect.centery < self.rect.centery :
                    self.rect.centery += 5
  else:
                    self.rect.centery -= 5

 
     if self.rect.left <= 0:
                self.rect.centerx = width/2
     if self.rect.right >= width/2:
                self.rect.centerx = width/2

     if self.rect.top <= 0:
  self.rect.centery = 0 
            if self.rect.bottom >= height:
  self.rect.centery = height


        def actualizar(self, time, bandera):
            if bandera == self.id_:
  if self.cambio[2] == 15: 
      self.cambio[2] = self.cambio[1] = self.cambio[0] = 0
                pos = pygame.mouse.get_pos() 
                self.rect.centerx = pos[0]
                self.rect.centery = pos[1]
  
  self.cambio[2] = self.cambio[2]+1
  self.speed[0] = (self.cambio[0] + (float(pos[0] - self.anterior[0]))/(time*16) )
  self.speed[0]/self.cambio[2]
  self.speed[1] = (self.cambio[1] + (float(pos[1] - self.anterior[1]))/(time*16) )
  self.speed[1]/self.cambio[2]

                self.anterior[0] += self.speed[0] * time
                self.anterior[1] += self.speed[1] * time
  
            else:
                self.cambio[2] = self.cambio[1] = self.cambio[0] = 0
                self.anterior[0] = self.rect.centerx
                self.anterior[1] = self.rect.centery

  self.speed[0] = self.speed[0]*(1-self.coeficiente_arrastre)
  self.speed[1] = self.speed[1]*(1-self.coeficiente_arrastre)
                self.rect.centerx += self.speed[0] * time
                self.rect.centery += self.speed[1] * time

                if self.rect.left <= 0 or  self.rect.right >= width:
                    self.speed[0] = - self.speed[0]
                    self.rect.centerx += self.speed[0] * time

                if self.rect.top <= 0 or self.rect.bottom >= height:
                    self.speed[1] = -self.speed[1]
                    self.rect.centery += self.speed[1] * time


     if self.rect.left <= width/2:
                self.rect.centerx = width/2

                for i in range(len(handles)):
                    if  math.hypot((handles[i].rect.centerx - self.rect.centerx), (handles[i].rect.centery - self.rect.centery) ) <= (self.diametro/2) and handles[i].id_ != self.id_ :

                        angulo = math.atan2(self.rect.centery - handles[i].rect.centery, self.rect.centerx-handles[i].rect.centerx )
   angulo_invertido = math.atan2(-self.speed[1], -self.speed[0])
   angulo_reflexion = angulo_invertido + (angulo * angulo_invertido)*2
   self.speed[1] = math.sin(angulo_reflexion)
   self.speed[0] = math.cos(angulo_reflexion)

                for i in range(len(pucks)):
                    if math.hypot((pucks[i].rect.centerx - self.rect.centerx), (pucks[i].rect.centery - self.rect.centery) ) <= (self.diametro/2) :
                        angulo = math.atan2(self.rect.centery - pucks[i].rect.centery, self.rect.centerx-pucks[i].rect.centerx )
   angulo_invertido = math.atan2(-self.speed[1],-self.speed[0])
   angulo_reflexion = angulo_invertido + (angulo * angulo_invertido)*2
   self.speed[1] = math.sin(angulo_reflexion)
   self.speed[0] = math.cos(angulo_reflexion)

#Funciones

def select_handle(vector):
    pos = pygame.mouse.get_pos()
    for i in range(len(vector)):
        if  math.hypot(int(vector[i].rect.centerx-pos[0]), int(vector[i].rect.centery-pos[1]) ) <= (vector[i].diametro/2):
            return vector[i].id_
    return -1

##########################################
# Carga la imagen y la convierte al formato
# optimizado de pygame
##########################################
def load_image(filename, transparent=False):
            try: image = pygame.image.load(filename)
            except pygame.error, message:
                    raise SystemExit, message
            image = image.convert()
            if transparent:
                    color = image.get_at((0,0))
                    image.set_colorkey(color, RLEACCEL)
            return image

def main():
    screen = pygame.display.set_mode((width, height))
    pygame.display.set_caption("Clase de simulación")

    background_image = load_image("images/background.png")
    fuente = pygame.font.Font(None, 40)
    fuente_a = pygame.font.Font(None, 80)
    fuente_b = pygame.font.Font(None, 80)

    puntos_a = fuente_a.render('0', True, (255, 255, 255), (159, 182, 205))
    puntos_a_r = puntos_a.get_rect()

    puntos_b = fuente_b.render('0', True, (255, 255, 255), (159, 182, 205))
    puntos_b_r = puntos_b.get_rect()

    principal = fuente.render('', True, (255, 255, 255), (159, 182, 205))
    principal_r = principal.get_rect()

    principal_r.centerx = 400
    principal_r.centery = 160

    puntos_a_r.centerx = 100
    puntos_a_r.centery = 90

    puntos_b_r.centerx = 700
    puntos_b_r.centery = 90

    clock = pygame.time.Clock() 
    screen = pygame.display.set_mode((width, height))
    bandera = -1

#    for i in range(nHandles):
    handles.append( Handle(random.randrange(0,width), random.randrange(0,height), 0))
    handles.append( Handle(random.randrange(0,width), random.randrange(0,height), 1))

    for i in range(nHandles, nHandles+npocks):
     pucks.append( Puck(random.randrange(0,width), random.randrange(0,height), i) )

    while True:
        time = clock.tick(60)
        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit(0)
            if event.type == pygame.MOUSEBUTTONDOWN:
                pos = pygame.mouse.get_pos()
                bandera = select_handle(handles)
  if bandera < 0: 
   bandera = select_handle(pucks)
            elif event.type == pygame.MOUSEBUTTONUP:
                bandera = -1

        # En esta parte se ponen las cosas que se redibujan

#        for i in handles:
#            i.actualizar(time, bandera)
#            screen.blit(i.image, i.rect)

        screen.blit(background_image, (0, 0))
        handles[0].inteligencia(time)
        handles[1].actualizar(time, bandera)

        for i in pucks:
            i.actualizar(time, bandera)
            screen.blit(i.image, i.rect)

 puntos_a = fuente_a.render(str(puntuacion[0]), True, (255, 255, 255), (159, 182, 205))
 puntos_b = fuente_a.render(str(puntuacion[1]), True, (255, 255, 255), (159, 182, 205))

        screen.blit(handles[1].image, handles[1].rect)
        screen.blit(handles[0].image, handles[0].rect)
 screen.blit(puntos_a, puntos_a_r)
 screen.blit(puntos_b, puntos_b_r)
 screen.blit(principal, principal_r)

        #-------------------> Aqui se termian de redibujar los componentes
        pygame.display.flip()
        #-------------------> Aqui termina el while 

    return 0


if __name__ == '__main__':
    pygame.init()
    main()


Referencias:

miércoles, 23 de mayo de 2012

Wiki Contributions Week 16 - Final

Autoevaluation

Starting the semester, everyone was joining groups which would be working on different things that would help to build the project. I joined the CUDA group, not knowing that I needed a CUDA enabled GPU, which I obsiously don't have. So I started trying to help there, looking for good sources and information which could help the others in the group, but I kinda felt like I wasn't doing much, so I tried another alternatives. Then I started to program some MPI examples, and others stuff, just to get a feel of how MPI programming works, but not joining another group in the process. There I learned some interesting stuff using mpi4py, but still not doing a great progress.

Finally, somehow I ended with the cluster group, but I didn't know anything about what they were doing, and I was supposed to do a Web Interface for the Cluster in Ruby, which resulted in a failure because I couldn't do any notable progress, which is entirely my fault, and because of that, others had to do my part.
In conclusion, I didn't do anything to add to the cluster.Period.

Nominations

Juan Carlos, Rafael, and Roberto the only ones with own initiative.

Proyecto de Móviles - Emmergency

lunes, 21 de mayo de 2012

Proyecto de Integrados - Arduino Facebook RSS Reader

Descripción:

Mediante una pequeña pantalla LCD de 16x2, el Arduino muestra las notificaciones de un determinado usuario de Facebook(en este caso yo). Estas notificaciones son descargadas por un script de Python, que usando la librería feedparser graba las últimas 10 notificaciones, y las envía mediante conexión serial al Arduino, que después las mostrará en el LCD.

Materiales:
  • Arduino UNO R3
  • Protoboard
  • Display LCD 16x2(JHD 162A)
  • Potenciómetro rotacional lineal.
  • Cable para conexiones
  • Cable USB-Arduino
Diagrama de Conexiones

Circuito

Para conectar el display LCD al arduino, se conectan los siguientes pines:
  • Pin LCD RS al pin digital 12
  • Pin LCD Enable al pin digital 11
  • Pin  LCD D4 al pin digital 5
  • Pin  LCD D5 al pin digital 4
  • Pin  LCD D6 al pin digital 3
  • Pin  LCD D7 al pin digital 2
  • Pin LCD Bklt - a GND.
  • Pin LCD Bklt + al pin 13 del Arduino.
Adicionalmente, se conecta un potenciómetro de rotación lineal a +5V y GND del Arduino, con su salida al pin VO(pin 3) del display LCD.

Funcionamiento

Funcionamiento del Display LCD

El LCD tiene un interfaz paralelo, significando esto que el microcontrolador tiene que manipular varios pines del interfaz a la vez para controlarlo. El interfaz consta de los siguientes pines:
  • Un pin de selección de registro (RS) que controla en qué parte de la memoria del LCD estás escribiendo datos. Puedes seleccionar bien el regisro de datos, que mantiene lo que sale en la pantalla, o un registro de instrucción, que es donde el controlador del LCD busca las instrucciones para saber cual es lo siguiente que hay que hacer.
  • El pin de lectura/escritura (R/W)que selecciona el modo de lectura o el de escritura.
  • Un pin para habilitar (enable) que habilita los registros.
  • 8 pines de datos (D00-D07). Los estados de estos pines (nivel alto o bajo) son los bits que estás escribiendo a un registro cuando escribes, o los valores de lectura cuando estás leyendo.
Hay también un pin de contraste del display (Vo), pines de alimentación (+5V y GND) y pines de retro-iluminación (Bklt+ y Bklt-), que te permiten alimentar el LCD, controlar el contraste del display, o encender y apagar la retro-iluminación, respectivamente.

El proceso de controlar el display involucra la colocación de los datos que componen la imagen de lo que quieres mostrar, en los registros de datos, y luego, colocar las instrucciones, en el registro de instrucciones. La librería LiquidCrystal te simplifica todo este proceso de forma que no neesitas saber las instrucciones de bajo nivel.

Los LCD-s compatibles con Hitachi pueden ser controlados de dos modos: 4 bits u 8 bits. El modo de 4 bits requiere siete pines de E/S de Arduino, mientras el modo de 8 bits requiere 11 pines. Para mostrar texto en la pantalla, puedes hacer la mayoría de las cosas en modo 4 bits.

Funcionamiento del Arduino
El Arduino sirve de interfaz entre el script de python que descarga las notificaciones, y como se muestran en el display. El IDE de Arduino tiene una librería llamada LiquidCrystal, que ayuda bastante al manejo de un display conectado con el Arduino. Esta librería permite a la placa Arduino controlar displays LCD basados en el chipset Hitachi HD44780 (o compatibles), que se encuentra en la mayoría de LCDs de texto. La biblioteca trabaja en modo 4-bit o en 8-bit (es decir, por medio de 4 u 8 líneas de datos, además de RS, ENABLE, y, opcionalmente, las líneas de control RW).

Contiene funciones como:
  • begin(). Usada para definir las dimensiones del display.
  • clear(). Usada para borrar los contenidos del display.
  • write(), print(). Usadas para mostrar contenido en el display.
  • setCursor(). Usada para definir donde escribir en el display.
También cuenta con una función para poder hacer scroll al texto, es decir moverlo fuera de la pantalla para poder mostrar largas cadenas de texto que normalmente no podrían ser mostradas. Pero yo no hice uso de esta función, ya que el scroll que hace es total, y al mover todo el texto, cosas que yo deseo sean estáticas también se moverían por lo tanto tuve que implementar un scroll propio, que será explicado el el código.

Entonces, la función del Arduino es recibir las notificaciones desde el script de python mediante una conexión serial, para después imprimirlas en la pantalla LCD. La conexión serial me permite enviar una cadena de texto u otra información char por char, entonces para que el Arduino pueda identificar que información se está enviando, antes de enviar cualquier cosa, el primer char es un identificador, como la letra "N" para las notificaciones, o la letra "T" para actualizar el reloj. Con esto podemos asegurarnos que no se confunda la información. 

Funcionamiento del Código Python

El código en Python es un script simple, que usando la librería feedparser, descarga las notificaciones de mi Facebook personal(copiando el link rss de mi Facebook, por lo cual EN EL CÓDIGO QUE PUBLICARÉ SERÁ OMITIDO, Y ES IMPORTANTE SI DESEAN PROBARLO ESPECIFICAR SU PROPIO LINK RSS DE FACEBOOK).

Después de descargarlas, les realizo varias funciones a cada notificación, como quitarles acentos para evitar problemas en el display, cortarlos a un tamaño que pueda ser mostrado, y demás.Primero el código establece la conexión con el Arduino, después actualiza la hora con la hora de la computadora, entonces las notificaciones son enviadas al Arduino. Y el código espera hasta que se cumple un tiempo determinado(5 minutos aproximadamente por defecto) para poderse reiniciar y actualizar las notificaciones.


Imágenes del Circuito en Funcionamiento





Video

video

Código Python:

#!usr/bin/python
import serial, time
import feedparser
import unicodedata
  
#Funcion usada para quitar los acentos de las notificaciones, para poder evitar errores al
#mostrar caracteres en el display LCD
def reemplazar_acentos(s):
   return ''.join((c for c in unicodedata.normalize('NFD', s) if unicodedata.category(c) != 'Mn'))
 
#Funcion que maneja operaciones para cortar las notificaciones a una longitud que pueda ser
#mostrada por el display LCD.
def cortar_notificacion(notif):
 string = ""
 list_string = notif.split()
  
 if "evento" in notif:
  for i in range(len(list_string)):
   if list_string[i] == "evento":
    string+=list_string[i]
    string+=" "
    break
   else:
    string+=list_string[i]+" "
  string+=list_string[i+1]+" "
  string+=list_string[i+2]+"."
  
  
 elif "solicitud" in notif:
  for i in range(len(list_string)):
   if list_string[i] == "solicitud":
    string+=list_string[i]
    string+="."
    break
   else:
    string+=list_string[i]+" "
   
 else:
  for i in range(len(notif)):
   if notif[i] == ":":
    string+="."
    break
   elif notif[i] == ".":
    string+=notif[i]
    break
   else:
    string+=notif[i]
 if len(string) > 60:
  new_string = ""
  for i in range(57):
   new_string+=string[i]
  new_string+="..."
  string = new_string
 return string
 
#Funcion que descarga las notificaciones y las guarda en una lista, con el string de la notificacion, y su fecha(no usada actualmente)
def get_notificaciones():
 parser = feedparser.parse('Link al RSS de Facebook')
 notificaciones = []
 for i in range(0, 10):
  notificaciones.append(cortar_notificacion(reemplazar_acentos(parser.entries[i].title)))
 return notificaciones
 
#Funcion que da formato a la fecha descargada
def formatear_fecha(fecha):
 datos = fecha.split()
 dia = datos[2]
 mes = datos[3]
 hora = datos[4]
 new_hora = ""
 for i in range(len(hora)-2):
  new_hora += hora[i]
 hora = new_hora
 datos = "%s/%s %s"%(dia, mes, hora)
 return datos
  
if __name__ == "__main__":
 try:
  #Abrimos el puerto serial, este puede variar dependiendo de la conexion, en este ejemplo es
  #COM3
  print "Abriendo puerto serial"
  ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
  #Esperamos 2 segundos, es importante o se puede perder la primera escritura en la conexion
  while True:
   time.sleep(2)
   #Actualizamos el reloj y obtenemos las notificaciones
   print "Actualizando el reloj"
   notificaciones = get_notificaciones()
   #Damos el formato de fecha que deseamos al tiempo actual, y lo enviamos al Arduino
   tiempo = time.strftime("%H:%M:%S", time.localtime())
   ser.write("T")
   ser.write(tiempo)
   print tiempo
   #Obtenemos el tiempo actual para compararlo despues y reiniciar cuando llegue a un
   #determinado tiempo para actualizar las notificaciones
   t = time.time()
   control = True
   i=0
   #Repetimios hasta que control cambie a false(se cumpla el tiempo de actualizacion)
   while(control):
    #Leemos un string enviado desde el arduino, este string debe ser un 'YES' o un
    #'NO', esto para poder saber si el Arduino esta listo para recibir una nueva
    #notificacion
    string = ser.read(100)
    #Si esta listo, y aun no enviamos las 10 notificaciones, escribimos una notificacion
    if "YES" in string and i<10:
     print 'yes'
     ser.write("N")
     ser.write(notificaciones[i].encode('utf-8'))
     print "Escribiendo notificacion: %s"%notificaciones[i]
     i+=1
    #Comparamos el tiempo actual con el inicial, si se cumplio el tiempo solicitado salimos
    #del while
    if "REINICIAR" in string:
        control = False
       #Abrimos y cerramos el puerto para reiniciar la conexion, y actualizar, esperamos de nuevo 2 segundos
       print "Reiniciando"
       ser.write('R')
    
 except serial.SerialException as e:
  print e

Código Arduino:

#include <TimedAction.h>
#include <LiquidCrystal.h>
//Variable LCD que usaremos para controlar la pantalla
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
//Variables para controlar el reloj
byte hora = 12, minu = 0, seg = 0;
//Variable usada para contar el tiempo que paso desde
//otra comparacion de tiempo, y saber cuando se cumple
//1 segundo sin usar delays
long previousMillis = 0; 
//Strings usadas para almacenar las notificaciones
String string = "";
String notifications[10];
//Variables para controlar cual notificacion se
//muestra actualmente
int currentString = 0, maxim = 0, num = 0;
//Especie de threads, que corren cada cierto tiempo
TimedAction scrollAction = TimedAction(250, scroll);
TimedAction readSerialAction = TimedAction(1, readSerial);
 
//Setup del Arduino, definimos el LCD, y encendemos el pin 13
//que esta conectado al backlight del display LCD
//Tambien inicializamos una conexion serial con 9600 de baud rate
//e imprimimos YES para dar a conocer al script de python que
//es posible leer  una notificacion
void setup(){
  lcd.begin(60, 2);
  pinMode(13, OUTPUT);
  digitalWrite(13, HIGH);
  Serial.begin(9600);
  Serial.print("YES");
}
 
//Funcion simple que converte los numeros del reloj(ints)
//en un string, agregandole un 0 si es menor a 10 para poder
//mostrarlo correctamente.
String getClockShowNumber(int number){
  if(number < 10){
    return ("0"+number);
  }
  else{
   return (""+number);
  }
}
//Funcion que actualiza el reloj si ya se cumplio 1 segundo
//desde la ultima vez que se actualizo
void updateDisplay(){
    delay(250);
    unsigned long currentMillis = millis();
    if(currentMillis - previousMillis > 1000) {
      previousMillis = currentMillis;
      Clock();
      char text_buffer[51];
      sprintf(text_buffer,"<%02d/%02d>%02d:%02d:%02d                                   ",
      num+1, 10, hora, minu, seg);
      lcd.setCursor(0, 0);
      lcd.print(text_buffer);
      text_buffer[8] = 0;
    }
}
//Funcion que checa los valores del reloj para sumarlos
//correctamente
void Clock(){
    //updateDisplay();
    seg = seg + 1;
    if(seg > 59){
      seg = 0;
      minu = minu + 1;
      if(minu > 59){
        minu = 0;
        hora = hora + 1; 
        if(hora>23){
          hora = 1;
        }
      }
    }
  }
  
//Funcion que hace un scroll especial al LCD, dando scroll
//solamente a la segunda linea del display, y dejando la
//primera intacta
void scroll(){
  String temp_string = notifications[num] + "   ";
  for(int i = 0; i < temp_string.length()-16; i++){
    lcd.setCursor(0, 1);
    for(int j=i; j<16+i; j++){
        //readSerialAction.check();
        lcd.print(temp_string[j]);
    }
    updateDisplay();
  }
}
 
//Funcion que actualiza el reloj despues de recibir la hora
//actualizada desde el script de python en la computadora
void updateClock(String time){
   //Arreglo[hora, min, seg]
   String temp[3] = {"", "", ""};
   int currentString = 0;
   for(int index = 0; index < time.length(); index++){
     if(time[index] == ':'){
       currentString++;
     }
     else{
       temp[currentString] += time[index];
     }
   }
   //Serial.print(temp[0]+":"+temp[1]+":"+temp[2]);
    hora = temp[0].toInt();
    minu = temp[1].toInt();
    seg = temp[2].toInt();
}
 
//Funcion que lee trafico desde la conexion serial, identifica
//que tipo de datos se recibiran, y los almacena o actua dependiendo
//del tag especificado.
void readSerial(){
  int i = 0;
  boolean control = true;
if(Serial.available()){
          switch(Serial.read()){
            case 'R':
              Serial.print("NO");
              //Serial.print("Reset");
              for(int i=0; i<10; i++){
                notifications[i] = "";
              }
              //lcd.clear();
              currentString = 0;
              maxim = 0;
              num = 0;
              Serial.print("YES");
              break;
               
            case 'N':
              Serial.print("NO");
              //Serial.print("Notification");
              while(control){
                updateDisplay();
                string = "";
                char c;
                while(Serial.available() > 0){
                     control = false;
                     c = Serial.read();
                     string+=c;
                }
              }
              notifications[currentString] = string;
              currentString++;
              maxim = currentString;
              if(maxim == 10){
                Serial.print("REINICIAR");
                delay(100);
              }else{
                 Serial.print("YES"); 
              }
              break;
            case 'T':
              Serial.print("NO");
              //Serial.print("Clock");
              String time = "";
              while(control){
                updateDisplay();
                char c;
                while(Serial.available() > 0){
                     control = false;
                     c = Serial.read();
                     time+=c;
                }
              }
              updateClock(time);
              Serial.print("YES");
              break;
          }
        }
        else{
         if(maxim > 0){
            if(num < maxim-1){
                num++;
            }
            else{
                num = 0;
            }
            updateDisplay();
            scrollAction.check();
             
          }
        }
}
 
//Loop, simplemente checa cada cierto tiempo si hay trafico serial
void loop(){
        readSerialAction.check();
 
  }

Referencias: 

jueves, 17 de mayo de 2012

Wiki Contributions - Week 15


For this week, being the topic Benchmarking, I investigated something about that, and found a pretty interesting piece of code that does exactly a benchmark of a certain computer using processes and threads and running normally, to find which ones are more efficient in certain circumstances, like executing a simple mathematical  operation or doing an I/O operation. The results were horrible, in my case, because my computer is not that good, but analyzing the information it is seen that the operation  runs "faster"(or more like less slower) most of the time using processes.

Link to the wiki: 

Nominaciones:

Juan Carlos, Victor y Saúl

miércoles, 9 de mayo de 2012

Wiki Contributions - Week 14

For this week, what I wanted to do is to issue some commands from a web page in ruby on rails, to do some things through ssh on a virtual computer. I managed to do this, but not as I expected, because I can't actually get to understand how ruby on rails works, and the only thing I could do is put my code that runs an ls command through ssh, somewhere between other operations of the web application. The code runs, and effectively does a ssh, but obviously I wanted the web application to ask for a command to run, which I couldn't done.

Anyway the specific code that I added to a ruby on rails application is the following:

#!/usr/bin/env ruby
require 'rubygems'
require 'net/ssh'

HOST = '192.168.1.75'
USER = 'user'
PASS = 'its'

Net::SSH.start( HOST, USER, :password => PASS ) do|ssh|
  output = ssh.exec!('ls')
  puts output
end

The code uses the Net::SSH gem, which I explained previously and there is an entry about it on the wiki.
This is not exactly the code itself, because I had to adapt it to the ruby on rails application, which works pretty weird and the only thing I could do to print the output data from the remote ls instruction, was to use some kind of html function that ruby on rails added to the code.



I updated the wiki with this and other examples I found using Net::SSH.
Nominaciones:


Alex and Victor

Apple iPhone 4S vs Samsung Galaxy S III

Para conocer más de las capacidades de los mejores celulares actuales, que mejor que comprar un Samsung Galaxy S III y un iPhone 4S, dos de los mejores smartphones actuales.

A continuación se mostrarán las especificaciones de hardware, y algunas de las nuevas características en software del Samsung Galaxy S III en comparación con lo existente en el iPhone 4S.

Hardware

Samsung Galaxy S III                                   iPhone 4S



Comparación de especificaciones:

Software
En el anterior análisis hemos repasado las cualidades técnicas que ha presentado Samsung con el Galaxy S3 y cúales de ellas suponen un reto para Apple y su iPhone 5. Sin embargo, es dentro del software donde la batalla parece que será mucho más encarnizada.
Salvando las diferencias entre Android e iOS, vamos a repasar las funcionalidades exclusivas que Samsung ha integrado en su Galaxy S3 para mejorar la experiencia de Ice Cream Sandwich frente a lo que puede ofrecer Apple desde su sistema operativo con el próximo iPhone 5.
Siri vs S-Voice
  • Siri
    Siri(de las siglas Speech Interpretaton and Recognition Interface) es un asistente personal inteligente y un navegador de conocimiento que funciona como una aplicación de iOS. La aplicación usa una interfaz de usuario de lenguaje natural para responder preguntas, hacer recomendaciones y realizar acciones mediante peticiones a un conjunto de servicios web. 
  • S VoiceS Voice es un software de reconocimiento de voz y asistente personal que llegará en el Samsung Galaxy S III. Reconoce el habla natural tanto como Siri del iPhone 4S. Comprende 8 lenguajes, incluyendo Inglés(Británico), Inglés(Americano), Francés, Español y Koreano.

En esta ocasión, el nuevo Galaxy S3 presenta S-Voice. Puede ser cierto que no sea tan sofisticado como Siri, pero su lanzamiento multidioma, y en concreto, con el español entre los que reconoce, tanto en su vertiente latinoamericana como “ibérica”, lo pone a la altura de la propuesta de Apple, incluso algo por encima.
AllShare vs AirPlay
  • AllShare
    "allshare" es una interfaz para compartir multimedia entre dispositivos electrónicos Samsung. Por medio de dispositivos conectados a una red inalámbrica, éstos pueden compartir información digital. Por ejemplo, un celular con "allshare" activado puede ser accesado para ver contenido digital o escuchar música, todo esto desde una televisión HDTV con "allshare" activado. En esta situación una televisión HDTV puede accesar contenido web desde el teléfono, como youtube. Otros servicios incluyen usar el teléfono como control remoto viendo películas desde computadoras o dispositivos HD conectados a la red.

  • AirPlay
    AirPlay(anteriormente llamado AirTunes cuando solo estaba destinado a audio) es una suite/stack de protocolos propietarios desarrollada por Apple, que permite el stream inalámbrico de audio, vídeo, fotos, junto con metadatos relaconados, entre dispositivos. 
NFC
  • S Beam
    La aplicación S Beam ofrece a los usuarios la posibilidad de transferir contenidos de una manera ágil y rápida. Los archivos de 1 GB podrán ser transferidos a una velocidad bastante considerable, puesto que en tres minutos pueden estar en su destino. De esta manera, todo hace pensar que los archivos más pequeños (fotografías, documentos, canciones o incluso discos enteros) pueden ser enviados a otro dispositivo en apenas unos segundos. La gran ventaja de este sistema es que no hacen falta cables, de manera que podremos enviarlos sin conectar el dispositivo a ningún sitio. Las transferencias se realizan de forma inalámbrica. Por ahora, la única desventaja es que este sistema es incompatible con otros teléfonos, puesto que S Beam solo funciona en los Samsung Galaxy S3
  • iPhone 4SiPhone 4S no cuenta con soporte para 4S. Por parte de iPhone 5 solo se ha confirmado que tendrá fuerte soporte a NFC, pero no se ha especificado más a detalle.

Samsung ha hecho ya realidad algunas de las funciones NFC que se le suponen al iPhone, y en concreto es la de S Beam, una forma cómoda y rápida de sincronizar y compartir contenido multimedia con otros dispositivos tan solo aproximando los dos dispositivos.
iCloud vs DropBox
  • iCloud
    iCloud es un sistema de almacenamiento nube o cloud computing de Apple Inc. Anunciado el 6 de junio 2011 en la Conferencia de Desarrolladores Globales de Apple (WWDC por sus siglas en Ingles). El servicio permite a los usuarios almacenar datos, como archivos de música en servidores remotos para descargar en múltiples dispositivos como iPhones, iPods, iPads y las computadoras personales que funcionen con Mac OS X (Lion o mas reciente) o Windows de Microsoft (Windows Vista o mas reciente). 


  • DropBox
    Dropbox es un servicio de alojamiento de archivos multiplataforma en la nube, operado por la compañía Dropbox. El servicio permite a los usuarios almacenar y sincronizar archivos en línea y entre computadoras y compartir archivos y carpetas con otros. Existen versiones gratuitas y de pago, cada una de las cuales con opciones variadas. Samsung ofrece 50 GB de almacenamiento gratuito para el Samsung Galaxy S III.
La sincronización en la nube llega de la mano del acuerdo de Samsung con Dropbox para este Galaxy S3 por la que los usuarios dispondrán de 50GB para guardar online todo el contenido del terminal y mantenerlo sincronizado con sus otros aparatos. Si bien Apple se ha mantenido impasible ante el resto de funciones, esta parece haberle dolido, y se conoce que rechaza las aplicaciones en su App Store que utilicen los servicios de Dropbox. Por lo tanto, la batalla entre este conocido servicio e iCloud parece que también enfrentará a Samsung y Apple, que no parece dispuesta a cederle el terreno de sus usuarios, cuando llegue el iPhone 5.
iTunes Match vs Scan and Match
  • Scan and Match
    Scan and Match escanea tu disco duro en busca de música y después compara la librería de música que Samsung tiene en la nube, y si existe una coincidencia se agrega a tu librería de Music Hub para que puedas acceder a ella en cualquier lado. El servicio de Musica Hub tiene aproximadamente 17 millones de canciones, y esta disponible para Samsung Galaxy S2 en adelante, smart tv, galaxy tablets, y via web en tu computadora. Este servicio cuesta $9.99/mes para un smartphone y una tablet, y $12,99/mes para hasta 4 dispositivos. A diferencia de Itunes Match que cuesta $24,99/año.
  • iTunes Match
     iTunes Match es un servicio para almacenar nuestra música en iCloud (toda la biblioteca musical), tanto la música de CD’s como la que se haya comprado previamente en la iTunes Store, para tenerla disponible en cualquier momento y cualquier lugar en nuestros dispositivos iOS, Apple TV o bien desde luego la PC o Mac.
    Como solamente se suben a la nube las canciones que no hayamos comprado en la iTunes Store, el proceso es relativamente rápido, sobre todo si hemos comprado una gran cantidad de canciones en la tienda de Apple, pues éstas automáticamente ya estarán en nuestra cuenta de iTunes Match, ya que las toma del catálogo de más de 20 millones de canciones con que cuenta.
Referencias:

Arduino - Conexión Serial

Gracias a la inspiración de mi compañero David Sosa, me dio una fuerte sensación de intentar realizar una conexión serial con mi Arduino UNO de alguna manera. Inicialmente creía que la única forma de hacerlo era mediante Processing(lenguaje de programación y entorno de desarrollo integrado de código abierto basado en Java), pero al investigar me di cuenta que existían múltiples formas de realizarlo, y la que me parecio más interesante fue usando pySerial, una librería de python.


En este post veremos un ejemplo de uso de pySerial de forma básica con un script sencillo, para después ir mejorandolo hasta ser usado desde un sitio web usando CGI.


Conexión Serial


Se utiliza para la comunicación entre la placa Arduino y un ordenador u otros dispositivos. Todas las placas Arduino tienen al menos un puerto serie (también conocido como UART o USART): Serial. Se comunica a través de los pines digitales 0 (RX) y 1 (TX), así como con el ordenador mediante USB. Por lo tanto, si utilizas estas funciones, no puedes usar los pines 0 y 1 como entrada o salida digital.


Demostración


En las primeras demostraciones se utilizará un simple LED para visualizar como se controla su encendido y apagado mediante una comunicación serial. Las conexiones son como se aprecia en la imágen:




Conexión mediante Serial Monitor


Para poder realizar una conexión serial con el Arduino UNO, primero es necesario programarlo para que inicie una conexión serial y este listo para recibir los datos desde la computadora. Para esto podemos usar el ejemplo que tiene por default Arduino(con unas pequeñas modificaciones), con el código siguiente:


const int ledPin = 13; // the pin that the LED is attached to
int incomingByte;      // a variable to read incoming serial data into

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
  // initialize the LED pin as an output:
    Serial.println("Hello world!");

  pinMode(ledPin, OUTPUT);
}

void loop() {
  // see if there's incoming serial data:
  if (Serial.available() > 0) {
    // read the oldest byte in the serial buffer:
    incomingByte = Serial.read();
    // if it's a capital H (ASCII 72), turn on the LED:
    if (incomingByte == 'H') {
      digitalWrite(ledPin, HIGH);
        Serial.println("Encendiendo LED");
    } 
    // if it's an L (ASCII 76) turn off the LED:
    if (incomingByte == 'L') {
      digitalWrite(ledPin, LOW);
      Serial.println("Apagando LED");
    }
  }
}


El código es bastante sencillo, define una constante que será usada para definir el pin donde conectaremos el LED que se encenderá y apagará. Y además un byte para almacenar el byte que se recibe en el buffer serial.

Se inicializa la conexión serial, y también se define el pin 13 como salida en el setup(). Después en el loop() se verifica que haya tráfico serial entrante y se lee el byte más viejo del buffer, almacenandolo en la variable incomingByte(es int, y almacenará el número ASCII del carácter que se envió). Se compará el valor recibido con 'H' para saber si encender el LED, o 'L' para apagarlo, y además se envía a la computadora mediante el puerto serial un mensaje para cada caso.


Ahora para probar el funcionamiento de la comunicación serial, podemos usar el Serial Monitor, una herramienta del IDE de Arduino para enviar datos mediante el puerto serial(Tools>Serial Monitor).
En ella simplemente ingresamos una 'H' para encender el LED, y una 'L' para apagarlo:



Conexión mediante un script con pySerial


La siguiente tarea es realizar el mismo procedimiento pero mediante pySerial.


Qué es pySerial?


La biblioteca pySerial provee muchos módulos con los cuales se puede tener casi total control del puerto Serial de una computadora. Ya que las computadoras pueden tener más de un puerto Serial configurado, pySerial utiliza números del 0 al 255 para cada uno de los puertos, de tal forma que se pueda invocar cada puerto de manera individual y que se asegure la compatibilidad con muchas plataformas.


Esta biblioteca tiene una gran gama de aplicaciones, qeu van desde la comunicacion con dispositivos móviles como celulares y miniordenadores, hasta el control de plantas industriales automatizadas. Las funciones que este módulo provee, posibilitant, por ejemplo, establecer comunicacion con los bancos o registros de memorias de muchos dispositivos y poder manejar sus datos. En el área de la robotica, actualmente muchas plantas automatizadas en las que se utilizan robots o maquinaria industrial, utilizan el protocolo RS-232 y puerto seriales para poder comunicarse con dichos dispositivos, y por ejemplo, enviar instrucciones de funcionamiento o recibir la retroalimentacion de estos dispositivos, estableciendo una comunicacion bilateral o Full-duplex. Todo esto se realiza a través del puerto Serial y pySerial tiene la capacidad de realizar esto

Para instalar pySerial, podemos hacer una de varias cosas:

1. Instalarlo desde el código fuente. Para esto descargamos el código de: http://pypi.python.org/pypi/pyserial , extraemos los archivos, entramos a la carpeta y corremos el comando:
python setup.py install
2. Si contamos con pip, podemos simplemente correr el comando:
pip pyserial
3. O si contamos con easy_install:
easy_install -U pyserial
Ya instalado podemos escribir un pequeño script para mandar el estado del LED mediante la línea de comandos, como el siguiente:

#!/usr/bin/python                                                                                                       
import serial, time, sys
#Correr: 
#./arduino_serial.py "Estado del LED" "Puerto Serial"
#Ejemplo:
#./arduino_serial.py H ttyACM1

#Intentamos recoger los argumentos de la linea de comandos
try:
 puerto = sys.argv[2]
 estado = sys.argv[1]
#Si no podemos, damos un mensaje de error y salimos
except IndexError:
 print "Error al ingresar los argumentos.\nDebe ser: python arduino_serial.py 'Estado del LED' 'Puerto Serial'"
 sys.exit()
#Intentamos abrir la comunicacion serial y realizar algunas
#operaciones en ella
try:
 ser = serial.Serial('/dev/%s'%sys.argv[2], 9600, timeout = 1)
 time.sleep(2)
 message = ser.read(100)
 print message
 ser.write('%s'%sys.argv[1])
 message = ser.read(100)
 print message
#Si no podemos, mostramos un mensaje de error
except serial.SerialException as e:
 print "%s"%e

El código es igualmente simple. Tomamos desde la línea de comando dos argumentos, uno: el estado que deseamos dar al LED del arduino, y dos: el puerto serial con el que nos vamos a comunicar(este lo podemos encontrar en el IDE de Arduino en la pestaña Tools>Serial Ports.


Con el string del puerto serial inicializamos la conexión con dicho puerto serial, dando un timeout de 1 segundo. Ahora es importante también dar un retraso de dos segundos, debido a que al conectar el puerto serial, el Arduino se resetea, lo que significa que si no ponemos este delay, el reseteo interferirá con nuestro intento de envíar el caractér 'H' o 'L' al Arduino, y no ocurrirá nada.


Volviendo al código, leemos lo que el arduino envío inicialmente al correr y lo almacenamos en una variable para luego imprimirla. Escribimos el estado que se ingreso en la línea de comando por el puerto serial para cambiar el estado del LED, y de nuevo leemos un mensaje que haya enviado el led, confirmando el cambio de estado.


El resultado en la línea de comando es el siguiente:




Conexión mediante un script CGI y pySerial 


Para concluir, haremos algo similar a lo anterior, pero ahora mediante un formulario web, utilizando python y CGI.


¿Qué es CGI?


El CGI (Por sus siglas en inglés “Common Gateway Interface” es de las primeras formas de programación web dinámica.
En sí, es un método para la transmisión de información hacia un compilador instalado en el servidor. Su función principal es la de añadir una mayor interacción a los documentos web que por medio del HTML se presentan de forma estática.
Entonces usando CGI, hice un pequeño y simple formulario web, el cual simplemente cuenta con dos botones de radio, para poder seleccionar entre los dos estados del LED, y un botón para confirmar la selección.
Inicialmente este método no funcionaba, ya que era necesario dar permisos al archivo para poder abrir una comunicación serial, esto lo arregle de la siguiente forma(no es la recomendada, pero igualmente funciona):
chmod o+rw /dev/ttyACM1
(Nota: Hay que cambiar ACM1 por el respectivo puerto serial, de nuevo, este puede ser encontrado al buscar en la pestaña Tools>Serial Port del IDE de Arduino)
Con esto podemos utilizar el formulario web para enviar los estados por el puerto serial, el script de python es el siguiente:
#!/usr/bin/python                                                                                                       
import serial,time,cgi, sys
print "Content-type: text/html\n"
print "<html><head><meta http-equiv='refresh' content='0;url=http://localhost/'/><title>LED Test</title></head><body><p>\n"
datos = cgi.FieldStorage()
if datos.has_key("estado") \
    and datos["estado"].value != "":
try:
 ser = serial.Serial('/dev/ttyACM1', 9600)
 time.sleep(2)
 ser.write("%s"%datos["estado"].value)
 ser.close()

except serial.SerialException as e:
 print "no device connected - printing to terminal only "%s 
 sys.exit()
print "</p></body></html>"


Sin entrar mucho en detalles, el código lee todo el contenido del cgi, busca si existe la llave "estado", correspondiente a los radio button, y si estos no estan vacios(se selecciono alguno), intenta iniciar la conexión serial y manda el estado seleccionado al arduino. 


Para hacer funcionar esto, es necesario tener instalado apache, y mover el código a /usr/lib/cgi-bin, además de colocar el respectivo index.html en /var/www, que se puede descargar de aquí:


La página es muy simple, y se ve así:


Los archivos, tanto el html como el .py pueden descargarse de AQUÍ.

Imágen del circuito:



Lo mismo pero ahora con un Display

Ahora para mostrar el control de algunas salidas más, utilizaré los pines 13-10 para dar una salida BCD,  que represente los números del 0 al 9, para poder mostrarlos en un display BCD a 7 segmentos.

Para hacer esto hay que reprogramar el Arduino, usando el siguiente código:

const int A = 13;
const int B = 12;
const int C = 11;
const int D = 10;

int incomingByte; 

void setup() {
  // initialize serial communication:
  Serial.begin(9600);
  // initialize the LED pin as an output:
  Serial.println("Hello world!");

  pinMode(A, OUTPUT);
  pinMode(B, OUTPUT);
  pinMode(C, OUTPUT);
  pinMode(D, OUTPUT);
}

void loop() {
  // see if there's incoming serial data:
  if (Serial.available() > 0) {
    // read the oldest byte in the serial buffer:
    incomingByte = Serial.read();
    
    switch(incomingByte){
     case '1':
       digitalWrite(A, HIGH);
       digitalWrite(B, LOW);
       digitalWrite(C, LOW);
       digitalWrite(D, LOW);
      break;
     
     case '2':
       digitalWrite(A, LOW);
       digitalWrite(B, HIGH);
       digitalWrite(C, LOW);
       digitalWrite(D, LOW);
      break;
     
     case '3':
       digitalWrite(A, HIGH);
       digitalWrite(B, HIGH);
       digitalWrite(C, LOW);
       digitalWrite(D, LOW);
      break;
     
     case '4':
       digitalWrite(A, LOW);
       digitalWrite(B, LOW);
       digitalWrite(C, HIGH);
       digitalWrite(D, LOW);
      break;
     
     case '5':
       digitalWrite(A, HIGH);
       digitalWrite(B, LOW);
       digitalWrite(C, HIGH);
       digitalWrite(D, LOW);
      break;
     
     case '6':
       digitalWrite(A, LOW);
       digitalWrite(B, HIGH);
       digitalWrite(C, HIGH);
       digitalWrite(D, LOW);
      break;
     
     case '7':
       digitalWrite(A, HIGH);
       digitalWrite(B, HIGH);
       digitalWrite(C, HIGH);
       digitalWrite(D, LOW);
      break;
     
     case '8':
       digitalWrite(A, LOW);
       digitalWrite(B, LOW);
       digitalWrite(C, LOW);
       digitalWrite(D, HIGH);
      break;
     
     case '9':
       digitalWrite(A, HIGH);
       digitalWrite(B, LOW);
       digitalWrite(C, LOW);
       digitalWrite(D, HIGH);
      break;
     
     case '0':
      digitalWrite(A, LOW);
       digitalWrite(B, LOW);
       digitalWrite(C, LOW);
       digitalWrite(D, LOW);
      break;
    }
  }
}

Este código es similar al anterior, en el sentido que leerá un byte mediante la conexión serial, y dependiendo de que caracter sea, mostrará una salida diferente. Pero ahora se darán múltiples salidas con las combinaciones binarias del número correspondiente al caracter. Para controlarlo mediante el Serial Monitor y el script sencillo en python, se pueden usar los códigos anteriores, pero para manipularlo mediante CGI, se requiren algunos cambios, para lo cual añadi una página en el apartado http://localhost/display, donde aparece una ventana como la siguiente:

Los archivos pueden descargarse AQUÍ.

Los cambios son mínimos, simplemente cambie los radio buttons por una lista desplegable con los números del 0 al 9 en el html, y el identificador de ésta en el .py y el html.


Referencias: