Escritorio remoto en la Raspberry Pi

vnc_demo
Todos nos hemos conectado remotamente a un equipo, ya sea usando el programa de “Conexión a Escritorio Remoto” o la solución de TeamViewer gratuita. Ambos programas son un ejemplo de Virtual Network Computing (VNC), un sistema de escritorio compartido gráfico que usa el protocolo Remote Frame Buffer (RFB) para controlar de manera remota otra computadora.

¿Cuál es el punto?

El punto es que podemos tomar el control de la raspberry de forma gráfica instalando un servidor vnc.

Prerequisitos

Tener configurada una ip estática. ¿Cómo se hace?

¿Qué necesitamos instalar?

pi@raspberrypi ~ $sudo apt-get install tightvncserver

Una vez que la instalación termine, levantamos el servicio de la siguiente manera

pi@raspberrypi ~ $sudo vncserver

Nos solicitará un password para restringir el acceso remoto, lo tecleamos un par de veces y luego podemos rechazar la opción del segundo password.

Ahor necesitamos un cliente, instalamos una extensión del navegador chrome VNC Viewer for Google Chrome.

vnc_client

Ya está, ya podemos explorar el sistema operativo, mover archivos de configuración y hacer pruebas sin tener que conectar periféricos a la Raspberry Pi.

Si deseamos que esta opción este disponible cada vez que reiniciemos la Raspberry, tenemos que usar el siguiente script, lo podemos copiar y luego guardarlo como “tightvncserver”.

#!/bin/sh
# /etc/init.d/tightvncserver
# Thanks to Neil Black at http://www.neil-black.co.uk/raspberry-pi-beginners-guide
VNCUSER='pi'
case "$1" in
start)
su $VNCUSER -c '/usr/bin/tightvncserver :1'
echo "Starting TightVNC Server for $VNCUSER "
;;
stop)
pkill Xtightvnc
echo "TightVNC Server stopped"
;;
*)
echo "Usage: /etc/init.d/tightvncserver {start|stop}"
exit 1
;;
esac
exit 0

Después movemos el archivo “tightvncserver”, le damos permisos de ejecución y lo configuramos para que se ejecute al arranque

pi@raspberrypi ~ $ sudo cp tightvncserver /etc/init.d
pi@raspberrypi ~ $ sudo chmod a+x /etc/init.d/tightvncserver
pi@raspberrypi ~ $ sudo update-rc.d tightvncserver defaults

Nota: hacemos caso omiso de la advertencia “…missing LSB tags and overrides”

Para probar nuestro script, reiniciamos la raspberry

pi@raspberrypi ~ $ sudo shutdown -r now

Para deshabilitar el inicio automático del servidor vnc, escribimos la siguiente línea

pi@raspberrypi ~ $ sudo update-rc.d tightvncserver remove

Si quisieramos transferir archivos desde el cliente a la Raspberry Pi, podemos usar FileZilla y contectarnos por ssh (puerto 22), esta es la pantalla de configuración.
filezilla

Advertisements

¿Cómo usar control NES como dispositivo de entrada?

Introducción
20150517_155115
Para los que pertenecemos a la llamada generación de los millennials, ver un control NES nos transporta al época en que pasábamos tardes enteras jugando títulos como Mario Bros., Contra, Duck Hunt, Kung Fu, Baseball, etc. Ya sea que te haya tocado jugar con la consola de Nintendo o con versiones similares como family (famicom), la diversión estaba garantizada. Este fue mi juego favorito y ahora se puede jugar en este sitio http://www.8bbit.com/game.php?id=2732.

Claro que no es lo mismo jugar con el teclado que jugar con el control, la experiencia de usuario deja mucho que desear, las combinaciones de botones no son fáciles de hacer.

El proyecto de fin de semana

El objetivo es utilizar un viejo control NES como dispositivo de entrada. Con ayuda de la biblioteca NESpad.h (ver información del proyecto aquí) es muy sencillo realizar la interfaz del control usando un Arduino.

Inspiración

Materiales

  • 1 control NES (se puede conseguir en un tianguis por $15.00)
  • 1 Arduino
  • 5 Pines
  • Termofit (20 cm)
  • Material para soldar

Diagrama
20150517_155124

NES pin Arduino Pin
STROBE/LATCH Digital 2
CLOCK Digital 3
DATA Digital 4
+5V +5V
GND GND

