Vamos a programar #9 - El Código de VEncoder 2.

El día de hoy; y como lo prometí hace algunos días, les traigo el código fuente de VEncoder 2.
Las entregas se van a repartir en las siguientes:

  1. El código de VEncoder2.
  2. Porqué funciona cómo funciona.
  3. Agregando funciones que necesito.
  4. Errores, Intentando arreglarlos.

El Código de VEncoder2.

Lo primero que vamos a hacer es echarle un vistazo al código de fuente, tratare de explicar un poco que es lo que hace cada parte del código. Algo importante que debo mencionar es que el código ya tiene "sus ayeres", originalmente estaba planeado para uso personal, así que el código puede estar "sucio". Si eres algo curioso, de seguro te preguntaras ¿VEncoder2, donde esta el 1?, la respuesta es simple, nunca vio la luz del sol, y no porque fuera malo u otra cosa, simplemente no se me ocurrió que le resultara interesante a alguien más a parte de mí, de hecho la idea de hacer un blog surgió a partir de alguien a quien siempre le agradeceré... Pero eso es otra historia, sigamos en los que estábamos.

Inicialmente estaba pensado para convertir los vídeos y que fueran compatibles con el PSP y PSvita

Abriendo el proyecto.

Lo primero que debemos de hacer es descargar el proyecto (aquí) y abrirlo con #Develop o visual studio. Para poder usrlos, primero debemos de agregar un par de referencias a los siguientes ensamblados: MediainfoNet.dll y ListviewEx.dll (incluidas en la descarga). La primer librería solo se trata de un "wrapper" para poder usar la librería MediaInfo.dll que sirve para obtener la información detallada de los archivos multimedia (Resolucion, BitRate, etc) y la segunda es una versión extendida del ListView, posee algunas funciones extra a un ListView normal, estas las veremos mas adelante.
Al inicio, puede aparecer este mensaje de error.
Para agregar las referencias solo hay que ir a "Proyecto>Agregar referencia" y en la ficha "Examinar", buscar la carpeta debug que viene incluida en el archivo que descargaste y seleccionar "ListViewEx.dll y MediaInfoNet.dll"


Analizando un poco el código.

El código está dividido en dos regiones:
  1. Declaraciones (globales)
  2. Funciones
El resto del código que no está dentro de ninguna de estas, es código propio del formulario o de los controles; eventos constructores y todo lo demás no tienen ninguna Region definida.

Para VEncoder2 solo usaremos 4 variables globales

    #region Declaraciones
       private bool IsConvertingSomething = false;
       private bool RequestForCancel = false;
       private int ElapsedTimeGlobal = 0;
       private int ElapsedTimeCurrent = 0;
    #endregion

Los nombres son realmene explicitos, entonces creo que no hace falta mencionar cada una, de todos modos sobre la marcha, veremos que tipo de valores se le asignan a cada una.

Función SaveProfile

Para no hacer muy largo el post,el día de hoy solo analizaremos las primeras dos funciones que nos encontraremos al abrir el proyecto. La primera funcion es la que se encarga de abrir un archivo VEPX con los ajustes para la conversion.


/// <summary>
/// Funcion que se encarga de guardar loa ajustes en archivo VEPX
/// </summary>
/// <param name="XmlPath">Ruta en donde se guardara</param>
/// <param name="ProfileName">Nombre que se usara para identificar el archivo</param>

private void SaveProfile(string XmlPath, string ProfileName)
{
  XmlWriterSettings settings = new XmlWriterSettings();
  settings.Indent = true;
  settings.IndentChars = "\t";
  using (XmlWriter XWriter = XmlWriter.Create(XmlPath, settings))
  {
    XWriter.WriteStartDocument();
    XWriter.WriteComment("Archivo de preset para VEncoder 2");
    XWriter.WriteStartElement("Perfil");
    XWriter.WriteAttributeString("v", "1");
    XWriter.WriteAttributeString("appname", "VEncoder2");
    XWriter.WriteElementString("Name", ProfileName);
    XWriter.WriteElementString("WH", CboResolucion.Text);
    XWriter.WriteElementString("VBit", TXTVBit.Text);
    XWriter.WriteElementString("Ratio", CBORatio.Text);
    XWriter.WriteElementString("Profile", CBOProfile.Text);
    XWriter.WriteElementString("Nivel", CboLevel.Text);
    XWriter.WriteElementString("ASample", CboFreq.Text);
    XWriter.WriteElementString("ABit2", CBOBitsA.Text);
    XWriter.WriteElementString("Channel", CBOAChannels.Text);
    XWriter.WriteElementString("ExtraParams", TXTExtraparams.Text);
    XWriter.WriteEndElement();
    XWriter.WriteEndDocument();
  }
}


Lo que va a hacer es leer los controles y guardar sus valores en un archivo XML, para eso, usamos XMLWritter. De hecho lo hacemos de forma inpractica, pero debido a que siempre van a ser los valores de los mismos controles, ademas de que no son muchos, entonces podemos hacer de forma explicita, indicando el nombre del control cuyo valor queremos obtener y simplemente guardarlo. Por ejemplo,  XWriter.WriteElementString("WH", CboResolucion.Text); lo que hace es guardar en un elemento XML llamado "WH" el texto contenido en CboResolution. Para poder entender un poco mejor a que se hace referencia, recomiendo echarle un vistazo al proyecto, cada control tiene su nombre, entonces una vez que te familiarices con los nombres, sera más rápido entender el código.

Funcion LoadProfile

La siguiente función hace lo contrario, en lugar de guardar, está lee desde un archivo existente.
/// <summary>
/// Carga un archivo XML con los ajustes para realizar la cadificacion.
/// </summary>
/// <param name="InXMLFile">Ruta al archivo que se debe de leer</param>
private void LoadProfile(string InXMLFile)
{
using (XmlReader XMLR = new XmlTextReader(InXMLFile))
 {
 XMLR.ReadToFollowing("Name");
 lnklblLoadProfile.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("WH");
 CboResolucion.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("VBit");
 TXTVBit.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("Ratio");
 CBORatio.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("Profile");
 CBOProfile.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("Nivel");
 CboLevel.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("ASample");
 CboFreq.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("ABit2");
 CBOBitsA.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("Channel");
 CBOAChannels.Text = XMLR.ReadElementContentAsString();
 XMLR.ReadToFollowing("ExtraParams");
 TXTExtraparams.Text = XMLR.ReadElementContentAsString();
 }
}


Verás que el código es realmente similar al anterior, de hecho lo es, pero para poder entenderlo es necesario estar familiarizado con los nombres de los controles.

