Traducción


by Transposh - Plugin de traducción para WordPress

Categorías

VUSiBino Demo – Parte II – El Host

El software del host sirve para comunicarse con un dispositivo USB. En este caso nuestro dispositivo ha sido programada con un identificador que impide que otros hosts y programas le puedan enviar mensajes por error. Lo primero que haremos será identificar nuestro dispositivo y ver si está activo, luego aprovechándonos del entorno gráfico de windows (y hasta que termine de ver cómo funcionan wxWidgets) usaremos sus funciones para comunicarnos con él. Nada espectacular, sólo modificaremos el funcionamiento del led en pin13, leeremos y escribiremos un bufer del USB y cambiaremos el número de serie del VUSiBino. Este software está basado en los ejemplos originales de la librería v-USB de objdev y el tutorial de code and life sobre v-USB concienzudamente maltratados para hacerlos irreconocibles y obedientes a mis órdenes. Como decía un jefe mío “no lo quiero bonito, lo quiero YA”.

El código está repartido en varios ficheros por comodidad, imported_functions.c, dialog_functions.c, usb_functions.c y main.cpp. Otros ficheros importantes incluídos son los del directorio libusb, donde residen las rutinas del driver USB, resource.rc, resource.h, manifest.xml y el Readme.txt. Para editar el diálogo he usado resedit.exe, aunque también se pueden usar resource hacker o resed.exe

usb_functions.c

Este archivo contiene las funciones para comunicarse con el pincho, la mayoría son copiadas directamente del ejemplo de CodeAndLife.

Primero definimos dos variables que se van a usar en distintas partes del código, p_hwnDlg y usbSerial. La primera es una referencia al diáologo principal, y dado que no quiero cambiar las definiciones de las funciones copiadas, la hago pública para evitarme líos. Lo mismo con usbSerial, donde se guarda el número de serie del dispositivo para enviarlo o recibirlo.

Tras eso aparece usbGetDescriptorString, una función que sirve para leer la información de un dispositivo usb.

HWND p_hwndDlg;
char usbSerial[256];

// used to get descriptor strings for device identification
static int usbGetDescriptorString(usb_dev_handle *dev, int index, int langid, char *buf, int buflen)
{
    char buffer[256];
    int rval, i;
    // make standard request GET_DESCRIPTOR, type string and given index
    // (e.g. dev->iProduct)
    rval = usb_control_msg(dev, USB_TYPE_STANDARD | USB_RECIP_DEVICE | USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) + index, langid, buffer, sizeof(buffer), 1000);

    if(rval < 0) // error
        return rval;

    // rval should be bytes read, but buffer[0] contains the actual response size
    if((unsigned char)buffer[0] < rval)
        rval = (unsigned char)buffer[0]; // string is shorter than bytes read

    if(buffer[1] != USB_DT_STRING) // second byte is the data type
        return 0; // invalid return type

    // we're dealing with UTF-16LE here so actual chars is half of rval,
    // and index 0 doesn't count
    rval /= 2;

    // lossy conversion to ISO Latin1
    for(i = 1; i < rval && i < buflen; i++)
    {
        if(buffer[2 * i + 1] == 0)
            buf[i-1] = buffer[2 * i];
        else
            buf[i-1] = '?'; // outside of ISO Latin1 range
    }
    buf[i-1] = 0;

    return i-1;
}

Ahora es turno de la otra función directamente copiada usbOpenDevice. Esta función busca un dispositivo en concreto y deja un manejador para referirnos a él, o un error si no lo ha encontrado. La he modificado para que devuelva el número de serie en la variable pública usbSerial (línea 102), y deje en un campo de texto la información del error en caso de darse con la función “appendLogText”. Esta función llama a la anterior, otras funciones de libusb y algunas definiciones del driver para obtener los nombres de dispositivos y enontrar el nuestro.