configuración de colores/pines

Paso 1. Cortar el conector del control y soldar a cada cable un pin protegido por el termofit.

Paso 2. Realizar la conexión con el Arduino de acuerdo a el diagrama (ver documentación completa aquí).

Paso 3. Cargar el programa en el Arduino.


/*
this example from the NESpad Arduino library
displays the buttons on the joystick as bits
on the serial port - rahji@rahji.com

Version: 1.3 (11/12/2010) - got rid of shortcut constructor - seems to be broken

*/

#include

// put your own strobe/clock/data pin numbers here -- see the pinout in readme.txt
NESpad nintendo = NESpad(2,3,4);

byte state = 0;

void setup() {
Serial.begin(57600);
}

void loop() {

state = nintendo.buttons();

// shows the shifted bits from the joystick
// buttons are high (1) when up
// and low (0) when pressed
// Serial.println(~state, BIN);

//vamos a imprimir el o los botones seleccionados

if(state & NES_A)
Serial.println("NES_A");
if(state & NES_B)
Serial.println("NES_B");
if(state & NES_SELECT)
Serial.println("NES_SELECT");
if(state & NES_START)
Serial.println("NES_START");
if(state & NES_UP)
Serial.println("NES_UP");
if(state & NES_DOWN)
Serial.println("NES_DOWN");
if(state & NES_LEFT)
Serial.println("NES_LEFT");
if(state & NES_RIGHT)
Serial.println("NES_RIGHT");

delay(100);
}

Paso 4. Probar la comunicación con el monitor serial.

Paso 5. Probar el control con un juego real usando Processing.

Tomamos prestado el código del juego snake del sitio de openprocessing.org escritor por Anson Liang.

Modificamos el código para incluir la comunicación serial y replicar la funcionalidad del evento keypress.

/* OpenProcessing Tweak of *@*http://www.openprocessing.org/sketch/27164*@* */
/* !do not delete the line above, required for linking your tweak if you upload again */

// Snakes
// By: Anson Liang
// This is a classic snake game, just made it for fun.
//
import processing.serial.*;
color col=color(255,255,192);
color foodColor = color(255,0, 0);
float speed = 100;
int cx, cy;

int moveX = 0;
int moveY = 0;
int snakeX = 0;
int snakeY = 0;
int foodX = -1;
int foodY = -1;
boolean check = true;
int []snakesX;
int []snakesY;
int snakeSize = 1;
int windowSize = 200;
boolean gameOver = false;
PFont Font = createFont("Arial",20, true);
String buff = "";
boolean pausado = false;
boolean firstTime = true;
Serial port;
void setup(){
size(int(windowSize), int(windowSize),P3D);

background(0);
speed = 100;
speed=speed/frameRate;
snakesX = new int[100];
snakesY = new int[100];

cx = width/2;
cy = height/2;

snakeX = cx-5;
snakeY = cy-5;
foodX = -1;
foodY = -1;
gameOver = false;
check = true;
snakeSize =1;
// Uses the first available port
port = new Serial(this, Serial.list()[7], 57600);
//print(Serial.list()[7]); ///dev/tty.usbserial-A9007KC7
}

void draw(){

while (port.available() > 0) {
//print("listening");
serialEvent(port.read());
}

if(!pausado && speed%10 == 0){

background(0);
runGame();
firstTime = true;
}else if(firstTime && pausado){
String modelString = "pause";
textAlign (CENTER);
textFont(Font);
text(modelString,100,100,40);
firstTime = false;
}

speed++;
}

void serialEvent(int serial) {

if(serial != '\n')
{
buff += char(serial);
}else if(buff.length()> 0)
{
//quitamos el caracter de retorno de carro!!
buff = buff.substring(0,buff.length()-1);

if(buff.equals("NES_UP") == true) { if(snakesY[1] != snakesY[0]-10){moveY = -10; moveX = 0;}}
if(buff.equals("NES_DOWN") == true) { if(snakesY[1] != snakesY[0]+10){moveY = 10; moveX = 0;}}
if(buff.equals("NES_LEFT") == true) { if(snakesX[1] != snakesX[0]-10){moveX = -10; moveY = 0;}}
if(buff.equals("NES_RIGHT") == true) { if(snakesX[1] != snakesX[0]+10){moveX = 10; moveY = 0;}}
if(buff.equals("NES_START") == true) {
if(!gameOver){
pausado = !pausado;
}
else{
reset();
}
}
//limpiamos el buffer
buff = "";

}
}