Creo que por ahora es todo, sirve que le echas in vistazo al código y ya despues seguiremos con la explicación a lo demás.

Los leo luego.

El vídeo correcto #2 - Cómo funciona VEncoder.

El día de hoy le quiero mostrar como es que funciona VEncoder 2, esto en lo que arreglo un poquito el código fuente para poder publicarlo y que cada quien cree su propia versión.

Paso 1. Abriendo los archivos.

El primer paso y el más obvio es agregar los archivos que se quieren convertir. Cómo estamos usando FFMpeg, el tipo de de archivos que podemos convertir es muy alto, pero para no complicarnos veremos los más comunes.
Los archivos mas comunes, suelen venir con extensiones:
  • MP4
  • MKV
  • WMV
  • AVI
  • MPEG
Y para no complicarnos mas vamos a decir que FFMPeg es capaz de convertir cualquiera de los anteriores a MP4 que es el mas usado (en realidad es MP4 usando codec X264 para el video y AAC para el audio). Entonce si tienes un video que no se ve y quieres convertirlo, puedes usar este programa.
Para agregarlos hay que usar el botón que dice "Abrir archivos" se abrirá la ventana de selección de archivos. Solo tenemos que seleccionar los que queramos convertir.
En mi caso voy a convertir algunos vídeos que grabe con una cámara relativamente vieja y que no son compatibles con ninguna de las pantallas que tengo.
La intefaz está dividida en dos partes, la primera esta diseñada para poder gestionar los archivos que se agregan, es posible quitar algunos de los archivos (si cambias de opinion), seleccionar solo algunos para convertirlos en ese momento o simplemente quitar todo.
La parte del control de archivos consiste en 5 botones y una casilla de verificación.
De forma corta, que es lo que hace cada botón:
  • "Abrir Archivos", muestra el dialogo de selección de archivos y los agrega a la lista de conversión.
  • "Quitar todos", limpia toda la lista de archivos.
  • "Quitar marcados", quita de la lista solo los archivos que tengan marca de selección.
  • "Marcar todos", le pone marca de selección a todos los archivos.
  • "Invertir selección", Si un archivo de la lista tiene marca de selección, la quita; en caso contrario se la pone.
  • "Convertir solo los marcados", cuando se le dice la programa que empiece ala conversion, solo hará la de aquellos que tienen marca selección.

"Ajustando" los ajustes.

Para realizar la conversión, primero hay que poner los valores adecuados para que al final el vídeo sea compatible.
Una configuración compatible con la mayoría de las pantallas del mercado es la siguiente;
Los ajustes como los de la imagen funcionan.

Y darán como resultado lo siguiente:

-y -i "C:\MI Video.mp4" -c:v libx264 -s 1280x720 -b:v 4000k -aspect 16:9 -profile:v high -threads 0 -r 23.974 -level 4.1 -crf 22 -f mp4 -pass 1 -an NUL
-y -i "C:\Mi video.mp4" -c:v libx264 -s 1280x720 -b:v 4000k -aspect 16:9 -profile:v high -threads 0 -r 23.974 -level 4.1 -crf 22 -f mp4 -pass 2 -c:a libvo_aacenc -b:a 128k -ar 44100 -ac 2 "C:\Users\Ra\Desktop\MI Video.mp4"

La linea de comandos solo es ilustrativa. Cuando ya se modificaron los ajustes a los que cada uno necesita, solo basta con presionar el botón "Empezar".

Agilizando el trabajo.

Cómo lo mas seguro es que vas a realizar mas de una vez el mismo tipo de conversión, debe de haber una forma de guardar una configuración para poder usarla después. La solución fue crear archivos de "perfil", en el se guardan las configuraciones para solo poder cargarlas.

Si descargaste VEncoder, al momento de extraer el paquete, de seguro viste un par de archivos con extensión VEPX. Estoso son configuraciones guardadas, para usarlas, en la pantalla principal, deberás de hacer clic en en las letras azules que por default dicen:"Archivo de perfil: ninguno", se abrirá un dialogo con filtro para los archivos VEPX, solo debes de escoger uno válido para usarlo.
Las ventajas son simples, ahorra el trabajo de cambiar los ajustes cada vez.

Pero que son los archivos VEPX.

Los archivos VEPX en realidad son archivos XML con los ajustes, el código de VEncoder se encarga de asignar cada valor al correspondiente para la conversión.
De hecho si abres el archivo con Notepad++ se verá similar a este.
<?xml version="1.0" encoding="utf-8"?>
<!--Archivo de preset para VEncoder 2-->
<Perfil v="1" appname="VEncoder2">
 <Name>HD Medium</Name>
 <WH>1280x720</WH>
 <VBit>4096</VBit>
 <Ratio>16:9</Ratio>
 <Profile>high</Profile>
 <Nivel>41</Nivel>
 <ASample>44100</ASample>
 <ABit2>128</ABit2>
 <Channel>2</Channel>
 <ExtraParams />
</Perfil>

La parte programacional la veremos en el siguiente Post, Solo como recordatorio, el programa aun es BETA, por lo que los fallos aun estan, pero no te preocupes, en el siguiente post empezaremos a analizar y a depurar el codigo de VEncoder2.

Por ahora es todo, los leo luego.

El vídeo correcto.

Un ligero problema.

Hace tiempo conseguí una smart TV para poder ver mas cómodamente algunos vídeos que conseguía del internet, tiempo después conseguí otra Tv que planeaba usar cómo monitor, pero por cuestiones "practicas" terminó siendo usada para lo mismo que la primera. En ambas tv´s, hay puertos USB y las dos soportaban la reproducción de medios, vídeos, fotos y música.
La primera vez que intente reproducir vídeo que previamente ya había visto en la smart TV, me llevé una sorpresa al ver que algunos archivos no eran compatibles con la otra pantalla, eso a pesar de que las dos soportan full HD.
La solución mas obvia es volver a convertir los videos y adaptarlo para que sean compatibles con ambas televisiones. Ahora solo quedaba una pregunta: ¿Qué programa usar?. Cuando buscas en internet, aparecerán infinidad de programas, alguno gratis, otros de paga y otros con malware :|,  al buscar entre los programas freeware, me di cuenta que la mayoría usaban FFmpeg cómo "motor" de conversión, entonces decidí buscar directamente ese programa para ver que pasaba.

FFmpeg la mejor solución.