static usb_dev_handle * usbOpenDevice(int vendor, const char *vendorName, int product, const char *productName)
{

    struct usb_bus *bus;
    struct usb_device *dev;
    char devVendor[256], devProduct[256];
    char strInfo[256]; //Only necessary to interact with dialog
    time_t now = time(0);       //Get time of the event
    struct tm  tstruct;
    char       buf[80];
    tstruct = *localtime(&now);
    strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);

    usb_dev_handle * handle = NULL;
    usb_init();
    usb_find_busses();
    usb_find_devices();

    for(bus=usb_get_busses(); bus; bus=bus->next)
    {
        for(dev=bus->devices; dev; dev=dev->next)
        {
            if(dev->descriptor.idVendor != vendor || dev->descriptor.idProduct != product)
                continue;

            // we need to open the device in order to query strings
            if(!(handle = usb_open(dev)))
            {
                fprintf(stderr, "Warning: cannot open USB device: %s\n", usb_strerror());
                sprintf(strInfo, "%s;Warning: cannot open USB device: %s\r\n", buf,  usb_strerror());
                appendLogText(p_hwndDlg, ID_LOG, strInfo);
                usb_close(handle);
                continue;
            }

            // get vendor name
            if(usbGetDescriptorString(handle, dev->descriptor.iManufacturer, 0x0409, devVendor, sizeof(devVendor)) < 0)
            {
                fprintf(stderr, "Warning: cannot query manufacturer for device: %s\n", usb_strerror());
                sprintf(strInfo, "%s;Warning: cannot query manufacturer for device: %s\r\n", buf, usb_strerror());
                appendLogText(p_hwndDlg, ID_LOG, strInfo);
                usb_close(handle);
                continue;
            }

            // get product name
            if(usbGetDescriptorString(handle, dev->descriptor.iProduct, 0x0409, devProduct, sizeof(devVendor)) < 0)
            {
                fprintf(stderr, "Warning: cannot query product for device: %s\n", usb_strerror());
                sprintf(strInfo, "%s;Warning: cannot query product for device: %s\r\n", buf, usb_strerror());
                appendLogText(p_hwndDlg, ID_LOG, strInfo);
                usb_close(handle);
                continue;
            }
            if(dev->descriptor.iSerialNumber > 0)
            {
               usbGetDescriptorString(handle, dev->descriptor.iSerialNumber,0x0409, usbSerial, sizeof(usbSerial));
            }

            if(strcmp(devVendor, vendorName) == 0 && strcmp(devProduct, productName) == 0)
                return handle;
            else
                usb_close(handle);
        }
    }
    return NULL;
}

Ahora una de las que añadí yo, “sendCommands”. Sirve para mandar comandos a nuestro dispositivo, estos comandos son los descritos al principio de “vusibino.c”, básicamente lo que hacemos es llamar a la función anterior para comprobar que el VUSiBino está enchufado y en caso de ser así llamar a una función “”usb_control_message” que le dice al dispositivo qué es lo que tiene que hacer y qué información adicional requerimos del buffer. Notificamos en un miembro del diálogo principal la acción, y preguntamos si hemos pedido el buffer, en cuyo caso escribimos en un objeto de texto la información recibida. Esto último rquiere de preparar un poco las propiedades del objeto para evitarnos sustos con los formatos de los datos. ya lo describiremos más adelante.

int sendCommands(HWND hwndDlg, int statusline, int statuslabel, int myCommand, const char *statusMsg, const char *labelMsg, bool etiqueta)
{
//usb_control_msg(usb_dev_handle *dev, int requesttype, int request, int value, int index, char *bytes, int size, int timeout);
    char buffer[256]; //Buffer of the messages
    usb_dev_handle *handle = NULL;
    handle = usbOpenDevice(0x16C0, "chafalladas.com", 0x05DC, "VUSiBino");

    if(handle == NULL)
    {
        SetDlgItemText(hwndDlg, ID_STATUS1, "Could not find USB device!\n"); //(ID_STATUS1);
        return 0;
    }
    SetDlgItemText(hwndDlg, statusline, statusMsg); //Notify action in status bar;
    usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_IN, myCommand, 0, 0, (char *)buffer, sizeof(buffer), 5000); //Send command to the vusibino
    usb_close(handle);
    if (labelMsg=="GETBUF")
    {
        SetDlgItemText(hwndDlg, statuslabel, buffer); //Notify action in a label
    }
    return 0;
}

SendBytes la usamos para envar información al USB, es muy similar a SendCommands, pero en usb_control_message le indicamos que estamos enviando información por el “endpoint” de entrada “USB_ENDPOINT_OUT”

