Creación de una Aplicación en P5.Js y Socket.io – Parte 2

Anteriormente parte 1

VIENDO EL ARCHIVO MAIN.JS

Recordemos que nuestro principal objetivo es tener una aplicación donde exista una grilla de cuadrados los cuales pueden ser pintados desde un menú de colores haciendo click en un color y luego click en el cuadrado que se desea pintar, esto debe ser comunicado a los demás clientes que se hayan conectado a nuestra aplicación, además cualquier cliente puede nuevamente pintar el mismo cuadrado; recordemos que una condición es que esta grilla se pueda renderizar de forma adecuada sin importar el dispositivo, la dimensión y el aspecto está totalmente controlado por P5Js; sin embargo, P5Js hace uso de valores absolutos en pixeles tal que si definimos que un cuadrado tenga 120×120 pixeles en un computador de mesa podría verse bien dada las dimensiones del mismo, pero eso no ocurriría en un teléfono móvil; para ello debemos hacer uso de ciertos artilugios que permitan la renderización .

MANOS AL CÓDIGO

Lo primero entonces es declarar una serie de variables globales las cuáles serán accesibles en todo la aplicación por lo mismo que son globales, entonces:

var socket=io();
var dx=5*window.innerWidth/6; //variable general del ancho
var dy=5*window.innerHeight/6; //variable general del alto

Cuando se realizó la comunicación desde el servidor con socket.io() este puso a disposición un socket que nos permitirá emitir y escuchar eventos, luego de ello debemos de trabajar en función de proporciones, window.innerWidth y window.innerHeight nos da la dimensión en ancho y alto de la ventana de la ventana del dispositivo, de ello nuestra aplicación cogerá los 5/6. Si dx y dy son las dimensiones de nuestra aplicación, podemos entonces segmentarla como un grilla, tal que en vertical sean once espacios y en horizontal sean 10 espacios:

2.png

var grilla_y=11;
var grilla_x=10;

Podemos obtener la dimensión de cada grilla haciendo:

var w_c=dx/grilla_x; // variable del ancho de un cuadrado
var h_c=dy/grilla_y; //variable del alto de un cuadrado 

Como hemos de crear una grilla de cuadrados, lo ideal es almacenarlo en un arreglo, tal que podamos acceder a su posición, así también recordemos que los cuadrados tienen como propiedad un id que tiene la misma estructura de la posición en el arreglo:

var malla_cuadrados=[]; //cuadrícula de cuadrados
var mando_colores=[]; // cuadrícula de botones de asignación de color

Finalmente creamos una variable global relacionada al color sin inicializar, y su función principal es almacenar y comunicar el color que se ha elegido

var color_actual;

P5Js tiene dos funciones básicas principales, setup y draw, la primera es llamada cuando el programa inicia y es la encargada de inicializar todas las variables o configuraciones de entorno que nuestra aplicación requiera, la segunda es la encargada de ejecutar infinitamente el código que se inserte en el, lo cual será de mucha utilidad cuando se tenga que repintar un cuadrado ante un cambio de color.

Función Setup

Lo primero que debemos hacer en esta función es crear el canvas, el canvas es el lienzo sobre el cual se ha de dibujar todos nuestros elementos:

let canvas=seteo_canvas(dx,dy);

¿Pero qué hace la función seteo_canvas(), como observamos recibe la dimensión de nuestra aplicación y hace lo siguiente:

function seteo_canvas(){
    let canvas=createCanvas(dx,dy);
    let posx=(windowWidth-width)/2;
    let posy=(windowHeight-height)/2;
    canvas.position(posx, posy);
    canvas.parent("juego");
    return canvas

}

P5Js  usa el método createCanvas que recibe las dimensiones del canvas, si lo dejamos tal cual, posicionará el canvas en la esquina superior izquierda creando un nuevo tag canvas, pero queremos controlar ello, esto lo hacemos indicando la posición tanto en X e Y usando las variables que nos proporciona P5Js que son windowWidth que nos da el ancho de la ventana y width que nos proporciona el ancho de nuestra aplicación, similar a lo que hicimos para hallar dx, solo que en términos de P5Js y que con el método position le daríamos una posición, finalmente lo asociamos al tag main de nuestro index.html a través del método parent que recibe un id y devolvemos un canvas ya configurado.

Luego de crear el canvas debemos de crear la grilla de cuadrados, al ser una grilla posicionarlo se hará a través de un bucle for anidado tal que en cada iteración del bucle anidado se genere una instancia de la clase cuadrados y se guarde en malla_cuadrados:

for(fila=0;fila<grilla_y-1;fila++){
        for(columna=0;columna<grilla_x;columna++){
            let c=new Cuadrados(w_c/2+w_c*columna,h_c/2+h_c*fila,w_c,h_c,fila,columna);
            malla_cuadrados.push(c);       
        }
    }

Observemos 1 cuadrado:

let c=new Cuadrados(w_c/2+w_c*columna,h_c/2+h_c*fila,w_c,h_c,fila,columna);

Cada w_c es un segmento de la grilla, como el cuadrado está centrado le damos como primera posición la mitad de un segmento de grilla, y el cual aumenta en un segmento en cada iteración de columna, de forma similar con la posición Y solo que con la fila. Observamos además que la grilla es de 10×10, ya que el último nivel lo reservamos para el menú de colores.

Función menu_colores

Vamos a crear una línea divisoria haciendo uso de la función line(), le asignamos la posición inferior un grosor de línea de 4 (strokeWeight) y un color con stroke(), también una lista de colores en formato RGB haciendo uso de color, y finalmente tan igual que la creación de cuadrados, los creamos a través del uso de bucle for.

function menu_colores(){
    //Línea divisoria
    stroke(155,56,89);//color de línea
    strokeWeight(4);
    line(0, (grilla_y-1)*h_c-2,grilla_x*w_c,(grilla_y-1)*h_c-2);
    //Fin de línea divisoria
    //cuadrados de mando
    strokeWeight(1);
    noStroke();
    lista_colores=[color(19,22,76),color(19,76,40),color(196,47,11),color(242,245,2),color(255)]
    for (let i = 0; i < 5; i++) {
        fill(lista_colores[i]);
        rectMode(CENTER);
        let rectangulo=rect(w_c/2+(w_c)*i, (grilla_y-1)*h_c+h_c/2, w_c, h_c);    
        mando_colores.push(rectangulo);
    }

}

Función Draw

Recordemos que draw es la función que va iterar infinitamente, por ende es la encargada de la actualización de estado de nuestros cuadrados los cuáles cambiarán de color, para ello debemos de dibujar los cuadrados, para dibujar los cuadrados tenemos el método de la clase que es dibujar cuadrado, luego de ello dibujamos el menú de colores:

function draw(){
    malla_cuadrados.forEach(cuadrado=>{
        cuadrado.dibujarCuadrado();
    });
    menu_colores();    
}

 

Hemos utilizado el bucle forEach el cual recorrerá por cada elemento (que es un cuadrado) y llamará al método dibujar cuadrado, luego se ejecutará la función de los colores. Hasta este punto solo tendremos la grilla de cuadrados y el menú de de colores puestos en la escena, Ahora debemos de estar a la escucha de los eventos, para ello P5Js puede registrar una serie de eventos relacionados al ratón, ¿Cuál de esos eventos necesitamos?, pues el evento que sucede cuando   el botón del ratón ha sido presionado y suelto  el cual se registra en la función mouseClicked().

Función mouseClicked

Al no ser un botón, no se puede asignar un “listener“, y mouseClicked registrará cualquier evento que implique un presionar y soltar del ratón en toda la pantalla, debemos de ser un poco más creativos para determinar cuando se presionó un cuadrado específico o si fue el cuadro de colores, para ello ante el evento debemos de rastrear toda la grilla, tanto de cuadrados como de colores y determinar la posición del ratón en X e Y de donde se generó el evento:

function mouseClicked(){
    //Grid de cuadrados
    for(let fila=0;fila<(dy/h_c)-1;fila++){
        for(let columna=0;columna<dx/w_c;columna++){
            if((w_c*columna<mouseX && mouseX<w_c*(columna+1)) &&(h_c*fila<mouseY && mouseY<h_c*(fila+1))){
                //console.log(" fila: ", fila+1," columna: ",columna+1 );
                let pos_aux=columna+(grilla_y-1)*fila;
                let aux_x=malla_cuadrados[pos_aux].x;
                let aux_y=malla_cuadrados[pos_aux].y;
                let aux_cuadrado=new Cuadrados(aux_x,aux_y,w_c,h_c,fila,columna,color_actual);
                aux_cuadrado.dibujarCuadrado();
                malla_cuadrados[pos_aux]=aux_cuadrado;
                //el socekt emite
                socket.emit("dibujar",{id:pos_aux,mi_color:malla_cuadrados[pos_aux].mi_color}) ;
                //console.log(malla_cuadrados[pos_aux].id);
            }
        }
    }

    //Grid de mandos
    colores=[{r:19,g:22,b:76},{r:19,g:76,b:40},{r:196,g:47,b:11},{r:242,g:245,b:2},{r:255,g:255,b:255}];
    if(mouseY>(grilla_y-1)*h_c && mouseY<grilla_y*h_c){
        for(let columna=0;columna<dx/w_c;columna++){
            if(w_c*columna<mouseX && mouseX<w_c*(columna+1)){
                color_actual=colores[columna];
                //console.log(color_actual); revisamos si el color actual está cambiando
            }
        }
    }
}

En la grilla de cuadrados hemos ejecutado el doble bucle for, note el artilugio sobre la condición del límite (dv/h_c)-1, dv: es el área de trabajo, y h_c: es un segmento de la grilla, esta división y resta posterior es 10, ¿Por qué usar esta forma y no 10 a secas?, pues porque tu grilla puede variar, así no tendrías que estar modificando todo el código, de forma similar ocurre con (dx/w_c). Ahora revisemos el condicional, como deseamos saber la posición del ratón, esta la podemos obtener con los eventos mouseX y mouseY de P5Js, pero no solo ello, también si está en un segmento específico de la grilla, es por ello que tenemos w_c*columna y h_c*fila, ¿Por qué en un condicional?, pues porque podemos hacer click fuera del área de trabajo, debemos asegurar que el click se hizo dentro del área de trabajo y en un cuadrado específico, cuando obtenemos ello, almacenamos la posición, la posición  la buscamos dentro de malla cuadrados obteniendo la posición X e Y, creamos un nuevo cuadrado en la misma posición, fila y columna, con un detalle, ahora sí agregamos el parámetro del color asignando el valor de la variable color_actual, esto lo hacemos el siguiente fin, recordemos que color_actual tiene como función almacenar un color, por defecto es blanco y solo varía cuando un color del cuadro de colores ha sido seleccionado, esto responde a la siguiente dinámica:

“El usuario selecciona un color modificando el valor de color_actual, el usuario luego de seleccionar un color, selecciona un cuadrado de la grilla para modificar el color, se modifica el color creando una nueva instancia de cuadrado asignando como parámetro de color el valor de la variable color_actual”

Cuando se crea una instancia de cuadrado esta reemplaza al cuadrado anterior en su posición en el arreglo de malla_cuadrados. Vemos ya en acción el registro del evento emit, vamos a emitir dibujar enviando al servidor para que retransmita a todos los clientes el cambio de color en una posición indicada que se almacena en la propiedad id.

En la grilla de mandos o cuadro de colores, la misma dinámica de detección de la posición del ratón; sin embargo, tenemos un arreglo de colores que en función de la posición iremos asignando a la variable global color_actual.

El socket a la Escucha del evento personalizado

Hasta el momento hemos localizado la posición donde se genera un evento, hemos comunicado el evento y sus parámetros a los demás clientes, pero también la siguiente perspectiva en que un mismo clientes está emitiendo y estando a la escucha de los eventos registrados, esto ya lo habíamos revisado en el servidor, toca entonces en el cliente poner a la escucha, sabemos que el evento registrado es “redibujar

socket.on("redibujar",(datos)=>{
    console.log(datos);
    malla_cuadrados.forEach(cuadrado=>{
        if(cuadrado.id==datos.id){
            cuadrado.mi_color=datos.mi_color;
        }
    })
})

Cuando el evento es comunicado el cliente recibe los datos y recorre el arreglo de malla_cuadrados, buscando a través del id, cuando estos sean iguales, modificaremos la propiedad mi_color del cuadrado asignado.

Esto resuelve de forma completa nuestra aplicación, espero que haya sido de su agrado.

 

 

 

Creación de una Aplicación en P5.Js y Socket.io – Parte 1

¿QUÉ ES LO QUE USAREMOS?

En este post vamos abordar el desarrollo de una aplicación que haga uso de p5js, socket.io y node.js. En resumen p5js tal como está en la descripción de su propia página es una librería de javascript heredera de processing que tiene como principal objetivo hacer el proceso de codificación sencillo y accesible para artistas, diseñadores y educadores. Socket.io es la librería que permite establecer una comunicación bidireccional en tiempo real entre un cliente (tu navegador) y un servidor, esto puede sonar a websockets, pero socket.io no es websockets, hace uso de el y proporciona mayores características que permiten ir más allá; finalmente como se menciona se requiere hacer uso de un servidor y para ello que mejor si hablamos de javascript que hacerlo con Nodejs que nos permite establecer una comunicación con el mismo de forma muy escalable, lo cual viene siendo tendencia en la actualidad

¿QUÉ QUEREMOS HACER?

Una cuadrícula que se renderiza y dimensiona de acuerdo al dispositivo en el que se visualice, la cuadrícula tendrá como color por defecto el blanco, posee un menú de colores en la parte inferior, tal que al seleccionar un color y luego seleccionar una celda esta celda se pinte del color que se ha seleccionado (todo esto se logra con p5JS); pero hasta aquí solo hemos logrado tener una pequeña aplicación que solo nos sirve a nosotros, la idea es que muchas personas a la vez puedan ir cambiando los colores de la cuadrícula y observar que celdas han sido cambiadas por otros en tiempo real, esto se logra a través de la comunicación entre los clientes y el servidor, el servidor despacha los cambios y los clientes ejecutan los cambios al instante (esto se logra a través de socket.io); finalmente todo ello debe tener un lado del servidor que sea el que comunique a los demás clientes lo que está sucediendo.

MANOS AL CÓDIGO EN EL BACKEND

508192_b.gif

Lo primero es iniciar al proyecto (por cierto estoy haciendo uso de VS Code), para ello en la terminal de tu editor o en la terminal de NodeJS:

npm init --yes

Luego de iniciar el proyecto, debemos de preparar la estructura de nuestra aplicación, debemos reconocer que tenemos un lado frontend y backend, en lo personal me gusta seguir la siguiente estructura:

proyecto
|->index.js
|->frontend
  |->public
     |->index.html
     |->js
     |->css
     |->img
    

Entonces nos ocupamos del backend y esto lo hacemos en el archivo index.js, ¿Qué necesitamos del servidor?, pues que esté habilitado y puesto a la escucha en un puerto específico, para habilitarlo, hemos de hacer uso de algunos módulos, cuando el servidor esté habilitado, debemos de entregarlo a socket.io para que “fluya” a través de él, entonces necesitamos primero instalar express y socket.io; express es casi el framework por defecto para trabajar con nodejs y socket.io como ya hemos mencionado es para establecer la comunicación bidireccional:

npm i express socket.io

Cuando termine de instalarse observarás que se ha creado la carpeta node_modules, carpeta en la cual han de residir las librerías en cuestión; bueno ahora en el index.js:

const express=require("express"); //requerimos al módulo
const path=require("path"); // módulo que nos permite trabajar con directorios
const IO_server=require("socket.io"); // bueno ya hemos hablado de este
const app=express(); // creamos una aplicación express

Con app hemos de implementar todo lo restante; entonces, debemos de informar a nuestra aplicación que hemos de usar archivos estáticos (podemos decir que los archivos estáticos son aquellos que solo se cargan una vez y son inmutables) para ello express hace uso del método estático (valga la redundancia) static:

app.use(express.static(path.join(__dirname,"frontend/public")));

El método static recibe como parámetro una dirección de tipo String en donde se encuentran los archivos estáticos, pero esta dirección debe ser indicada desde el mismo sistema, pero dado que el sistema de nuestro servidor puede ser windows o linux, colocar la dirección en crudo puede ser considerado como hard-codeo, en consecuencia usamos la propiedad __dirname  de nodejs y lo unimos con la carpeta frontend/public donde están nuestros archivos, esta unión se realiza con el método join del módulo path.

Ahora que hemos indicado nuestros archivos estáticos debemos de poner nuestro servidor a la escucha:

let servidor=app.listen(process.env.PORT || 3000,()=>{
    console.log("Estamos en línea");
})

Para ello usamos el método listen, este método recibe como parámetro opcional  el puerto, pues si no se indica será el sistema quien asigne un puerto, ahora bien, dentro de dicho parámetro hemos hecho uso de la propiedad process.env.PORT nos permite acceder al puerto del entorno del usuario, cuestión muy útil cuando subamos nuestra aplicación a entornos como heroku por ejemplo; el segundo parámetro es un callback que podemos usar en su forma más básica para notificar que el servidor está en funcionamiento, este servidor a la escucha lo entregamos a una variable “servidor“.

Hasta este punto ya no tenemos nada más que hacer con express, ahora debemos de configurar lo relacionado a socket.io, ¿Qué ocurre aquí?, socket.io necesita ser unido a un servidor y asignarlo a una variable que ha de gestionar todo lo relacionado a él:

const io=IO_server(servidor);

Hecho esto, hay que entender una dinámica básica en socket.io, un servidor o cliente puede estar a la escucha de eventos y al mismo tiempo puede emitirlos, es ahí que podemos decir que es bidireccional, para implementar la escucha y emisión de un evento hacemos uso de los métodos on y emit los cuáles registran los eventos que han de funcionar de forma bidireccional; sin embargo, el primer evento que se debe registrar es cuando se establece una conexión; es decir que estaremos a la escucha usando on, este evento es conocido como “connection“, si observamos el constructor del método on, este escucha el evento y hace el tratamiento a través de un callback, el callback recibe como parámetro un socket, el cual luego del evento connection es quién ha de distribuir los eventos entre los clientes:

io.on("connection",(socket)=>{
    console.log("Conexión extablecida",socket.id);
})

Recordemos que en el lado del backend socket.io ha de estar recibiendo y reenviando los eventos sucedidos entre los clientes, y esto lo logrará a través del socket que ha recibido al establecerse una comunicación con un cliente; entonces, vamos a registrar dos eventos, el primero será estando a la escucha de un evento al que llamaremos “dibujar” el cual se generará cuando en un cliente pinte un cuadrado, cuando esto suceda, debemos de saber dos cosas la posición x,y del cuadrado que ha sido pintado y el color con el cual ha sido pintado, estos datos los recibiremos como parámetros, y lo emitiremos a los demás, con una pequeña particularidad, cuando emitamos el evento debemos asegurarnos que lo emitimos a todos menos al mismo cliente que genero el evento, pues no tiene sentido notificarle a él del evento siendo él quién lo generó, este evento que emitimos, lo realizamos con el método emit, emit recibe como parámetro un registro de evento (al que llamaremos “redibujar“) y los datos a enviar:

io.on("connection",(socket)=>{
    console.log("Conexión extablecida",socket.id);
    socket.on("dibujar",(datos)=>{
        console.log(datos);
        socket.broadcast.emit("redibujar",datos);
    })

})

Un detalle que agregar, para asegurar que el evento que el cliente ha generado no se le comunique a el mismo pero sí a los demás, usamos el modificador broadcast. Muy bien, esto es todo lo que debemos de hacer en nuestro fichero index.js; ahora debemos de pasar al frontend.

MANOS AL CÓDIGO EN EL FRONTEND

Ahora debemos de abordar la parte visual de nuestra aplicación, para ello creamos el fichero main.js y cuadrados.js dentro de la carpeta js y pasamos hacia el fichero index.html, en index.html debemos de colocar las librerías de p5js y p5.dom.js para ello usaremos sus CDN, también el fichero cuadrado.js y main.js; creamos una tag “main” y asignamos como id=”juego”:

1

¿Qué otra cuestión particular notamos? pues la dirección:

src="/socket.io/socket.io.js"

Este script es otorgado por socket.io desde el backend.

Esto es todo lo que haremos respecto al index.html; ahora vamos al fichero cuadrados.js.

