Tiempo fuera.


Hola a todos, el día de hoy solo escribo para avisar que hoy no habrá post y para desearles a todos un feliz año, nunca pensé que todos los artículos que escribí iban a ser bien aceptados, por ahora solo queda agradecer a todos por las vistas al blog y ademas, invitarlos a que continúen dándose una vuelta por acá.

La semana que viene continuaremos con Clock View (por ahora estoy corto de hardware y hay un trecho muy largo desde China).

Nuevamente les agradezco y nos vemos el año que viene.

Att XWork.

P.D: Sean Felices, Siempre hay un motivo para serlo.

Vamos a programar #26 - Inútil Apps #2 - Clock View

Hola de nuevo a todos, el día de hoy vamos a continuar con el código de Clock View, la vez anterior solo implementamos las parte mas "criticas" de el, y  ya podría considerarse cómo un reloj funcional, pero vamos a agregar unas cuantas funciones para hacerlo más funcional.

Para empezar echemos un vistazo al nuevo código.
//Clockview 1.0
#include <DS1302.h>
#include "LedControl.h"
//Constantes para los pines usados en la matriz
const int MaxDIn = 12;
const int MaxCS = 11;
const int MaxCLK = 10;
int MaxNDevices = 3;
bool IsConnected = false;
//Constantes para los pines usados en el reloj
const int kCePin = 7;  // RST
const int kIoPin = 8;  // Dat
const int kSclkPin = 9;  // Serial Clock
//Inicializacion de la matriz
//DIN,CLK,CS
LedControl lc = LedControl(MaxDIn, MaxCLK, MaxCS, MaxNDevices);
//Inicializacion del reloj
DS1302 rtc(kCePin, kIoPin, kSclkPin);
//Algunas variables
char Texto[24];
int MatrixB = 4;
bool H24 = true;
bool HalfSecond = true;
bool IsScreenEnable = true;
bool ShowSeconds = true;
//Matriz con los "Numeros"
const unsigned char Numbers[] = {
 B111110, B100010, B111110, //0
 B100100, B111110, B100000, //1
 B111010, B101010, B101110, //2
 B100010, B101010, B111110, //3
 B001110, B001000, B111110, //4
 B101110, B101010, B111010, //5
 B111110, B101010, B111010, //6
 B000010, B000010, B111110, //7
 B111110, B101010, B111110, //8
 B001110, B001010, B111110, //9
 B111110, B001010, B001110, //A
 B111110, B001100, B111110, //M
 B111110, B001010, B001110, //P
 B111110, B001000, B111110, //H
 B111110 ,B011010, B101110, //R
};
//Definimos simbolos
const unsigned char Symbols[] = {
 B0100010, B0010100, B0001000, B1111111, B0101010, B0010100, //BT 0
 B0011110, B0100011, B0101101, B0100101, B0100101, B0011110, //Clock Adjust 1
 
};
//Imprimir simbolos
void PrintSymbol(byte Index,byte SymDevice){
 for (int Y = 1; Y < 7; Y++){
  lc.setRow(SymDevice, Y, Symbols[Index * 5 + Y - 1]);
 }
}
//Funcion para escribir los numero en la matriz.
void PrintNumber(byte NumberOne, byte NumberTwo, byte Device){
 for (int X = 1; X < 8; X++){
  if (X < 4){
   lc.setRow(Device, X, Numbers[NumberOne * 3 + X - 1]);
  }
  if (X == 4){
   lc.setRow(Device, 4, 0);
  }
  if (X > 4){
   lc.setRow(Device, X, Numbers[NumberTwo * 3 + X - 5]);
  }
 }
}
//Imprimir el tiempo en las matrices y en el monitor serie
void printTime(){
 Time t = rtc.time();
 const String day = dayAsString(t.day);
 char buf[50];
 snprintf(buf, sizeof(buf), "%s %04d-%02d-%02d %02d:%02d:%02d",
  day.c_str(), t.yr, t.mon, t.date, t.hr, t.min, t.sec);
 PrintNumber(AdjustTime(t.hr, H24) / 10,AdjustTime(t.hr, H24) % 10, 0);
 PrintNumber(t.min / 10, t.min % 10, 1);
 if (ShowSeconds){
  PrintNumber(t.sec / 10, t.sec % 10, 2); 
 }else{
  if(H24)
   PrintNumber(13, 14, 2);
  if(!H24 && (t.hr-12) < 13)
   PrintNumber(10, 11, 2);
  if(!H24 && (t.hr-12) > 12)
   PrintNumber(10, 12, 2);
 }
 //PrintSymbol(0, 2);
 if (HalfSecond == true){
  digitalWrite(13, HIGH);
  HalfSecond = false;
 }else{
  digitalWrite(13, LOW);
  HalfSecond = true;
 }
 Serial.println(buf);
}
//Ajustar la hora
int AdjustTime(int Hour, bool In24Hformat){
 if(In24Hformat == true){
  return Hour;
 }
 else{
  Hour = Hour - 12;
  return Hour;
 }
}
void CheckPetition(char DATA[]){
 int i = 0;
 int j = 0;
 String Texto(DATA);
 //>SETH2016121221001007
 //Bright = Texto.substring(4).toInt();
 //MatrixB
 if (Texto.startsWith(">DISS")){
  ShowSeconds = !ShowSeconds;
  Serial.print("No/Se muestran los segundos");
  for (int CurrentDevice = 0; CurrentDevice < MaxNDevices; CurrentDevice++){
   lc.shutdown(CurrentDevice, !IsScreenEnable);
  }
  for (j = 0; j < 11; j++) {
   DATA[j] = 0;
  }
  i = 0;
 }
 if (Texto.startsWith(">SCRA")){
  IsScreenEnable = !IsScreenEnable;
  Serial.print("Las matrices se apagaron/encendieron");
  for (int CurrentDevice = 0; CurrentDevice < MaxNDevices; CurrentDevice++){
   lc.shutdown(CurrentDevice, !IsScreenEnable);
  }
  for (j = 0; j < 11; j++) {
   DATA[j] = 0;
  }
  i = 0;
 } 
 if (Texto.startsWith(">SETF")){
  H24 = !H24;
  Serial.print("EL reloj cambio de formato 12H-24H");
  for (j = 0; j < 11; j++) {
   DATA[j] = 0;
  }
  i = 0;
 } 
 if (Texto.startsWith(">SETH")){
  AdjustTime(Texto.substring(9, 5).toInt(),Texto.substring(11,9).toInt(),Texto.substring(13, 11).toInt(),
  Texto.substring(15, 13).toInt(), Texto.substring(17, 15).toInt(), Texto.substring(19, 17).toInt(),
  Texto.substring(21, 19).toInt());
  Serial.print("La hora se ajusto.");
  for (j = 0; j < 11; j++) {
   DATA[j] = 0;
  }
  i = 0;
 }
 if (Texto.startsWith(">SETB")){
  MatrixB = Texto.substring(5).toInt();
  Serial.print("El brillo se cambio");
  for (int CurrentDevice = 0; CurrentDevice < MaxNDevices; CurrentDevice++){
   lc.setIntensity(CurrentDevice, MatrixB);
  }
  for (j = 0; j < 11; j++) {
   DATA[j] = 0;
  }
  i = 0;
 }
 else {
  for (j = 0; j < 11; j++) {
   DATA[j] = 0;
  }
  i = 0;
 }
}
//Convertir los dias
String dayAsString(const Time::Day day){
 switch (day){
  case Time::kSunday: return "DOM";
  case Time::kMonday: return "LUN";
  case Time::kTuesday: return "MAR";
  case Time::kWednesday: return "MIE";
  case Time::kThursday: return "JUE";
  case Time::kFriday: return "VIE";
  case Time::kSaturday: return "SAB";
 }
 return "(unknown day)";
}
//Ajustar la hora
void AdjustTime(int Year,int Month, int Day, int Hour, int Minute, int Second, int DayOfWeek){
 Time::Day CurrentDay;
 switch (DayOfWeek){
  case 1:
   CurrentDay = Time::kSunday;
   break;
  case 2:
   CurrentDay = Time::kMonday;
   break;
  case 3:
   CurrentDay = Time::kTuesday;
   break;
  case 4:
   CurrentDay = Time::kWednesday;
   break;
  case 5:
   CurrentDay = Time::kThursday;
   break;
  case 6:
   CurrentDay = Time::kFriday;
   break;
  case 7:
   CurrentDay = Time::kSaturday;
   break;
 }
 //Esta parte se usa para actualizar la hora.
 rtc.writeProtect(false);
 rtc.halt(false);
 Time t(Year, Month, Day, Hour, Minute, Second, CurrentDay);
 rtc.time(t); 
}
//Setup
void setup(){
 for (int CurrentDevice = 0; CurrentDevice < MaxNDevices; CurrentDevice++){
  lc.shutdown(CurrentDevice, false);
  lc.setIntensity(CurrentDevice, MatrixB);
  lc.clearDisplay(CurrentDevice);
 }
 pinMode(13, OUTPUT);
 Serial.begin(9600);
}
//Loop
void loop(){
 int i = 0;
 printTime();
 delay(500);
 if (Serial.available()) {
  while (Serial.available() > 0) {
   Texto[i] = Serial.read();
   i++;
  }
  Texto[i] = '\0';
 }
 CheckPetition(Texto);
}