void reset(){
snakeX = cx-5;
snakeY = cy-5;
gameOver = false;
check = true;
snakeSize =1;
moveY = 0;
moveX = 0;
}
void runGame(){

if(gameOver== false){
drawfood();
drawSnake();
snakeMove();
ateFood();
checkHitSelf();

}else{
String modelString = "game over";
textAlign (CENTER);
textFont(Font);
text(modelString,100,100,40);
}
}
void checkHitSelf(){
for(int i = 1; i < snakeSize; i++){
if(snakeX == snakesX[i] && snakeY== snakesY[i]){
gameOver = true;
}
}
}
void ateFood(){
if(foodX == snakeX && foodY == snakeY){
check = true;
snakeSize++;
}
}
void drawfood(){
fill(foodColor);
while(check){
int x = (int)random(1,windowSize/10);
int y = (int)random(1,windowSize/10);
foodX = 5+x*10;
foodY = 5+y*10;

for(int i = 0; i < snakeSize; i++){
if(x == snakesX[i] && y == snakesY[i]){
check = true;
i = snakeSize;
}else{
check = false;
}
}

}

rect(foodX-5, foodY-5, 10, 10);

}
void drawSnake(){
fill(col);

for(int i = 0; i 0; i--){
snakesX[i] = snakesX[i-1];
snakesY[i] = snakesY[i-1];
}
}

void snakeMove(){
snakeX += moveX;
snakeY += moveY;
if(snakeX > windowSize-5 || snakeX windowSize-5||snakeY < 5){
gameOver = true;
}
snakesX[0] = snakeX;
snakesY[0] = snakeY;

}

void keyPressed() {
if(keyCode == UP) { if(snakesY[1] != snakesY[0]-10){moveY = -10; moveX = 0;}}
if(keyCode == DOWN) { if(snakesY[1] != snakesY[0]+10){moveY = 10; moveX = 0;}}
if(keyCode == LEFT) { if(snakesX[1] != snakesX[0]-10){moveX = -10; moveY = 0;}}
if(keyCode == RIGHT) { if(snakesX[1] != snakesX[0]+10){moveX = 10; moveY = 0;}}
if(keyCode == 'R') {reset();}
}

Cambia tu aburrido mouse por este joystick sacado de la basura

Hoy encontré un stick de un viejo control de playstation 1, que hace tiempo había rescatado de la basura con la esperanza de reutilizarlo alguna vez. Así que sin pensarlo dos veces me dí a la tarea de soldar unos pines a los potenciometros, conectar algunos cables, escribir algo de código para mi Arduino Duemilanove y reciclar código de un sketch de Processing.

El funcionamiento del joystick es sencillo, son dos potenciometros de los que se leen los valores x y y; sólo hay conectarlos a las entradas análogas del Arduino y enviar los valores a la computadora.

joystick_arduino

 

Este es el sketch para el Arduino

int ledPin = 13;
int joyPin1 = 0; // variable x conectado al pin análogo 0
int joyPin2 = 1; // variable y conectado al pin análogo 1
int value1 = 0; // valor digitalizado del pin 0
int value2 = 0; // valor digitalizado del pin 1
void setup() {
beginSerial(9600);
}
//convierte un numero a su valor ascii
int tochar(int data) {
return (data + 48);
}
void loop() {
// lee los valores de los potenciometros
value1 = analogRead(joyPin1);
//pequeña pausa entre las lecturas
delay(100);
//lee el segundo potenciometro
value2 = analogRead(joyPin2);

//envia el valor de x
serialWrite(‘X’);
//envia el valor numerico digito por digito, solo 8 bits a la vez
if(value1>0){
while(value1){
serialWrite(treatValue(value1%10));
value1 = value1/10;
}
}else serialWrite(‘0’);
serialWrite(10);
//envia el valor de y
serialWrite(‘Y’);
if (value2>0){
while(value2){
serialWrite(treatValue(value2%10));
value2 = value2/10;
}
}else serialWrite(‘0’);
serialWrite(10); //el fin de linea marca el final de la lectura
}

