miércoles, diciembre 12, 2012

Query parser para servicios Web API


Desde hace unos días estuve buscando la forma de pasar a un servicio Rest (de Web API) una expresión que pudiera ser útil para consultar tablas en forma dinámica (no se conoce a priori su estructura).

Las expresiones que deseaba pasarle al parser eran del tipo:
GET /Service/Customers?$where=Name eq ‘ACME’

Esto ayuda a independizarse de la base de datos, evitar SQL injection y brindar una api de consumo amigable.

Todas las soluciones que pude encontrar se basan en parsers tipados, solución que quizás funcione para el 90%, pero a mi me tocó estar en el porcentaje restante.

Ejemplo de parser está de OData para Web API
http://blogs.msdn.com/b/alexj/archive/2012/12/06/parsing-filter-and-orderby-using-the-odatauriparser.aspx

Linq2Rest
https://bitbucket.org/jjrdk/linq2rest

Tuve la suerte de encontrarme con la referencia de operadores usada en MongoDB
http://docs.mongodb.org/manual/reference/operators/

Donde usando JSON se puede pasar una expresión de filtro estilo:
where={ $and: [{'gid': {'$gt': 1}}, {'layer': 0}] }

He aquí el código, parece simple, y lo es, pero anda :)



/*
 * QueryParser
 * 
 * Autor: Felixls
 * Fecha: Diciembre 2012
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Sample.Models;

using Newtonsoft.Json.Linq;

namespace Sample.Controllers
{
    /*
     * Parser de consultas con sintaxis de MongoDB
     * http://docs.mongodb.org/manual/reference/operators/
     * 
     * Ejemplos:
            ?where={"age": {"$gt": 20}}
            { 'gid': {'$eq': 1}}
            { 'gid': 1}
            { 'gid': {'$gt': 1}, 'layer': 0}
            { $and: [{'gid': {'$gt': 1}}, {'layer': 0}] }
            { $or: [{'gid': {'$gt': 1}}, {'layer': 0}] }
            { 'handle': 48, $and: [{'gid': {'$gte': 1}}, {'layer': 0}] }
            { 'handle': 48, $or: [{'gid': {'$gte': 1}}, {'layer': 0}] }
            { $and: [{'gid': {'$gt': 1}}, {$or: [{'layer': 0}, {'layer': {'$eq': 1}}]}] }
            { 'gid': {'$gt': 1}, $or: [{'layer': 0}, {'layer': {'$eq': 1}}] }
            { 'gid': {'$in': [1, 5]} }
            { 'fecha': {'$in': ['2012-12-09', '2012-12-10']} }
            { 'fecha': {'$in': ['2012-12-09', '2012-12-10']}, 'tipomov': 2 }
            { 'fecha': {'$in': ['2012-12-09', '2012-12-10']}, $or: {'tipomov': 2} }
            { $and: [{'gid': {'$gt': 1}}, {$or: [{'layer': 0}, {'layer': {'$eq': 1}}]}] }
            { 'gid': {'$gt': 1}, $or: [{'layer': 0}, {'layer': {'$eq': 1}}] }
            { 'gid': {'$in': [1, 5]} }
            { 'fecha': {'$in': ['2012-12-09', '2012-12-10']} }
            { 'fecha': {'$nin': ['2012-12-09', '2012-12-10']}, 'tipomov': 2 }
            { 'fecha': {'$in': ['2012-12-09', '2012-12-10']}, $or: {'tipomov': 2} }
            { 'codigo': {'$gt': 'L49'}, $or: [{'tipomov': 3}, {'tipomov': {'$lt': 2}}, {$and: {'fecha': {'$in': ['2012-12-09', '2012-12-10']}}}] }
            { 'codigo': {'$gt': 'L49'}, $or: [{'tipomov': 3}, {'tipomov': {'$lt': 2}}], $and: {'fecha': {'$nin': ['2012-12-09', '2012-12-10']}} }

     * Operadores soportados

            $eq  =
            $ne  !=
            $lt  <
            $lte <=
            $gt  >
            $gte >=
            $in      { qty: { $in: [ 5, 15 ] } }
            $nin !$in
            $and     { $and: [ { price: 1.99 }, { qty: { $lt: 20 } }, { sale: true } ] }
            $and implicito    { price: 1.99, qty: { $lt: 20 } , sale: true }
            $or      { price:1.99, $or: [ { qty: { $lt: 20 } }, { sale: true } ] }
         
     * NO SOPORTADOS
            $all, $nor, $not.

            //TODO: Geoespaciales (a futuro soportadas) 
            $near    { location: { $near: [100,100] } }
            $bbox    { loc: { $within: { $box: [ [0,0], [100,100] ] } } }
            $within  { location: { $within: { shape } } }
                    { location: { $within: { $box: [[100,0], [120,100]] } } }
               { location: { $within: { $center: [ center, radius } } }
               { location: { $within: { $box: [[100,120], [100,100], [120,100], [240,200]] } } } 
            $polygon { loc: { $within: { $polygon: [ [0,0], [3,6], [6,0]  ] } } }
            $center  { location: { $within: { $center: [ [0,0], 10 ] } } } 
            $maxdistance { location: { $near: [100,100], $maxDistance: 10 } }
            $nearSphere { loc: { $nearSphere: [0,0] } }
            $centerSphere { loc: { $centerSphere: { [0,0], 10 / 3959 } } } 
     */
    public class QueryParser
    {
        private List atts;
        private dynamic root;
        private string result = "";
        private int state = 0;
        private int level = 0;

        public QueryParser(string s, List atts)
        {
            this.atts = atts;
            root = JObject.Parse(s);
            
            result = " where ";
            state = 0;
            level = 0;

            recurse(root);
        }

        private void recurse(dynamic obj)
        {
            string p;
            
            foreach (var current in obj)
            {
                p = ParseAttribute(current);
                if (p != null)
                {
                    if (level == 1)
                    {
                        if (state == 0)
                            result += " and ";
                        if (state == 1)
                            result += " or ";
                    }
                    result += p;
                    level = 1;
                }
                else
                {
                    if (current.Name == "$or")
                        LogicalOperator(current, 1);
                    else if (current.Name == "$and")
                        LogicalOperator(current, 0);
                    else
                        throw new Exception("Syntax error");
                }
            }
        }

        private void LogicalOperator(dynamic current, int theState)
        {
            if (current.Value is JArray)
            {
                if (level == 1)
                    result += " and ";
                state = theState;
                level = 0;
                result += "(";
                for (int i = 0; i < current.Value.Count; i++)
                {
                    if (i == 1)
                        level = 1;
                    recurse(current.Value[i]);
                }
                result += ")";
            }
            else
            {
                state = theState;
                recurse(new List(current.Value));
            }
        }

        public string Where()
        {
            return result;
        }

        private string ParseAttribute(dynamic obj)
        {
            foreach (FeatureAttribute att in this.atts)
            {
                if (obj.Name == att.Name)
                {
                    var op = obj.Value;
                    var s = "";
                    if (op is JValue)
                        s += att.Name + "='" + op + "'";
                    else if (op["$eq"] != null)
                        s += att.Name + "='" + op["$eq"] + "'";
                    else if (op["$ne"] != null)
                        s += att.Name + "!='" + op["$ne"] + "'";
                    else if (op["$lt"] != null)
                        s += att.Name + "<'" + op["$lt"] + "'";
                    else if (op["$lte"] != null)
                        s += att.Name + "<='" + op["$lte"] + "'";
                    else if (op["$gt"] != null)
                        s += att.Name + ">'" + op["$gt"] + "'";
                    else if (op["$gte"] != null)
                        s += att.Name + ">='" + op["$gte"] + "'";
                    else if (op["$in"] != null)
                    {
                        JArray rango = op["$in"];
                        if (rango.Count() == 2)
                            s += att.Name + " between '" + rango[0] + "' and '" + rango[1] + "'";
                    }
                    else if (op["$nin"] != null)
                    {
                        JArray rango = op["$nin"];
                        if (rango.Count() == 2)
                            s += att.Name + " not between '" + rango[0] + "' and '" + rango[1] + "'";
                    }
                    else
                    {
                        throw new Exception("Syntax error");
                    }
                    return s;
                }
            }
            return null;
        }
    }
}