Analicemos un poco el código, para empezar, he creado algunas variables que sirven para controlar algunos de los aspectos del reloj. Podemos elegir entre mostrar la hora en formato de 12 o de 24 horas, decidir si se quiere mostrar o no los segundos y ademas, ahora podemos apagar las matrices, útil para cuando llega la hora de dormir.
En la matriz que almacena los datos de los números, ademas de agregar las letras "a", "m" y "p", se agregaron las letras "h" y "r", estas se usan cuando la hora esta en formato de 24 horas y ademas la opción de "no mostrar los segundos" está activa
La opción de 24 horas y la opción de "No mostrar los segundos" activos.

La opción de 24 horas no está activa y la opción de no mostrar los segundos tampoco.
Para poder mostrar algunas opciones, se han agregado algunos símbolos, pero por ahora no se usan, decidí ponerlos en una matriz a parte para que se puedan modificar sin alterar los numero, ademas estos serán un poco mas "grandes" que los números, lo que hace necesario crear una función aparte para imprimirlos en las matrices.

//Imprimir simbolos
void PrintSymbol(byte Index,byte SymDevice){
 for (int Y = 1; Y < 7; Y++){
  lc.setRow(SymDevice, Y, Symbols[Index * 5 + Y - 1]);
 }
}

Cómo notarás, la mayor parte del código es similar a la función "PrintNumber", solo que en este caso, lee completamente de 5 en 5 y no de 3 en tres como en la función para mostrar los números. Recibe cómo parámetros, el indice en donde se encuentra la primera fila a desplegar y ademas, también recibe en que numero de matriz se va a mostrar el resultado.

La función "PrintTime" se ha modificado para que la hora se muestre de acuerdo a las opciones que el usuario haya pedido, en los casos anteriores, para mostrar la información de un formato u otro, solo basta con que se le mande un comando para que se hagan los cambios pertinentes. Toda la parte de los comandos lo veremos un poquito más adelante.
//Imprimir el tiempo en las matrices y en el monitor serie
void printTime(){
 Time t = rtc.time();
 const String day = dayAsString(t.day);
 char buf[50];
 snprintf(buf, sizeof(buf), "%s %04d-%02d-%02d %02d:%02d:%02d",
  day.c_str(), t.yr, t.mon, t.date, t.hr, t.min, t.sec);
 PrintNumber(AdjustTime(t.hr, H24) / 10,AdjustTime(t.hr, H24) % 10, 0);
 PrintNumber(t.min / 10, t.min % 10, 1);
 if (ShowSeconds){
  PrintNumber(t.sec / 10, t.sec % 10, 2); 
 }else{
  if(H24)
   PrintNumber(13, 14, 2);
  if(!H24 && (t.hr-12) < 13)
   PrintNumber(10, 11, 2);
  if(!H24 && (t.hr-12) > 12)
   PrintNumber(10, 12, 2);
 }
 //PrintSymbol(0, 2);
 if (HalfSecond == true){
  digitalWrite(13, HIGH);
  HalfSecond = false;
 }else{
  digitalWrite(13, LOW);
  HalfSecond = true;
 }
 Serial.println(buf);
}
Los cambios que se hicieron, son prácticamente los que indican el despliegue de la información, los nuevos "if" solo controlan que es lo que se debe de mostrar.

Cómo mencione en hace un poco, se han añadido algunos comandos que se usaran para modificar las propiedades del reloj sin la necesidad de re-compilar el código cada vez, actualmente los comandos disponibles son los siguientes:
  1. >DISS (Display Sesconds) - Intercambia entre mostrar o no los segundos.
  2. >SCRA (Screen Active) - Apaga o enciende las matrices.
  3. >SETF (Set Format) - Intercambia entre el formato de 12/24 horas.
  4. >SETHYYYYMMDDHHmmSSdd (Set Hour) - Cambia la hora del reloj, donde YYYY es el año, MM son los meses, DD son los días, HH son las horas en formato de 24 horas, mm son los minutos, SS los segundos y dd es el dia de la semana del 1 al 7 en donde el día 1 es domingo.
  5. >SETBxx (Set Brightness) - Cambia el brillo de las matrices, en donde xx es un valor que este entre el rango de 1 a 15, donde 1 es lo menos brillantes y 15 es el máximo.
Los primeros comandos al ser booleanos, solo pueden ser "cierto" o "falso", por lo que cuando se envía el comando, lo único que se hace es invertir su valor, después se en el monitor serial, se despliega información indicando que ese parámetro se ha cambiado.
Algo importante por mencionar es que todos los comados se enviaran por la interfaz serial, por lo que se puede usar el monitor serial del IDE de arduino u otros métodos que veremos un poco después.


Cuando se quiere ajustar la hora, podemos usar el comando ">SETH", si por ejemplo queremos establecer la siguiente hora:20:30:45 Miércoles 28 de diciembre del 2016, entonces el comando deberá de tener el siguiente aspecto. ">SETH2016122820304504". Si la hora que se quiere ingresar tiene un formato incorrecto, el reloj mostrará las 00:00:00 del 1/1/2000 domingo. si eso llegará a pasar, se corrige cuando se ingresa un fecha válida.

Para el caso del brillo, cualquier valor fuera de los limites validos (1-15) hará que las matrices tengan el brillo más bajo.
Por ultimo, se ha agregado una función que prende y apaga el LED que está conectado al PIN 13, esto con la finalidad de que se use como indicador, la mayoría de los relojes digitales, tiene un par de puntos entre cada par de dígitos que prenden y apagan cada segundo, si se quiere mostrar algo similar, bastara con conectar LED al pin 13.
Este reloj está lejos de ser terminado por completo, pero por ahora es todo, si en el transcurso de la semana llegan los otros componentes que quiero agregar, la semana que viene, veremos cómo agregar un medio para que todas las configuraciones que se cambien, queden guardadas y así si por alguna razón se quiere desconectar todo el proyecto, no se tendrán que hacer cambios a los ajustes cada vez.
Ademas de que agregaremos una solución "móvil" para que el reloj se pueda controlar a distancia.
Y bien, por ahora es todo, el código completo actualizado, cómo siempre, lo puedes descargar de mi dropbox.
Por ahora es todo. Los leo luego.

Vamos a programar #25 - Inútil Apps #2 - Clock View

Hola a todos, el día de hoy vamos a realizar otro pequeño proyecto con arduino, es muy simple en realidad, pero creo que sirve para reforzar lo básico, además, si cómo yo, eres fanático de los LED's de seguro este proyecto te encantará.


Cuando hice el proyecto para mostrar la información de ITunes en las matrices LED, usaba una librería que se encargaba de desplegar las letras, los números y además, también proporcionaba algunos efectos básicos: scroll a la derecha, scroll a la izquierda, etc.

Para está ocasión, vamos a hacer algo un poco más sencillo, porque me parece un desperdicio de recursos utilizar toda la librería que use en el proyecto antes dicho para mostrar la hora.

Para los componentes de hoy vamos a requerir:
  1. Un arduino Uno/Mega/Nano.
  2. Un protoboard.
  3. Dos o más matrices led con driver Max7219.
  4. Modulo de reloj en tiempo real con DS1302.
  5. Liberia "LedControl.h"
  6. Liberia "DS1302.h"
  7. Algunos cables jumper para hacer las conexiones.
  8. Un par de manos (o en su defecto una).

Las conexiones.

Lo primero que debemos de hacer las conexiones entre los componentes. Yo en mi caso voy a usar 3 matrices LED, justo ahora compre más por internet, pero como van a tardar mas de 15 días en llegar, solo usaré las que tengo disponibles.
Las conexiones que voy a poner en este post lo más seguro es que cambien un poco en futuras actualizaciones del proyecto, por lo que si quieres usar pines diferentes en el arduino, eres libre de hacerlo.


Para empezar, vamos a conectar las matrices de izquierda a derecha, es decir si miramos de frente, la primer matriz estará al lado izquierdo, y la ultima estará al lado derecho.
Las conexiones de la matriz que se conecta al arduino irán de las siguiente manera:
  1. VCC. Se conecta a +5 voltios.
  2. GND. Se conecta a tierra.
  3. DIN. Lo conectaremos al pin 12 del arduino.
  4. CS. Lo conectaremos al pin 11 del arduino.
  5. CLK. Lo conectaremos al pin 10 del arduino.
Para la siguiente matriz solo conectaremos las salidas iguales con iguales, es decir:
  1. VCC. Se conecta a VCC en la entrada de la siguiente matriz.
  2. GND. Se conecta a tierra.
  3. DIN. Lo conectaremos a DIN en la entrada de la siguiente matriz.
  4. CS. Lo conectaremos a CS en la entrada de la siguiente matriz.
  5. CLK. Lo conectaremos CLK en la entrada de la siguiente matriz.
Las otras se conectaran de la misma forma, hay que recordar que, cómo máximo se pueden usar ocho por cada objeto "LedControl".

Para el modulo de reloj, las conexiones se harán de la siguiente manera:
  1. VCC. Se conecta a +5 voltios.
  2. GND. Se conecta a tierra.
  3. CLK (Serial clock). Lo conectaremos al pin 9 del arduino.
  4. DAT (IO). Lo conectaremos al pin 8 del arduino.
  5. RST (Chip enable). Lo conectaremos al pin 7 del arduino.
Una vez que se realizaron las conexiones anteriores, la parte del hardware está lista, ahora vamos a ver el código que hace funcionar las cosas..

El código en C.