La mejor solución era usar FFmpeg directamente, pero solo que habia un pequeño problema, ¡Utiliza linea de comandos!, es decir que hay que configurar cada vez que se quiera hacer una conversion, aun si se trata de hacer un pequeño cambio, hay que editar manualmente la linea de comandos y despues pasarla a ffmpeg.
La solución fue hacer un programa en c# que construyera la línea de comandos y la pasara a FFmpeg.
Les presento VEncoder 2 beta. otra GUI que se encargará de hacer el trabajo sucio por nosotros. Cómo de costumbre haré un tutorial para que tu mismo hagas una versión, pero por ahora le comparto la mía.
Con está GUI podemos hacer los siguiente:

  • Realizar conversiones en lote.
  • Crear la linea de comandos para después usarla con FFmpeg direcamente.
  • Cambiar los ajuste de la conversión de manear rápida.
  • Guardar configuraciones en archivos *.vepx para poder usarlos después.

Que necesitas:



  • Un equipo con windows 7 o superior
  • La aplicación, la puedes descargar de aquí
  • FFMPEG de 32 o 64 bits, dependiendo de que versión de windows uses.
Una vez que tengas loa anterior, solo debes de descomprimir el archivo y después copiar ffmpeg al mismo directorio.
FFmpeg y todos los archivos de la aplicación deben de estar juntos.
El código lo publicaré después, si tienes una pantalla de gran resolución, harbas visto que el blog se descuadra, entonces primero arreglare eso y ya después publicare el código fuente.
En el siguiente post explicare un poco el funcionamiento de esta GUI, luego el código fuente y funciones "avanzadas" de FFMpeg.

Hay que recordar que está en modo beta y las instrucciones no las pongo para que así, dexcubras tu mismo que es lo que está bien y que es lo que está mal, ya despues veremos cómo usarlo y cómo agregar las funciones que necesitamos desde el código fuente.

Por ahora es todo. Los leo luego

Una manita de gato.


Hola a todos. El día de hoy solo escribo para avisar que el blog está en etapa de diseño, habrán cambios y algunas secciones no funcionaran o no lo harán de manera correcta. Más no te preocupes, solo será por unos días, después volveremos a la normalidad.
Gracias a todos por la paciencia.

Vamos a programar #8 - Inútil-apps #1 - Max7219 + arduino.

El dia de hoy terminaremos de hacer nuestro proyecto. Ya hicimos la parte del hardware, la parte de windows y ahora solo nos resta la parte del software para Arduino.

¿Que necesitamos?

El dia de hoy solo vamos a usar el arduino IDE, ademas de que el arduino que vayamos a usar, deberá de estar conectado al pc para poder subir el programa.
Adicionalmente usaremos la librería MaxMatrix. Una vez que la descargamos y la extrajimos, debemos de instalarla antes de poder usarla, para eso primero la copiaremos a la carpeta que el IDE de arduino crea para nosotros. Generalmente será "C:\Users\XXX\Documents\Arduino\libraries", en la carpeta "libraries" copiaremos la carpeta "MaxMatrix" ya descomprimida.
Despues en el IDE arduino procedremos a instalarla, para eso haremos clic en el menú "Programa>Incluir Liberia>Gestionar Librerías", nos aparecerá una ventana:
Cuando se va instalar por primera vez, aparecerá un botón que dirá "Instalar", cuando ya esta instalada, solo aparece el nombre de la librería.
Buscaremos el elemento que diga "MaxMatrix Built-In" y haremos clic en instalar.

El código

Ahora veremos el código que hace funcionar las cosas:

#include <MaxMatrix.h>
#include <avr/pgmspace.h>