Pantallazo-joystick_storingInput

y este el sketch de Processing
[cc lang=”java” tab_size=”2” lines=”100”]

// Joystick
// por Dennys Regalado Díaz
//
// Creado el 11 de septiembre de 2009
////////////
int num = 60;
float mx[] = new float[num];
float my[] = new float[num];

import processing.serial.*;

String buff = “”;
int x_val=0,y_val=0;
int NEWLINE = 10;

Serial port;

void setup(){
size(400,400);
smooth();
noStroke();
fill(255, 153);
// Print a list in case COM1 doesn’t work out
println(“Available serial ports:”);
println(Serial.list());

//port = new Serial(this, “COM1”, 9600);
// Uses the first available port
port = new Serial(this, Serial.list()[0], 9600);
}

void draw()
{
background(41);
while (port.available() > 0) {
serialEvent(port.read());
}

// Reads throught the entire array
// and shifts the values to the left
for(int i=1; i<num; i++) {
mx[i-1] = mx[i];
my[i-1] = my[i];
}

int xx = (int) map(constrain(x_val,10,1024),0,1024,10,width-10);
int yy = (int) map(constrain(y_val,10,1024),0,1024,10,height-10);
mx[num-1] = xx;
my[num-1] = yy;
float r = random(125,205);
float g = random(100,205);
float b = random(150,200);

for(int i=0; i<num; i++) { fill(r,g,b,153); ellipse(mx[i], my[i], i/2, i/2); } println(“x:”+xx+”, y:” + yy); } void serialEvent(int serial) { if(serial != NEWLINE) { buff += char(serial); } else if(buff.length()> 0) {
char c = buff.charAt(0);
buff = buff.substring(1);
if (c == ‘X’)
x_val = Integer.parseInt(buff);
else if (c == ‘Y’)
y_val = Integer.parseInt(buff);
buff = “”;
}
}
[/cc]

Visualización de datos en 15 minutos con Tableau

Este post es sobre una de la áreas que he estado explorando recientemente, la visualización de datos. Les recomiendo descargar la versión pública de Tableau y comenzar a jugar con la herramienta, está pensada para usuarios sin ningún tipo de background de programación, de hecho la intención es facilitar la exploración de datos y poder contar una historia de forma visual.

No es necesario conectarse a una base de datos para explotar un conjunto de datos, podemos usar como fuente un archivo de texto separado por comas (.csv), separado por tabuladores (.tab o .tsv) o un archivo plano (.txt).

Voy a describir el proceso para generar una visualización que muestra el porcentaje de la población que habla una lengua indígena en el estado de Oaxaca de acuerdo al censo 2010 del INEGI.

1. Descargar la fuente del INEGI. Estas son las instrucciones para consultar la información del inegi, una vez que obtengamos la vista que necesitamos, nos vamos al final de la página y elegimos formato “.csv” y luego el botón exportar.

consulta interactiva de los resultados del censo de población y vivienda 2010 en la siguiente liga: http://bit.ly/q1d7tl, active las casillas de Entidad y municipio y Habla indígena y lengua, presione el botón de Ver consulta, presione el signo de + Habla lengua indígena y de igual manera el signo de + de la entidad de su interés para que se desagregue la información.

 

2. Ya tenemos la fuente, ahora abrimos Tableu, en la sección de Connect in a file, elegimos “Text file”, buscamos nuestra fuente de datos y la cargamos.

3. A continuación veremos dos secciones del lado izquierdo, “Dimensions” y “Measures”, tomamos de las dimensiones y métricas que deseamos visualizar arrastrándolas a cualquiera de las regiones: Color, Size, Label, Detail o Tooltip.

marksTableau

 

4. Seleccionamos una de las plantillas que tenemos disponibles, algunas se mostrarán deshabilitadas hasta el set de datos cumpla con ciertas características como un número mínimo de dimensiones y métricas.

showmeTableau

 

5. La visualización esta lista para compartir, para realizar la publicación debemos registrarnos en el sitio con anterioridad.Sheet 5

Voy a continuar explorando otras herramientas con raw y plot.ly y publicar sobre ellas.

Cámara de vigilancia con Raspberry Pi y una webcam

La necesidad de tener una cámara de vigilancia para el cuarto de los bebés es muy común hoy en día. Así que decidí realizar una por mí mismo usando una Raspberry, una vieja webcam y este tutorial de Makezine.

