Ir al contenido principal

Usando DualShock 4 en C/C++

Hace unas semanas empecé a trabajar en un proyecto con un coche de radio control y se me ocurrió la manera de manejarlo con el control del PS4, el DualShock 4 (DS4).

¿Por qué C/C++?

Existen varias bibliotecas para manejar el DS4 por ejemplo, el ds4drv1 pero dado que el proyecto en el que estoy trabajando está escrito en C/C++ necesito desarrollar la biblioteca.

¿Cómo funciona el DS4?

El DS4 es un dispositivo HID2 este se puede conectar por medio de USB o Bluetooth (ver compatibilidad3)
Para establecer la conexión con el DS4 se tiene que enviar el VendorId y ProductId, una vez establecida la conexión este envía "reportes" los cuales son tramas de 64 bytes que contienen información del estado de los botones y del DS4.

La tabla 1 muestra la descripción del reporte que consta de 64 bytes.
byte index bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
[0] Report ID (USB)
[1] Dualshock Left stick X axis (0 = Stick Left - Move left left)
[2] Dualshock Left stick Y axis (0 = Stick Left - Move up up)
[3] Dualshock Right stick Right Stick axis X
[4] Dualshock Right stick Right Stick axis Y
[5] Dualshock triangle button Dualshock circle button Dualshock cross button Dualshock square button D-PAD : hat format (0x08 is released)
1000
0111 0110 0101 0100 0011 0010 0001 0000
7=NW 6=W 5=SW 4=S 3=SE 2=E 1=NE 0=N
↑← ↓← ↓→ ↑→
[6] Dualshock R3 button Dualshock L3 button Dualshock option button Dualshock share button Dualshock R2 button (8) Dualshock L2 button (4) Dualshock R1 button (2) Dualshock L1 button (1)
[7] Counter (counts up by 1 per report) T-PAD click (2) Dualshock PS button (1)
[8] Dualshock L2 button Trigger (0 = released/unpressed, 0xFF = fully pressed)
[9] Dualshock R2 button Trigger
[10] Unknown, seems to count downwards, non-random pattern
[11] Unknown, seems to count upwards by 3, but by 2 when [10] underflows
[12] Battery Level
[13] Unknown
[14 - 15] Accel Z (signed): orientation acceleration measures
[16 - 17] Accel Y
[18 - 19] Accel X
[20 - 21] Gyro X: orientation measures
[22 - 23] Gyro Y
[24 - 25] Gyro Z
[26 - 29] Unknown
[30] EXT/HeadSet/Earset: bitmask
  • 01111011 is headset with mic (0x7B)
  • 00111011 is headphones (0x3B)
  • 00011011 is nothing attached (0x1B)
  • 00001000 is bluetooth? (0x08)
  • 00000101 is ? (0x05)
[31 - 32] Unknown: speculation: theses 5 next (reserved) nibbles for future additional products
[33] Unknown: speculation: could be bitmaps for control commands like volume, etc. T-PAD event active:
(seen only)

  • 0x00: No data for T-PAD
  • 0x01: Set data for 2 current touches
  • 0x02: set data for previous touches at [44-51]
[34] T-PAD: auto incrementing number to track last update?
[35] 0 if finger №1 is down. T-PAD: tracking numbers, unique to each finger (№1) down, so for each lift and repress, it gets a newly incremented figure.
[36 - 38] T-PAD: each finger (№1) location/positional data: static upon finger lifting, to maintain state.
To decode, each coordinated (x & y) is using 12 bits, you need to mask/split and swap the middle byte : e.g: 0x8a 4|0 28 → 0x08a 284 → x= 138 y= 644
[39] 0 if finger №2 is down. T-PAD: tracking numbers, unique to each finger (№2) down.
[40 - 42] T-PAD: each finger (№2) location.
[44 - 47] T-PAD: the previous touches (№1) track and location
[48 - 51] T-PAD: the previous touches (№2) track and location
[52 - 63] TODO
Tabla 1: Estructura del reporte.

Para configurar el led o activar los vibradores se envía una trama de 11 bytes.
La tabla 2 muestra la descripción del reporte a enviar de 11 bytes.
Tabla 2: Descripción de trama para enviar al DS4.
byte index bit 7 bit 6 bit 5 bit 4 bit 3 bit 2 bit 1 bit 0
[0] 0x05
[1] 0x00
[2] 0x04
[3] 0xf0 disables the rumble motors, 0xf3 enables them
[4] Rumble (right / weak)
[5] Rumble (left / strong)
[6] RGB color (Red)
[7] RGB color (Green)
[8] RGB color (Blue)
[9] Unknown
[10] Unknown

Codificado

Para el ejemplo se desarrolló sobre el sistema operativo GNU/Linux Ubuntu 16.04 y utilizando la biblioteca de hidapi4.

Lo primero es instalar la biblioteca de hidapi, terminado la instalación se tienen que declarar unas udev rules para el funcionamiento sin acceso root.

Abrir o crear el archivo
/etc/udev/rules.d/61-dualshock.rules
Se declara lo siguiente:
SUBSYSTEM=="input", GROUP="input", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="0268", MODE:="666", GROUP="plugdev"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev"