PROGMEM const unsigned char CH[] = {
  3, 8, B00000000, B00000000, B00000000, B00000000, B00000000, // space
  1, 8, B01011111, B00000000, B00000000, B00000000, B00000000, // !
  3, 8, B00000011, B00000000, B00000011, B00000000, B00000000, // "
  5, 8, B00010100, B00111110, B00010100, B00111110, B00010100, // #
  4, 8, B00100100, B01101010, B00101011, B00010010, B00000000, // $
  5, 8, B01100011, B00010011, B00001000, B01100100, B01100011, // %
  5, 8, B00110110, B01001001, B01010110, B00100000, B01010000, // &
  1, 8, B00000011, B00000000, B00000000, B00000000, B00000000, // '
  3, 8, B00011100, B00100010, B01000001, B00000000, B00000000, // (
  3, 8, B01000001, B00100010, B00011100, B00000000, B00000000, // )
  5, 8, B00101000, B00011000, B00001110, B00011000, B00101000, // *
  5, 8, B00001000, B00001000, B00111110, B00001000, B00001000, // +
  2, 8, B10110000, B01110000, B00000000, B00000000, B00000000, // ,
  4, 8, B00001000, B00001000, B00001000, B00001000, B00000000, // -
  2, 8, B01100000, B01100000, B00000000, B00000000, B00000000, // .
  4, 8, B01100000, B00011000, B00000110, B00000001, B00000000, // /
  4, 8, B00111110, B01000001, B01000001, B00111110, B00000000, // 0
  3, 8, B01000010, B01111111, B01000000, B00000000, B00000000, // 1
  4, 8, B01100010, B01010001, B01001001, B01000110, B00000000, // 2
  4, 8, B00100010, B01000001, B01001001, B00110110, B00000000, // 3
  4, 8, B00011000, B00010100, B00010010, B01111111, B00000000, // 4
  4, 8, B00100111, B01000101, B01000101, B00111001, B00000000, // 5
  4, 8, B00111110, B01001001, B01001001, B00110000, B00000000, // 6
  4, 8, B01100001, B00010001, B00001001, B00000111, B00000000, // 7
  4, 8, B00110110, B01001001, B01001001, B00110110, B00000000, // 8
  4, 8, B00000110, B01001001, B01001001, B00111110, B00000000, // 9
  2, 8, B01010000, B00000000, B00000000, B00000000, B00000000, // :
  2, 8, B10000000, B01010000, B00000000, B00000000, B00000000, // ;
  3, 8, B00010000, B00101000, B01000100, B00000000, B00000000, // <
  3, 8, B00010100, B00010100, B00010100, B00000000, B00000000, // =
  3, 8, B01000100, B00101000, B00010000, B00000000, B00000000, // >
  4, 8, B00000010, B01011001, B00001001, B00000110, B00000000, // ?
  5, 8, B00111110, B01001001, B01010101, B01011001, B00111110, // @
  4, 8, B01111110, B00010001, B00010001, B01111110, B00000000, // A
  4, 8, B01111111, B01001001, B01001001, B00110110, B00000000, // B
  4, 8, B00111110, B01000001, B01000001, B00100010, B00000000, // C
  4, 8, B01111111, B01000001, B01000001, B00111110, B00000000, // D
  4, 8, B01111111, B01001001, B01001001, B01000001, B00000000, // E
  4, 8, B01111111, B00001001, B00001001, B00000001, B00000000, // F
  4, 8, B00111110, B01000001, B01001001, B01111010, B00000000, // G
  4, 8, B01111111, B00001000, B00001000, B01111111, B00000000, // H
  3, 8, B01000001, B01111111, B01000001, B00000000, B00000000, // I
  4, 8, B00110000, B01000000, B01000001, B00111111, B00000000, // J
  4, 8, B01111111, B00001000, B00010100, B01100011, B00000000, // K
  4, 8, B01111111, B01000000, B01000000, B01000000, B00000000, // L
  5, 8, B01111111, B00000010, B00001100, B00000010, B01111111, // M
  5, 8, B01111111, B00000100, B00001000, B00010000, B01111111, // N
  4, 8, B00111110, B01000001, B01000001, B00111110, B00000000, // O
  4, 8, B01111111, B00001001, B00001001, B00000110, B00000000, // P
  4, 8, B00111110, B01000001, B01000001, B10111110, B00000000, // Q
  4, 8, B01111111, B00001001, B00001001, B01110110, B00000000, // R
  4, 8, B00100110, B01001001, B01001001, B00110010, B00000000, // S
  5, 8, B00000001, B00000001, B01111111, B00000001, B00000001, // T
  4, 8, B00111111, B01000000, B01000000, B00111111, B00000000, // U
  5, 8, B00001111, B00110000, B01000000, B00110000, B00001111, // V
  5, 8, B00111111, B01000000, B00111000, B01000000, B00111111, // W
  5, 8, B01100011, B00010100, B00001000, B00010100, B01100011, // X
  5, 8, B00000111, B00001000, B01110000, B00001000, B00000111, // Y
  4, 8, B01100001, B01010001, B01001001, B01000111, B00000000, // Z
  2, 8, B01111111, B01000001, B00000000, B00000000, B00000000, // [
  4, 8, B00000001, B00000110, B00011000, B01100000, B00000000, // \ backslash
  2, 8, B01000001, B01111111, B00000000, B00000000, B00000000, // ]
  3, 8, B00000010, B00000001, B00000010, B00000000, B00000000, // hat
  4, 8, B01000000, B01000000, B01000000, B01000000, B00000000, // _
  2, 8, B00000001, B00000010, B00000000, B00000000, B00000000, // `
  4, 8, B00100000, B01010100, B01010100, B01111000, B00000000, // a
  4, 8, B01111111, B01000100, B01000100, B00111000, B00000000, // b
  4, 8, B00111000, B01000100, B01000100, B00101000, B00000000, // c
  4, 8, B00111000, B01000100, B01000100, B01111111, B00000000, // d
  4, 8, B00111000, B01010100, B01010100, B00011000, B00000000, // e
  3, 8, B00000100, B01111110, B00000101, B00000000, B00000000, // f
  4, 8, B10011000, B10100100, B10100100, B01111000, B00000000, // g
  4, 8, B01111111, B00000100, B00000100, B01111000, B00000000, // h
  3, 8, B01000100, B01111101, B01000000, B00000000, B00000000, // i
  4, 8, B01000000, B10000000, B10000100, B01111101, B00000000, // j
  4, 8, B01111111, B00010000, B00101000, B01000100, B00000000, // k
  3, 8, B01000001, B01111111, B01000000, B00000000, B00000000, // l
  5, 8, B01111100, B00000100, B01111100, B00000100, B01111000, // m
  4, 8, B01111100, B00000100, B00000100, B01111000, B00000000, // n
  4, 8, B00111000, B01000100, B01000100, B00111000, B00000000, // o
  4, 8, B11111100, B00100100, B00100100, B00011000, B00000000, // p
  4, 8, B00011000, B00100100, B00100100, B11111100, B00000000, // q
  4, 8, B01111100, B00001000, B00000100, B00000100, B00000000, // r
  4, 8, B01001000, B01010100, B01010100, B00100100, B00000000, // s
  3, 8, B00000100, B00111111, B01000100, B00000000, B00000000, // t
  4, 8, B00111100, B01000000, B01000000, B01111100, B00000000, // u
  5, 8, B00011100, B00100000, B01000000, B00100000, B00011100, // v
  5, 8, B00111100, B01000000, B00111100, B01000000, B00111100, // w
  5, 8, B01000100, B00101000, B00010000, B00101000, B01000100, // x
  4, 8, B10011100, B10100000, B10100000, B01111100, B00000000, // y
  3, 8, B01100100, B01010100, B01001100, B00000000, B00000000, // z
  3, 8, B00001000, B00110110, B01000001, B00000000, B00000000, // {
  1, 8, B01111111, B00000000, B00000000, B00000000, B00000000, // |
  3, 8, B01000001, B00110110, B00001000, B00000000, B00000000, // }
  4, 8, B00001000, B00000100, B00001000, B00000100, B00000000, // ~
  4, 8, B00111110, B01000001, B01000001, B00100010, B00010000, // Ç
};

int DIN = 12;
int CS =  11;
int CLK = 10;
int maxInUse = 1;
int Velocity = 60;
int Bright = 8;
byte buffer[10];
char Texto[64];

MaxMatrix m(DIN, CS, CLK, maxInUse);

void setup() {
  m.init();
  m.setIntensity(Bright);
  Serial.begin(9600);
}

void loop() {
  int i = 0;
  int j = 0;
  delay(500);
  if (Serial.available()) {
    while (Serial.available() > 0) {
      Texto[i] = Serial.read();
      i++;
    }
    Texto[i] = '\0';
  }
  PrintData(Texto);
  Serial.println("Done");
}

void PrintData(char DATA[])
{
  int i = 0;
  int j = 0;
  int X = 0;
  int Z = 0;
  String Texto(DATA);
  if (Texto.startsWith(">SPD")) {
    Velocity = Texto.substring(4).toInt();
    for (j = 0; j < 11; j++) {
      DATA[j] = 0;
    }
    i = 0;
  }
  if (Texto.startsWith(">LUM")) {
    Bright = Texto.substring(4).toInt();
    m.setIntensity(Bright);
    for (j = 0; j < 11; j++) {
      DATA[j] = 0;
    }
    i = 0;
  }
  if (Texto.startsWith(">DEMO")) {
    for (j = 0; j < 11; j++) {
      DATA[j] = 0;
    }
    i = 0;
  }
  else {
    printStringWithShift(DATA, Velocity);
    for (j = 0; j < 11; j++) {
      DATA[j] = 0;
    }
    digitalWrite(13, LOW);
    i = 0;
  }
}