En cuadrados.js hemos de crear la clase de cuadrados que han de conformar la grilla, tendrá como constructor la posición x e y, el ancho, el alto, la fila y columna a la que pertenecen y un valor opcional de tipo objeto referido al color; esta clase solo tendrá un método que será dibujarCuadrado(), valga la redundancia que lo que hará será dibujar un cuadrado; entonces:

class Cuadrados{
    constructor(x,y,w_c,h_c,fila,columna,mi_color={r:255,g:255,b:255}){
        this.x=x;
        this.y=y;
        this.w_c=w_c;
        this.h_c=h_c;
        //colores
        this.id=columna + 10*fila;
        this.mi_color=mi_color;
    }

    dibujarCuadrado(){
        rectMode(CENTER)
        stroke(0);
        fill(this.mi_color.r,this.mi_color.g,this.mi_color.b);
        rect(this.x,this.y,this.w_c,this.h_c);
    }
}

La clase posee sus propiedades, las cuáles serán empleadas por el método, bien, observemos un poco el método, P5Js posee formas primitivas, entre ellas se encuentran los cuadriláteros que se definen con la función rect que recibe como parámetros, la posición X e Y, ancho y alto; pero rect solo dibuja el cuadrado, recordemos que deseamos que el color por defecto sea blanco, esto se logra con la función fill() que puede recibir una configuración RGB o un valor entre 0 a 255 si queremos que esté solo en la escala de grises, si fill se encarga de pintar el cuadrado, no podemos obviar la línea que dibuja el cuadrado, y esto si pinta con la función stroke(), stroke posee la misma configuración de color que fill. Cuando se dibuja un cuadrado, el origen x,y está situado en la esquina superior izquierda; sin embargo, para métodos de cálculo posterior debemos de hacer que su origen se sitúe en el centro del cuadrado y es por ello que usamos el atributo rectMode(CENTER), esto es todo cuánto a la descripción de las sentencias dentro del método. Ahora debemos pasar al fichero main.js pero esto lo abordaremos en una posterior publicación

Aplicación de Conexión con la Wemos D1

¿Qué es la Wemos D1?

Es una tarjeta de desarrollo similar a Arduino orientada especialmente al Internet de las cosas (IoT). Está basada en el SoC (System on Chip) ESP8266 el cual cuenta con conectividad a internet. Para el desarrollo de aplicaciones se puede elegir entre los lenguajes Arduino y Lua, pero al tratarse de Arduino se nos será más sencillo de usar y procederemos a trabajar con él.

image-2_thumb.jpg

¿Qué deseamos hacer con la Wemos D1?

El objetivo de nuestro experimento es el envío de datos a través de la red wifi hacia un servidor, esto resulta útil cuando se entre a modo producción ya que podemos tener una placa conectada a una red y ya no directamente a un computador y realizar el envío de datos que se van censando.

¿Qué herramientas hemos de implementar?

  • De lado de la aplicación web haremos uso de NodeJs, express, pug y morgan.
  • De lado del hardware haremos uso de una placa Wemos D1 y para su programación el Arduino IDE.

¿Cómo haremos el envío de datos?

Como ya comentamos lo haremos a través de la red wifi, eso implica que estamos haciendo uso del protocolo TCP/IP,  y de esta familia el HTTP, el cual se podría decir que es el protocolo más común en la conexión con internet, HTTP tiene un conjunto de métodos de petición, el que nos atañe es el método POST, que básicamente se encarga del envío de datos hacia el servidor; entonces hacemos el envío de datos mediante un método POST pero en formato JSON, hemos de configurar a nuestro servidor de tal forma que no permita otro tipo de dato que no sea en formato JSON. Entonces hay que ponernos manos a la obra.

Creación y Configuración del Servidor en Node JS y Express

Ya que el tópico que nos concierne no se trata del desarrollo de un servidor, vamos acelerar un poco esta sección (asumo que tienes NodeJs instalado), bueno primero debes de crear una carpeta donde ha de estar tu aplicación, luego de ello abres tu editor favorito (en mi caso Visual Studio Code), abres la terminal que viene incorporada con él, para crear nuestro proyecto debemos de hacer:

npm init --yes

Ahora instalamos nuestros módulos, los módulos que requerimos son express que nos sirve para el manejo de nuestra aplicación, morgan para la revisar que tipo de solicitudes se realizan a nuestro servidor y las respuestas del mismo, pug como motor de vistas (aunque dado el punto hasta donde hemos de llegar no será muy necesario) y mongoose para la gestión de una base de datos  haciendo:

npm i express morgan pug mongoose

Adicionalmente puedes instalar nodemon, nodemon es una herramienta que al inicializarse está a la escucha de cualquier cambio que se realice en los ficheros del servidor o incluso del cliente de nuestra aplicación, si ya lo tienes instalado puedes omitir esta instrucción, sino, te recomendaría que lo instales de forma global para que te sirva en futuras aplicaciones así como también para evitar el monótono reinicio manual de tu servidor.

npm i -g nodemon

Hecho todo esto, creamos la estructura básica de nuestra aplicación, creamos un fichero y lo llamamos index.js, en el mismo nivel una carpeta con el nombre backend y la otra con frontend, dentro de la carpeta backend creamos las carpetas config, database, routes, services, views, dentro de la carpeta frontend creamos las carpeta public y dentro de public las carpetas css, js e img.

esqueleto

Los demás ficheros que ves en la imagen se irán creando automáticamente o posteriormente, dentro de la carpeta config, creamos un fichero con el mismo nombre config.js con el siguiente código:

config

Dentro de la carpeta routes, creamos un fichero con el nombre rutas.js y dentro de el colocamos el siguiente código:

rutas

Como puedes observar estamos importando los servicios que hemos creado;sin embargo, no está resaltado ya que no se han usado, esto es por la dimensión de la aplicación y de lo que se intenta mostrar, esta estructura ya deja conectado los servicios y si en caso hubiese un procesamiento de datos, como por ejemplo un llamado a la base de datos o incluso guardar datos en la misma, se haría a través de los servicios. Entonces ¿Qué es lo que sucede en este fichero?, rutas está manejando las peticiones de las direcciones, y el tipo de petición, como puedes observar una petición de tipo POST, el cual se realiza directamente a la raíz de nuestra aplicación, cuando eso suceda hemos de imprimir en consola el cuerpo de la petición a través del req.body y enviaremos como respuesta un “Conectado“.

En la carpeta views creamos una carpeta que llamaremos layout y un fichero al que llamaremos index.pug, ambos al mismo nivel, ahora dentro de layout creamos un fichero al que llamaremos layout.pug

layout

Ok, vamos a detenernos un poco aquí, nuestra aplicación esta haciendo uso bootstrap para el css y cierta funcionalidad, bootstrap requiere de jquery; además de bootstrap estamos usando font-awesome para colocar iconos en nuestra aplicación, ambas librerías son implementadas a través de sus CDN, ¿Lo vamos a usar en estos momentos? NO, recuerda que estamos armando una aplicación que ha de servir para muchas cosas, no solo para el ejercicio de hoy. Cuando hemos creado nuestro layout vemos que estamos usando la palabra reservada block, block para pug representa uno de sus mecanismos para la herencia, esto lo vamos apreciar de la siguiente forma, en el archivo index.pug debemos de colocar lo siguiente:

index

Una enorme cantidad de código ¿huh?, pues aunque parezca risible esto es poderoso, hemos heredado del layout, si deseáramos agregar algo, simplemente tendríamos que modificar el bloque “contenido_principal“, pero por el momento no.

Ahora pasemos al lado del cliente, el lado del cliente es lo que de cara el usuario ve, entonces esto debe disponer de ciertos ficheros, por ejemplo debemos de crear un fichero para los estilos dentro de la carpeta css al que llamaremos main.css, luego dentro de js haremos los propio, un fichero main.js, si en caso nuestra aplicación requiera de imágenes, ya tenemos preparado nuestra carpeta que ha de cumplir esa función.

Finalmente nos avocamos al fichero index.js, este fichero es el que dará el arranque de nuestra aplicación y en él ha de colocarse todos los middlewares para que nuestra aplicación sea robusta, un middleware a pocas palabras es una función que realiza un procesamiento de datos cuando se realiza una petición específica y antes que se entregue una respuesta al cliente, podemos poner como ejemplo una autenticación de usuarios, la autenticación de usuarios se realiza a través de un formulario el cual viaja al servidor, este antes de entregar una respuesta es procesado por el middleware el cual valida si el usuario existe y que si su clave es la correcta, si en caso fuese correcto da pase a que se entregue una respuesta positiva con una vista específica y si no lo es, simplemente retorna nuevamente al formulario entregando una alerta de usuario o clave equivocados. Entonces el código de nuestro index.js será:

indexjs

Muy bien expliquemos un poco que es lo que está pasando aquí, de las líneas 1 al 6 estamos importando nuestros módulos, tanto los que hemos instalado como los que hemos creado (rutas, por ejemplo); cuando ya hemos importado los módulos debemos de crear un objeto de la aplicación y esto está en la instrucción de la línea 9, hecho ello debemos de configurar nuestro motor de vistas agregando su ubicación (la carpeta views) y definiendo como motor a pug.

En la línea 16 hacemos uso de morgan para observar las peticiones, luego en la línea 17 y 18 le decimos a express que procesaremos peticiones en formato JSON y bajo la codificación (application/x-www-form-urlencoded) (debemos de poner atención al content-type). En la línea 19 implementamos el uso de páginas estáticas, las páginas estáticas son aquellas que solo requieren ser descargadas una vez, estos son los ficheros  css o js que se encuentren de cara al cliente y que hemos colocado en nuestra carpeta public.

Finalmente en la línea 22, solicitamos la conexión con nuestra base de datos, y si en caso se obtuviera éxito en la conexión, la aplicación estará activa y a la escucha en el puerto que le hemos designado, en nuestro caso el puerto 3000, esto responde a la lógica de que no tiene sentido que la aplicación arranque hasta que los datos de las bases de datos necesarios para nuestra aplicación no están ya en línea, ahora bien, esto trae un pequeño contratiempo, pues tienes que tener mongodb instalado y que esté corriendo, lo cual es otro tópico, es por ello que te sugiero que cambies todo de la línea 22 en adelante por:

app.listen(config.puerto,()=>{console.log("Estamos conectados");});

Con esto nuestra aplicación ya está lista, para ponerla en marcha en la terminal de nuestro Editor, hacemos:

nodemon

Si ya lo hemos instalado, veremos que nodemon por defecto buscar el fichero index.js, si hemos decidido llamarlo de otra forma solo basta con:

nodemon nombre_archivo

Configuración del Wemos D1

Existen muchos tutoriales, aunque no específicamente relacionados a la Wemos D1, sino al mini o en su defecto al esp8266 y que se conecta directamente al Arduino, entonces esto hace un tanto insufrible entender el como funciona, además de la existencia de muchos artículos con distintos métodos para su implementación lo hace aún más complicado, más aún cuando yo al ser un desarrollador de lenguaje de alto nivel pretendo incursionar en lenguajes  y sus librería de medio nivel, no encontré tutorial en el cual obtuviese una referencia por cada paso que se da, entonces decidí hacer el propio, tratando de cubrir todas las posibles preguntas que se nos puedan presentar.

Entonces nuestro objetivo principal es el envío de datos, haciendo uso del wifi  el cual hace uso del protocolo TCP/IP específicamente del http, esta referencia se hace ya que existe y que es muy usado el UDP, la diferencia básica entre uno y el otro, es que en el primero los datos son enviados y por así decirlo verificados y no existe “pérdida de datos” (sí…claro), mientras que el otro envía los datos sin verificar si estos llegaron de forma correcta y los sigue enviando, este último es muy usado en videollamadas, donde la pérdida de datos no se aprecia más que quizá en un deterioro momentáneo de la imagen, no es muy ilógico pensar en esta forma de comunicación ya que si te encuentras colectando datos del ambiente el que se pierda un par de datos no resulta tan perjudicial ante la masiva colección de datos que se obtenga ya que para definir que algo ha variado críticamente al menos debe expresarse en un intervalo de tiempo algo prudente (no puedes decir que llovió solo porque cayó una gota), ahora ¿Cómo es la lógica del programa?, pues debemos de hacer lo siguiente:

  • Obtener la conexión wifi con nuestra red
  • Verificar si la conexión se mantiene
  • Si la conexión se mantiene entonces podemos hacer envío de datos a nuestro servidor a través del método POST y directamente a la raíz de nuestra aplicación web.
  • Cerrar la conexión, no se puede enviar una nueva sino se ha cerrado la anterior.

Debemos de tener una consideración respecto al como se comunica el Wemos, como haz podido apreciar, el Wemos solo tiene una salida micro-usb, por el cual se suministra energía y datos, así que procura conseguir un cable con dichas condiciones y no solo de energía, al hacerlo te darás cuenta que W10 buscará el driver y en muchas ocasiones quizá ya lo tengas instalado, sino debes de instalarlos (CH340)

Entonces procedemos a  abrir nuestro IDE de Arduino, como nota y quizá en siguientes publicaciones tenga éxito en ello, soy de los que prefieren el minimalismo al momento de programar, es algo tedioso tener que usar un IDE acorde a cada lenguaje que estés programando, es cierto que muchos IDE’S hacen un trabajo genial pero no siempre es completo, en este caso es con Visual Studio Code, al inicio estuve programando Arduino con él, estuvo genial; sin embargo, Arduino expresa cierto errores en la importación de librerías y sus rutas respectivas, problemas de ser usado con intermediarios, estoy revisando que lo causa, pero hasta los comentarios en las redes hablan de un defecto propio de Arduino y que por el momento resulta imposible de solucionar, tampoco podemos usar el Arduino Web Editor, ya que hemos de instalar una librería que no está soportada en él, esto implica que, de forma justificada debamos de usar el IDE de Arduino para programar rompiendo el minimalismo al programar.

Puesto todos los justificantes sobre la mesa procederemos a trabajar; muy bien lo primero que necesitamos hacer es instalar las librerías que la obtenemos de aquí, como te haz dado cuenta es un fichero JSON, el cual debe ser colocado en:

libreriasesp8266.png

Luego de ello nos dirijamos hacia la sección de placas e instalamos la librería, para ello vamos a “Herramientas”->”Placa”->”Gestor de Tarjetas”, en el buscamos “esp8266”:

driver1

Hecho eso seleccionamos nuestra placa con la misma instrucción anterior, solo que ahora en lugar de entrar a “Gestionar Tarjetas“, buscaremos nuestra tarjeta en el listado que aparece. Existen tutorial donde se hace referencia a la Wemos D1 R2, pero esta de igual forma ha de trabajar:

placa.png

Con esto ya hemos finalizado la preparación de nuestro entorno en Arduino, pasemos al código. Lo primero que debemos de hacer es incluir las librerías:

#include ;
#include ;

Ahora debemos de definir la velocidad de paso de bits por segundo y la conexión con el wifi, te muestro el código en la función setup() y explicaremos poco a poco:

void setup(){
 Serial.begin(115200);
 Wifi.begin("el_nombre_de_tu_red","la_clave_de_tu_red");
 while(Wifi.status()!=WL_CONNECTED){
   delay(500);
   Serial.println("Intentando conectar");
 }
 Serial.println("Estamos conectados");
}

Expliquemos un poco este asunto, con Serial.begin() hemos configurado la velocidad de bits por segundo, y también como mencionamos en el esquema inicial, es primordial asegurar la conexión antes que realizar cualquier cosa, esto se logra con Wifi.begin() el cuál tiene sobrecarga de constructores, entre uno de ellos se encuentra el paso de parámetro de la red y su respectiva clave. Necesitamos asegurarnos que nuestro dispositivo se conecta a la red, para ello el objeto Wifi hace uso del método Wifi.status() el cual devuelve una serie de mensajes de acuerdo a su condición, WL_CONNECTED hace referencia a que se ha logrado la conexión, entonces mientras el valor que devuelve el método estatus sea diferente a ese mensaje se realizará un bucle a través de un while con una demora de medio segundo, finalmente cuando se logra la conexión se sale del bucle e imprimimos que estamos conectados.

El siguiente paso es que mientras estemos conectados podamos enviar nuestros datos a el servidor, como esto ha de ser repetitivo, lo debemos de colocar en la función loop() y a través de un condicional que esté preguntando si estamos conectados, conectados enviamos los datos

loop

 Como mencionamos Wifi.status() retorna un mensaje respecto a la conexión, siendo que WL_CONNECTED es que estamos conectados, si esa condición se cumple, debemos de armar la cabecera y el cuerpo de nuestro mensaje donde estarán nuestros datos, esto lo logramos creando una instancia de la clase HTTPClient, lo primero es inicializar el cliente indicando la ip y el puerto dentro de una cadena String, para ello hacemos uso del método begin() la ip de nuestro computador lo podemos hallar abriendo una consola CMD y digitando ipconfig, el puerto es el que en nuestro servidor habíamos configurado, en este caso es el puerto 3000.

Iniciada la conexión del cliente procedemos agregar la cabecera, esto es importante pues de acuerdo a como esté configurado tu servidor aceptará o no la consulta, nosotros hemos configurado que nuestro servidor atienda en el formato JSON y que el tipo de datos se envía como application/x-www-form-urlencoded, entonces debemos de decirle a nuestra cabecera que la información que vamos a enviar está en ese mismo tipo de dato y ello se define en la línea 19 a través del método addHeader().

El cuerpo de la consulta es agregado como parámetro cuando se llama al método POST de la instancia de la clase HTTPClient, estamos un texto que dice “Hola Mundo“, y este método nos ha de retornar un valor, este valor corresponde a una respuesta código de HTTP y es necesario observar si existe algún error, es por ello que se evalúa si existe un valor -1, lo cual implica un error.

Si todo ido bien, podemos obtener la respuesta en forma de una cadena de texto usando el método getString(), finalmente debemos de cerrar el cliente con el método end().

Muy bien con todo ello, ya tenemos preparado los dos macro componentes de nuestra aplicación, nuestro servidor está corriendo y nuestra placa ya está transmitiendo datos, para se observe el resultado final, les dejo aquí un vídeo con la prueba, por cierto, disculpen la emoción que me embargó que se obtuviera la data.

Arduino y Envío de Datos a un Servidor Parte 2

Anteriormente:

Habíamos ya obtenido la configuración necesario de los servicios y estábamos creando los ficheros en cada una de las carpetas del esqueleto de nuestra aplicación:

inicio7

Ahora vamos a seguir las demás carpetas:

Carpeta Routes

La carpeta routes es la encargada del direccionamiento de las peticiones que se hagan a través del cliente, por ejemplo cuando un cliente accede a nuestra página web lo que está haciendo es una petición de la raíz (“/”) y si tuviéramos un menú como por ejemplo data, es el cliente quién realiza una petición a la ruta (“/data”), para ello usamos una instancia de la clase Router de Express, entonces dentro de la carpeta creamos un archivo rutas.js,  este fichero ha de gestionar dos tipos de solicitud, una relacionada a los datos y otra a la raíz, entonces:

const servicio=require("../services/servicio");//las rutas son las que consumen los servicios que hemos creado
const rutas=require("express").Router(); //creamos la instancia de Router
//cuando se ingresa a la aplicación se realiza una petición get
rutas.get("/",(req,res)=>{
 res.render("index"); // ¿recuerdan el archivo index.pug?, pues aquí se renderiza

});

rutas.get("/data,async(req,res)=>{
 let datos=await servicio.getDatos();
 res.send(datos);
});

module.exports=rutas;

Expliquemos un poco esto, anteriormente hemos creado un fichero index.pug, en el cual cuando se renderice arrojará un fichero index.html, ahí radica la magia, render es un método de express relacionado con la respuesta hacia el cliente. La segunda ruta que es “/data”, tiene como callback una función que posee async/await pero, ¿Por qué lo posee?, pues esta función ha de hacer uso del servicio y su método getDatos() y getDatos es una función que está trabajando con las promesas y esto toma su tiempo, así que es como decir que si getDatos ha de tomar su tiempo la función que lo use también ha de tomar su tiempo, ahora que recordemos que lo retorna getDatos es un arreglo de datos y que será enviado a través del método send asociado a la respuesta al cliente. Finalmente exportamos las rutas.

El fichero Index.js

El  fichero index es el fichero más importante pues es el que ha de poner en marcha el servidor, en el se encuentra todas las configuraciones globales requeridas para el despliegue de nuestra aplicación, entonces vamos a colocar todo el contenido y procederemos a explicar las sentencias más importantes:

const express=require("express");
const config=require("./backend/config/config");
const path=require("path");
const mongoose=require("mongoose");
const rutas=require("./backend/routes/rutas");
const app=express();