Primero vamos a ver el código fuente completo y después explicare cada una de las partes que lo compone.

//Clockview 1.0
#include <DS1302.h>
#include "LedControl.h"
//Constantes para los pines usados en la matriz
const int MaxDIn = 12;
const int MaxCS = 11;
const int MaxCLK = 10;
int MaxNDevices = 3;
//Constantes para los pines usados en el reloj
const int kCePin = 7;  // RST
const int kIoPin = 8;  // Dat
const int kSclkPin = 9;  // Serial Clock
//Inicializacion de la matriz
//DIN,CLK,CS
LedControl lc = LedControl(MaxDIn, MaxCLK, MaxCS, MaxNDevices);
//Inicializacion del reloj
DS1302 rtc(kCePin, kIoPin, kSclkPin);
//Algunas variables
int MatrixB = 1;
//Matriz con los "Numeros"
const unsigned char Numbers[] = {
 B11111, B10001, B11111, //0
 B10010, B11111, B10000, //1
 B11101, B10101, B10111, //2
 B10001, B10101, B11111, //3
 B00111, B00100, B11111, //4
 B10111, B10101, B11101, //5
 B11111, B10101, B11101, //6
 B00001, B00001, B11111, //7
 B11111, B10101, B11111, //8
 B00111, B00101, B11111, //9
 B11111, B00101, B00111, //A
 B11111, B01100, B11111, //M
 B11111, B00101, B00111, //P
};
//Funcion para escribir los numero en la matriz.
void PrintNumber(byte NumberOne, byte NumberTwo, byte Device){
 for (int X = 1; X < 8; X++){
  if (X < 4){
   lc.setRow(Device, X, Numbers[NumberOne * 3 + X - 1]);
  }
  if (X == 4){
   lc.setRow(Device, 4, 0);
  }
  if (X > 4){
   lc.setRow(Device, X, Numbers[NumberTwo * 3 + X - 5]);
  }
 }
}
//Imprimir el tiempo en las matrices y en el monitor serie
void printTime(){
 Time t = rtc.time();
 char buf[10];
 snprintf(buf, sizeof(buf), "%02d:%02d:%02d", t.hr, t.min, t.sec);
 PrintNumber(t.hr / 10, t.hr % 10, 0);
 PrintNumber(t.min / 10, t.min % 10, 1);
 PrintNumber(t.sec / 10, t.sec % 10, 2);
 Serial.println(buf);
}
//Convertir los dias
String dayAsString(const Time::Day day) {
 switch (day){
  case Time::kSunday: return "Sunday";
  case Time::kMonday: return "Monday";
  case Time::kTuesday: return "Tuesday";
  case Time::kWednesday: return "Wednesday";
  case Time::kThursday: return "Thursday";
  case Time::kFriday: return "Friday";
  case Time::kSaturday: return "Saturday";
 }
 return "(unknown day)";
}
//Setup
void setup(){
 for (int CurrentDevice = 0; CurrentDevice < MaxNDevices; CurrentDevice++)
 {
  lc.shutdown(CurrentDevice, false);
  lc.setIntensity(CurrentDevice, MatrixB);
  lc.clearDisplay(CurrentDevice);
 }
 Serial.begin(9600);
 //Esta parte se usa para actualizar la hora.
 //rtc.writeProtect(false);
 //rtc.halt(false);
 //Time t(2016, 12, 7, 23, 57, 00, Time::kWednesday);
 //rtc.time(t);
}
//Loop
void loop() {
 printTime();
 delay(1000);
}

Lo primero que hacemos es incluir las librerías "LedControl.h" y "DS1302.h", creamos constantes para los pines que usara la primer matriz, para el reloj y después creamos los objetos "lc" y "rtc" para la matriz y para el reloj respectivamente.
El objeto lc se le asigna un objeto "LedControl"; el cual recibe cuatro parámetros: los números de pines a usar, el primer parámetro debe de ser el pin que se usará para conectará a "DIN" en la primer matriz, el segundo conectará a "CLK", el tercero conectará a "CS" y finalmente el cuarto, sirve para indicar cuantas matrices se usarán con ese objeto.
Para el reloj, crearemos un objeto "DS1302" y para "inicializarlo", le pasaremos como parámetros, los pines que se usarán. El primer parámetro es el pin que conecta a "RST", el segundo parámetro es el pin que conecta a "DAT" y finalmente el tercero es el pin que conecta "CLK".
Una vez que tenemos lo objetos listos, creamos unas variables, la primera es "MatrixB" que las usaremos para almacenar el brillo que tendrán las matrices, por ahora la única forma de cambiarlo, es editando el valor que le asignamos, pero un poco mas adelante, seremos capaces de cambiar el brillo de forma externa.

Después viene una matriz que contiene los datos que vamos a usar para crear los "números" que después veremos en las matrices, pero antes de eso hay que saber como es que se controlan los LED´s de las matrices.

LedControl y Matrices LED.

Para este proyecto estamos usando la librería LEdControl.h, está nos ofrece varias maneras de controlar los LED's de la matriz, las funciones disponibles son: setLed, setRow y setColum.
La función setLed tiene una forma similar a la siguiente:
void setLed(int addr, int row, int col, boolean state);
El primer parámetro indica en que dirección se encuentra la matriz que queremos usar (de cero a siete), es decir, si tenemos 4 matrices conectadas en "serie", y queremos prender el primer led de la tercera matriz, tendremos que poner un 3 en el primer parametro.
El segundo parámetro indica en que columna está el LED que queremos encender (mejor dicho controlar).
El tercero indica en que columna está el LED que queremos controlar.
El último parametro indica el estado en que vamos a poner el LED, cuando se pone "True", el LED se enciende y cuando se pone "False", el LED se apaga.
void setRow(int addr, int row, byte value);
Está funcion recibe 3 parametros, el primero es el mismo que el de la funcion anterior, el segundo indica que fila es la que se va controlar, despues, el ultimo parametro nos indica que LED´s son los que se van a prender.Para este último, hay una forma relativamente sencilla de cómo establecer los LED´s que deben de prender, tomando por ejemplo el numero 2 y el número 5 que queremos mostrar,
Como lucirá el numero 25
Antes que nada, es importante tomar en cuenta donde se ubica el origen, en mi caso (y supongo que para los que tienen un kit similar al que uso yo), el origen se encuentra en la esquina inferior izquierda, viendo la matriz como en la primer imagen, por lo que las columnas están en el lado izquierdo de la matriz y las filas en el lado inferior, entonces los LED's se controlaran de abajo hacia arriba y de izquierda a derecha si usamos la funcion "setRow". una vez que ya has determinado donde empieza la matriz, se deben de hacer los cambios pertinentes.
Para asignar el ultimo parámetro, miremos la imagen anterior, en ella se puede ver claramente que cuando un led está encendido tiene el valor de 1 y cuando está apagado es 0 y cada digito esta compuesto por 3 llamadas a la funcion "setRow", para hacer de forma rapida el dos, podriamos llamar a la función "setRow" y como ultimo parámetro el  valor "B00011101" en la primera llamada, en la segunda, el ultimo parametro "B00010101" y en la tercera llamada a la función usar cómo ultimo valor "B00010111".
Cómo los 0´s que están al lado izquierdo no son significativos, podemos omitirlos y simplemente poner "B11101" para la primer llamada a la funcion "setRow", si te fijas en el código, veras que hay algunos valores que conservan los ceros a la izquierda, esto solo es con fines "estéticos" y para que todos los valores estén alineados.

Mostrando dígitos en las matrices.

Para mostrar los dígitos en las matrices, he creado una función que se encarga de eso. La función "PrintNumber", recibe tres parámetros, el primero es el primer numero que queremos mostrar (al lado izquierdo), el segundo es el numero que queremos mostrar y finalmente,se debe de indicar en que numero de matriz vamos a despegarlos.
Todos los datos de los numero están pensados de tal forma que se puedan poner dos dígitos por cada matriz, entonces cada una nos da un rango de 00 a 99, pero para mostrar algo cómo la hora. los segundos o los minutos funciona. Para que la función "funcione", debemos de alinear los datos que componen a cada numero, si te fijas bien leemos de tres en tres y debido al acomodo que tiene la matriz "Numbers", cuando le pedimos el número 0, leerá los valores Numbers[0], Numbers[1] y Numbers[2].
La formula que dexcribe cómo se obtienen los valores del primer dígito es la siguiente:
Indice = NumeroDado * 3 +ValorDelIterador - 1
 Y para el segundo:
Indice = NumeroDado * 3 +ValorDelIterador - 5
Y con eso, podemos obtener los valores del 0 al 9 y a partir de ahi, componer cualquier numero. Por eso es importante el orden en que está cada parte de la matriz (arreglo desde este momento para evitar confusiones.).

Completando el resto del código.