SUBSYSTEM=="input", GROUP="input", MODE="0666"
SUBSYSTEM=="usb", ATTRS{idVendor}=="054c", ATTRS{idProduct}=="05c4", MODE:="666", GROUP="plugdev"
KERNEL=="hidraw*", SUBSYSTEM=="hidraw", MODE="0664", GROUP="plugdev"
Recargar udev rules
sudo udevadm control --reload-rules

El siguiente código es un ejemplo que lee los botones y jostick del DS4.
//============================================================================
//       _       _         _   _
//      /_\  ___| | ____ _| |_| |
//     //_\\/ __| |/ / _` | __| |
//    /  _  \__ \   < (_| | |_| |
//    \_/ \_/___/_|\_\__,_|\__|_|
//
// Name        : Ds4.h
// Author      : Marco Antonio Cruz
// Version     : 0.01v
// Description : Macros de DualShock 4.
// Referencias : http://www.psdevwiki.com/ps4/DS4-USB
// Gcc flags   : -lhidapi-hidraw -lhidapi-libusb
//============================================================================

#ifndef DS4_H_
#define DS4_H_

#define DS4_VENDOR_ID 1356
#define DS4_PRODUCT_ID 1476

/**
 * Mascara de botones
 */
#define BTN_NORTH  0x00
#define BTN_NORTH_EAST 0x01
#define BTN_EAST  0x02
#define BTN_SOUTH_EAST 0x03
#define BTN_SOUTH  0x04
#define BTN_SOUTH_WEST 0x05
#define BTN_WEST  0x06
#define BTN_NORTH_WEST 0x07

#define BTN_TRIANGLE 0x80
#define BTN_CIRCLE  0x40
#define BTN_CROSS  0x20
#define BTN_SQUARE  0x10

#endif /* DS4_H_ */
//============================================================================
//       _       _         _   _
//      /_\  ___| | ____ _| |_| |
//     //_\\/ __| |/ / _` | __| |
//    /  _  \__ \   < (_| | |_| |
//    \_/ \_/___/_|\_\__,_|\__|_|
//
// Name        : Ds4.cxx
// Author      : Marco Antonio Cruz
// Version     : 0.01v
// Description : Ejemplo de uso de DualShock 4.
// Referencias : http://www.psdevwiki.com/ps4/DS4-USB
//============================================================================

#include <iostream>
#include <stdio.h>
#include <unistd.h>
#include <hidapi.h>
#include "ds4.h"

using namespace std;

/**
 * Lectura del stick izquierdo.
 * Buffer Data format
 * byte index  bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
 * [1]      Axi x
 * [2]      Axi y
 */
void readLeftStick(unsigned char buf[]) {
 wprintf(L"Left Axi x: %x, y: %x\n", buf[1], buf[2]);
}

/**
 * Lectura del stick derecho.
 * byte index  bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
 * [3]      Axi x
 * [4]      Axi y
 */
void readRightStick(unsigned char buf[]) {
 wprintf(L"Right: Axi x: %x, y: %x\n", buf[3], buf[4]);
}

/**
 * Lectura de botones: triangulo, circulo, equis y cuadrado.
 * Buffer Data format
 * byte index  bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
 * [5]  &  ()    X []  x  x  x   x
 *
 * & : Triangulo
 * (): Circulo
 * X : Equis
 * []: Cuadrado
 * x : No importa
 */
void readButtons(unsigned char buf[]) {
 if (buf[5] & BTN_TRIANGLE) {
  wprintf(L"Triangle\n");
 }
 if (buf[5] & BTN_CIRCLE) {
  wprintf(L"Circle\n");
 }
 if (buf[5] & BTN_CROSS) {
  wprintf(L"Cross\n");
 }
 if (buf[5] & BTN_SQUARE) {
  wprintf(L"Square\n");
 }
}

/**
 * Lectura del pad.
 * Buffer Data format
 * byte index  bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
 * [5]   x       x   x  x  1  0  0  0
 *
 *  - 1000
 * ↑← 0111
 * ← 0110
 * ↓← 0101
 * ↓ 0100
 * ↓→ 0011
 * → 0010
 * ↑→ 0001
 * ↑ 0000
 */
void readPad(unsigned char buf[]) {
 switch (buf[5] & 0xF) {
 case BTN_NORTH:
  wprintf(L"North\n");
  break;
 case BTN_NORTH_EAST:
  wprintf(L"North east\n");
  break;
 case BTN_EAST:
  wprintf(L"East\n");
  break;
 case BTN_SOUTH_EAST:
  wprintf(L"South east\n");
  break;
 case BTN_SOUTH:
  wprintf(L"South\n");
  break;
 case BTN_SOUTH_WEST:
  wprintf(L"South west\n");
  break;
 case BTN_WEST:
  wprintf(L"Old west\n");
  break;
 case BTN_NORTH_WEST:
  wprintf(L"North west\n");
  break;
 default:
  break;
 }
}

/**
 * Lectura de datos del DS4.
 */