int sendBytes(HWND hwndDlg, int statusline, int statuslabel, int myCommand, const char *statusMsg, char *bytesSent)
{
//usb_control_msg(usb_dev_handle *dev, int requesttype, int request, int value, int index, char *bytes, int size, int timeout);

    usb_dev_handle *handle = NULL;
    handle = usbOpenDevice(0x16C0, "chafalladas.com", 0x05DC, "VUSiBino");
    if(handle == NULL)
    {
        SetDlgItemText(hwndDlg, ID_STATUS1, "Could not find USB device!\n"); //(ID_STATUS1);
        return 0;
    }
    SetDlgItemText(hwndDlg, statusline, statusMsg); //Notify action in status bar;
    usb_control_msg(handle, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_ENDPOINT_OUT, myCommand, 0, 0, bytesSent, strlen(bytesSent)+1, 5000); //Send string to the vusibino
    usb_close(handle);
    SetDlgItemText(hwndDlg, ID_SERIAL, usbSerial); //(ID_STATUS1);
    return 0;
}

Por último una función para recoger el número de serie. Basta con abrir el dispositivo yt ver si está conectado.

void GetSerial(HWND hwndDlg)
{
    char strInfo[256]; //Buffer of the messages
    usb_dev_handle *handle = NULL;

    handle = usbOpenDevice(0x16C0, "chafalladas.com", 0x05DC, "VUSiBino");
    if(handle == NULL)
    {
        SetDlgItemText(hwndDlg, ID_STATUS1, "Could not find USB device!\n"); //(ID_STATUS1);
        return;
    }
    else
    {
        SetDlgItemText(hwndDlg, ID_STATUS1, "Found 0x16C0 - chafalladas.com - 0x05DC - VUSiBino"); //(ID_STATUS1);
        SetDlgItemText(hwndDlg, ID_DEVICE, "Device: 0x16C0 - chafalladas.com - 0x05DC - VUSiBino"); //(ID_STATUS1);
        sprintf(strInfo, "Serial number: %s", usbSerial); //Format the log line
        SetDlgItemText(hwndDlg, ID_SERIAL, strInfo); //(ID_STATUS1);
        usb_close(handle);
    }
    return;
}

dialog_functions.c

De momento sólo hay esta función para sumar líneas en un campo de edición. Primero colocamos el cursor al final del texto, enfocando el control y simulando una combinación de teclas, de otra forma el cursor siempre se pone al principio y las líneas se añaden arriba, empujando a las primeras hacia abajo de forma contraria a la que estamos acostumbrados. Luego añadimos la cadena que desamos al texto ya existente. No está pensado para grandes cantidades de información y si queremos que lo haga, es mejor emplear una solución que lea de un fichero porciones manejables de texto y las represente, en oto caso corremos el riesgo de llenar el espacio de memoria del programa con una cantidad gigantesca y poco manejable de texto que afectaría a su rendimiento y el de la máquina. Probad a cargar un fichero de  5MB en notepad o  un programa similar y os haréis una idea de lo que digo.