Objetivo

La cámara debe:

  • ser fácil de usar de forma remota a través de una página web
  • mostrar imágenes a color de alta resolución
  • permitir rotar la cámara de izquierda a derecha y de arriba hacia abajo, ya sea con arrastrando el dedo sobre dispositivos touch o con las flechas del teclado.

Lista de Materiales

  • una webcam ($500.00)
  • raspberry pi + SD card 4G + fuente de alimentación ($1,000)
  • impresión del case ($250)
  • 2 servomotores pequeños ($90 x 2 = $180)
  • 1 cable USB reciclado
  • 1 cincho sujeta cables

El costo total del proyecto es de aproximadamente de $2,000, el costo más grande es sin duda la raspberry, si no incluimos el costo de la webcam, el costo se puede reducir a $1,500.

imageimage

imageimage

imageimage

Problemas

Estos son los modelos en 3D de la carcasa de la cámara, listos para ser impresos. En las dos imágenes de arriba se muestran los dos “brazos” que embonan con los dos servo motores. Sobre el de la izquierda se monta la cámara y permite girar hacia arriba y hacia abajo. El brazo de la derecha va montado sobre la caja que protege a la Raspberry Pi,  y permite mover la cámara de izquierda a derecha.

El detalle es que los orificios sobre los cuales embonaran los servo motores, no cuentan con dientes que encajen con la cabeza de los motores. Esto causa que la cabeza del servo gire pero los brazos no lo hagan con la misma fuerza y respuesta que se espera. También provoca que con el peso de la cámara el brazo inferior se tambaleé y terminé por caerse.

Mejoras

Hardware

Para solucionar el problema anterior, decidí usar los accesorios originales de los servo motores y pegarlos a los brazos de la cámara. Con esto mejoró notablemente la respuesta de los servo motores, pero continuó teniendo problemas con el peso de la cámara. Después de realizar una serie de movimientos de izquierda a derecha, el peso de la cámara la hace caer.

Software

El código original de Matt Stultz sólo incluye la respuesta a los eventos del teclado, usando la biblioteca de jquery mobile agregamos los eventos touch para controlar los servo motores. Pronto subiré las modificaciones del proyecto a un repositorio de GitHub.

Posibles mejoras

Se encuentra pendiente publicar la IP de la raspberry para que se puede tener acceso a la cámara desde Internet.

¿Cómo usar la comunicación serial en un ATtiny85 programado desde Arduino?

Lo primero que hay que hacer es conocer los pines de nuestro microcontrolador. Para eso consultamos el archivo pins_arduino.h que corresponde al core que estamos utilizando.

pines

Después es necesario agregar un bloque de configuración con el nombre “ATtiny85 8 Mhz clock (w / Arduino as ISP)”, en el archivo boards.txt.

append_8mhz_attiny85_entry

attiny85-8arduinoisp.name=ATtiny85 8 MHz clock (w/ Arduino as ISP)
attiny85-8arduinoisp.upload.using=arduino:arduinoisp
attiny85-8arduinoisp.upload.maximum_size=8192
attiny85-8arduinoisp.build.mcu=attiny85
attiny85-8arduinoisp.build.f_cpu=8000000L
attiny85-8arduinoisp.build.core=attiny45_85

Resulta que la biblioteca de SoftwareSerial que incluye el arduino 1.0.5 no funciona para nuestros fines, por lo que hay que descargar la versión más reciente en la siguiente liga.

Ver más información sobre esta biblioteca aquí.

Agregamos la biblioteca desde nuestro IDE: Sketch > Importar Librería > Add Library…, elegimos el .zip que acabamos de descargar (NewSoftSerial10c.zip) y listo.

addLibrary

Usamos el siguiente ejemplo:

/*
Takes an input on the AtTiny85 from the VERY TRICKY analogue input pin 2 (as labeled in HLT tutorial)

and outputs these with Software serial to the arduino uno, or Duemillanove

Hookup
ATTINY85 pin 3 -> Arduino Uno pin 0
ATTINY85 pin 4 -> Arduino Uno pin 1

*/

#include
// Definitions
#define rxPin 3
#define txPin 4

//SoftwareSerial mySerial(rxPin, txPin);
NewSoftSerial mySerial(rxPin, txPin);