La función "PrintTime" se encargará de obtener la hora y de llamar a la función "PrintNumber" para desplegar la información en la matriz LED, debido a que hay que pasar los dígitos por separado, no es posible usar "12" cómo parámetro para la función "PrintNumber", entonces lo que hacemos es dividir, primero entre 10 y para el segundo dígito, solo tomamos el residuo de la división, por lo que si tenemos el numero 21, al hacer la división entre 10, obtenemos 2; este lo pasamos cómo primer parámetro y luego obtenemos el residuo que es 1 y lo asignamos como valor del segundo parámetro, ademas de que la hora la mostramos en el monitor serie, eso es opcional pero para fines de debug funciona.
Después viene la parte de las funciones propias del arduino, "setup()" y "loop()", En la parte de setup, es donde vamos a inicializar los componentes (prácticamente aquí todo lo que se necesita ejecutar una sola vez), ademas hay unas lineas que están comentadas, estas solo sirven para ajustar la hora del reloj, si es la primera vez que vas a usar el modulo de reloj, o si cambias la batería o hiciste cualquier cosa y quieres establecer la fecha y hora, esas lineas las debes de des-comentar y editar solo la que contiene la fecha y hora, cuando hayas cargado el proyecto al arduino, si modificas alguna parte del código y ya no quieres actualizar la hora, debes de comentar de nuevo esas lineas para evitar que la hora se cambie de nueva cuenta.
Finalmente en "loop()" ponemos el código que se va a ejecutar de manera cíclica, en este caso, solo es la función que muestra la hora y un retraso de 1000ms que se usa para que la hora solo se actualice cada segundo.

Y bien, por ahora es todo, pero en el próximo post vamos a agregar un poco mas de hardware para que el uso sea mas sencillo. Cómo siempre, el código lo puedes bajar de mi dropbox para que lo revises.
Por ahora es todo. Los leo luego.

Vamos a programar #24 - Haciendo un instalador en NSIS #3 - Datos útiles.

EL día de hoy vamos a continuar con la creación de instalador en NSIS. En post anteriores, ya hemos visto alguna cosas útiles para incluirlas en el instalador. Para continuar  y hacer un instalador lo más presentable posible, veremos algunas cosas más que ayudaran a crear un buen instalador.


Tip #2 - Apéndice de todos los valores posibles para el des-instalador.

En el post anterior vimos que hay valores que agregandolos al registro de windows, mostraran información adicional, incluirlas o no ya es cuestión de cada quien, pero yo prefiero agregar al menos las mas primordiales, esto puede ayudar a que el usuario en determinado momento opte o no por quitar nuestra aplicación.

En la siguiente tabla se muestran todas las entradas posibles y que función cumplen, además de se muestra que tipo de entrada es.

Propiedad Informacion Tipo de entrada
AuthorizedCDFPrefix La direccion URL para actualizar la aplicacion REG_SZ
Comments Comentarios adicionales para mostrarse REG_SZ
DisplayIconEl icono que se mostrara para representar a la aplicacion. REG_SZ
DisplayName El nombre del programa a mostrarse REG_SZ
DisplayVersion La version instalada del programa REG_SZ
EstimatedSize Tamaño estimado del programa  (en KB) DWORD
HelpLink Direccion URL para el soporte REG_SZ
HelpTelephone Numero telefonico para ofrecer soporte REG_SZ
InstallDate Fecha de instalacion REG_SZ
InstallLocation Ubicacion del programa instalado REG_SZ
InstallSourceUbicacion de la fuente de instalacion REG_SZ
Language Idioma del programa REG_SZ
ModifyPath La ruta del programa de re-instalacion, reparacion REG_SZ
NoModifyNO muestra la opcion de "Modificar". DWORD
NoRepairNO muestra la opcion de "Reparar"DWORD
Publisher Nombre del publicador REG_SZ
Readme La ubicacion del archivo "LEAME" REG_SZ
UninstallString La ruta del des-instalador REG_SZ
URLInfoAboutLa direccion URL con informacion del programa REG_SZ
URLUpdateInfo La direccion URL con informacion de las actualizaciones REG_SZ
Version La version del programa (X.xx.xx) REG_SZ
VersionMajor La version Major del programa (x.XX.xx) REG_SZ
VersionMinor La version Minor del programa (x.xx.XX) REG_SZ


Para agregar una entrada del tipo REG_SZ o DWORD usando NSIS usaremos las siguientes instrucciones:


WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "HelpLink" "http://xworkforall.blogspot.com/2016/07/el-video-correcto-2.html"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "NoModify" 1

La instrucción WriteRegStr es las que se usará cuando se quieran escribir valores REG_SZ (normalmente cadenas de texto) y la instrucción WriteRegDWORD, escribirá los valores DWORD (números por lo general).


Ahora supongamos que queremos agregar un comentario al instalador que usamos cómo ejemplo (antes de continuar, si no has visto el código fuente del instalador de VEncoder 2 sugiero que lo descargues y le eches un vistazo). Para agregar los comentarios, necesitamos escribir un valor en el registro, retomado el código anterior , solo hay que usar la función que escribe un valor REG_SZ y escribir un valor llamado "Comments" y su contenido será el texto que queremos mostrar.

WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "HelpLink" "http://xworkforall.blogspot.com/2016/07/el-video-correcto-2.html"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "NoModify" 1
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "Comments" "Eres libre se usarlo y modificarloa tu gusto, pero no reclamarlo cómo tuyo"

Tras re-compilar el instalador y ejecutarlo, al buscar la información en el registro lucirá cómo se ve en la siguiente imagen:

Tip #3 - Asociando una extensión a nuestro programa.

Otra parte de nuestro instalador, es la posibilidad de que ciertos tipos de archivo se abran con nuestro programa. NSIS ayuda a hacer esta tarea de forma sencilla.


Retomando el ejemplo que se incluye en la descarga, al compilarlo y ejecutarlo, verás que todos los archivos con extensión VEPX, cambian y en lugar de aparecer el icono de archivo desconocido, aparece uno como el de la imagen anterior. Si hacemos doble clic en el archivo, se abrirá un cuadro de dialogo preguntándonos que programa queremos usar para abrir ese tipo de archivos.
Normalmente, queremos que cuando el usuario haga clic en ellos, se abra nuestro programa y a partir de ahi, trabajaremos con este archivo.
Para poder abrir directamente el archivo con nuestro programa, debemos de crear entradas en el registro de Windows.

HKEY_CLASSES_ROOT
   MyProgram.exe
      shell
         open
            command
               (Default) = C:\MyDir\MyProgram.exe /a "%1"
         print
            command
               (Default) = C:\MyDir\MyProgram.exe /a /p "%1"
         printto
            command
               (Default) = C:\MyDir\MyProgram.exe /a /p "%1" "%2"

El código anterior muestra cómo se deben de crear las entradas en el registro de windows.
Para eso vamos a agregar el siguiente código en NSIS:

WriteRegStr HKCR ".VEPX" "" "VEncoder.VEPX"
WriteRegStr HKCR "VEncoder.VEPX" "" "Archivo de perfil para Vencoder"
WriteRegStr HKCR "Vencoder.VEPX\DefaultIcon" "" "$INSTDIR\VEPX.ico" 
WriteRegStr HKCR "VEncoder.VEPX\shell\Open\" "" "Cargar en VEncoder 2"
WriteRegStr HKCR "VEncoder.VEPX\shell\Open\command" "" "$INSTDIR\Vencoder 2.exe"


Lo que hace el código anterior es lo siguiente (hay que recordar que todas las claves se crean en HKey Classes Root, HKCR):

  1. Creamos la entrada a la extensión ".VEPX", y en el valor predeterminado, escribimos que toda la información referente a esta extensión, estará en la clave "VEncoder.VEPX".
  2. Luego creamos la clave "Vencoder.VEPX" y al valor por default le asignamos el texto que describe nuestro archivo, en este caso usamos "Archivo de perfil para VEncoder"
  3. Luego asignamos el icono que queremos usar para representar a nuestros archivos. Debemos de crear una clave llamada "DefaultIcon" y al valor por default, le asignamos la ruta en donde se encuentra nuestro icono, archivo dll o exe que lo contenga; si se usa un archivo dll o exe, hay que indicar el indice (wscript.exe 4 por ejemplo).
  4. Para agregar items en el menú contextual de Windows, dentro de la clave "VEncoder.VEPX" y en general para cualquier programa que hayamos hecho, hay quie agregar una clave llamada "Shell". Para que la opción de abrir con nuestro programa sea la primera, debemos de agregar una sub-clave llamada "Open" y al valor por default, agregar el texto que queremos mostrar en el menú contextual.
  5. Para que la entrada anterior funcione, debemos de asignar que "cosa" debe de pasar cuando hagamos clic en el. Para el caso  concreto de VEncoder, solo abriremos la aplicación, por lo que creamos una nueva sub-clave llamada "command" y en el valor debemos de asignar el programa junto con los parámetros para su ejecución, retomando la estructura anterior al código, ahí se muestran como se deben de pasar los argumentos y el nombre de archivo, sera representado por "%1". Si VEncoder aceptara parámetros (cosa que no es así por el momento), la instrucción debería de quedar similar a la siguiente: '$INSTDIR\Vencoder 2.exe "%1"', usaríamos primero las comillas simples y después las comillas dobles.
El resultado del código anterior se verá cómo este:


Para agregar entradas adicionales, solo debemos de repetir los últimos dos paso, solo cambiando el nombre de la clave:
WriteRegStr HKCR ".VEPX" "" "VEncoder.VEPX"
WriteRegStr HKCR "VEncoder.VEPX" "" "Archivo de perfil para Vencoder"
WriteRegStr HKCR "Vencoder.VEPX\DefaultIcon" "" "$INSTDIR\VEPX.ico" 
WriteRegStr HKCR "VEncoder.VEPX\shell\Open\" "" "Cargar en VEncoder 2"
WriteRegStr HKCR "VEncoder.VEPX\shell\Open\command" "" "$INSTDIR\Vencoder 2.exe"
WriteRegStr HKCR "VEncoder.VEPX\shell\Editar\" "" "Esta es una prueba"
WriteRegStr HKCR "VEncoder.VEPX\shell\Editar\command" "" "$INSTDIR\Vencoder 2.exe"