void printCharWithShift(char c, int shift_speed) {
  if (c < 32) return;
  c -= 32;
  memcpy_P(buffer, CH + 7 * c, 7);
  m.writeSprite(maxInUse * 8, 0, buffer);
  m.setColumn(maxInUse * 8 + buffer[0], 0);
  for (int i = 0; i < buffer[0] + 1; i++)
  {
    delay(shift_speed);
    m.shiftLeft(false, false);
  }
}

void printString(char* s) {
  int col = 0;
  while (*s != 0)
  {
    if (*s < 32) continue;
    char c = *s - 32;
    memcpy_P(buffer, CH + 7 * c, 7);
    m.writeSprite(col, 0, buffer);
    m.setColumn(col + buffer[0], 0);
    col += buffer[0] + 1;
    s++;
  }
}

void printStringWithShift(char *s, int shift_speed) {
  while (*s != 0) {
    printCharWithShift(*s, shift_speed);
    s++;
  }
}

Poniendo todo junto.

Una vez que esta compilado y subido el código al arduino, es momento de poner todo junto. Para eso abriremos el programa que hicimos en C#, cuando lo abramos, ITunes también se abrirá

Primero debemos de escoger el puerto en el cual esta conectado el Arduino, en mi caso el puerto es "COM3", la velocidad de la conexión en Baudios, debe de ser la misma que pusimos al momento de iniciar la conexión serial en el código para el arduino, en nuestro caso usamos  9600.

Cómo luce la configuración en mi caso.

Lo mas adecuado es conectar primero el arduino al PC y después iniciar la aplicación, de lo contrario no aparecerá en la lista, pero aun puedes escribir el puerto, por ejemplo: si tu arduino esta conectado al puerto "COM4", pero en la lista no aparece bastara con que escribas COM4
No se ve el puerto "COMX" solo escribelo.
Y listo, ahora te ahorraras el trabajo de andar buscando la ventana de ITunes para ver que es lo que estás escuchando.


Parecido mas no igual.

Al ver el código de seguro dirás: ¿Oye, eso no lo he visto ya?, probablemente el mayor codigo del arduino es similar para mostrar el efecto scroll en la matriz, pero ademas de eso le he agregado funciones para poder cambiar el brillo de la matriz sin tener que cambiar el código en el arduino cada vez, lo mismo para la velocidad del texto, generalmente los ejemplos que vienen de otros sitios, la velocidad del texto y el brillo de la matriz es fijo, si quieres cambiarlo debes de conectar el arduino a su IDE, cambiar el código, subirlo y luego usarlo, en este caso evitamos eso creando funciones para ahorrarnos eso, aquí solo basta un clic y listo, el brillo está cambiado.

Por ahora es todo, si tienes dudas no dudes en preguntar. Si quieres ver todo el proceso, aquí está el primer paso.
Los leo luego.

Vamos a programar #7 - Inútil-apps #1 - C# y arduino.

Avanzando lo anterior.

Hola de nuevo a todos el dia de hoy vamos a terminar de implementar lo que estabamos haciendo el post anterior.
Vamos a ver el código que hace funcionar a todo, está compuesto por dos partes, una de ellas es un programa para windows que se encargara de obtener la informacion de lo que estamos escuchando en ITunes.
De seguro te preguntarás: ¿Por qué Itunes y no otro programa? La respuesta es simple, ITunes ofrece soporte para que aquellos programadores que quieran hacaer uso del API de ITunes puedan hacerlo de manera sencilla, además de que el API tambien funciona en la plataforma .NET de microsoft.

La parte del PC. La aplicación  para Windows.
Una vez que creamos en proyecto en #Develop o en C# de Visual studio. Lo primero que vamos a hacer es agregar una referencia a ITunes X.XX Type Library. Para eso iremos al menu Proyecto>Agregar Referencia.
La versión puede variar un poco dependiendo de la versión de itunes que se usa.
La aplicación esta compuesta por lo siguientes controles (además del formulario):
  • El formulario
      • (name): MainForm
  • Un GroupBox
    • 4 Label (Los nombres no son necesarios)
    • 1 ComboBox
      • (name): cboportname
    • 1 TextBox
      • (name): txtBaudNumber
      • Text: 9600
    • 1 TrackBar
      • (name): TrB
      • Maximum: 15
      • Minimum: 1
    • 1 NumericUpDown
      • (name): NUDVelocity
      • Maximum: 500
      • Minimum: 20
    • Button
      • (name):BtnConnect
      • Text: Conectar
    • Button
      • (name):BtnDisconnect
      • Text: Desconectar
Agregando los controles y estableciendo las propiedades como se muestra en la lista, solo bastara con agregar el siguiente codigo a MainForm, reemplazando todo el codigo previo.

/*
 * MPM Develop
 * Usuario: Ra
 * Fecha: 13/07/2016
 * Forget Your troubles C'mon get happy
 */
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO.Ports;
using System.Windows.Forms;
using iTunesLib;
using System.Configuration;

namespace Message_Sender
{
    public partial class MainForm : Form
    {
        int MessageType = 0;
        private enum Trackinfo
        {
            Title = 0,
            Artist,
            Album,
            BPM,
            Duration
        }
        private SerialPort Port = new SerialPort();
        private string GetTrackOnItunes(Trackinfo Detail)
        {
            iTunesApp ItunesInfo = new iTunesAppClass();
            switch (ItunesInfo.PlayerState) {
                case ITPlayerState.ITPlayerStateStopped:
                    return "Detenido  ";
                case ITPlayerState.ITPlayerStatePlaying:
                    IITTrack Track = ItunesInfo.CurrentTrack;
                    switch (Detail)
                    {
                        case MainForm.Trackinfo.Title:
                            return "Cancion: " + Track.Name + "  ";
                        case MainForm.Trackinfo.Artist:
                            return "Artista: " + Track.Artist+ "  ";
                        case MainForm.Trackinfo.Album:
                            return "Album: " + Track.Album+ "  ";
                        case MainForm.Trackinfo.BPM:
                            return "BPM: " + Track.BPM.ToString()+ "  ";
                        case MainForm.Trackinfo.Duration:
                            return "Duracion: " + TimeSpan.FromSeconds(Track.Duration).ToString()+ "  ";
                        default:
                            return "??? ??? ??? ???  ";
                    }
                case ITPlayerState.ITPlayerStateFastForward:
                    return ">> Avance Rapido >>  ";
                case ITPlayerState.ITPlayerStateRewind:
                    return "<< Retroceso Rapido <<  ";
                default:
                    return "??? ??? ??? ???  ";
            }
        }