//motor de vistas
app.set("views",path.join(_dirname,"/backend/views"));
app.set("view engine","pug);
//middlewares
app.use(express.urlenconded({extended:true});
app.use(express.json());
app.use(express.static(path.join(_dirname,"frontend/public/")));
app.use(rutas);
mongoose.connect(config.mongodb.URI,{useNewUrlParser:true}).then(db=>{
 app.listen(config.puerto,()=>{console.log("Estamos conectados");});
});   

Nuestra aplicación ha de ser gestionada por el framework express.js  por lo tanto debemos de requerirlo, necesitamos de las configuraciones relacionadas al puerto y la dirección de la base de datos que se encuentran en el fichero config, así también necesitamos enlazar la ruta raíz de la aplicación con valga la redundancia con nuestra aplicación, esto lo hacemos con el módulo path que pertenece a nodejs y enlazaremos estas rutas con su método join que une segmentos de ruta, para saber la ruta raíz hacia nuestra aplicación utilizamos la variable de entorno _dirnamenecesitaremos también hacer uso de las bases de datos entonces requerimos a mongoose, También requerimos decirle a express como se han de gestionar las rutas y ello lo definimos en el fichero rutas.js es por eso que lo requerimos.

Cuando todos los módulos que necesitamos ya los hemos instanciado, lo primero que debemos de hacer es crear nuestra aplicación de express y esto lo hemos hecho con:

  const app=express();

Con nuestra aplicación debemos de configurarla y una de las primeras configuraciones es decirle como  y quien ha de renderizar las vistas (views -> index.pug), entonces debemos de configurar el motor de vistas y eso se ha realizado con las instrucciones:

app.set("views",path.join(_dirname,"/backend/views")); //indicamos donde están las vistas
app.set("view engine","pug);//y quien las va a gestionar;

Luego de ello debemos de implementar los middlewares, los middlewares son funciones que realizan acciones previas a la entrega o canalización de solicitudes de datos, los middlewares al realizar acciones pueden hacer tratamiento de los datos y pasarlos a otras funciones o ser ellas mismas la función final, una mejor definición aquí, los middlewares son empleados o llamados a través del método use, los middlewares más importantes de nuestra aplicación son las rutas y el uso de páginas estáticas (que solo necesitan ser cargadas una vez) (express.static):

app.use(express.static(path.join(_dirname,"frontend/public/")));
app.use(rutas);

Por último, ya que estamos usando mongo como la base de datos para almacenar y solicitar los datos, no tiene sentido que la aplicación inicie si las conexión con la base de datos no se ha concretado, ya que podríamos obtener muchos errores, entonces:

 mongoose.connect(config.mongodb.URI,{useNewUrlParser:true}).then(db=>{
     app.listen(config.puerto,()=>{console.log("Estamos conectados");});
})

La conexión a la base de datos se realiza a través del método connect el cual se gestiona a través de las promesas, entonces si es que se logró la conexión con la base de datos, ponemos nuestro servidor con todas las configuraciones previas en funcionamiento, esto se hace a través del método listen de la aplicación express().

La carpeta JS de la ruta frontend/public

En esta carpeta creamos el fichero main.js, este fichero será el que solicite la data constantemente, entonces:

$document.ready(function(){ //cuando el documento haya cargado llamará a la función
  setInterval(function(){ //creamos una función anónima
   $.get("/data,{},res=>{
     console.log(res);
    });
  },5000);
});

Expliquemos lo puesto hasta ahora, recordemos que el fichero index es:

2

Estamos haciendo de la librería jquery, entonces lo que queremos es que cuando el documento esté cargado se llame a una función, en este caso una función anónima donde implementaremos la función setInterval, la cual ejecutará una función cada intervalo de tiempo que le asignemos:

setInterval(function(){ 
    $.get("/data,{},res=>{
     console.log(res);
    });
  },5000);

Nuestra función será ejecutada cada 5 segundos, ¿Y qué es lo que haremos cada 5 segundos? pues debemos hacer una solicitud get a la ruta “/data” (recordemos que esa ruta ya la hemos implementado en el servidor), nuestra solicitud no incluirá parámetro alguno, y por el momento cuando obtiene la respuesta simplemente la imprime por consola, recordemos que la data que se nos envía es un arreglo de objetos que tienen un atributo “Y” donde se almacenó el valor del sensor.

Muy bien, tenemos los datos, pero no solo deseamos imprimirlos por consola,sino que estos se muestren a través del navegador en un gráfico de barras, para ello hemos de usar chart.js, para hacer uso de chart.js lo hemos incluido en nuestro index, pero también hemos creado un canvas el cual tiene como id:”mi_grafico”, entonces el primer paso es capturar el id e indicar que el contexto será un gráfico en 2d (getContext), y esto lo hacemos con:

let elemento_dom=documento.getElementById("mi_grafico").getContext("2D");

Ahora creamos un arreglo vacío, en este arreglo lo que queremos es almacenar los últimos 10 valores que contenga nuestra base de datos, es decir los últimos 10 valores obtenidos en la respuesta del método get, es por ello que luego recorremos la respuesta y asignamos sus valores al arreglo.

let datos=[];
for(let i=res.length-1;i>res.length-11;i++){
 datos.push(res[i].y);
}

Al tener los datos podemos pasarlo al constructor del gráfico de barras de chart.js, el constructor de barras es de la siguiente forma:

var myBarChart = new Chart(ctx, {
    type: 'bar',
    data: data,
    options: options
})

El atributo ctx es la variable donde hemos capturado el canvas, entonces podemos hacer los siguiente:

let chart=new Chart(elemento_dom,{
  type:"bar",
  data:{contenido}


}); 

Debemos de poner especial interés en el atributo data, data posee un conjunto de propiedades como por ejemplo las etiquetas de cada uno de los elementos del gráfico (eje x), como estamos capturando los últimos 10 elementos, entonces el mismo de captura será pues como una etiqueta, las etiquetas pueden ser un arreglo de etiquetas y una etiqueta puede tomar un número o una cadena de texto, las etiquetas se expresan en el atributo labels. Luego necesitamos configurar el datasets (dataset_properties) es un arreglo de objetos con propiedades, haremos uso de la propiedad label y llamaremos a nuestro gráfico “Datos”, luego usaremos la propiedad data y le asignaremos el arreglo de objetos datos, entonces quedando finalmente:

data:{
 labels:datos,
 datasets:[{
  label:"Datos",
  data:datos
 }]
}

Entonces nuestro archivo main.js quedaría:

main.png

Con esto está todo listo de nuestra aplicación, que nos queda, pues ponerla en marcha para ello debemos de ir a la terminal y hacer lo siguiente:

node index.js

Incluso mejor que ello deberíamos instalar el módulo nodemon de forma global de la siguiente forma (esto hará que siempre esté instalado y sea de uso para cualquier otra aplicación), igualmente en la terminal:

npm i -g nodemon
//cuando terminó de instalar hacemos en el terminal
nodemon 

Nodemon por defecto siempre buscará el archivo index.js y estará atento a todo cambio que se suceda en los ficheros de js, entonces toca finalmente ir al navegador y hacer en el:

localhost:3000

Arduino y Envío de Datos a un Servidor Parte 1

Anteriormente habíamos podido conectar nuestro arduino a la red (aquí), tal que cualquier cliente se conecte al ip que hemos asignado a nuestro dispositivo pueda ver los datos que se obtienen del sensor de luz. En esta ocasión hemos de enviar la data a un servidor que montemos y que se muestre a través de gráficos para que se aprecie en tiempo real la data y como esta cambia en el transcurso del tiempo; sin embargo, la variante es que lo haremos a través de JavaScript y el uso de Firmata, así que no entraremos mucho en detalle del significado de las sentencias a menos que sea uno nuevo, así que empecemos.

conjunto

Del lado de Arduino

Para la programación utilizaremos el arduino web editor; sin embargo en esta ocasión la programación no recae con fuerza sobre lo que programemos sobre Arduino sino que lo que queremos lograr en palabras comprensibles es que Arduino sea reconocido por así decirlo como un periférico y que podemos acceder a los datos que recibe a través del sensor (recordar que estamos usando el sensor del artículo anterior, que es un sensor de luz), para lograr ello hemos de usar el protocolo Firmata, ¿Qué es Firmata?, podemos usar esta definición:

Firmata es un protocolo genérico para la comunicación con microcontroladores desde software instalado en un ordenador. Este protocolo se puede implementar en cualquier arquitectura de microcontroladores, así como en cualquier paquete de software. El objetivo de firmata es permitir controlar completamente Arduino desde software instalado en un ordenador, sin escribir una sola línea de código de Arduino (autor).

Muy bien entonces abrimos el arduino web editor, como definitivamente ya tienes instalado el plugin de arduino (sino no funcionaría tu arduino con el arduino web editor) buscamos el icono en la barra de tareas de windows, hacemos click derecho en el y marcamos la opción “go to Arduino create” , cuando se abre la interfaz en el navegador seleccionamos arduino web editor y ahora sí estaremos en la interfaz principal, en la interfaz principal debes ir a examples, marcar la pestaña “from libraries” (te saldrá una lista de librería) desplazarte hasta la librería firmata hacer click en el y en el menú que se despliega seleccionar el fichero “StandardFirmataPlus“, opcionalmente puedes guardar el fichero cambiando su nombre y guardando o simplemente quemando de una sola vez su contenido en nuestro arduino, y eso es todo lo que tendremos que hacer con el arduino

1.png

52608784_263557454562541_5756407597242515456_n.jpg

Del Lado del Servidor

Para el servidor te recomiendo que leas esto (parte 1, parte 2), con el fin que puedas entender o conocer los módulos que hemos de emplear, es cierto que se podría hacer un poco más ligero; sin embargo, quedarían en el aire más preguntas de las que podrían quedar con este post, es decir, muchas librerías de lado del cliente construyen su propio servidor, al cual pues poco o nada de control puedes tener sobre el, o existen otras que las instrucciones se centran exclusivamente en el desarrollo de la recepción de datos de Arduino.

logo

Dicho esto para poder trabajar con el servidor lo haremos con NodeJs, lo que significa que lo debes de instalar, así también se ha de usar mongo para las bases de datos con lo cual también debes de instalarlo, así también no te olvides de iniciar Mongo, o agregarlo para el inicio automático cuando el computador se encienda; hecha la instalación, crea una carpeta, dale un nombre, y abre tu editor favorito (en mi caso VSCode) y navegas hasta la carpeta que haz creado con tu editor, bueno abres la terminal incorporada de VSCODE (Ver->Terminal o abres la terminal de node js y navegas hasta la carpeta que haz creado) y haces:

npm init --yes

Dicha instrucción es el arranque de un proyecto en NodeJs, ahora hemos de proceder a instalar los módulos necesarios, ¿Qué módulos hemos de necesitar?, para el manejo del servidor, las rutas, los ficheros hemos de utilizar express, express no tiene una curva de aprendizaje muy alta y hace gran parte del trabajo que necesitamos hacer en NodeJs, luego necesitamos gestionar las base de datos, y aunque podríamos hacerlo directamente con mongo, para agilizar el trabajo hemos de utilizar mongoose, mongoose nos permite realizar todas las operaciones y configuraciones que haríamos con mongo pero de una forma más sencilla, también necesitamos un motor de vistas y para ello utilizaremos pug, pug nos permite desplegar páginas en html que tengan un tratamiento previo, el tratamiento previo es muy utilizado cuando nuestra páginas han de desplegar contenidos dinámicos (como por ejemplo un red social donde cada usuario tiene su propio perfil), en nuestro caso o para la dimensión de la aplicación esto un paso opcional, ya que podríamos utilizar solo las páginas estáticas, pero lo quise montar ya que con esta aplicación podrías intentar ir más allá de  lo que representa este ejemplo; finalmente y quizá el más importante es la instalación de jhonny-five, jhonny-five es una librería que te permite usar JavaScript para poder ejecutar ordenes o recibir datos desde tu arduino, y esto es lo vital pues tenemos a nuestro arduino enviando datos y debemos de capturarlos y podemos hacer ello a través de Jhonny-five; procedemos a instalar las librerías:

npm i express pug mongoose

johnny-five-fb

Ahora procedemos a instalar jhonny-five, para instalar esta librería debes saber que tiene unos pre-requisitos, los cuales están listados aquí, estos incluyen el módulo del conjunto de herramientas de windows los cuáles podrían tener sus propios pre-requisitos, entonces:

npm i -g --production windows-build-tools
npm i -g node-gyp
npm i johnny-five

Hecha la instalación de los módulos, debes de crear la siguiente estructura de archivos:

inicio7

Aunque esta estructura es genérica, habrá algunas carpetas que en su momento te darás cuenta que no son necesarias, muy bien entonces vamos carpeta por carpeta, dentro de

Carpeta Config

la carpeta config creamos un fichero que se llame config y sea del tipo js, dentro del fichero colocamos:

const config={
 puerto:process.env.PORT || 3000,
 mongodb:{
           URI:process.env.MONGODB_URI || "mongodb://localhost:27017/arduino1"
         }
}module.exports=config;

El fichero config se encarga de almacenar las direcciones o accesos a bases de datos o puertos, como podemos observar estamos requiriendo en primera instancia a las propiedades del entorno (PORT y MONGO_URI) que por lo general son brindadas cuando subimos nuestra aplicación a la nube, en caso de no existir dichas propiedades lo que se haces asignar arbitrariamente un valor como por ejemplo en el caso del puerto el cual mientras esté en el localhost será de 3000, esto de igual forma ocurre con la dirección de la base de datos, sobre la base de datos no te preocupes si esta no existe, pues si mongo detecta que no existe la crea.

Carpeta Views

Dentro de la carpeta views creamos un fichero de tipo pug que se llame index (index.pug), dentro del index vamos a hacer uso de las librería de jQuery y ChartJS (para la creación de las barras estadísticas) y todo ello será centralizado en nuestro propio fichero que se llama main.js, quedando de la siguiente forma:

2.png

Como podrás notar hemos creado un canvas que tiene como id mi_grafico, este canvas será donde despleguemos nuestra gráfica de barras, esto es todo sobre la carpeta views.

Carpeta Database

Su nombre está más que claro de cual es el fin que persigue esta carpeta, mongoose trabaja a través de esquemas, estos esquemas podríamos decir que son representaciones de la estructura de los documentos pertenecientes a la base de datos y que se expresa a través de los modelos, entonces debemos de crear un fichero que se llame modelo.js y en el hacemos:

const mongoose=require("mongoose");//requerimos al módulo general mongoose
const Schema=mongoose.Schema; // creamos una instancia de Esquema

const datos=new Schemma({//creamos un esquema que se llama datos
  y:Number // el cual tiene un único tipo de dato que se llama "Y" y es de tipo Number
});

//exportamos el modelo, así cuando otro fichero lo requiera importará el modelo ya implementado.
module.exports=mongoose.model("data",datos);

Carpeta Services

Dentro de esta carpeta crearemos ficheros que serán los encargados de realizar toda la lógica que se necesita en la aplicación, como lo es guardar elementos en la base de datos o llamar a los elementos de la base de datos, necesitamos dos ficheros, uno de los cuáles será el que realice los servicios relacionados a la recepción de datos de parte del sensor y lo almacene dentro de la base de datos que hemos creado, este fichero lo llamaremos servicio_sensor.js, y debe contener lo siguiente:

const five=require("johnny-five") //requerimos el módulo johnny-five el cual se comunica con el arduino
const Modelo=require("../database/modelo"); //requerimos el modelo que hemos creado
const placa=new five.Board({port:"COM5"});

let valor=0; //creamos una variable general que este almacene el valor del sensor y que pueda ser utilizada por todas las demás funciones
placa.on("ready",function(){ //nos ponemos a la escucha de eventos en nuestra placa
  let luz=new five.Light("A0");
  luz.on("change",function(){
     valor=this.value;
  });
});
async function guardar(){
 let x=new Modelo({y:valor}); // creamos un documento a través del modelo
 await x.save(); //guardamos el documento
}
module.export=setInterval(guardar,2000); //exportamos la función

Expliquemos un poco lo que hay aquí, la instrucción:

new five.Board({port:"COM5"});

Crea una representación de tu placa física (del arduino) y debemos de indicarle el puerto en el que está conectado tu arduino, en el caso de windows es a través del COM, ahora bien Board (el enlace posee información de los parámetros, métodos y eventos de la clase, como ready por ejemplo) tiene más propiedades y parámetros, luego hemos asignado esta instancia ha la constante placa, luego debemos de poner a la escucha a nuestra placa y para ello utilizamos el método on, este método requiere estar a la escucha pero de eventos y el primer evento que debe escuchar es el evento ready, ready sucede luego que la placa se haya inicializado, cuando ocurre ready este llama a un callback (función), es en esta función en el que  inicializamos la escucha del sensor (sensor de luz recuerda) a través de:

let luz=new five.Light("A0");
luz.on("change",function(){
valor=this.value;
});

La clase Light  opcionalmente puede recibir el parámetro relacionado al pin (pero por lo general sino lo colocas no podrá detectarlo, así que lo tomaremos como obligatorio), el pin es el pin donde se encuentra tu sensor en la placa física, en nuestro caso en el pin A0. A la instancia de la clase (luz) debemos de ponerla a la escucha del evento “change” el cual es el evento que está a la escucha de cualquier valor que se obtenga desde el pin, cuando se captura un valor este forma parte de las propiedades de la instancia Light, y podemos obtener dicho valor a través de la propiedad value (value devuelve el valor en crudo sin tratamiento alguno), finalmente el valor obtenido lo asignamos a la variable general “valor” que hemos creado.

Pero necesitamos guardar dicho valor dentro de nuestra base de datos, entonces por eso hemos creado una función que realice ello:

async function guardar(){
let x=new Modelo({y:valor}); // creamos un documento a través del modelo
await x.save(); //guardamos el documento
}}