void appendLogText(HWND hwndDlg, int infoBox, LPCTSTR newText)
{
//Log snippet based on these answers
//https://stackoverflow.com/questions/7200137/appending-text-to-a-win32-editbox-in-c

    int left,right;
    int len = SendMessage(GetDlgItem(hwndDlg, infoBox), EM_GETSEL,(WPARAM)&left,(LPARAM)&right);
    //Set the focus on the text edit control.
    SetFocus( GetDlgItem(hwndDlg, infoBox));
    // Simulate ctrl+page down to put the cursor at the bottom the box.
    HWND temp = GetActiveWindow(); //Prevent the keystrokes to be sent out of the dialog.
    if (temp == hwndDlg) // Then your current window has focus
    {
        keybd_event( VK_CONTROL, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0 );
        keybd_event( VK_END, 0x45, KEYEVENTF_EXTENDEDKEY | 0, 0 );
    // Simulate a key release
        keybd_event( VK_CONTROL, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
        keybd_event( VK_END, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, 0);
    }
    //Enter the text to the log at the end of the textbox
    SendMessage(GetDlgItem(hwndDlg, infoBox), EM_GETSEL,(WPARAM)&left,(LPARAM)&right);
    SendMessage(GetDlgItem(hwndDlg, infoBox), EM_SETSEL, len, len);
    SendMessage(GetDlgItem(hwndDlg, infoBox), EM_REPLACESEL, 0, (LPARAM)newText);
    SendMessage(GetDlgItem(hwndDlg, infoBox), EM_SETSEL,left,right);
}

main.cpp

Aquí definimos la ventana principal y sus acciones. Las llamo directamente desde el bucle del diálogo, lo cual hace el código bastante engorroso.

Tras definir las acciones para el VUSiBino, definimos una variable global para saber el estado del led y otra que es usada para controlar los diálogos.

// same as in vusibino.c
#define USB_LED_BLINK 0
#define USB_LED_ON 1
#define USB_LED_OFF 2
#define USB_SEND_MESSAGE  3
#define USB_READ_MESSAGE  4
#define USB_SET_SERIAL 5

unsigned char sw_led = 0;

//Open dialogs
HINSTANCE hInst;

La función principal es DlgMain, el diáologo donde pondremos los botones, campos e información del estado del dispositivo. Definimos algunas variables que usaremos, como el evento que recoge la ventana, un buffer de texto para enviar y otro para formatear la información. Preguntamos la hora a la que se ha pasado por última vez para reflejarla en el visor de sucesos.

Y cargamos dos imágenes para reflejar el estado del led en la ventana. hLedOn y hLedOff con la función “LoadImageW“.

BOOL CALLBACK DlgMain(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
int wmEvent; //Identifiers of control and eventes related with them
char buffer[256], strInfo[256]; //Buffer of the messages

time_t now = time(0); //Get time of the event
struct tm tstruct;
char buf[80];

HICON hLedOn;
hLedOn = (HICON)LoadImageW(GetModuleHandleW(NULL), MAKEINTRESOURCEW(ID_LED_ON), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0);
HICON hLedOff;
hLedOff = (HICON)LoadImageW(GetModuleHandleW(NULL), MAKEINTRESOURCEW(ID_LED_OFF), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0);

 

Acto seguido comparamos la variable uMsg para buscar mensajes del sistema relacionados con nuestro diálogo.

El mensaje “WM_INITDIALOG” salta cuando creamos el diálogo, en ese momento ponemos un icono a la ventana, dibujamos el icono del led y preguntamos en número de serie del dispositivo. Salimos del bucle diciendo que hemos hecho algo.

“WM_CLOSE” se lanza cuando el usuario pincha en la “x” de una ventana para cerrarla. La acción que tomamos es cerrar el diálogo y con él el programa.

switch(uMsg)
{
case WM_INITDIALOG: //Initialize dialog
{

#define USB_CFG_DEVICE_NAME_LEN 10
/* Same as above for the device name. If you don't want a device name, undefine
* the macros. See the file USB-IDs-for-free.txt before you assign a name if
* you use a shared VID/PID.
*/
// Serial PLT001

SendMessage(GetDlgItem(hwndDlg, ID_CHK_LED_OFF), BM_SETCHECK, BST_CHECKED, 0);
SendMessage(GetDlgItem(hwndDlg, ID_LED), STM_SETIMAGE, IMAGE_ICON, (LPARAM)hLedOff);

HICON hIcon;
hIcon = (HICON)LoadImageW(GetModuleHandleW(NULL), MAKEINTRESOURCEW(IDI_ICON1), IMAGE_ICON, GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), 0);
if (hIcon)
{
SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)hIcon);
}

GetSerial(hwndDlg);
}
return TRUE;

case WM_CLOSE: //Close Dialog
{
EndDialog(hwndDlg, 0);
}
return TRUE;

Si hay otros comandos, el mensaje “WM_COMMAND” nos lo dirá. Guardamos el momento en que se recogió el comando y miramos los parámetros del comando, el la palabra superior estará la información que necesitamos, en este caso la etiqueta de identificación del objeto que haya pulsado el usuario en la ventana de diálogo. Estas etiquetas las podemos definir con sencillez usando alguno de los programas de editor de recursos anteriormente mencionados.

ID_CLOSE es el nombre del botón de cerrar el diálogo.