        private void ArduinoMessage(string Message)
        {
            try
            {
                Port.Write(Message);
            } catch (Exception e) {
                MessageBox.Show(e.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public MainForm()
        {
            InitializeComponent();
        }

        private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e)
        {
            SerialPort sp = (SerialPort)sender;
            string indata = sp.ReadExisting();
            if (indata == "Done")
            {
                MessageType++;
                if (MessageType > 4)
                {
                    MessageType = 0;
                }
                ArduinoMessage(GetTrackOnItunes((Trackinfo)MessageType) + "  ");
            }
        }

        void MainFormLoad(object sender, EventArgs e)
        {
            string[] Ports = SerialPort.GetPortNames();
            cboportname.Items.AddRange(Ports);
            Port.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler);
        }

        void BtnConnectClick(object sender, EventArgs e)
        {
            try {
                if (Port.IsOpen == false)
                {
                    Port.BaudRate = int.Parse(txtBaudNumber.Text);
                    Port.PortName = cboportname.Text;
                    Port.Open();
                }
            } catch (Exception ex) {
                MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        void BtnDisconnectClick(object sender, EventArgs e)
        {
            if (Port.IsOpen == true)
            {
                Port.Write("Se ha desconectado el cliente");
                Port.Close();
            }
        }

        void MainFormFormClosing(object sender, FormClosingEventArgs e)
        {
            if (Port.IsOpen == true)
            {
                Port.Close();
            }
        }

        void TrBMouseUp(object sender, MouseEventArgs e)
        {
            ArduinoMessage(">LUM"+TrB.Value.ToString());
        }

        void NUDVelocityValueChanged(object sender, EventArgs e)
        {
            ArduinoMessage(">SPD"+NUDVelocity.Value.ToString());
        }
    }
}
La funcion del codigo es muy sencilla, lo unico que haces es primer crear un objeto del tipo Port y le asignamos un puerto (en mi caso COM3) con la velocidad de transferencia en 9600 Baudio. Después obtiene la informacion desde ITunes y la envía para el arduino.


Ahora solo nos resta por hacer el código para el arduino, pero eso será en la próxima ocasion.

Todo el código fuente lo puedes descargar aquí

Los leo luego
La siguiente parte está aquí.

Vamos a programar #6 - Inútil-apps #1 - Visualizar información de Itunes en matriz led con arduino.

El día de hoy les voy a mostrar una solución a un pequeño problema al que supongo, muchos de nosotros nos enfrentamos diariamente.
Siempre que me pongo a programar, abro ITunes y pongo un poco de música, pero en ocasiones tengo tantas ventanas abiertas que es difícil saber que es lo que estoy escuchando en ese momento (especialmente porque luego me preguntan que estoy escuchando.).
Por eso el día de hoy decidí hacer algo que me ayudara a saber que es lo que estoy escuchando sin la necesidad de buscar la ventana de itunes. La solución, emplearemos un arduino junto a una matriz de LEDs para mostrar esa información y ahorrarme el trabajo de buscar la ventana cada vez que quiera saber que escucho en ese momento.

¿Que necesitamos?

Las cosas que vamos a usar son la siguientes:
  • 1 Arduino (nano, mega, uno)
  • 1 matriz de led (Kit que incluye el driver Max7219)
  • Compildor de c# (usare #Develop, pero puedes usar el que viene con Visual Studio)
  • Arduino IDE
  • Cautín
  • Soldadura
  • Un poco de paciencia.
Imagen 1: Kit que incluye: Matriz de led 8x8,Driver Max7219, capacitor cerámico de 0.1 microfaradios, capacitor electrolítico de 10 microfaradios, resistencia de 10 Kilo ohm, circuito impreso y soporte para la matriz y el driver.

Paso 1: Las conexiones.

 El primer paso seria ensamblar el kit, Si conseguiste uno similar al de la imagen, el proceso de armado es muy sencillo, el capacitor cerámico y la resistencia pueden ir en cualquier orientación, solo hay que tener cuidado de ponerlos en su sitio.
Imagen 2: En el circuito impreso la letra "C" indica que ahi debe de ir un capacitor, en este caso debemos de de poner el capacitor cerámico en donde no hay hay marcas de polaridad, en cambio el capacitor electrolítico si posee polaridad. 

A tener en cuenta.

El capacitor electrolítico posee polaridad, si se conecta de manera incorrecta hay el riesgo de que todo el circuito no funcione o incluso el capacitor puede explotar (literal).
Mas no hay de que preocuparse porque hay manera de identificar como es que se debe de colocar.
Aspecto de la mayoria de los capacitores electroliticos, hay otros diseños, pero por ahora solo de este tipo.
Si observamos la imagen detenidamente, veremos que hay varias marcas en el capacitor. En primer lugar vemos 1500uF16v, esto nos indica que este capacitor tiene una capacitancia de 1500 micro Farads, ademas indica que el voltaje máximo a manejar es de 16 volts. Despues hay una linea blanca, esta sirve como referencia para indicarnos que es el negativo del capacitor (por ende la otra será el positivo).
Para soldarlo en la placa, hay que revisar donde nos marca el postivo y el negativo para  el capacitor.

Esta matriz esta controlada por el integrado Maxx7219CNG.
Imagen 3: Max7219cng
Una vez que ya está puesto el socket, la manera de saber como ponerlo es sencilla, todos los circuitos integrados que vienen en este tipo de presentación, vienen con una marca, un medio circulo, esto nos indica que ahí esta el primer pin.
Imagen 4: Cómo estan configurados los pines en el Max7219/7221.


Si conseguiste el kit como el de la primer imagen, deberas de ponerlo de modo que la muesca (medio circulo) quede a la izquierda (viéndolo como en la imagen 2).

Luego continuaremos con las conexiones entre el arduino y el max7219, para eso usaremos el proto-board.
las conexiones se harán de la siguiente manera:
Para el kit

  • DIN lo conectaremos al PIN 12 del arduino.
  • CS lo conectaremos al PIN 11 del arduino.
  • CLK lo conectaremos al PIN 10 del arduino.
Para conectar directamente al Max7219

  • El  PIN 1 del Max7219 al PIN 12 del arduino.
  • El  PIN 12 del Max7219 al PIN 11 del arduino.
  • El  PIN 13 del Max7219 al PIN 10 del arduino.
Vista frontal de la matriz conectada al protoboard.
Vista posterior de la matriz con las conexiones indicadas (Sin la conexion a tierra y VCC)
Conexion a los pines del arduino.
Y con esto ya estaría cubierta toda la parte de la conexión, en el próximo post terminaremos con la implementación del código que hará funcionar las cosas. Ademas si eres de los que no puede conseguir el kit para la matriz LED, también aprenderemos a armar una propia.

Por el momento es todo y los leo la próxima.

La siguiente parte está aquí.

Vamos a programar #5 - Calculadora de resistencias para android.


Terminando lo anterior

El dia de hoy vamos a terminar de hacer una aplicacion para android que nos ayudara a obtener el valor de una resistencia basado en los colores que está posea.
En el post anterior vimos como implementar la interfaz.

Asi que ahora nos vamos a ocupar del código que hace funcionar las cosas.

Terminando la interfaz.

Para los datos que van a tener los controles Spinner, decidí cargarlos desde los recursos, Para eso en el panel "Project" buscaremos la siguiente ruta: app>res>values>strings.xml lo abriremos agregaremos entre las etiquetas <resources></resources> el siguiente codigo:

<resources>
    <string name="app_name">Calculador de resistencias</string>
    <string-array name="Colores1">
        <item>Negro</item>
        <item>cafe</item>
        <item>Rojo</item>
        <item>Naranja</item>
        <item>Amarillo</item>
        <item>Verde</item>
        <item>Azul</item>
        <item>Violeta</item>
        <item>Gris</item>
        <item>Blanco</item>
    </string-array>
    <string-array name="Colores2">
        <item>Dorado</item>
        <item>Plata</item>
    </string-array>
</resources>


Ahora solo queda implementar el codigo en java. lo puedes copiar asi como está en app>java
com.mdev.claculadoraderesistencia>mainactivity.java, se incluyen los comentarios pertinentes para poder implementarlo

package com.mdev.rcalc.calculadoraderesistencias;

import android.content.DialogInterface;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.Spinner;
import static java.lang.Math.pow;

public class MainActivity extends AppCompatActivity {
 //Spinners
 Spinner SpnC1,SpnC2,SpnC3,SpnC4;
 //Boton
 Button BtnCallIt;
 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  BtnCallIt = (Button)findViewById(R.id.BtnCalculateIt);
  //En esta seccion llenamos los Spinners con los datos de los recursos.
  //Asociamos los controles del layput a nustras variables de control (spinner)
  SpnC1 = (Spinner)findViewById(R.id.SpnColor1);
  SpnC2 = (Spinner)findViewById(R.id.SpnColor2);
  SpnC3 = (Spinner)findViewById(R.id.SpnColor3);
  //Usamos el mismo adaptador para los primeros 3 ya que contienen los mismos datos.
  ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(this,
    R.array.Colores1, android.R.layout.simple_spinner_item);
  adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  //Asignamos el contenido del adaptador a nuestros controles
  SpnC1.setAdapter(adapter);
  SpnC2.setAdapter(adapter);
  SpnC3.setAdapter(adapter);
  //Hacemos lo mismo para el cuarto, pero aqui usamos el recurso "Colores2" que contiene los
  //datos para la cuarta franja, solo en este caso.
  SpnC4 = (Spinner)findViewById(R.id.SpnColor4);
  ArrayAdapter<CharSequence> adapter2 = ArrayAdapter.createFromResource(this,
    R.array.Colores2, android.R.layout.simple_spinner_item);
  adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
  SpnC4.setAdapter(adapter2);
  //Creamos el handler para el evento click
  BtnCallIt.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view)
   {
   //Usamos la funcion ShowDialog para mostrar el resultado
   ShowDialog("El Resultado es:",BuildNumber(SpnC1.getSelectedItemPosition(),SpnC2.getSelectedItemPosition(),
       SpnC3.getSelectedItemPosition())+" "+GetTolerance(SpnC4.getSelectedItemPosition()));
   }
  });
 }
 //Mostrar dialog de manera sencilla
 private void ShowDialog(String Title, String Caption)
 {
  new AlertDialog.Builder(this)
    .setTitle(Title)
    .setMessage(Caption)
    .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
     public void onClick(DialogInterface dialog, int which) {
      // algo
     }
    })
    .setIcon(android.R.drawable.ic_menu_info_details)
    .show();
 }
 //Funcion para obtener la toleranacia de la resistencia
 private String GetTolerance(int Value)
 {
  if (Value == 0)
   return "+5% de tolerancia";
  else
   return "+10% de tolerancia";
 }
 //Funcion para generar el valor de la resistencia basado en sus colores
 private String BuildNumber(int Value1, int Value2, int Value3)
 {
  String Significant;
  //Los manejo en string para solo concatenarlos obtener el valor
  Significant = Integer.toString(Value1) + Integer.toString(Value2);
  //Convertimos el valor a uno numerico y lo multiplicamos a la potencia de 10
  double Resultado = Integer.parseInt(Significant)*pow(10,Value3);
  //Para regresar el valor en KΩ
  if (Resultado/1000 >= 1 && Resultado/1e3 < 1000 )
  {
   return (String.valueOf(Resultado / 1e3) + "KΩ");
  }
  //Para regresar el valor en MΩ
  if (Resultado/1e6 >= 1)
  {
   return (String.valueOf(Resultado / 1e6) + "MΩ");
  }else{
   //Regresamos el valor en Ω
   return (String.valueOf(Resultado)+"Ω");
  }

 }
}