domingo, junio 03, 2012

MCUCard para ATMega128 (TQFN64)


MCUCard es la extensión de la Multiboard Micro Trainer 3.0 para experimentar con un ATMega128 (128k Flash, 4Kbytes EEPROM, 4Kbytes de RAM interna y TQFN64 [53 I/O]).

Salió casi perfecta, con solo un detalle con respecto a la programación ISP, el ATMega128 no usa las líneas MOSI y MISO, sino PE0 y PE1 (página 300 del datasheet), solucionado con jumpers.

La placa da bastante trabajo, la tuve que hacer con la plancha 5 veces y para soldarlo estuve 3 horas.







martes, mayo 15, 2012

Reconocimiento de voz con AVR de 8 bits


Desde hace unas semanas estoy trabajando en un experimento que espero se convierta en proyecto.

El objetivo es dotar del poder del entendimiento del habla a un robot que estoy armando, muy lentamente, con microcontroladores AVR de gama baja (ATMega8/16/128).

Estos micros poseen entre 0.5KB a 4KB de Memoria SRAM y entre 8KB a 128KB de Flash corriendo de unos pocos Mhz, el reconocimiento del habla típico en PCs requiere de una cantidad de memoria considerable y de una velocidad de proceso de varios GHz.

Debido a estas restricciones me limitaré a reconocer una x cantidad de comandos de voz, como podrían ser "Avanzar", "Retroceder", etc, o bien dígitos, "1", "2", etc.