void read(hid_device *handle) {
 unsigned char buf[64];
 int res = hid_read_timeout(handle, buf, 64, 200);
 if (res == -1 || res == 0) {
  wprintf(L"error reading data\n");
  return;
 }
 readPad(buf);
 readButtons(buf);
 readLeftStick(buf);
 readRightStick(buf);
}

/**
 * Envia informacion al DS4.
 * Buffer Data format 11
 * byte index  bit 7  bit 6  bit 5  bit 4  bit 3  bit 2  bit 1  bit 0
 * [0]   0x05
 * [1]   0x00
 * [2]   0x04
 * [3]   0xf0 disables the rumble motors, 0xf3 enables them
 * [4]   Rumble (right / weak)
 * [5]   Rumble (left / strong)
 * [6]   RGB color (Red)
 * [7]   RGB color (Green)
 * [8]   RGB color (Blue)
 * [9]   Unknown
 * [10]   Unknown
 */
void write(hid_device *handle) {
 const unsigned char rojo = 0xff;
 const unsigned char verde = 0x00;
 const unsigned char azul = 0x0f;
 const unsigned char strongRumble = 0x0F;
 const unsigned char weakRumble = 0xF0;
 unsigned char sendBuf[] = { 5, 255, 4, 0, weakRumble, strongRumble, rojo, verde, azul, 0, 0 };
 hid_write(handle, sendBuf, 11);
}

int main(int argc, char* argv[]) {

 int res;
 unsigned char buf[64];
 wchar_t wstr[64];
 hid_device *handle;

 if ((res = hid_init()) != 0) {
  wprintf(L"unable hid init\n");
  return -1;
 }

        /**Envio vendorId y productId para establecer conexion**/
 handle = hid_open(DS4_VENDOR_ID, DS4_PRODUCT_ID, NULL);
 if (handle == NULL) {
  printf("unable to open device\n");
  return -1;
 }

        /**Imprimo informacion del HID**/
 res = hid_get_manufacturer_string(handle, wstr, 64);
 wprintf(L"Manufacturer String: %s\n", wstr);
 res = hid_get_product_string(handle, wstr, 64);
 wprintf(L"Product String: %s\n", wstr);
 res = hid_get_serial_number_string(handle, wstr, 64);
 wprintf(L"Serial Number String: (%d) %s\n", wstr[0], wstr);
 res = hid_get_indexed_string(handle, 1, wstr, 64);
 wprintf(L"Indexed String 1: %s\n", wstr);
 res = hid_read(handle, buf, 64);
        /**Activo el led y los vibradores**/
 write(handle);
        /**Bucle para leer los botones**/
 while (true) {
                /**Leo los botones**/
  read(handle);
 }

 res = hid_exit();
 return 0;
}


[1] [Christopher Rosell]. (2013). ds4drv. Retrieved from https://github.com/chrippa/ds4drv  
[2] [Wikipedia]. (2004). HID. Retrieved from https://es.wikipedia.org/wiki/HID  
[3] [Christopher Rosell]. (2013). Bluetooth dongle compatibility. Retrieved from https://github.com/chrippa/ds4drv/wiki/Bluetooth-dongle-compatibility  
[4] [Alan Ott]. (2010). Hidapi. Retrieved from https://github.com/signal11/hidapi

Comentarios

Entradas más populares de este blog

Problemas resueltos de álgebra lineal - ESCOM

Me gustaría compartir un doc que me ayudo mucho cuando estudiaba en la ESCOM. Son un conjunto de problemas de álgebra lineal resueltos paso a paso, esto ayuda mucho a la hora del examen (En especial cuando haces el ETS xD) Los autores son:       M.C. Florencio Guzmán Aguilar       Dr. Samuel Domínguez Hernández

Instalado Cuda + Tensorflow + Jupyter con Conda en Ubuntu 20.04

Una forma alternativa de instalar tensorflow + cuda en Ubuntu, la ventaja de esta forma es que es más sencillo y se pueden tener varias versiones de tensorflow con cuda. También es posible crear un kernel para jupyter y tener diferentes opciones a la hora de usarlo.

Clasificador de gatos y perros usando TensorFlow

El otro día estaba buscando información acerca de redes de convolución y encontré un estupendo video 1 en youtube, el cual explicaba como realizar un clasificador de imágenes en 5 min, así que dije ¿Por que no hago un clasificador de gatos y perros, pero a mi estilo?  - why not?. Configurando todo Básicamente es utilizar el ejemplo de TensorFlow llamado retrain.py que es un script de entrenamiento de una red de convolución Inception-v3 2 y entrenar nuestra red con las imágenes que queremos clasificar, en este caso gatos y perros. Vamos a necesitar muchas pero muchas imágenes de gatos y perros así que  para obtener las imágenes de nuestro entrenamiento tenemos dos posibles ideas. * Ir a Imagenet y obtener el archivo con todas las url de las imágenes y usando wget descargarlas. * Descargar las imágenes de google images utilizando un plugin para chrome o firefox.  Una vez obtenidas las imágenes para nuestro entrenamiento vamos a crear nuestro proyecto, primero tenemos qu