Y el resultado será como el siguiente:

Igual que en los post anteriores, el código competo lo puedes descargar de aqui.
Y bien, por ahora es todo, dudas o comentarios, puedes ponerlos en la parte de abajo.

Los leo luego.

Vamos a programar #23 - Haciendo un instalador en NSIS #2 - Datos utiles.

Hola de nuevo a todos, el día de hoy vamos a continuar con el tema de los instaladores, en el post anterior vimos lo básico de como escribir el código en NSIS. Hoy veremos unas cuantas cosas que nos ayudaran a hacer un instalador más detallado.

Tip #1 - El Desintalador.

Cuando escribimos el instalador, lo primero que nos debemos de preguntar, es si el usuario no va a cambiar de opinión, si en algun momento decide ya no usar la aplicación, querrá borrarla de su computadora. Y para esto, recurrirá al des-instalador. La manera más sencilla de hacerlo, es mediante el panel de control.
Si observamos bien, vemos que hay una entrada a nuestro des-instalador, pero no sabemos nada más.
Para poder identificar de manera más precisa cual es nuestro programa debemos de incluir información que se encargue de esa tarea.
Este es un ejeplo de un programa que muestra información que puede ser de ayuda para el usuario.
Entre los detalles más importantes que debemos de incluir, están los siguiente:
  • Nombre que identifique al des-instalador.
  • Ruta en la que se encuentra.
  • Nombre del publicador.
  • Icono.
  • Tamaño de la instalación.
  • Versión del programa.
  • Vinculo a la página del creador del programa.
  • Vinculo de ayuda del programa.
Para poder hacerlo, necesitamos agregar algunas entradas al registro. Windows tiene una seccion dedicada a los instaladores, y ademas a definido una serie de entradas por default. si incluimos la informacion de estás, windows la mostrará, si no lo hacemos, el instalador aparecerá cómo en la primer imagen; sin información.
Sabiendo lo anterior, en NSIS podemos escribir claves en el registro durante el proceso de instalación. Para agregar la información que requerimos podemos agregar código como el siguiente:

WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Vencoder" "DisplayName" "Vencoder 2.0"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "UninstallString" '"$INSTDIR\uninstall.exe"'
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "Publisher" "MDev 2015"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "DisplayIcon" "$INSTDIR\Main.ico"
${GetSize} "$INSTDIR" "/M=*.* /S=0K " $0 $1 $2
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "EstimatedSize" "$0"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "DisplayVersion" "2.1" 
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "URLInfoAbout" "http://xworkforall.blogspot.com"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "HelpLink" "http://xworkforall.blogspot.com/2016/07/el-video-correcto-2.html"
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "NoModify" 1
WriteRegDWORD HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\VEncoder" "NoRepair" 1


Las lineas de código scriben entradas en el registro de Windows la instruccion "WriteRegStr" escribe un valor de cadena (texto por lo general), la instruccion WriteRegDWORD, escribe un valor DWORD.
En orden de aparicion, las lineas del código anterior se usan para representar la siguiente información:

  1. Nombre que se mostrara en la entrada al des-instalador.
  2. Ruta en la que se encuentra el des-instalador, siempre debe de ir entre comillas.
  3. Información del publicador (empresa).
  4. Icono que se usara para mostrarse en la lista de programas.
  5. ${GetSize} Es una función de NSIS que se usa para obtener el tamaño de un folder o archivo. Está la veremos con mayor detalle un poco más adelante, pero en este caso se usa para obtener el tamaño de la instalación. Para que funcione es importante que previamente ya se hayan copiado todos los archivos a la carpeta de instalación.
  6. Cuando ya obtuvimos el tamaño de la instalación; está función escribe esa información. Para que funcione, el tamaño debe de estar en KiloBytes.
  7. Muestra la versión del programa.
  8. Vinculo a una dirección de internet "Acerca de".
  9. Vinculo a una dirección con ayuda para el programa.
  10. Cuando NO se tiene la opcion de modificar los componente ya instalados.
  11. Cuando NO se tiene la opcion de reparar la instalacion.
Para que des-instalador funcione, minimamente se deben de escribir las entradas "DisplayName" y "UninstallString", de ahí en fuera las otras son totalmente opcionales, pero siempre es recomendable incluir al menos todas las anteriores para tener el mínimo de información necesaria.

Al escribir todas las entradas anteriores, cuando vayamos a la seccion de des-instalar programas, nuestra aplicación tendrá el siguiente aspecto:

Y bien, es todo por hoy, en el siguiente post veremos cómo asociar una extensión con nuestro programa, recuerda que el código de este post lo puedes bajar de mi dropbox, viene con un ejemplo de un instalador para VEncoder 2.

P.D Hay que adaptar el código de VEncoder para funcionar desde "Program Files", ya que cuando se usa desde está ruta, requiere  de algunos cambios.

Los leo luego.

Vamos a programar #22 - Haciendo un instalador en NSIS.

Hola de nuevo a todos, el día de hoy les voy a mostrar el código detrás de un instalador que hice ya hace tiempo.
Cómo muchos de ustedes sabrán. Yo soy programador y he hecho programas de muchos tipos, algunos más complejos que otros. Dependiendo del grado de complejidad muchos de ellos vienen divididos, es decir; algunas funciones criticas las hago en bibliotecas DLL y al momento de distribuir el programa, cuando llega al usuario final, en ocasiones los usuarios "pierden alguna" DLL o algún recurso que el programa necesita. Inicialmente solo empaquetaba todo en un archivo ZIP y en el programa principal, programaba que hiciera los cambios en el sistema que fueran necesarios para que tdo funcionara correctamente, además siempre incluía un manual de ayuda (que al parecer nadie leía) para que el usuario pudiera arreglar los problemas comunes.
Pero cómo siempre aparecían errores decidí buscar una manera de minimizarlos lo más posible. Para eso busque la forma de hacer un instalador, y poder distribuir todo lo necesario en un solo paquete y además hacer los ajustes necesarios para que el usuario solo ejecutará la aplicación y evitar disgustos.

Al inicio probé con una herramienta que se llama Create Install Free, al inicio era gratuita, funcionaba de maravilla, permitía agregar y registrar bibliotecas en windows, fuentes y controles OCX, era una solución muy buena. Tiempo después paso a tener costo, y me hubiera gustado comprarlo, pero no tenía una tarjeta de crédito valida para realizar la compra. Entonces dejé de usarlo y busqué otra forma de crear un paquete de instalación.

Así fue cómo llegué a NSIS.


Nullsoft Scriptable Install System (NSIS) es un "manejador de script" Windows de código abierto con requerimientos mínimos, desarrollado por Nullsoft, los creadores de Winamp. NSIS ha crecido en popularidad como una alternativa al uso extenso de productos comerciales como InstallShield y es actualmente utilizado para un sin número de aplicaciones distribuidas a través de Internet.
NSIS es liberado bajo una combinación de licencias de software libre, principalmente la licencia de zlib/libpng, de esta forma haciendo a NSIS software libre..

Creando un instalador.

Lo primero que vamos a hacer es descargar el programa, una vez que está descargado e instalado, vamos a escribir un archivo de texto con notepad++ o con cualquier editor de texto plano.
Hoy veremos directamente cómo hacer uso de Nsis Modern User Interface. Prefiero ir directamente a está parte porque la mayoría de las funciones o procesos que vamos a realizar,  se pueden llevar a cabo con el uso de este plug-in que viene integrado por default. En adición, este se encarga de que todas las ventanas del instalador tengan la misma apariencia que el sistema operativo.

Una vez que tenemos todo listo vamos a empezar a escribir el código. Tomemos está parte de código y revisemos cada una de las partes que lo componen.

!include "MUI2.nsh"
!include "FileFunc.nsh"
!include "Library.nsh"

SetCompressor lzma
SetOverwrite on
SetDatablockOptimize on
!define VERSION "2.0"
Name "VEncoder 2"

OutFile "Setup.exe"
InstallDir "$PROGRAMFILES\Vencoder 2"
InstallDirRegKey HKCU "Software\MPM\VENCODER 2" ""
ShowInstDetails show
RequestExecutionLevel admin
CRCCheck on
XPStyle on

