Usar Arduino como si fuera un ratón

Para simular un ratón con una placa Arduino Leonardo contamos con la librería Mouse.h. Al igual que vimos con el teclado hay que usar las funciones Mouse.begin() y Mouse.end() para indicar que se empieza y se termina la simulación de ratón.

Se distingue entre la acción de pulsar un botón del ratón y la de presionar y luego soltar. Para simular la pulsación de un botón se puede usar la función Mouse.click(button). Mientras que para presionar se usa Mouse.press(button) y para liberar Mouse.release(button) (no existe un releaseAll para el ratón). La variable button hace referencia a una de las siguientes constantes definidas en la libreria Mouse.h :

  • MOUSE_LEFT
  • MOUSE_RIGHT
  • MOUSE_MIDDLE

Para simular el movimiento del ratón podemos usar la función Mouse.move(xVal, yVal, wheel) siendo los dos primeros parámetros la cantidad de movimiento (no, la posición) en el eje X y en el Y de la pantalla. El tercero indica el desplazamiento de la rueda central del ratón. Estos desplazamientos pueden ser positivos o negativos en un rango de -128 y 127 (izquierda-derecha, arriba-abajo). Su valor es un poco confuso ya que se refiere a «lo que se ha movido el ratón en ese eje» y afecta al cursor desde su posición actual. No es fácil trasladar ese valor a pixeles ya que también depende, entre otras cosas, de como el ordenador al que este conectado interprete esos valores. Una de las pegas de esta forma de trabajar es que el movimiento del ratón es relativo a las coordenadas actuales del cursor en la pantalla por lo que es difícil situar el ratón en un punto exacto de la misma. El truco para hacerlo con cierta precisión es llevar el ratón a una esquina de la pantalla y desde ahí tratar de moverlo al punto deseado. Vemoa sun ejemplo muy básico de esta idea:

import "Mouse.h"

void setup() {
  Mouse.begin();
}

void loop() {

  //Mover a la esquina superior izquierda
  for(int i = 0; i < 20; i++){
	Mouse.move(-128, -128, 0);
  }

  //Mover a un punto determinado
  for(int i = 0; i < 10; i++){
	Mouse.move(50, 20, 0);
  }
  
  Mouse.click(MOUSE_LEFT);

  delay(5000);
}

Este texto mejorado y ampliado forma parte de mi libro sobre como mejorar tus programas en Arduino. Puedes echarle un vistazo aquí.

Usar Arduino como si fuera un teclado

Las placas Arduino basados en los microcontroladores 32u4 o SAMD (Leonardo, Esplora, Zero, Due y MKR) pueden simular ser un teclado conectado al puerto USB. Para ello es necesario usar la librería Keyboard.h. Lo primero para simular un teclado es llamar a la función Keyboard.begin() y para finalizar la simulación Keyboard.end().

Antes de comenzar con el resto de las funciones hay que hacer una aclaración. No es lo mismo pulsar que presionar, presionar es solo el gesto de bajar la tecla sin liberarla mientras que pulsar consiste en presionar y liberar la tecla.

Para simular la pulsación de una tecla podemos usar Keyboard.write(char) a la que se pasa como parámetro el código ASCII del carácter. El código del carácter se puede pasar de varias formas:

//Simular la pulsación de la tecla A
Keyboard.write('A');        //Character
Keyboard.write(65);         //Decimal          
Keyboard.write(0x41);       //Hexadecimal       
Keyboard.write(0b01000001); //Binario

Al permitir enviarle el código de la tecla en diversos formatos podremos usarlo para simular teclas que no impriman un carácter como pueden ser la teclas con flechas de dirección. Para ello la librería incluye un listado de constantes que representan el valor de estas teclas.