int sensorPin = 1; //7 PB2 Ain1
int sensorVal = -1;

boolean switchFans = 0;

// the setup routine runs once when you press reset:
void setup() {
pinMode(sensorPin, INPUT);
mySerial.begin(9600);
}

// the loop routine runs over and over asensorpingain forever:
void loop() {

sensorVal = analogRead(sensorPin);
mySerial.print("Input Val: ");
mySerial.print(sensorVal);
mySerial.println();
}

Al intentar compilar se generan los siguientes errores:

C:\Program Files (x86)\arduino-0019\libraries\NewSoftSerial\NewSoftSerial.cpp: In static member function 'static void NewSoftSerial::enable_timer0(bool)':
C:\Program Files (x86)\arduino-0019\libraries\NewSoftSerial\NewSoftSerial.cpp:520: error: 'TIMSK0' was not declared in this scope
C:\Program Files (x86)\arduino-0019\libraries\NewSoftSerial\NewSoftSerial.cpp:526: error: 'TIMSK0' was not declared in this scope

Por lo que es necesario modificar el archivo fuente NewSoftSerial.cpp en las líneas 517 y 523, como se muestra en la figura.

NewSoftwareSerial_changesMás información aquí.

Al descargar el programa no olviden seleccionar como tarjeta ATtiny85 8 Mhz clock (w / Arduino as ISP) y como programador Arduino as ISP.

Para verificar la comunicación serial utilizaremos el arduino para recibir los mensajes que envía el ATtiny y posteriormento los visualizaremos desde el IDE empleando el Monitor Serial del menú de Herramientas.

Cargamos un sketch mínimo como este a nuestro Arduino, el cual nos servirá de monitor.

It just looks like this
///////
void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:
 
}

//////

Después cargamos el programa, sin olvidar reajustar los parámetros Tarjeta (en mi caso Arduino Duemilanove w/ATmega168)  y Programador (AVRISP mkJI) en nuestro IDE.

Posteriormente, hacemos las siguientes conexiones:

ATTINY85 pin 3 -> Arduino Uno pin 0
ATTINY85 pin 4 -> Arduino Uno pin 1

En el pin 7 de nuestro ATtiny conectamos alguna señal analógica que podamos variar, en este ejemplo usé un potenciometro alimentado del mismo Arduino con +5V.

Ejemplo de conexión con el Arduino

Después simplemente abrimos el Monitor Serial desde el sketch que acabamos de cargar y giramos el potenciometro para visualizar diferentes valores.

serialMonitor

Ver guía completa aquí.

 

¿cómo portar tu proyecto Arduino a un microcontrolador de bajo costo?

En este post se describe, por medio de pantallazos, la forma en que se debe configurar un Arduino (Windows 7) para usarlo como programador de un ATtiny85.

Esta opción nos brinda una forma sencilla de portar nuestros proyectos a un microcontrolador de bajo costo ($60 pesos).

 

pasos1_y_2

paso3 paso4 paso5y6

Fuente: Programming an ATtiny w/ Arduino 1.0

Para poder usar la biblioteca SoftwareSerial son necesarios unos pasos extras:

Es necesario cambiar  a 8Mhz para utilizar SoftwareSerial

Es necesario cambiar a 8Mhz para utilizar SoftwareSerial

  1. Seleccionar Tarjeta > ATtiny85 (internal 8 MHz clock) en el menú de Herramientas.
  2. Elegir la opción “Grabar Secuencia de Inicio” del menú Herramientas.

Nota: Si no aparece la opción “ATtiny85 (internal 8 MHz clock)”, agregar el siguiente bloque al final del archivo “boards.txt”, reiniciar el IDE de Arduino y verificar nuevamente.

attiny85-8.name=ATtiny85 (internal 8 MHz clock)
attiny85-8.bootloader.low_fuses=0xe2
attiny85-8.bootloader.high_fuses=0xdf
attiny85-8.bootloader.extended_fuses=0xff
attiny85-8.upload.maximum_size=8192
attiny85-8.build.mcu=attiny85
attiny85-8.build.f_cpu=8000000L
attiny85-8.build.core=arduino:arduino
attiny85-8.build.variant=tiny8

Para más información consultar la siguiente liga.

Hint: Comparar los archivos boards.txt con la versión de este repositorio.