Al implementar la aplicacion y correrla se obtiene lo anterior, como lo dije esto solo es ilustrativo, no es para aprender a prgramar, pero si tienes dudas no dudes en preguntar.
Por sí se ofrece, el proyecto entero lo puedes descargar aquí, lo puedes importar completo para ver como funciona.
Los leo luego

Vamos a programar #4 - Calculadora de resistencias para android

Hace algunos días alguien me envió una petición, en ella me preguntaba de la posibilidad de hacer la misma calculadora para resistencia pero en una versión para android.
El día de hoy vamos a ver precisamente eso: Cómo hacer una calculadora de resistencias para android.

¿Qué necesitamos?

  1. Android estudio (se puede usar eclipse).
  2. Un dispositivo que corra android (opcional ya que Android studio incluye emuladores de dispositivos con android).
  3. Un poco de paciencia.
**Nota: Este no es un tutorial para aprender a programar, para poder realizarlo debes de tener el mínimo de conocimiento en programación y Android studio ya instalado**

Paso 1. Creando el proyecto.

 Crearemos un proyecto nuevo.
Seleccionamos proyecto nuevo.
Luego llenaremos los datos que se nos piden. Por lo general  se pueden rellenar con los nombres que uno quiera, pero conveniencia se debe de usar uno de tal modo que no sea tan largo. en este caso y para mi proyecto le puse así:
Por conveniencia se debe de incluir "com" en esta caso el nombre final de mi proyecto es "com.mdev.calculadoraderesistencias".
La siguiente ventana nos muestra el tipo de dispositivo y la versión de android para la cual queremos crear nuestro proyecto, Para este solo marcaremos la opción de "teléfono y tableta" y en Minimum SDK podemos escoger cualquiera, pero solo para esta aplicación. Entre mas "baja" sea la API, en mayor numero de dispositivos funcionara, pero habrá casos en los que alguna característica no este disponible en la versión que seleccionemos, por lo que tendremos que elegir una mas alta. Para este caso podemos elegir cualquiera, ya que no emplearemos funciones complejas. En mi caso opté por la API 7, porque en el teléfono que tengo actualmente, está instalado Android 2.1.
Ademas hay que resaltar que si la aplicación corre en Android 2.1, correrá en 3.0, 4.4, 5.0, etc. entonces siempre en la medida de lo posible, hay que elegir la versión mas baja, así garantizaremos que funcionara en otros dispositivos con una versión superior, 
En este caso uso el API 7 por mi teléfono.
Después se nos preguntara si queremos agregar una "actividad", para está aplicación usaremos "Empty Activity".