Bien, comienzo esta locura de experimento y espero llegar a algo.. :D

La voz humana


La voz de una persona se emite en un rango de frecuencias entre 60Hz a 7000Hz, sin embargo podemos considerar que las frecuencias más usuales están entre los 120Hz a 2000Hz. Para capturar este  espectro de frecuencia por la teoría de Nyquist se debe hacer muestras a por lo menos el doble de rápido que la frecuencia más alta de la señal, por lo que nos da unos 4000 muestras por segundo.

El éxito de este experimento estará en lograr esas 4000 muestras/segundo con un uC a, digamos, 16Mhz.

Bueno lo primero que hice fue abordar el problema por partes, he aquí la primera parte, la analógica.

Filtro de frecuencias
Implementé esto en la protoboard, digamos que se trata de un amplificador operacional cuádruple (LM324N) con un filtro pasa alta a la entrada y un pasa bajos a la salida.


Alimenté el circuito con 5v y me puse a disfrutar como bailaba la gráfica en el osciloscopio mientras pensaba como digitalizar esa importante cantidad de información analógica, entonces activé la función Math del osciloscopio, puse FFT y salió esto:

Aquí digo "UNO"

Y aquí "DOS"

En la web encontré una implementación de la FFT (Transformada Rápida de Fourier) escrita por ChaN.

Y hasta aquí llegué por ahora, voy a probar la biblioteca de FFT e intentaré capturar/procesar los valores del ADC, si lo logro el paso siguiente será determinar el comando dictado, ahí el truco estará en hacerlo por correlación (estadística), alguna red neuronal, o ... ya se verá..

18/05/2012 - Captura y procesamiento de ADC

Realicé una prueba sencilla conectando la salida de audio a la entrada ADC0 de un ATmega8A ejecutandose a 16Mhz, con una referencia de voltaje de 5v y enviando datos de captura por puerto serie a la pc.
La velocidad del AVR es más que suficiente para cubrir con la tarea del sampleado, la biblioteca FFT en uso a 64 puntos(muestras) permite un throughput de hasta 19kbps con un tiempo de procesamiento de 3.4ms.
Estos 64 puntos los sumé en un vector de 32 bytes (la mitad útil del resultado de la FFT), la suma la realizo 20 veces y envío el vector de sumas por puerto serie.
Desde la PC tiré los valores a una planilla de cálculo con un gráfico simple que muestra el espectro de audio (de las sumas en realidad).
Capturé los valores de diferentes palabras y estos son los gráficos resultantes, los colores que corresponden a cada uno de los conjuntos obtenidos son:

UNO

DOS

ADELANTE

RETROCEDER



Nota: Aunque la línea azul (dato 1) parezca similar en algunas gráficas, es muy diferente (ver la escala del eje y).

Para hacer el código de la correlación usando tipos de datos flotantes el ATmega8 me queda corto de memoria, voy a tener que armar una plaquita para un ATmega16/32 o 128.

Como pensaba el reconocimiento es totalmente factible utilizando una correlación estadística o una red neuronal.


23/05/2012 - Prueba 001 - Clasificación por distancia

La prueba fue relativamente exitosa (~70% de acierto), como se puede ver en este video:
Lo que está funcionando es un muestreo a 7800Hz, recolectando 16 muestras del espectro (salida del FFT) y su respectiva comparación por distancia con respecto a un patrón almacenado en la flash (atributo PROGMEM en AVR). Como se puede ver depende mucho del tono, duración de la palabra, ruido, etc, estos factores son los que espero mejorar con el algoritmo de redes neuronales del modelo oculto de Markov.


Continuará...

domingo, abril 22, 2012

Tutorial AVR desde 0


Tengo disponible un tutorial de microcontroladores Atmel AVR.
La idea es facilitar el inicio en este mundo para los que conocen el lenguaje C y tienen alguna noción de microcontrolares.


Descarga: http://dl.dropbox.com/u/24861335/Felixls/tutoriales/TutorialAVRdesde0.pdf



Cualquier duda o corrección es bienvenida.
Gracias!

domingo, abril 15, 2012

USBTIR - Transceptor de Infrarrojo por USB


Luego de unos cuantos meses de leer sobre AVR, armar la entrenadora, aprender paso a paso desde un LED Blink, etc, voy a comenzar y publicar mi primer proyecto con micros AVR.

La necesidad
Poseer un control remoto universal, uno de verdad digamos, que soporte cualquier aparato sin que salga 600$ o más como el Logitech Harmony

Todo el que tiene un hijo, o no, sabe perfectamente que el destino de todo control remoto indefectiblemente es el suelo , pagar 600$ (~140 dólares) cada vez que sucede no creo que sea una buena inversión.

La función de estos controles es obvia, es controlar la mayor cantidad de aparatos del hogar, como el TV/VCR/Blue ray/HomeTheater/...
He probado varios de los baratos y , uno medianamente bueno siempre le faltan 5 para el peso y porque no solucionar ese tema en lugar de tener estos controles sobre la mesa

Es por ello que buscando y buscando por la web me encontré con el UIRT y UIRT2, ambos hechos con PIC pero para puerto serie
http://www.fukushima.us/UIRT2/

La idea sería tener el código fuente y que funcione por USB ya que la pc que hace de HTPC en mi hogar no tiene puerto serie y además me sirve para el propósito de aprender a usar VUSB la biblioteca USB de los AVR que no tienen ese soporte por hardware.

Encontré algo que podría ser seguramente la solución peeeeero no es un proyecto open source
http://www.usbuirt.com/overview.htm

USBTIR
Me maté con el nombre :) , las funciones que cumple este aparatejo serían:

* Recibir códigos de un control con un protocolo conocido el cual posea las mayoría de las posibilidades de los otros controles
* Procesar y mapear los códigos a otros protocolos (RC5, RC6, NEC, etc)
* Recibir y decodificar las señales de todos los protocolos conocidos
* Emitir la señal infrarroja con la frecuencia y tiempos especificados
* Permitir controlar una PC via simulación de mouse y teclado
* Aunque sea conectable por usb que funcione sin necesidad de PC (standalone)

Y aquí está como quedó el niño USBTIR

El "chasis" es un trabajo de enanos, hacer esas perforaciones/calados, AAAAA GRRR