ID_SETSERIAL es el nombre del botón de poner un número de serie nuevo al VUSiBino, con “Edit_GetLine” leemos el texto que se ha escrito en un campo de edición y lo guardamos en “p_serial”. Por compatibilidad con las funciones de libusb, tenemos que activar la propiedad “OEM_Convert” del control, en otro caso es muy posible que terminemos con bytes de sobra en la cadena. Tras eso llamamos a sendBytes diciendo que hwndDlg es la ventana donde están los elementos a modificar, “ID_STATUS1”, donde se reflejará la acción tomada, indicamos que usaremos la operación “USB_SET_SERIAL” al usb, que escibiremos la cadena “Set serial” en la línea de estado y que el nuevo número de serie es “p_serial”. Con GetSerial escribimos el nuevo número de serie en el control que nos informa de tal dato.

Si pinchamos el botón “Send” ID_SEND será el mensaje enivado al diálogo, enviaremos “USB_SEND_MESSAGE” al dispositivo y el contenido de “ID_EDIT” se copiará a p_message para ser tratado por “sendBytes”.

ID_RECEIVE es el identificador del botón Receive, que pide al VUSiBino la cadena que guarde en su buffer y la muestra en el control de texto estático ID_RETURN. Le indicamos que la lea y la formatee con “GETBUF”.

case WM_COMMAND: //Various commands processing
{

tstruct = *localtime(&now);
strftime(buf, sizeof(buf), "%Y-%m-%d.%X", &tstruct);

wmEvent = HIWORD(wParam);//These look kewl I'll save them for later

switch(LOWORD(wParam))
{
case ID_CLOSE: //Close
{
EndDialog(hwndDlg, 0);
}
return TRUE;

case ID_SETSERIAL: //Set new serial number
{
char p_serial[16];
Edit_GetLine(GetDlgItem(hwndDlg, ID_T_SERIAL),0,p_serial,16);
sendBytes(hwndDlg, ID_STATUS1, ID_STATUS1, USB_SET_SERIAL, "Set Serial", p_serial);
GetSerial(hwndDlg);
}
return TRUE;
case ID_SEND: //Set buffer text
{
char p_message[16];
Edit_GetLine(GetDlgItem(hwndDlg, ID_EDIT),0,p_message,16);
sendBytes(hwndDlg, ID_STATUS1, ID_STATUS1, USB_SEND_MESSAGE, "Sent message", p_message);
}
return TRUE;
case ID_RECEIVE: //Get buffer text
{
sendCommands(hwndDlg, ID_STATUS1, ID_RETURN, USB_READ_MESSAGE, "Read message", "GETBUF", true);
}
return TRUE;

“ID_CHK_LED_ON” y los otros dos “ID_CHK_LED_…” son similares. Una ves sabemos que se ha pulsado comprobamos que ha sido pulsado dicho control (pasar por encima el ratón puede mandar el mensaje también), enviando un mensaje, “BM_GETCHECK” asociado a ese control. Si así ha sido, lo escribimos en el log con la hora que tomamos antes con appendLogText, enviamos un mensaje al diálogo principal para que cambie el icono de led encendido o apagado y enviamos el comando correspondiente al dispositivo con sendCommands.

case ID_CHK_LED_ON:
{
if (wmEvent == BN_CLICKED) //Is option clicked?
{
LRESULT chkState = SendMessage(GetDlgItem(hwndDlg, ID_CHK_LED_ON), BM_GETCHECK, 0, 0); //Which is the status?
if (chkState == BST_CHECKED)
{
sprintf(strInfo, "%s;Led ON -===========================-\r\n", buf); //Format the log line
appendLogText(hwndDlg, ID_LOG, strInfo);
sw_led = 1;
SendMessage(GetDlgItem(hwndDlg, ID_LED), STM_SETIMAGE, IMAGE_ICON, (LPARAM)hLedOn);
sendCommands(hwndDlg, ID_STATUS1, ID_STATUS1, USB_LED_ON, "Led ON -===========================-","Pin as output",true);
}
}
}
return TRUE;

Esto es todo lo relacionado con la demo de VUSiBino para windows. La versión multiplataforma tardará un poco en llegar, en el siguiente envío trataremos de cómo compilar los codigos, subir el correspondiente firmware y la preparación del siguiente circuito, VUSiBIno tachometer, para leer el tacómetro de un ventilador (y cualquier otro que envíe pulsos) y usar PWM para controlar su velocidad. Todo el código actualizado se encuentra en Github.

Ficheros.

Esquemas

Binarios

Código fuente.

Github.

VUSiBino Demo en Github

 

Deja un comentario