La descripcion del código anterior es la siguiente:

  • Lo primero que hacemos es incluir "MUI2.nsh" que es la "librería" que contiene todas las funciones del plugin MUI, en NSIS cuando se quiera incluir una librería, se debe de hacer usando la palabra reservada "!include" seguida por el nombre del plug-in ó "librería", para NSIS, estás tienen una extension "nsh".
  • SetCompressor, selecciona el compresor que se usará, en este caso usaremos el compresor LZMA, pero ademas de este, podemos usar ZLIB o BZIP2, por lo general yo uso este porque a mi criterio; ofrece mayores niveles de compresión..
  • SetOverWrite, Sirve para indicar si se deben de sobre-escribir los archivos que ya existan en el lugar de destino..
  • SetDataBlockOptimize. Sirve para hacer optimizaciones en el bloque de datos. Si este ya contiene un dato, en lugar de agregarlo de nuevo, simplemente se hace referencia a el. Puede ayudar a ahorrar un poco de espacio. (NSIS: SetDataBlockOptimize)
  • Definimos la versión de la aplicación.
  • Establecemos el nombre (es este caso VEncoder 2)
  • Establecemos el nombre de salida para el instalador una vez que ya está compilado. Puede ser cualquiera.
  • Establecemos cual va a ser el directorio de salida para los archivos que se van a instalar, hacemos uso de $PROGRAMFILES, que es una palabra reservada de nsis que generalmente se refiere a "C:\Program Files (x86)\" o la carpeta que contiene todos los programas instalados.
  • InstallDirRegKey. Obtiene la ruta de instalación del registro de windows. Cabe aclarar que esta solo tendrá un valor SOLO si anteriormente ya hemos instalado algún programa que haga uso de esta.
  • ShowInstDetails. Cuando se lleva a cabo el proceso de instalación, muestra cual es archivo que se está copiando.
  • RequestExecutionLevel. Indica el nivel de ejecución que requiere el instalador. Pueden ser los siguiente valores: none, user, highest, admin.
  • CRCCheck. Comprueba la integridad del instalador antes de empezar la instalación.
  • XPStyleOn. Habilita que el instalador tenga la apariencia de Windows.

Por lo general el pedazo de código anterior es un buen ejemplo de cómo empezar un script, solamente debes de modificar las partes correspondientes a tu aplicación a instalar.

Ahora sigue definir todas las páginas que componen el instalador.
Página de selección de componentes.
Cada instalador está compuesto por páginas, cada una de ellas tiene la función de guiar paso a paso al usuario. Para hacer uso de ellas hay que "declararlas" en código de nsis. Un instalador "básico" puede usar el siguiente código para definir las páginas más comunes.

!insertmacro MUI_PAGE_WELCOME
!insertmacro MUI_PAGE_LICENSE "COPYING.txt"
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_PAGE_FINISH
!insertmacro MUI_UNPAGE_WELCOME
!insertmacro MUI_UNPAGE_INSTFILES
!insertmacro MUI_LANGUAGE "Spanish"

El código anterior crea las siguientes páginas:
MUI_PAGE_WELCOME: Crea la página que contiene un mensaje de bienvenida.
MUI_PAGE_LICENSE "COPYING.txt": Crea la página que muestra un archivo de texto llamado "Copying.txt" cómo licencia, se puede usar cualquier fichero de texto y asignarlo cómo parámetro.

MUI_PAGE_COMPONENTS: Muestra una página que permite seleccionar que secciones son las que queremos instalar.


MUI_PAGE_DIRECTORY: Muestra la página que nos permite la selección de un folder para llevar a cabo la instalación.

MUI_PAGE_INSTFILES: Muestra el progreso de la instalacion.


MUI_PAGE_FINISH: Muestra un mensaje al finalizar la instalación.

Ademas las siguientes intrucciones MUI_UNPAGE_WELCOME, MUI_UNPAGE_INSTFILES crean las páginas correspondientes en el desisnstalador.
Luego hay que indicar que todas las páginas usan el idioma español, para eso usamos la siguiente instruccion MUI_LANGUAGE "Spanish", en donde "spanish" puede ser cualquier idioma soportado por NSIS,
Si miramos la ultima imagen, veremos un texto que esta en negritas y dice: "Las instalación ha terminado". Este string hay que definirlo, de hecho antes de crear las páginas, hay que definir los strings para todas las partes de cada pagina, el código que hace eso no lo pongo aquí, porque haria muy extenso el Post, pero al final agrego el código fuente para que lo revises.

NSIS trabaja con apartados llamados secciones, en ellas es donde se agregan los archivos que queremos incluir en la instalación, la ventaja de esto es que podemos crear varios apartados en los cuales se incluyan los archivos que son realmente importantes en la aplicación y otra sección en la que se incluyan archivos miscelanéos.
Para este caso tenemos tres secciones, 2 de ellas obligatorias y una de ellas opcional.

Section "Principal" MainSec
##Indicamos que está seccion es obligatoria
SectionIn RO
##Establecemos la carpeta de destino
SetOutPath "$INSTDIR"
File "Copying.txt"
File "Main.Ico"
File "MediaInfo.dll"
File "MediaInfoNET.dll"
File "Vencoder 2.exe"
File "Vepx.ico"
File "ListViewEx.dll"
SetOutPath $INSTDIR\Doc
File /r doc\*.*
SectionEnd

Section "Archivos adicionales" AdditionalSec
SetOutPath "$DOCUMENTS\Profiles"
File "MB300.Vepx"
File "PSP.vepx"
File "PSVita.vepx"
File "Screen HD.Vepx"
File "Screen FHD.Vepx"
SectionEnd