Como el guardar puede tomar su tiempo, debemos de manejarlo a través de las promesas, esto quiere decir que debemos de implementar el async/await para estar a la espera que concluya el guardado de los datos, el guardado de los documentos (ver sección updating)se hace creando una instancia de los mismos a través del uso del modelo y haciendo uso del método save; así también debemos de asegurar que el guardado se ejecute cada intervalo de tiempo, para ello cuando exportemos el fichero, lo que exportaremos será la función setIntervalsetInterval el cual tiene como requisito indicar una función y el tiempo en mili segundos en el cual se ha de volver a ejecutar dicha función, hemos colocado que se guarden los datos cada 2000 mili segundos lo que hace 2 segundos, esto variable ya que dependerá del comportamiento que desees, es decir que debes someterlo a la pregunta ¿Qué tan drástico será el cambio de valores en el sensor cada cierto tiempo x?, en nuestro caso estamos colocando a 2 segundos con fines demostrativos, pero piénsalo, bueno con esto hemos terminado con el fichero de servicio relacionado al sensor.

Ahora debemos de crear un fichero de servicio que atienda la lógica de la obtención de datos de parte del servidor, a este fichero lo llamaremos servicio.js y debe contener:

const Modelo = require("../database/modelo");
require("./sensor_servicio"); //ponemos en marcha el guardado de datos desde el sensor a la bbdd
async function getDatos(){
 let datos=[];
 datos=await Modelo.find();
 return datos;
}
module.exports={getDatos}

La recuperación también toma un tiempo por lo cual debemos de usar async/await; la recuperación lo hacemos a través del modelo y su método find, este método puede incluir un parámetro de búsqueda; sin embargo, como nosotros requerimos de todos los datos, podemos dejarlo en vacío, ahora bien cuando se devuelven los datos se devuelven un arreglo de estos datos, es por eso que hemos creado un arreglo y finalmente lo que hace la función es retornar dicho arreglo pero ya con los campos poblados. Con esto hemos finalizado todo lo requerido respecto a la carpeta services.

Así concluye la primera parte de este tutorial. Saludos.

Desarrollo de un CRUD – 2

Anteriormente:

Hasta el momento hemos configurado todo lo que necesitamos respecto a los módulos y de nodemon, ahora lo que debemos hacer es armar el esqueleto de nuestra aplicación, es decir que tipo de carpetas y archivos han de ser requeridos para su funcionamiento, una buena práctica es que nuestra aplicación sea lo más modular posible, y esto se logra a través de la subdivisión de los componentes que tiene esta, así también resultará más sencillo realizar cambios o revisar los errores que ocurran.

Esqueleto de la Aplicación