Como se puede observar no soldé todos los leds infrarrojos, el motivo es que tal cual está funciona muy bien, y además pienso, a futuro, usar esas salidas por medio de un cable para equipos que no capten la señal fácilmente.


Un mini-video

Licencia
Los archivos e imagenes de este sitio se encuentran disponible bajo la licencia Creative Commons (Attribution-Share Alike 3.0 Unported)


Descargas en la sección correspondiente del blog.

sábado, marzo 24, 2012

USBasp Clone


Cual es el mejor amigo del hobbista electrónico microcontrolero? El programador de microcontroladores!
He aquí una versión modificada del programador de micros Atmel el USBasp

Las características de este programador son:
  • Soporta varios sistemas operativos (Linux, Mac OS y Windows)
  • Fácil de armar
  • Programa a 5kb/s y opcionalmente se puede bajar la velocidad
  • Graba micros trabajando a 2.4v a 5v (el programador no alimenta el target)

Se lo utiliza con AVRDUDE entre otros.

El funcionamiento básico es hacer de puente USB a interfaz SPI no tiene lógica de grabación como en el caso del PICKit2, el trabajo más pesado lo hace el soft del lado de la pc.

A futuro (no muy cercano) el autor tiene pensado agregarle debug por RS232.

Se puede comprar en su sitio oficial a 15 euros, eso creo.

Armar este puede costar unos 18 dólares.

Para grabar el Atmega8 se puede armar un sencillo programador paralelo o serial, en mi caso usé este

Una vez grabado el micro y seteado los fuses se coloca en el programador, se conecta el usb y a quemar código se a dicho.

Ahh, ojo con los fuses en AVR, algunos pueden dejar deshabilitada la programación via SPI y no van a poder hacer nada con el micro (salvo programarlo via programador paralelo).

Licencia
Los archivos e imagenes de este sitio se encuentran disponible bajo la licencia Creative Commons (Attribution-Share Alike 3.0 Unported)


Descargas en la sección correspondiente del blog.

jueves, marzo 22, 2012

Multiboard Micro Trainer 3.0


Luego de otros meses de estar "unplugged" debido a obligaciones retomo el blog para continuar con mis experiencias.

Mucho a pasado con mis intentos de hacer el controlador dual de motores con IRFZ44N, para hacerla corta, no voy a continuar desarrollando para microcontroladores PIC.

Me decidí por la plataforma de microcontroladores AVR por muchas de sus ventajas -soportando sus desventajas- y para hacer mis prácticas necesité rediseñar mi entrenadora

Esta nueva entrenadora la pensé con las siguientes modificaciones:


La anterior Multiboard tiene disponibles puertos A, B, C, D y la mitad del E llegando hasta los mcu de 40 pines, la idea es que soporte micros QFN de 64 pines y que las MCUCards (ex PICCards) sean más anchas para poder dar espacio a una LPCxpresso o un módulo ZIF.

La fuente de alimentación incluida es un tema, no veo la necesidad de colocar dos reguladores con disipador ya que normalmente no se usa más de 1A. Por este motivo incluí un único regulador 7805 (5v) y el otro para 3.3v.
El max232 lo quité dejando esa conectividad como módulo, el cual podría ser un max, o un ft232, un conversor custom, etc.
Debajo de la MCUCard puse una memoria por i2c y un registro de desplazamiento para aumentar las I/O si fuera necesario.
Los cristales están muy alejados de los micros por eso voy a agregar la opción de incluir uno o varios en cada MCUCard.
Entonces, para dar soporte a micros de hasta 64 patas es necesario agregar 6 conectores más, entran 3 y 3 arriba y abajo de la placa.
Y por último están las salidas de 5v y 3.3v que sinceramente no he utilizado jamás y las volé.
Sin más palabras...

Hasta el momento tengo hecha esta MCUCard, es para micros AVR de 28 pines como el ATMEGA8


Esquemáticos
Mother

MCUCard28


Licencia
Los archivos e imagenes de este sitio se encuentran disponible bajo la licencia Creative Commons (Attribution-Share Alike 3.0 Unported)


Descargas en la sección correspondiente del blog.