Con el código previo, definimos dos secciones, cada "section" recibe 2 parámetros, uno de ellos es una descripción y el otro es un nombre que servirá para identificar cada una de las secciones.
Para la primera sección indicamos que es obligatoria con "SectionIn RO" cuando se establece, en la página de selección de componentes, aparecerá un casilla de selección de solo de lectura, cuando no se incluye, el usuario puede marcar o des-marcar la casilla de selección, así indicando que esa sección no se quiere instalar.
En la seccion "MainSec", después de que establecimos que sea obligatoria, agregamos todos los archivos necesarios de nuestra aplicación, primero establecemos que todos se copiaran a la carpeta de instalacion "$INSTDIR" ("C:\Program Files (x86)\VENcoder2\"), luego ponemos una lista en la que cada archivo inicia con la palabra clave "File" seguida por el nombre del archivo que queremos incluir, un poco más abajo encontramos la instrucción "SetOutPath" nuevamente con la ruta "$INSTDIR\Doc", esto indica que se debe de cambiar la salida de los archivo a está nueva dirección, luego al comando "File" le agregamos el parámetro "\r" que indica que buscara de forma recursiva lo archivos en la carpeta doc.

Cada seccion debe de incluir una descripcion, para definirlas, se usa código similar al siguiente:

!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${MainSec} "Contiene todos los archivos escenciales de la aplicacion"
!insertmacro MUI_DESCRIPTION_TEXT ${LinkSec} "Accesos directos a la aplicacion)"
!insertmacro MUI_DESCRIPTION_TEXT ${AdditionalSec} "Archivos de perfil con las configuraciones mas frecuentes (opcional)"
!insertmacro MUI_FUNCTION_DESCRIPTION_END

Despues llamamos a las macros que se encarhgan de crear las descripciones; MUI_FUNCTION_DESCRIPTION_BEGIN, recibe dos parámetros, uno de ellos es el nombre de la seccion a la cual se quiere poner su descripción y el segundo, es la cadena de texto.

Lo que se mostró en el post son las partes principales, pero el código completo junto con lo archivos, como siempre; lo dejo en mi dropbox para que lo descargues y lo revises. Incluye todo lo necesario para crear un instalador funcional para VEncoder.
En el siguiente Post vamos a ver alguna funciones no vitales, pero que resultan muy útiles.

Por ahora es todo, los leo luego

Vamos a programar #21 - Formateando código para HTML.

Hola de nuevo a todos. El día de hoy traigo una aplicación muy corta. Cómo todos sabrán, la mayor parte de este blog trata sobre cosas de programación, por lo que es muy común que publique código fuente en variedad de lenguajes.
Pero al momento de publicar algo, surge un pequeño problema, al copiar el código del editor al blog, este pierde todo el formato. Actualmente uso unos scripts que permiten darle color a la sintaxis, pero cosas como la indentación, se pierden. En lo personal, siempre me gusta ver código que este visiblemente bien, es decir; que se distinga en "donde estás". Un indicador muy claro, es la sangría. Si vez que todo está a la misma distancia, resulta fácil ver que todo es parte de un "if" o parte de un "while".
Para solucionar todo esto y poder formatear el código de una forma más rapida, decidí hacer una aplicación que se encargue del trabajo sucio.

La aplicación es realmente simple, de hecho solo son 10 lineas de código, es una versión beta, y lo único que hace es convertir todos los caracteres que no están admitidos en el html (como "< o >" ).
Dentro las funciones que nos proporciona el api de .NET existe a siguiente función:

RtBResultCode.Text = WebUtility.HtmlEncode(RTBInputCode.Text);

Está función se encuentra en el espacio System.Net; y hace exactamente lo que queremos, formatear todo el código para que lo podamos usar en el lenguaje HTML. Pero bueno si solo quisiera hacer eso no sería una solución a la indentación, de hecho si revisamos el siguiente código (que viene de este blog)
<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>


Pero eso no es lo que quiero, si nos damos cuenta todas las tabulaciones han sido reemplazadas por espacios que visualmente son más pequeños.
En cambio cuan usamos el código producido por el programa; lucirá de la siguiente forma:

<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>

Por ahora el código solo hace eso, pero si por ejemplo el código tiene espacios en blanco, no obtendremos un resultado diferente al del primer caso.

En la siguiente versión vamos implementar funciones que se encarguen de hacer todo de forma automatica. Si lo que quieres es publicar código, lo ,mejor es que luzca, que al momento de echar un vistazo rápido, puedas ver que al menos está bien estructurado.
 Además lo dejo así porque ando un poco corto de tiempo, pero espero ya no estarlo para la próxima semana. Como siempre, el programa lo puedes bajar de dropbox, en cuanto lo actualice haré el siguiente Post.

Por ahora es todo. Los leo luego.

Vamos a programar #20 - SlideShow en JavaScript + CSS

Hola de nuevo a todos, hace algunos días contacté con alguien que me encontré en Internet con la intención de hacernos publicidad mutuamente, a lo mejor ya viste el banner que puse en la esquina superior derecha.
EL problema es que no encontraba una forma de acomodar todas la cosas que ya había en la pagina principal, ¿Cómo debería acomodar esto?, la opción más sencilla era insertar todas las cosas una tras otra y esperar a que la pagina no se extendiera tanto. Si se pone una imagen diferente para cada página "amiga", si pongo al menos 3, ocuparía un buen espacio.
Para solucionar este problema decidí hacer un pequeño script que se encargue de intercambiar imágenes, así en lugar de poner 4 o 5 imágenes y desplazar todo 600 pixeles hacia abajo, puedo poner n numero de imágenes en un solo espacio de 320 * 110 (o el tamaño que sea la imagen más grande).

Para nuestro ejemplo tomemos las siguientes imágenes:




Al inicio estaba buscando una forma de hacerlo totalmente en javascript, pero alguien me dijo: "Si usas CSS3, es más sencillo". Entonces decidí hacer uso de ambas para lograrlo.

El código CSS.

El código CSS que vamos a usar  es el siguiente:

@keyframes FadeFromRight{
 from{
  right:-150px;
  opacity:0
 }
 to{
  right:0;
  opacity:1}
 }

.FadeImage{
 position:relative;
 -webkit-animation:FadeFromRight 0.5s;
 animation:FadeFromRight 0.5s
}
img{border-style:none}


Primeramente creamos una animación a la que llamamos "FadeFromRight", lo que va a hacer es crear el objeto desde la posición -150px con una opacidad del 0 (Transparente)  y hará una animación en la que se moverá hasta posición 0 y ademas, su nivel de opacidad sera de 1 (solido).

Después creamos una clase llamada ".FadeImage", en la cual ponemos que la animación que haga la imagen (o lo que sea que haga uso de esta clase), durará 0.5s.

Finalmente definimos un estilo para la etiqueta IMG.

El código HTML.


Luego insertaremos las Imágenes que vayamos a usar, Es preferible que todas estén dentro de una capa, luego a cada una de las imágenes que conformaran el Slide Show, les pondremos el mismo nombre de clase, suponiendo que las imágenes que vayamos a usar se llaman Ban1, Ban2, Ban3 y Ban4, el código html para insertarlas luciria como el siguiente:


<div>
 <img class="Banners FadeImage" src = "BAN1.jpg"/>
 <img class="Banners FadeImage" src = "BAN2.jpg"/>
 <img class="Banners FadeImage" src = "BAN3.jpg"/>
 <img class="Banners FadeImage" src = "BAN4.jpg"/>
</div>

Con el Código anterior estamos indicando que las 4 imágenes pertenecen a la clase "Banners" y también a la clase "FadeImage", con ello tomará los estilos que definimos antes para "FadeImage" y "IMG".

El Código JavaScript.

Para lograr el efecto que deseamos, hay que modificar los estilos, javascript nos permite hacerlo sin tanto problema. El código que nos permite cambiar los estilo de las imágenes es el siguiente:

var myIndex = 0;
ChangeImage();

function ChangeImage() {
 var i;
 var x = document.getElementsByClassName("Banners");
 for (i = 0; i < x.length; i++) {
  x[i].style.display = "none";
 }
 myIndex++;
 if (myIndex > x.length) {myIndex = 1}
 x[myIndex-1].style.display = "block";
 setTimeout(ChangeImage, 5000);
}


El código anterior recorrerá todos los elementos que estén en la clase "Banners" y a todos le asignara el estilo "none" que hará que los elementos no se vean, luego comprobará que el valor iterador esté en lo limites  de la cantidad de elementos que la clase "Banners" posea, después al final, se establece que la función "ChangeImage" se ejecutará cada 5000ms (5 segundos).

Tras implementar el código anterior obtendremos algo como lo siguiente (Si solo ves cuatro imágenes fijas, prueba actualizar tu navegador):



No está demás recordar que para que esto funcione, necesitamos un navegador no tan viejo, ademas de que se debe de hacer referencia a las imágenes usando su dirección absoluta. Si quieres hacer uso del código así tal cual, debes de asegurarte primero de que las imágenes están en el mismo directorio y que se llaman Ban1, Ban2, etc.

Cómo siempre, el código completo lo puedes descargar desde mi dropbox, recuerda que puedes usarlo, modificarlo o hacerle lo que quieras, si te resulta útil, no olvides mencionar que lo encontraste en el blog de Xwork.

Para terminar. Si no has visitado la página Gent0.com. ¡Qué estás esperando?!, hay buen información acerca de está distro de linux.

Bien, por ahora es todo. Los leo luego.

P,D. H071?? (solo para los que saben).

Vamos a programar #19 - Extraer una imagen embebida en un MP3 - ArtView

El día de hoy vamos a terminar (ahora si no es broma) el código para extraer las imágenes que se encuentran embebidas en los archivos MP3.


En el post anterior implementamos algunas partes del código y lo pasamos a funciones, antes de avanzar hay algo que quiero decir de una de las funciones anteriores. Una persona que visitó el blog me dijo que la función DataToAlbum no funcionaba bien, a pesar de que yo mostré imágenes en la que parecía que todo funcionaba bien, pero no era asi, al analizar todos los bytes que componían al string (solo la versión unicode), había un carácter no visible. El carácter es 0xFF, eso se debia a que también le pasábamos a la función GetString los bytes adicionales que se usan para indicar que se trata de un string unicote, para agregarlo, solo hay que movernos hasta el tercer byte. Recordemos que la función que hicimos recibía cómo parámetro una matriz de bytes donde el primero, era el byte que nos indicaba cual era el tipo de codificación; uno para unicode y cero para ASCII. En el caso de la codificacion ASCII no existía mayor problema porque seguido de ese byte, inmediatamente va lo que vendría siendo el string en sí, pero en el caso del string unicode, inmediatamente después del primer byte, siguen dos bytes que sirven para indicar que tipo de codificación se usa (en lectores de texto por ejemplo,0xFF 0xFE para unicode).

private string DataToAlbum(byte[] Data)
{
 if (Data[0] == 0)
  return Encoding.ASCII.GetString(Data,1,Data.Length-1).TrimEnd((char)0).TrimStart((char)0);
 if (Data[0] == 1)
  return Encoding.Unicode.GetString(Data,3,Data.Length-3).TrimEnd((char)0).TrimStart((char)0);
 else
  return "Desconocido";
}

Procesando por lotes.

El chiste de todo esto era extraer las imágenes debido a que cuando se convertían entre una versión a otra, por lo que me dijeron, algunos programas omiten la imagen, entonces lo ideal es extraer primero la imagen y después insertarla de nuevo, eso si, solo después de haber realizado la conversión a la versión de destino.

Para eso creamos la función BatchProcess cuyo código es el siguiente:

private void BatchProccess(List<string> Files, string SaveFolder)
{
 int Current = 0;
 PBProgress.Maximum = Files.Count;
 Thread DoWork = new Thread (new ThreadStart(() =>
 {
  foreach (string Item in Files)
  {
   ImageContent(Item,SaveFolder);
   Current = Current + 1;
   UpdateProgress(Current);
   if (Current == Files.Count) {
    IsWorking =false;
   }
  }
 }
 ));
 DoWork.Start();
}


Está función recibe cómo parámetros una lista del tipo string que contiene todos los archivo de los cuales queremos extraerles la imágenes, el otro parámetro es un string que contiene la ruta (fólder) en donde guardáremos las imágenes. Después creamos un thread para no bloquear la interfaz principal. De hecho cuando pruebes el código, al agregar los archivos, si agregas muchos, notarás que la aplicación se congela un poco (dependiendo de cuantos agregues y de que tan potente sea tu equipo), esto lo deje así para que notes la diferenta entre usar un thread aparte y cuando no.
El uso del thread (hilo a partir de este momento). Para esta aplicación es realmente sencillo, ya que no hacemos uso de las funciones para controlarla como es debido, de hecho para saber si aun sigue ocupado ese hilo usamos el flag IsWorking. Para poder saber el progreso del proceso usamos un delegado que se encargara de recibir el valor actual del progreso, después llamará a la funcion UpdateProgress que se encargará de actualizar la propiedad Value del ProgressBar.

El código de la funcion UpdateProgress (incluido el delegado) es el siguiente:

private delegate void UpdateProgressDelegate(int Value);

private void UpdateProgress(int Value)
{
 if (PBProgress.InvokeRequired)
 {
  PBProgress.Invoke(new UpdateProgressDelegate(UpdateProgress), Value);
 }
 else
 {
  PBProgress.Value = Value;
 }
}

El uso un poco más avanzado de los hilos lo veremos en el siguiente Post.
Y ya solo que da el código que se encargará de hacer el trabajo. El uso de un flag para determinar si hay o no un proceso es importante, ya que podemos cerrar la ventana principal y algun proceso todavía se puede estar ejecutando, suponiendo que eres cómo yo y rara vez le pide a Windows que te dé permiso para sacar tu dispositivo, si lo extraes en medio de un proceso de lectura/escritura, puede ser que se dañe tu USB.

El código completo lo puedes descargar aquí, Dudas o comentarios no dudes en decirlo. Antes de terminar quiero agradecer al que reportó el error, preferiría que no usaran perfiles anónimos para poder dar el reconocimiento que se debe.

Por ahora es todo, los leo luego.
P.D, “Arceus existe ;)”