Vamos a centrarnos inicialmente en el back-end, Nuestra aplicación debe contener ficheros que puedan gestionar los distintos requerimientos de una aplicación, para nuestra aplicación en particular vamos a necesitar ficheros que gestionen nuestra base de datos, las rutas, las vistas, ciertas configuraciones generales y los controladores, podemos entonces crear carpetas y dentro de ellas crear los ficheros que hemos de necesitar, entonces las carpetas quedarían de la siguiente forma:

inicio9

 

Hecho esto temporalmente nos vamos a centrar en las carpetas config, routes y services;  la primera contendrá información de importancia general para toda la aplicación, podemos colocar el token de nuestra aplicación, la dirección, usuario y clave de nuestra base de datos y otras tantas cosas más; en la carpeta routes, hemos de colocar todo lo referido a las solicitudes get y post que se hagan desde el cliente, el fichero que contenga tiene que albergar todas las posibles respuestas de las direcciones que se soliciten,  pero dado que de vez en cuando las respuestas pueden requerir de un relativo procesamiento es más adecuado pasar ese procesamiento a un servicio, entonces creo que está demás decir que en la carpeta services se crearán ficheros que se encargarán del procesamiento ya sea de solicitudes o de cálculos extensos.

Carpeta Config

Dentro de la carpeta “configuraciones” creamos un fichero llamado config.js; ya en el fichero creamos una constante llamada config y que es un objeto, este objeto tendrá como propiedades un puerto y una propiedad objeto llamada mongodb que contendrá la dirección hacia nuestra base de datos o incluso los usuarios y clave si es que se tuviera alguna, y además una propiedad referida al token de nuestra aplicación, exportamos el objeto y ya estará disponible para los demás, esto queda de la siguiente forma:

 

esqueleto2

Se estila que el puerto cuando trabajamos en localhost sea el puerto 3000 (aunque en la práctica puede ser cualquiera); sin embargo, cuando este suba a un servidor, será el servidor el que nos entregue un puerto de forma automática o tendremos nosotros que solicitar al administrador del servidor que nos habilite un puerto; si en caso es de forma automática entonces debemos hacer que NodeJs busque ese puerto y eso lo hace a través del objeto global process, este objeto nos provee información y control sobre el actual proceso de NodeJs, sin entrar en profundidad, process posee una propiedad env que retorna un objeto conteniendo el entorno del usuario, eso quiere decir que en process.env.PORT estamos solicitando a nuestro entorno cual es el puerto (PORT) asignado. Esto de igual forma ocurre con la base de datos, en el cual solicitamos la propiedad MONGODB_URI que será la dirección de nuestra base de datos, esto cobra interés cuando estamos llevando ya a la nube nuestra aplicación (y nuestro entorno, donde nos sirven el servidor contiene dichas propiedades), ambas propiedades de nuestro objeto tienen un parámetro (||), tal que en caso de no existir dicha propiedad pues nosotros asignamos tanto el puerto como la ruta.

Debemos de hacer una pequeña observación respecto a como debe ir la ruta, esta ruta es algo especial, es decir la ruta posee un formato que es definida en mongoDB de la siguiente forma:

mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database][?options]]

Esa es la estructura completa, incluso se aconseja en el caso de localhost indicar que estamos en el localhost y el puerto al cual está la escucha mongodb  que es el 27017, finalmente luego de ella se agrega el nombre de la base de datos, como nuestra aplicación no posee usuario ni clave, entonces quedaría como se muestra:

mongodb://localhost:27017/aplicacion

Si la base de datos no existe mongoDB la creará automáticamente.

Cuando ya todas las propiedades del objeto han sido definidas procedemos a exportarlo a través de la instrucción module.exports, esta instrucción de NodeJs hace un tratamiento de los ficheros y su contenido a través de los módulos, tal como mencionamos, los módulos surgen ante la problemática de la complejidad que puede resultar hacer una aplicación y que en JavaScript todos los ficheros comparten un mismo espacio de los nombres globales pudiendo en consecuencia sobrescribirse algunas de ellas por error. Los módulos permiten limitar el alcance de ello evitando conflictos con otros ficheros y solo exponiendo aquello que deseamos del mismo a través de la instrucción exports (aquí encontrarás una explicación mucho más detallada), es así que luego de crear nuestro objeto constante exportamos únicamente ese objeto, esto lo veremos con mayor amplitud posteriormente.

Carpeta Routes

Express es el framework con el cual estamos trabajando nuestra aplicación, nuestra aplicación responde a solicitudes realizadas a través del cliente, estas solicitudes se realizan cada vez que un usuario visita nuestra aplicación y hace click a un enlace o envía información  o la solicita a través de un formulario, en nuestro caso hace referencia a los “endpoints” (rutas para facilitar las cosas) y como el servidor responde a dichas solicitudes. Express trata las rutas a través de su propiedad Router el cual crea una nuevo objeto router, este objeto es el que hace el tratamiento entre los middlewares y los accesos a las rutas, su construcción es de la siguiente forma:

var router = express.Router([options]);

Como vemos las opciones valga la redundancia no son obligatorias, para ello (revisa el link – Router – si estás interesado en saber de que tratan estas opciones); bueno, todas estas instrucciones debemos de plasmarlas en un archivo dentro de la carpeta routes, a este archivo lo llamaremos rutas y será del tipo javascript.

Lo primero que debemos hacer es crear el objeto router, y lo haremos requiriéndolo, en NodeJs un módulo se requiere que es casi lo mismo a importar, pero se hace uso de la palabra reservada requiredentro de require podemos incluir el nombre del módulo, que si está instalado por lo general node js lo autocompletará o podemos indicar la ruta del archivo donde se encuentra los objetos o métodos que deseamos usar, como en nuestro caso ya tenemos instalado express, podemos hacer:

const rutas=require("express").Router();

Lo que está ocurriendo en esta línea es que estamos invocando a express y encadenando la creación del objeto router, si quisiéramos agregar opciones sería entre los paréntesis que deberíamos agregarlo, creado el objeto y que hemos llamado o asignado a una constante rutas, podemos generar respuestas a las solicitudes básicas dentro de un CRUD, que son crear, leer, actualizar y borrar, estas solicitudes se expresan a través de los métodos post, get,put, delete. Cuando un usuario entra a nuestra aplicación, la primera vista que se le presenta es la raíz, es decir que lo primero que lee es la raíz, para ello entonces debemos de hacer uso del método get  el cual tiene el siguiente constructor:

app.get(path,callback,[callback])

Debemos de indicar la ruta, si hablamos de la raíz entonces la ruta es “/“, y el callback es la función que ha de responder cuando el cliente haga solicitud de dicha ruta, ahora vemos que hay la opción de colocar una serie de callbacks, esto es porque más adelante por ejemplo si agregamos una autenticación tendría que pasar primero por saber si se está autenticado y si es que lo está pasar al siguiente callback para enrutar al cliente, pero por el momento solo hemos de usar un callback, entonces nuestra primera respuesta será de la siguiente forma:

rutas.get("/",(req,res)=>{
  res.send("Hola Mundo");
});

Como rutas es nuestro objeto router debemos usarlo para el manejo de las rutas, nota que el callback es una función flecha, que contiene dos parámetros, el primero es req, que hace referencia al request (consulta) que podría hacer el cliente, por ejemplo cuando un usuario se pudiera logear en una aplicación sus credenciales viajan a través del request o si solicitamos una data específica, también viaja por ese medio, cuando la ruta existe, se espera una respuesta que viaje hacia el cliente y esto se hace a través del parámetro res que es el response de la aplicación, dentro de la función flecha colocamos esta respuesta, hemos usado el método send el cual puede enviar objetos, en este caso una cadena de texto.

Con esto ya tenemos el enrutamiento básico de nuestra aplicación, es decir al menos ya podríamos tener una respuesta cuando el cliente visita la raíz de nuestra aplicación, pero debemos hacer que este objeto (rutas) esté disponible de su uso por los demás ficheros, en especial del index.js que es en donde hemos de dar funcionamiento a nuestra aplicación desde el lado del servidor, para ello, usaremos nuevamente la siguiente sentencia:

module.exports=rutas

Bueno, esto seria por el momento, en la siguiente sesión, veríamos un poco de lo que es la conexión con la base de datos e identificar el site map para ver las rutas que hemos de requerir en nuestra aplicación.

WebSockets y Socket.io – Haciendo un Webchat

Las aplicaciones en tiempo real son tema de desarrollo para este año y su expansión se espera para los siguientes, es cuestión de lógica que en la naturaleza humana se desee una interacción más vívida con otros seres humanos, una experiencia que antes era un poco impensable y  que tuvo y tiene como su mejor representante a los videojuegos, los videojuegos en tiempo real nos permiten vivir experiencias muy variadas aun cuando sea una mecánica sencilla de aprender, el hecho de interactuar con otro ser humano y que pareciese que está cerca de nosotros ha marcado el éxito de muchos de ellos como DOTA por ejemplo; también podemos decir que antes la implementación de este tipo de comunicaciones bidireccionales resultaban muy complejas, pero desde que se introdujo los websockets de forma nativa en html5 ha permitido un rápido crecimiento de aplicaciones de tiempo real; uno de los ejemplos más sencillos es la implementación de un webchat, es así que en esta oportunidad hemos de realizarlo, así que, manos a la obra.

Vamos a ir un poco veloz sobre las instrucciones de preparación con la finalidad que en un solo artículo podamos entender todo lo relacionado a un webchat, entonces:

  • Debemos de crear una carpeta con el nombre de nuestro proyecto, con el IDE VSCODE abrimos la carpeta, así también abrimos el terminal de VSCODE y en el hacemos:
npm init --yes
  • En el mismo terminal instalamos los módulos que en producción hemos de necesitar:
npm i express socket.io
  • Módulos que en la etapa de desarrollo son muy necesarios:
npm i -D nodemon
  • Vamos al fichero package.json y agregamos / modificamos las siguientes líneas:

2

  • Creamos la estructura de ficheros de nuestra aplicación (la cual está dentro de la carpeta del proyecto, si haz seguido los pasos a estas alturas ya tienes tanto la carpeta node_modules, y los ficheros package), en este caso debemos de tener la siguiente estructura:

1

Si deseas profundizar sobre el cómo y porqué de estos archivos o módulos, puedes revisarlo aquí.

Nos centramos sobre el fichero index.js que es en donde hemos de implementar nuestro servidor, para ello hemos de usar express y para la comunicación bidireccional hemos de usar socket.io, entonces debemos de requerirlos:

const express=require("express");
const path=require("path");
const Socketio=require("socket.io);
const app=express();

Para nuestra aplicación, no hemos de necesitar un motor de vistas, sino que las páginas serán cargadas una sola vez y sobre ellas se ha de trabajar, es por ello que debemos de decirle a express que se han de usar archivos estáticos y que estos serán despachados desde la carpeta “public“:

app.use(express.static(path.join(_dirname,"frontend/public")));

Ahora debemos de poner a la escucha nuestro servidor, pero hay que tener consideración de lo siguiente, nuestra aplicación podría estar alojada en la nube, por lo que de vez en cuando es el servidor quien asigna de forma automática el puerto sobre el cual la aplicación estará la escucha, cosa que no ocurre muy a menudo en nuestro localhost y que por lo general se estila usar el puerto 3000, entonces debemos hacer que pueda tener ambas opciones:

let servidor=app.listen(process.env.PORT || 3000,()=>{
  console.log("Estamos en funcionamiento");
})

app.listen nos devuelve un http.server que es el servidor listo que ha de usar socket.io, es por ello que lo asignamos a una variable servidor.

Hasta este punto ya hemos desarrollado todo lo relacionado a nuestro servidor, desde aquí solo resta usar socket.io; cuando hablamos de comunicación bidireccional, esta se implementa en socket.io a través de eventos, de los cuales existen sockets que están a la escucha o emitiendo dichos eventos. Para implementarse socket.io se compone de dos partes:

  • Un servidor integrado con (o montado sobre) NodeJs Http.Server (lo que nos devuelve el app.listen).
  • Una librería del lado del cliente que carga sobre el navegador

De lado del servidor

Desde el inicio hemos requerido el módulo, ahora lo que debemos es crear el servidor socket.io, por ello:

const io=Socketio(server)

Esta instrucción la encontramos aquí, y tiene 3 formas de implementarse:

const io=require("socket.io")();
o
const servidor=require("socket.io");
const io=new Server(httpServer,[opciones]);
o
const servidor=require("socket.io");
const io=servidor.listen(http.server,[opciones]);

Aunque pareciese un tanto confuso, opcionalmente puedes optar usar la palabra reservada “new” o hacer que escuche al http.server con el método listen.

Implementado el servidor, ahora debemos recordar que socket.io trabaja a través de eventos, y el primer evento que el servidor debe escuchar es cuando se ha realizado una conexión desde el cliente, este evento se denomina “connection” y se implementa a través del método on, este método tiene el siguiente constructor:

socket.on(evento_nombre,callback);

Entonces lo implementamos de la siguiente forma:

io.on("connection",socket=>{
  .....
});

El método on nos devuelve un socket, y lo vemos incluso cuando mostramos el constructor del método, pero ¿Qué es un socket?, usando la misma definición de socket.io:

El socket es la clase fundamental para la interacción con los clientes, un socket pertenece a un “namespace” (por defecto “/”) y utiliza un cliente para comunicarse“.

Es decir que sobre sockets hemos de emitir y estar a la escucha de los eventos que generemos. Entonces cuando se estableció ya una conexión, debemos de ponernos a pensar en la estructura que debe de tener nuestro chat. Nosotros debemos de poder recibir mensajes y llamaremos a ese evento: “chat_mensaje“, cuando el servidor escucha un mensaje, debe poder replicarlos a los demás, es decir debe emitirlo a los demás clientes y como se trata del mismo mensaje podemos llamar de igual forma al evento como “chat_mensaje“, quedando entonces:

io.on("connection", socket=>{
 socket.on("chat_mensaje", data=>{
   io.sockets.emit("chat_mensaje",data);
});
});

14

Todos los eventos se generan a partir de que se establece una comunicación con el cliente, es por ello que los demás métodos están dentro del método “on” asociado al servidor socket.io; recordemos que este “on” asociado al servidor nos devuelve un socket, entonces hemos colocado este socket a la escucha (a través de asociarlo a un método “on“) de un evento que se llama “chat_mensaje“, pero ahora este on asocia al callback un parámetro que puede ser de cualquier tipo (esencialmente ese es el dato que envía el cliente), pero como mencionamos cuando se recibe un mensaje este debería ser retransmitido a todos los demás, es ahí (nota los anidamientos) que se “emite” un evento, el emitir un evento se realiza a través del servidor hacia todos con el método “emit” el cual tiene el siguiente constructor:

socket.emit(evento_nombre,[data],[f(x)])

Como podemos observar el constructor es similar al método “on“, dado que buscamos transmitir los mensajes a los demás clientes y hacer lo más instructivo el ejemplo lo llamamos de la misma forma:

io.sockets.emit("chat_mensaje",data);

Pero observamos que hay algo de particular cuando decimos io.sockets, como ya sabemos io ya es el servidor de socket.io y que un socket está asociado a un namespace, entonces cuando colocamos el término sockets al servidor de socket.io estamos haciendo referencia al namespace de la raíz, esto tiene lógica pues desde el servidor y su raíz es que hemos de comunicar a todos los clientes el mensaje que está contenido en data. Entonces por el momento es todo lo que tenemos que hacer en el lado del servidor.

De lado del Cliente

Tal como hemos observado el árbol de ficheros en la carpeta “public” tenemos un index.html que debe tener la siguiente forma final:

15

En la cabecera tenemos que hemos agregado la fuente Nunito que nos proporciona google-fonts, además hemos agregado nuestra propia hoja de estilos en el fichero main.css, dado que es una hoja de estilos, no entraremos en profundidad al respecto de ello:

16

Entonces debemos de centrarnos en el contenido dentro de el id=chat_container, la estructura la es la siguiente:

17

El chat_container, es el contenedor de todo el chat y dentro de él nos encontramos con el chat_ventana que contiene dos div uno (salida) donde hemos de mostrar los mensajes que hagan otros usuarios y el segundo (acciones) con el fin de mostrar cuando se esté ejecutando alguna acción, como la de “escribir“, finalmente los inputs donde hemos de colocar nuestro usuario y mensaje.  

Finalmente llegamos a la zona de los scripts, y podemos observar que tenemos una librería denominada socket.io.js, esta librería es otorgada por el servidor para que el cliente pueda comunicarse con él, ya que recordemos que hasta el momento no hemos implementado como ha de conectarse el cliente con el servidor, pues lo hará a  través de esta librería, incluso si colocamos en el navegador la ruta obtendremos lo siguiente:

3.png

Con todo esto ya tenemos finalizado el index.html, ahora pasamos a revisar el fichero de JavaScript que se denomina chat.js.

Lo primero que debemos de hacer es obtener el objeto de socket.io para el cliente a través del término io de la siguiente forma:

const socket=io();

Si revisamos el constructor de io tenemos:

io([url],[opciones])

Observamos que tenemos la posibilidad de ingresar una ruta específica, esto es muy útil cuando trabajamos con diferentes espacios, como por ejemplo un chat diferente para cada área de la empresa, en este caso cuando no le colocamos un ruta por defecto esta será “/” la raíz, ahora bien lo que nos devuelve es un socket, ¿recuerdan? el socket es para todo y es con este mismo socket que estaremos a la escucha o emitiendo algún evento.

Hecho esto, vamos declarar las variables que tenemos por cada uno de los id:

let mensaje=document.getElementById("mensaje");
let usuario=document.getElementById("usuario");
let boton=document.getElementById("enviar");
let salida=document.getElementById("salida");
let acciones=document.getElementById("acciones");

Debemos asignar al botón un evento click:

boton.addEventListener("click",function(){
 e.preventDefault();
......
.....
});

Cada vez que se crea un mensaje y al enviarlo haciendo click, internamente debemos de emitir un evento relacionado al socket, pero este evento ya ha sido definido como “chat_mensaje“, entonces dentro del listener del botón hacemos:

socket.emit("chat_mensaje",{
 usuario:usuario.value,
 mensaje:mensaje.value
});

Como podemos observar conjuntamente con la emisión del evento estamos enviando un objeto que contiene tanto el valor del input usuario así como el mensaje (este objeto llegará al servidor y se retransmitirá a todos los clientes conectados). Ahora debemos de ponernos en la situación que nosotros no hemos emitido el mensaje pero si otro cliente, entonces implica que debemos de también estar a la escucha de un evento que será “chat_mensaje“, pero este ya debe estar fuera del listener del botón, entonces:

socket.on("chat_mensaje", function(data){
 salida.innerHTML+="<p><strong>"+data.usuario+"</strong>:"+data.mensaje;
 acciones.innerHTML="";
});

Cada vez que se escucha el evento “chat_mensaje” se agrega los datos recibidos como parte de una estructura html dentro del div salida, también vemos que el div acciones agrega una estructura vacía, y ¿Qué es lo que queremos hacer con el div acciones?, pues queremos que cuando un cliente esté digitando un mensaje, este se observe, para ello debemos de agregar un evento relacionado al teclado de la siguiente forma:

mensaje.addEventListener("keypress",function(){
  socket.emit("chat_escribiendo",usuario.value);
});

socket.on("chat_escribiendo",function(data){
 acciones.innerHTML="<p><em>"+data+"</em> está escribiendo</p>";
});

Este evento de teclado está asociado al div mensaje, lógico, cuando estemos digitando debe dispararse el evento, cuando ocurre el evento el socket emite su propio evento enviando el valor del usuario (básicamente el nickname que se colocó), pero tal como emite debe estar a la escucha del mismo evento (esto cuando otro cliente es el que escriba), cuando ello ocurra, debe en el div acciones recuperar la data y escribir un fragmento html. El lado del cliente queda finalmente:

19

En el lado servidor nuevamente

Hecho esto queda agregar este evento en el lado del servidor, entonces en index.js y dentro de:

io.on("connection", socket=>{
 socket.on("chat_mensaje", data=>{ 
    io.sockets.emit("chat_mensaje",data); 
 });
 socket.on("chat_escribiendo", data=>{
 socket.broadcast.emit("chat_escribiendo",data);
}); 

});

Notamos que ahora estamos usando la palabra reservada broadcast, esto responde a la siguiente lógica, cuando vamos a emitir el evento que estamos escribiendo un mensaje, no nos importa que nosotros seamos informados que estamos escribiendo un mensaje, sino solo los demás clientes, entonces broadcast es un modificador que hará ello.

Esto es todo lo que debemos realizar para tener nuestro webchat, podemos ver una imagen del resultado:

18