Keyboard.write(KEY_UP_ARROW);    //Flecha arriba
Keyboard.write(KEY_DOWN_ARROW);  //Flecha abajo          
Keyboard.write(KEY_RIGHT_ARROW); //Flecha derecha      
Keyboard.write(KEY_LEFT_ARROW;   //Flecha Izquierda

Si queremos simular la pulsación de varios caracteres alfanuméricos podemos usar la funciones Keyboard.print(string) y Keyboard.println(string) que reciben como parámetro un String y simulan la pulsación de todos sus caracteres, println ademas añade un salto de linea al final.

Para simular una tecla presionada se puede usar Keyboard.press(char) se pueden pulsar varias teclas a la vez. Incluso combinar funciones, por ejemplo el siguiente código seria como teclear «hola» con la tecla «Mayús» pulsada.

Keyboard.press(KEY_RIGHT_SHIFT);
Keyboard.print("hola"); //HOLA
Keyboard.release(KEY_RIGHT_SHIFT);

Para liberar una tecla tenemos dos funciones: Keyboard.release(char) que libera la tecla que le pases como parámetro y Keyboard.releaseAll() que libera todas las teclas que estén presionadas.

Veamos el ejemplo completo:

#include <Keyboard.h>

void setup() {
  Keyboard.begin();
}

void loop(){
  Keyboard.press(KEY_RIGHT_SHIFT);
  Keyboard.print("hola"); //HOLA
  Keyboard.release(KEY_RIGHT_SHIFT);
}

Listado de constantes definidas en Keyboard.h

  • KEY_LEFT_CTRL
  • KEY_LEFT_SHIFT
  • KEY_LEFT_ALT
  • KEY_LEFT_GUI
  • KEY_RIGHT_CTRL
  • KEY_RIGHT_SHIFT
  • KEY_RIGHT_ALT
  • KEY_RIGHT_GUI
  • KEY_UP_ARROW
  • KEY_DOWN_ARROW
  • KEY_LEFT_ARROW
  • KEY_RIGHT_ARROW
  • KEY_BACKSPACE
  • KEY_TAB
  • KEY_RETURN
  • KEY_ESC
  • KEY_INSERT
  • KEY_DELETE
  • KEY_PAGE_UP
  • KEY_PAGE_DOWN
  • KEY_HOME
  • KEY_END
  • KEY_CAPS_LOCK
  • KEY_F1
  • KEY_F2
  • KEY_F3
  • KEY_F4
  • KEY_F5
  • KEY_F6
  • KEY_F7
  • KEY_F8
  • KEY_F9
  • KEY_F10
  • KEY_F11
  • KEY_F12
  • KEY_F13
  • KEY_F14
  • KEY_F15
  • KEY_F16
  • KEY_F17
  • KEY_F18
  • KEY_F19
  • KEY_F20
  • KEY_F21
  • KEY_F22
  • KEY_F23
  • KEY_F24

Este texto mejorado y ampliado forma parte de mi libro sobre como mejorar tus programas en Arduino. Puedes echarle un vistazo aquí.

Trabajar con datos tipados de un dispositivo desde una página web

Cada vez hay más integración entre la web y la domática, «internet of things» o «web of things» son ejemplos de ello. Uno de los problemas que te puedes encontrar es que a nivel de programación son dos mundos muy dispares. En los dispositivos electrónicos reina C/C++ y similares, mientras que en la web el más usado es JavaScript. Aunque ambos lenguajes no trabajan directamente uno con otro lo hacen a través de API a veces surge el problema tratar los datos y es que mientras que C tiene un tipado de datos muy específico el de JS es menos concreto. ¿Qué podemos hacer cuando necesitamos un tipado de datos muy concreto que nos permita incluso trabajar a nivel de bits?. Simplemente usar datos tipados en JS.

Datos tipados en JavaScript

Pese a su fama JS tiene más tipos de datos que los conocidos String, boolean, number.

En este caso vamos a centrarnos en los siguientes tipos que podemos usar para imitar los tipos habituales de C/C++. Podemos elegir el tamaño en bits, si es con signo o sin signo incluso la forma en que se comporta cuando hay un desbordamiento. La única pega, solo funciona con arrays lo que nos supone una incomodidad a la hora de trabajar con un único elemento (tendremos que usar un array de tamaño 1).

TipoRangoTamaño (bytes)DescripciónTipo en C/C++
Int8Array -128…12718-bit con signoint8_t
Uint8Array 0…25518-bit sin signouint8_t
Uint8ClampedArray 0…25518-bit sin signo (clamped)uint8_t
Int16Array -32768…32767216-bit entero con signoint16_t
Uint16Array 0…65535216-bit entero sin signouint16_t
Int32Array -2147483648… 2147483647432-bit entero con signoint32_t
Uint32Array 0…4294967295432-bit entero sin signouint32_t
Float32Array 1.2E-38…3.4E38432-bit punto flotantefloat
Float64Array 5E-324…1.8E308864-bit punto flotantedouble
BigInt64Array -2^63…2^63 – 1864-bit entero con signoint64_t
BigUint64Array 0…2^64 – 1864-bit entero sin signouint64_t

Lo valores en punto flotante siguen el estándar IEEE.

Veamos algunos ejemplos de como usarlos y alguna propiedades interesantes (BYTES_PER_ELEMENT, byte que ocupa cada elemento; byteLength, longitud total del array en bytes):

var int8 = new Int8Array(3);
int8[0] = 42;
console.log(int8[0]); //42
console.log(int8.length); //3
console.log(int8.BYTES_PER_ELEMENT); //1
console.log(int8.byteLength); //3 = length * BYTES_PER_ELEMENT

var int16 = new Int16Array(3);
int16[0] = 42;
console.log(int16[0]); //42
console.log(int16.length); //3
console.log(int16.BYTES_PER_ELEMENT); //2
console.log(int16.byteLength); //6 = length * BYTES_PER_ELEMENT

Un dato clamped (acotado) es un dato que cuando se produce un desbordamiento de su valor por arriba o por abajo el dato toma su mayor y menor valor. O más simplemente explicado si intentas asignarle un valor mayor de 255 o menor que 0 se le asignará el valor 255 y 0 respectivamente. Lo datos que no son de tipo clamped simplemente ignoran los bits del desbordamiento. Podemos verlo mejor con un ejemplo:

var uInt8Clamped = new Uint8ClampedArray(1);
var uInt8 = new Uint8Array(1);

uInt8Clamped[0] = 256;
uInt8[0] = 256;
console.log(uInt8Clamped[0]); //255 
console.log(uInt8[0]); //0

uInt8Clamped[0] = 265;
uInt8[0] = 265;
console.log(uInt8Clamped[0]); //255 
console.log(uInt8[0]); //9

ArrayBuffer y Dataview

Aún hay un truco más que permite JS para trabajar con estos datos. Crear un ArrayBuffer que permite reservar un «espacio de memoria» indicando el tamaño del mismo en bytes. Desde un ArrayBuffer no puedes ni leer ni escribir esta memoria, para ello tienes que asignarlo a un array tipado como los uqe hemos visto antes. Puedes asignar el mismo ArrayBuffer a varios arrays tipados que lo compartirán:

var buffer = new ArrayBuffer(2);
var view8 = new Uint8Array(buffer);
var view16 = new Uint16Array(buffer);
view16[0] = 258;
console.log(view16[0]);// 258 -> 00000001 00000010
console.log(view8[0]); // 2 -> 00000010
console.log(view8[1]); // 1 -> 00000001

En el ejemplo se ve como un ArrayBuffer de 2 bytes se puede leer desde un array de 1 elemento de 16 bits o un array de un array de 2 elementos de 8 bits y como los valores se solapan.

Hay otra forma de hacerlo, con un DataView. Las ventajas de los DataView son dos:

  • Permiten escribir y leer en el ArrayBuffer con cualquier tipo de datos usando el métodoset y get correspondiente getUint8(), setUint8(), getInt8(), setInt8(), getUint16(), setUint16(), …
  • El orden en que se recuperan los bytes es más intuitivo

El ejemplo anterior con DataView

const buffer = new ArrayBuffer(2);
var view = new DataView(buffer, 0);
view.setUint16(0, 258); // (max unsigned 16-bit integer)
console.log(view.getUint16(0));// 258 -> 00000001 00000010
console.log(view.getUint8(0)); // 1 -> 00000001
console.log(view.getUint8(1)); // 2 -> 00000010