Vamos a programar #18 - Extraer una imagen embebida en un MP3.

Hola de nuevo a todos, el día de hoy vamos terminar con el programa encargado de extraer la imágenes que están embebidas en los archivos MP3.
El día de hoy vamos a implementar todo el programa y ademas haremos una pequeñas optimizaciones al código que ya teníamos previamente hecho.

Mejorando el código previo.

Primero vamos crear una función para calcular el tamaño de la etiqueta, en el código, lo que hacíamos primero, era buscar la etiqueta, luego leer la longitud, pero hay que recordar que dependiendo de la versión, se usan tres o cuatro bytes para esto.
Entoces basados en la informacion anterior, podemos hacer una función similar a la siguiente:



private int ByteArrToSize(byte[] Data)
{
 int Result = 0;
 if (Data.Length == 4)
 {
  Result = Data[0] * 256 * 256 * 256 + Data[1] * 256 * 256 + Data[2] * 256 + Data[3];
  return Result;
 }
 if (Data.Length == 3)
 {
  Result = Data[0] * 256 * 256 + Data[1] * 256 + Data[2];
  return Result;
 }
 else return 0;
}

Si recordamos en otro código, es exactamente lo mismo, solo que lo separamos para poder usarlo de manera rápida. Esta función recibe una matriz de bytes que contiene los 4 o 3 bytes después de la etiqueta "PIC" o "APIC". cómo resultado, devuelve un numero del tipo int que contiene la longitud de la etiqueta. Para usarla solo hay que sustituir la parte de los calculos por la llamada a la funcion.

Código anterior:

//Size
TotalLenght = TotalLenght + BinRead.ReadByte() * 256 * 256 * 256;
TotalLenght = TotalLenght + BinRead.ReadByte() * 256 * 256;
TotalLenght = TotalLenght + BinRead.ReadByte() * 256;
TotalLenght = TotalLenght + BinRead.ReadByte();
txtInfo.Text = txtInfo.Text + "\n La etiqueta APIC esta en la dirección: " + PicTagPos.ToString() + "\n";
txtInfo.Text = txtInfo.Text + "con longitud de" + TotalLenght.ToString() + " bytes\n";

Código nuevo implementando la función ByteArrToSize:


TotalLenght = ByteArrToSize(BinRead.ReadBytes(4));
txtInfo.Text = txtInfo.Text + "\n La etiqueta APIC esta en la dirección: " + PicTagPos.ToString() + "\n";
txtInfo.Text = txtInfo.Text + "con longitud de" + TotalLenght.ToString() + " bytes\n";


y cómo es un cálculo que lo vamos a hacer más de una vez, lo mejor es usar la función, originalmente la hice de la primer forma debido a que quería mostrar lo que estábamos haciendo a mano, pero para fines más prácticos, usamos la función, no hace falta ser experto para ver que en ambos casos se hace exactamente lo mismo, solo que la función determina; hasta cierto punto; que es lo que va a hacer, si usáramos el código original, debido a que es diferente como esta descrito el tamaño de la etiqueta. Primero había que comprobar para que caso íbamos a hacer el cálculo, y de leer un byte, multiplicar y sumar 3 o 4veces dependiendo del caso. En la función solo pedimos los datos y dependiendo de el numero de bytes que contenga el argumento, seguirá las condiciones pertinentes para devolver el valor correcto.

Otro de los procesos que decidí volver en una función; fue el de obtener la imagen, si recordamos, en el código original, una vez que ya teníamos definidos todos los datos de la imagen (tipo y longitud), en cada una de las versiones de las versiones, leíamos los datos, creábamos un MemoryStream y creábamos una imagen a partir de el.

El codigo es el siguiente:

using (MemoryStream MS = new MemoryStream(BinRead.ReadBytes(ImageLenght)))
{
 PicArt.Image = Image.FromStream(MS);
 using (FileStream file = new FileStream(SaveLocation + "." + ImageType(MType), FileMode.Create, FileAccess.Write))
 {
  MS.WriteTo(file);
  file.Flush();
  file.Close();
 }
 MS.Close();
}

Entonces para pasar todo el fragmento de código anterior, solo necesitamos crear una función que devuelva una imagen cómo resultado.

El siguiente código es la versión en función del primer código.

private Image StreamToImage(byte[] Data, string SaveLocation)
{
 using (MemoryStream MS = new MemoryStream(Data))
 {
  Image TempImage;
  using (FileStream file = new FileStream(SaveLocation, FileMode.Create, FileAccess.Write))
  {
   MS.WriteTo(file);
   file.Flush();
   file.Close();
   TempImage = Image.FromStream(MS);
  }
  MS.Close();
  return TempImage;
 }
}


Al igual que en el caso anterior, tomamos todo el código que ya teníamos y lo usamos tal cual estaba, solo que este caso, devolvemos un valor del tipo Image que contiene la imagen que extrajimos. La función recibe dos parámetros: uno del tipo byte[] que es el que contiene todos los datos leídos previamente desde el Stream donde se manda a llamar esta función y un valor del tipo string que contiene la ruta en donde se guardará la imagen (en adición a la imagen que devuelve la función).

El código nuevo.

Todos estos post comenzaron por el hecho de que cuando se realiza una conversión entre una versión de las etiquetas ID3 a otra, en muchos casos se pierde la imagen (o eso fue lo que me dijeron), entonces nuestro objetivo es crear un Back up de estás. Pero ahora que ya sabemos cómo extraerlas, sigue almacenarlas, pero creo que surgiría un pequeño problema.
Cuando guardábamos las imágenes, les poníamos como nombre de archivo el nombre de la canción, solo que en lugar de extensión MP3 le poníamos JPG o PNG (dependiendo el caso), pero imaginemos que tenemos el álbum completo, no tendría caso extraer la imagen para cada una, porque seria la misma para cada canción del mismo álbum, supongamos que tenemos un disco completo ya en MP3 y este contiene 20 canciones, si le extraemos la imagen a cada uno, tendríamos 20 archivos de imagen iguales con nombres distintos.
La solucion es un tanto sencilla, solo debemos de tomar cómo nombre de archivo algo que tengan en comun los archivos que no sea el artista. El album es un buen identificador, porque bastará que extraigamos una sola imagen para cubrir los 20 archivos.
Para poder usar el nombre del album cómo nombre de archivo para la imagen, primero debemos de saber cual es el nombre del album. Igual que con la imagen, dentro del tag ID3 hay un frame que describe la información para el album. En el caso de la version 2.3 es la siguiente:

Text information frames The text information frames are the most important frames, containing information like artist, album and more. There may only be one text information frame of its kind in an tag. If the textstring is followed by a termination ($00 (00)) all the following information should be ignored and not be displayed. All text frame identifiers begin with "T". Only text frame identifiers begin with "T", with the exception of the "TXXX" frame. All the text information frames have the following format:
<Header for 'Text information frame', ID: "T000" - "TZZZ", excluding "TXXX" described in 4.2.2.>
Text encoding $xx Information
text string according to encoding
Y para la version 2 es la siguiente:
The text information frames are the most important frames, containing information like artist, album and more. There may only be one text information frame of its kind in an tag. If the textstring is followed by a termination ($00 (00)) all the following information should be ignored and not be displayed. All the text information frames have the following format:
Text information identifier "T00" - "TZZ" , excluding "TXX", described in 4.2.2.
Frame size $xx xx xx
Text encoding $xx
Information <textstring>
Entonces sabiendo lo anterior podemos crear una código cómo el que sigue:


       private string DataToAlbum(byte[] Data)
      {
            if (Data[0] == 0)
                return Encoding.ASCII.GetString(Data, 1, Data.Length - 1);
            if (Data[0] == 1)
                return Encoding.Unicode.GetString(Data, 1, Data.Length - 1);
            else
                return null;
        }

El código anterior sirve para leer cualquier frame de texto (exceptuando los que dice la documentación), pero ademas incluye casos definidos para cuando el texto es ASCII o unicode. La función recibe como argumento una matriz de bytes que incluye todos los bytes que le siguen a la longitud del frame, es decir, el primer byte deberá de ser el que indica que tipo de codificación se va a usar; inmediatamente seguido de el string (que es el resto de la etiqueta)esto para el caso de la versión 2. Para la versión 2.3 se deben de pasar los bytes empezando desde el byte que indica la codificación del texto, omitimos los 2 bytes que se usan cómo flags.
La función se debe de mandar a llamar cuando ya tenemos separados los datos de este frame, es decir, primero hay que determinar que tipo de tag ID3 es, luego buscar "TALB" o "TAL" dependiendo de la versión y leer los datos de la misma forma que lo hicimos para el frame "APIC" o" PIC", esto se puede hacer dentro del mismo código que lee los datos para buscar la imagen y ya una vez que se encuentran, simplemente le pasamos la información a la función.

Es importante usar la codificación correcta. Si no lo hiciéramos, no podríamos leer "ドリドリ" que es el álbum de esta canción.


Es importante leer la codificación, porque para este caso, los caracteres del japones, no están disponibles en el estandar ASCII, Cuando el byte que nos indica que codificación se usa es 1, hay que recordar que la codificación es UNICODE, cuando es 0 es ASCII.


Y bien, por ahora solo queda pendiente la extracción por lotes, pero será en el siguiente post.
Los leo luego.