Finalmente nos preguntara por los detalles para la "Activity" que queremos crear, no modifivcaremos nada y daremos clic en el botón "Finish".


Paso 2. La interfaz.

Para editar el archivo XML que controla hay que buscarlo en el proyecto. Para eso iremos al apartdo "Project" que se encuentra en el extremo derecho. Haremos clic y se abrirá un panel, navegaremos: app>res>layout>activity_main.xml. Hacemos doble clic en al archivo xml y se abrira el diseñador mostrandonos la interfaz actual.


Para la interfaz debemos de agregar los siguientes controles:
  1. Textview
  2. Spinner.
  3. Botón


En el modo texto pegamos lo siguiente:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.mdev.calculadoraderesistencias.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Color 1:"
        android:id="@+id/TV1"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />
    <Spinner
        android:id="@+id/SpnColor1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/TV1"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:spinnerMode="dialog" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Color 2"
        android:id="@+id/textView"
        android:layout_below="@+id/SpnColor1"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true" />

    <Spinner
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/SpnColor2"
        android:spinnerMode="dialog"
        android:layout_below="@+id/textView"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="false"
        android:layout_alignParentStart="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Color 3"
        android:id="@+id/textView2"
        android:layout_below="@+id/SpnColor2"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentEnd="false" />

    <Spinner
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/SpnColor3"
        android:spinnerMode="dialog"
        android:layout_below="@+id/textView2"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Color 4"
        android:id="@+id/textView3"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@+id/SpnColor3" />

    <Spinner
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/SpnColor4"
        android:layout_below="@+id/textView3"
        android:spinnerMode="dialog"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Calcular"
        android:id="@+id/BtnCalculateIt"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignRight="@+id/SpnColor4"
        android:layout_alignEnd="@+id/SpnColor4" />

</RelativeLayout>

Tras implementarlo, la aplicacion debera lucir similar a la siguiente:



Por ahora es todo para no hacer tan largo este post, en la siguiente vamos a terminar de implementar toda la interfaz
Los leo luego.

Vamos a programar #3 - Estructura básica de una página HTML

Hola de nuevo a todos, el día de hoy voy a resolver dudas que les surgieron a muchos cuando hicimos la calculadora de resistencias. Es cierto que mi intención no era enseñarte a programar en HTML, pero muchos me preguntaron por MP como es que funcionaba el código completo, por que las etiquetas X iban antes que las Y, etc.
Por eso el día de hoy les voy a mostrar solo un poquito cómo es que esta estructurado el HTML.
Es posible crear una página web muy sencilla en pocos pasos ya que todas tienen una estructura básica que seguir.
Todas las paginas utilizan "etiquetas" y las mas importantes son:

<HTML> <HEAD> <TITLE> <BODY>


Estas se organizan de forma jerárquica, en donde la mas alta es <HTML>. Una pagina debe de empezar con esta etiqueta y terminar con su respectivo cierre </HTML> La siguiente etiqueta es: <HEAD>, Esta etiqueta es el encabezado de la pagina, aquí se puede incluir las características de la pagina, añadir los estilos CSS, el título y funciones de javascript, vbscript, etc. Ademas dentro de esta etiqueta se debe de incluir la etiqueta <TITLE>, esta sirve para cambiar el titulo que se muestra en la barra de titulo del navegador o como titulo de la pestaña. Hay que recordar que todas las etiquetas deben de llevar su respectivo cierre, en este caso como usamos la etiqueta <TITLE>, debemos de escribir el texto que queremos usar y de inmediato poner el cierre </TITLE>. Una vez que agregamos todo lo que necesitamos en el encabezado, también cerraremos el encabezado con </HEAD>.
Después viene la parte del cuerpo, en esta parte se incluirán los elementos que se mostraran al usuario; es decir: Texto, imágenes, vídeos, formularios, etc. La etiqueta <BODY> se usa para indicar que ahí es el comienzo del cuerpo. Dentro de esta etiqueta se pueden poner la mayoría de los elementos que componen a una pagina web: imágenes <IMG></IMG>, formularios <FORM></FORM>, vinculos <A></A>, etc. finalmente se debe de ponder la etiqueta de cierre </BODY> y generalmente no hay nada que agragar   despues del cierre del cuerpo asi que tambien hay que cerrar la etiqueta principal </HTML> y con esto se entiende que la pagina ha llegado a su fin.
Algo que es importante resaltar es que el HTML no reconoce minúsculas de mayúsculas, así que es valido; por ejemplo; poner <BODY> que <Body> o <body> y tampoco se toma en cuenta los espacios por lo que se pude poner:

<TITLE>Hola mundo</TITLE>

o

<title>
Hola mundo
</title>

En ambos casos le estamos indicando que el titulo de la pagina es "Hola mundo", solo que en el primer ejemplo todo está escrito en la misma linea. Por recomendación deberías de usar el primer ejemplo, este ahorra datos porque en el no se incluyen los saltos de línea.
Y ya para terminar un ejemplo del programa mas básico de todos.

<HTML>
<HEAD>
<TITLE>Hola Mundo</TITLE>
</HEAD>
<BODY>
Esta cuenta como una pagina funcional que muestra hola mundo.
</BODY>
</HTMl>


La única función del código anterior es mostrar "Esta cuenta como una pagina funcional que muestra hola mundo.", para probarlo en un navegador puedes escribir todo el código anterior en un bloc de notas y guardarlo en archivo con extensión html ("Hello.html" es un nombre por ejemplo). Después lo abres en un navegador de Internet y el resultado deberá de ser similar al siguiente:
Y con esto ya sabrás como es que funciona de forma básica el HTML. ya después veremos cuestiones un poco mas avanzadas. Por lo mientras puedes ver como funciona el código que hicimos antes y comparar con lo que acabas de leer.

Los leo luego.