SimpleJoyCon 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- simplejoycon-1.0.0/PKG-INFO +15 -0
- simplejoycon-1.0.0/README.md +77 -0
- simplejoycon-1.0.0/SimpleJoyCon/__init__.py +2 -0
- simplejoycon-1.0.0/SimpleJoyCon/componentes.py +51 -0
- simplejoycon-1.0.0/SimpleJoyCon/joycon_l.py +136 -0
- simplejoycon-1.0.0/SimpleJoyCon/joycon_r.py +140 -0
- simplejoycon-1.0.0/SimpleJoyCon.egg-info/PKG-INFO +15 -0
- simplejoycon-1.0.0/SimpleJoyCon.egg-info/SOURCES.txt +11 -0
- simplejoycon-1.0.0/SimpleJoyCon.egg-info/dependency_links.txt +1 -0
- simplejoycon-1.0.0/SimpleJoyCon.egg-info/requires.txt +1 -0
- simplejoycon-1.0.0/SimpleJoyCon.egg-info/top_level.txt +1 -0
- simplejoycon-1.0.0/setup.cfg +4 -0
- simplejoycon-1.0.0/setup.py +18 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: SimpleJoyCon
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Una libreria de bajo nivel simplificada, estetica y sin comillas para usar Joy-Cons en Python
|
|
5
|
+
Author: Instituto Internacional de Cosas Que Se Salieron de Control (Emicicx)
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Requires-Dist: hidapi
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: classifier
|
|
13
|
+
Dynamic: requires-dist
|
|
14
|
+
Dynamic: requires-python
|
|
15
|
+
Dynamic: summary
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# SimpleJoyCon 🕹️🚀
|
|
2
|
+
|
|
3
|
+
Una librería de Python de bajo nivel, ultra simplificada, estética y **sin comillas** para conectar y exprimir al máximo el hardware de tus Nintendo Joy-Cons mediante Bluetooth nativo.
|
|
4
|
+
|
|
5
|
+
A diferencia de otras opciones confusas, `SimpleJoyCon` maneja los hilos y decodificaciones internamente, ofreciendo una sintaxis de grado profesional orientada a objetos accesibles mediante atributos con puntos.
|
|
6
|
+
|
|
7
|
+
## ✨ Características Principales
|
|
8
|
+
* **Cero Comillas:** Olvídate de los diccionarios viejos. Accede a los componentes de forma estética (`joy.botones.A`, `joy.palanca.direccion`).
|
|
9
|
+
* **Conexión Automática Inteligente:** Si el control está apagado, la librería se queda esperando pacientemente y lo detecta en cuanto presionas un botón, sin tirar errores.
|
|
10
|
+
* **Modo Terremoto Desbloqueado:** Control de vibración masiva mediante inyección directa a frecuencias de resonancia física continua (`joy.vibracion(1.0)`).
|
|
11
|
+
* **Cronómetros de Pulsación:** Rastreo automático de tiempo milimétrico en milisegundos reales por botón (`joy.botones.A.ms`).
|
|
12
|
+
* **Contador de Clicks:** Registra de forma nativa dobles o triples clics rápidos (`joy.botones.A.clicks == 2`).
|
|
13
|
+
* **Modo Raw de Fábrica:** Consulta los valores numéricos directos de los potenciómetros (`joy.palanca.raw`) y sensores sin procesar.
|
|
14
|
+
* **Sensores de Movimiento 3D:** Acceso limpio a los 6 ejes espaciales del Giroscopio y Acelerómetro.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 🛠️ Instalación
|
|
19
|
+
Asegúrate de vincular tus Joy-Cons por Bluetooth en la configuración de tu sistema operativo y luego instala la librería mediante pip:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pip install SimpleJoyCon
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🚀 Ejemplo de Uso (Dúo L / R)
|
|
28
|
+
Mira la limpieza visual y estética que tiene tu código al usar esta librería:
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from SimpleJoyCon import L, R
|
|
32
|
+
import time
|
|
33
|
+
|
|
34
|
+
# Inicializar mandos (Iniciarán en bucle de auto-espera si están apagados)
|
|
35
|
+
joy_derecho = R()
|
|
36
|
+
joy_izquierdo = L()
|
|
37
|
+
|
|
38
|
+
try:
|
|
39
|
+
while True:
|
|
40
|
+
# Refrescar buffers de hardware
|
|
41
|
+
joy_derecho.actualizar()
|
|
42
|
+
joy_izquierdo.actualizar()
|
|
43
|
+
|
|
44
|
+
# Telemetría básica sin comillas y Modo Raw de porcentaje
|
|
45
|
+
print(f"Batería R: {joy_derecho.bateria.raw}% | Palanca R: {joy_derecho.palanca.direccion}", end="\r")
|
|
46
|
+
|
|
47
|
+
# 1. Tu sintaxis limpia ideal: el botón evalúa directo como Booleano
|
|
48
|
+
if joy_derecho.botones.A:
|
|
49
|
+
print(f"\n[BOTÓN A] Hundido por {joy_derecho.botones.A.ms} ms")
|
|
50
|
+
|
|
51
|
+
# Detectar Doble Click nativo
|
|
52
|
+
if joy_derecho.botones.A.clicks == 2:
|
|
53
|
+
print("¡Doble click registrado!")
|
|
54
|
+
|
|
55
|
+
# Si pasa de un segundo, ¡desata el Terremoto continuo!
|
|
56
|
+
if joy_derecho.botones.A.ms > 1000:
|
|
57
|
+
joy_derecho.vibracion(1.0)
|
|
58
|
+
else:
|
|
59
|
+
joy_derecho.vibracion(0.0)
|
|
60
|
+
|
|
61
|
+
# 2. Leer Giroscopio tridimensional del mando izquierdo de forma fluida
|
|
62
|
+
if abs(joy_izquierdo.giroscopio.x) > 40.0:
|
|
63
|
+
print(f"\nRotación L en X: {joy_izquierdo.giroscopio.x}°/s")
|
|
64
|
+
|
|
65
|
+
# Salida limpia
|
|
66
|
+
if joy_derecho.botones.Home or joy_izquierdo.botones.Capture:
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
time.sleep(0.002)
|
|
70
|
+
|
|
71
|
+
finally:
|
|
72
|
+
joy_derecho.cerrar()
|
|
73
|
+
joy_izquierdo.cerrar()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 📜 Licencia
|
|
77
|
+
Este proyecto está bajo la licencia MIT. ¡Siéntete libre de expandirlo y usarlo en tus proyectos de emulación, robótica o videojuegos!
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
class Boton:
|
|
4
|
+
def __init__(self, ms=0, clicks=0):
|
|
5
|
+
self.ms = ms
|
|
6
|
+
self.clicks = clicks
|
|
7
|
+
def __bool__(self):
|
|
8
|
+
return self.ms > 0
|
|
9
|
+
|
|
10
|
+
class Palanca:
|
|
11
|
+
def __init__(self):
|
|
12
|
+
self.x = 0.0
|
|
13
|
+
self.y = 0.0
|
|
14
|
+
self.direccion = 'CENTRO'
|
|
15
|
+
self.raw = [0, 0]
|
|
16
|
+
|
|
17
|
+
class Bateria:
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.estado = 'Desconocido'
|
|
20
|
+
self.raw = 0
|
|
21
|
+
def __str__(self):
|
|
22
|
+
return self.estado
|
|
23
|
+
|
|
24
|
+
class SensorMovimiento:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.x = 0.0
|
|
27
|
+
self.y = 0.0
|
|
28
|
+
self.z = 0.0
|
|
29
|
+
self.raw = [0, 0, 0]
|
|
30
|
+
|
|
31
|
+
class ContenedorBotonesR:
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self.A = Boton()
|
|
34
|
+
self.B = Boton()
|
|
35
|
+
self.X = Boton()
|
|
36
|
+
self.Y = Boton()
|
|
37
|
+
self.R = Boton()
|
|
38
|
+
self.ZR = Boton()
|
|
39
|
+
self.Plus = Boton()
|
|
40
|
+
self.Home = Boton()
|
|
41
|
+
|
|
42
|
+
class ContenedorBotonesL:
|
|
43
|
+
def __init__(self):
|
|
44
|
+
self.DArriba = Boton()
|
|
45
|
+
self.DAbajo = Boton()
|
|
46
|
+
self.DIzquierda = Boton()
|
|
47
|
+
self.DDerecha = Boton()
|
|
48
|
+
self.L = Boton()
|
|
49
|
+
self.ZL = Boton()
|
|
50
|
+
self.Minus = Boton()
|
|
51
|
+
self.Capture = Boton()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import hid
|
|
2
|
+
import time
|
|
3
|
+
from .componentes import ContenedorBotonesL, Palanca, Bateria, SensorMovimiento, Boton
|
|
4
|
+
|
|
5
|
+
class JoyconL:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.packet_counter = 0
|
|
8
|
+
self.botones = ContenedorBotonesL()
|
|
9
|
+
self.palanca = Palanca()
|
|
10
|
+
self.bateria = Bateria()
|
|
11
|
+
self.giroscopio = SensorMovimiento()
|
|
12
|
+
self.acelerometro = SensorMovimiento()
|
|
13
|
+
self._lista_nombres = ['DArriba', 'DAbajo', 'DIzquierda', 'DDerecha', 'L', 'ZL', 'Minus', 'Capture']
|
|
14
|
+
self._tiempos_inicio = {n: None for n in self._lista_nombres}
|
|
15
|
+
self._ultimo_click_tiempo = {n: 0 for n in self._lista_nombres}
|
|
16
|
+
self._conteo_clicks_interno = {n: 0 for n in self._lista_nombres}
|
|
17
|
+
|
|
18
|
+
print("[SimpleJoycon] Buscando Joy-Con Izquierdo (L) por Bluetooth...")
|
|
19
|
+
|
|
20
|
+
# BUCLE DE AUTO-CONEXIÓN INTELIGENTE
|
|
21
|
+
while True:
|
|
22
|
+
try:
|
|
23
|
+
self.device = hid.device()
|
|
24
|
+
self.device.open(0x057E, 0x2006)
|
|
25
|
+
self.device.set_nonblocking(True)
|
|
26
|
+
break
|
|
27
|
+
except Exception:
|
|
28
|
+
print(" -> Control Izquierdo no detectado. Reintentando... (Presiona un botón en el Joy-Con)", end="\r")
|
|
29
|
+
time.sleep(1.0)
|
|
30
|
+
|
|
31
|
+
print("\n[OK] ¡Joy-Con L Conectado!")
|
|
32
|
+
self._enviar_subcomando(0x48, [0x01])
|
|
33
|
+
self._enviar_subcomando(0x40, [0x01])
|
|
34
|
+
|
|
35
|
+
def _next_id(self):
|
|
36
|
+
c = self.packet_counter & 0x0F
|
|
37
|
+
self.packet_counter += 1
|
|
38
|
+
return c
|
|
39
|
+
|
|
40
|
+
def _enviar_subcomando(self, subcmd_id, data_bytes):
|
|
41
|
+
packet = [0x01, self._next_id()]
|
|
42
|
+
packet.extend([0x01, 0x00, 0x40, 0x40, 0x01, 0x00, 0x40, 0x40])
|
|
43
|
+
packet.append(subcmd_id)
|
|
44
|
+
packet.extend(data_bytes)
|
|
45
|
+
packet += [0x00] * (49 - len(packet))
|
|
46
|
+
self.device.write(bytes(packet))
|
|
47
|
+
time.sleep(0.01)
|
|
48
|
+
|
|
49
|
+
def vibracion(self, fuerza):
|
|
50
|
+
fuerza = max(0.0, min(1.0, float(fuerza)))
|
|
51
|
+
if fuerza == 0.0:
|
|
52
|
+
bytes_rumble = [0x01, 0x00, 0x40, 0x40, 0x01, 0x00, 0x40, 0x40]
|
|
53
|
+
else:
|
|
54
|
+
freq_alta_byte, freq_baja_byte = 0x04, 0x01
|
|
55
|
+
amp_alta = int(0x02 + (fuerza * (0x15 - 0x02)))
|
|
56
|
+
amp_baja = int(0x4D | 0x80) if fuerza == 1.0 else int(0x40 + (fuerza * (0x4D - 0x40)))
|
|
57
|
+
bytes_rumble = [freq_alta_byte, amp_alta, freq_baja_byte, amp_baja] * 2
|
|
58
|
+
packet = [0x01, self._next_id()]
|
|
59
|
+
packet.extend(bytes_rumble)
|
|
60
|
+
packet += [0x00] * (49 - len(packet))
|
|
61
|
+
self.device.write(bytes(packet))
|
|
62
|
+
|
|
63
|
+
def luces(self, led1=False, led2=False, led3=False, led4=False):
|
|
64
|
+
p = 0x00
|
|
65
|
+
if led1: p |= 0x01
|
|
66
|
+
if led2: p |= 0x02
|
|
67
|
+
if led3: p |= 0x04
|
|
68
|
+
if led4: p |= 0x08
|
|
69
|
+
self._enviar_subcomando(0x30, [p])
|
|
70
|
+
|
|
71
|
+
def _procesar_estado_boton(self, nombre, esta_pulsado_ahora):
|
|
72
|
+
ahora = time.perf_counter()
|
|
73
|
+
if esta_pulsado_ahora:
|
|
74
|
+
if self._tiempos_inicio[nombre] is None:
|
|
75
|
+
self._tiempos_inicio[nombre] = ahora
|
|
76
|
+
if ahora - self._ultimo_click_tiempo[nombre] < 0.35:
|
|
77
|
+
self._conteo_clicks_interno[nombre] += 1
|
|
78
|
+
else:
|
|
79
|
+
self._conteo_clicks_interno[nombre] = 1
|
|
80
|
+
self._ultimo_click_tiempo[nombre] = ahora
|
|
81
|
+
delta_ms = int((ahora - self._tiempos_inicio[nombre]) * 1000)
|
|
82
|
+
setattr(self.botones, nombre, Boton(delta_ms, self._conteo_clicks_interno[nombre]))
|
|
83
|
+
else:
|
|
84
|
+
self._tiempos_inicio[nombre] = None
|
|
85
|
+
if ahora - self._ultimo_click_tiempo[nombre] > 0.35:
|
|
86
|
+
self._conteo_clicks_interno[nombre] = 0
|
|
87
|
+
setattr(self.botones, nombre, Boton(0, self._conteo_clicks_interno[nombre]))
|
|
88
|
+
|
|
89
|
+
def actualizar(self):
|
|
90
|
+
sol = [0x01, self._next_id(), 0x01, 0x00, 0x40, 0x40, 0x01, 0x00, 0x40, 0x40, 0x00]
|
|
91
|
+
sol += [0x00] * (49 - len(sol))
|
|
92
|
+
self.device.write(bytes(sol))
|
|
93
|
+
packet = self.device.read(64)
|
|
94
|
+
if not packet or len(packet) < 48:
|
|
95
|
+
for n in self._lista_nombres:
|
|
96
|
+
self._procesar_estado_boton(n, True if getattr(self.botones, n) else False)
|
|
97
|
+
return
|
|
98
|
+
b1, b2 = packet[3], packet[4]
|
|
99
|
+
raw_down, raw_up, raw_right, raw_left = bool(b1 & 0x01), bool(b1 & 0x02), bool(b1 & 0x04), bool(b1 & 0x08)
|
|
100
|
+
raw_l, raw_zl = bool(b1 & 0x40), bool(b1 & 0x80)
|
|
101
|
+
raw_minus, raw_capture = bool(b2 & 0x01), bool(b2 & 0x20)
|
|
102
|
+
rb = (packet[2] & 0xF0) >> 4
|
|
103
|
+
if rb >= 8: self.bateria.estado, self.bateria.raw = 'Llena', 100
|
|
104
|
+
elif rb >= 6: self.bateria.estado, self.bateria.raw = 'Media', 75
|
|
105
|
+
elif rb >= 4: self.bateria.estado, self.bateria.raw = 'Baja', 25
|
|
106
|
+
else: self.bateria.estado, self.bateria.raw = 'Critica', 5
|
|
107
|
+
raw_s = packet[9:12]
|
|
108
|
+
sx = raw_s[0] | ((raw_s[1] & 0x0F) << 8)
|
|
109
|
+
sy = (raw_s[1] >> 4) | (raw_s[2] << 4)
|
|
110
|
+
self.palanca.raw = [int(sx), int(sy)]
|
|
111
|
+
self.palanca.x, self.palanca.y = round((sx - 2048) / 1300.0, 2), round((sy - 2048) / 1300.0, 2)
|
|
112
|
+
if abs(self.palanca.x) < 0.25 and abs(self.palanca.y) < 0.25: self.palanca.direccion = 'CENTRO'
|
|
113
|
+
elif abs(self.palanca.x) > abs(self.palanca.y): self.palanca.direccion = 'DERECHA' if self.palanca.x > 0 else 'IZQUIERDA'
|
|
114
|
+
else: self.palanca.direccion = 'ARRIBA' if self.palanca.y > 0 else 'ABAJO'
|
|
115
|
+
off = 37
|
|
116
|
+
to_16 = lambda l, h: (l | (h << 8)) if (l | (h << 8)) < 32768 else (l | (h << 8)) - 65536
|
|
117
|
+
ax, ay, az = to_16(packet[off], packet[off+1]), to_16(packet[off+2], packet[off+3]), to_16(packet[off+4], packet[off+5])
|
|
118
|
+
self.acelerometro.raw = [ax, ay, az]
|
|
119
|
+
self.acelerometro.x, self.acelerometro.y, self.acelerometro.z = round(ax*0.000244,3), round(ay*0.000244,3), round(az*0.000244,3)
|
|
120
|
+
gx, gy, gz = to_16(packet[off+6], packet[off+7]), to_16(packet[off+8], packet[off+9]), to_16(packet[off+10], packet[off+11])
|
|
121
|
+
self.giroscopio.raw = [gx, gy, gz]
|
|
122
|
+
self.giroscopio.x, self.giroscopio.y, self.giroscopio.z = round(gx*0.061,2), round(gy*0.061,2), round(gz*0.061,2)
|
|
123
|
+
self._procesar_estado_boton('DArriba', raw_up)
|
|
124
|
+
self._procesar_estado_boton('DAbajo', raw_down)
|
|
125
|
+
self._procesar_estado_boton('DIzquierda', raw_left)
|
|
126
|
+
self._procesar_estado_boton('DDerecha', raw_right)
|
|
127
|
+
self._procesar_estado_boton('L', raw_l)
|
|
128
|
+
self._procesar_estado_boton('ZL', raw_zl)
|
|
129
|
+
self._procesar_estado_boton('Minus', raw_minus)
|
|
130
|
+
self._procesar_estado_boton('Capture', raw_capture)
|
|
131
|
+
|
|
132
|
+
def cerrar(self):
|
|
133
|
+
self.luces(False,False,False,False)
|
|
134
|
+
self._enviar_subcomando(0x40, [0x00])
|
|
135
|
+
for _ in range(5): self.vibracion(0.0)
|
|
136
|
+
self.device.close()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import hid
|
|
2
|
+
import time
|
|
3
|
+
from .componentes import ContenedorBotonesR, Palanca, Bateria, SensorMovimiento, Boton
|
|
4
|
+
|
|
5
|
+
class JoyconR:
|
|
6
|
+
def __init__(self):
|
|
7
|
+
self.packet_counter = 0
|
|
8
|
+
self.botones = ContenedorBotonesR()
|
|
9
|
+
self.palanca = Palanca()
|
|
10
|
+
self.bateria = Bateria()
|
|
11
|
+
self.giroscopio = SensorMovimiento()
|
|
12
|
+
self.acelerometro = SensorMovimiento()
|
|
13
|
+
self._lista_nombres = ['A', 'B', 'X', 'Y', 'R', 'ZR', 'Plus', 'Home']
|
|
14
|
+
self._tiempos_inicio = {n: None for n in self._lista_nombres}
|
|
15
|
+
self._ultimo_click_tiempo = {n: 0 for n in self._lista_nombres}
|
|
16
|
+
self._conteo_clicks_interno = {n: 0 for n in self._lista_nombres}
|
|
17
|
+
|
|
18
|
+
print("[SimpleJoycon] Buscando Joy-Con Derecho (R) por Bluetooth...")
|
|
19
|
+
|
|
20
|
+
# BUCLE DE AUTO-CONEXIÓN INTELIGENTE
|
|
21
|
+
while True:
|
|
22
|
+
try:
|
|
23
|
+
self.device = hid.device()
|
|
24
|
+
self.device.open(0x057E, 0x2007)
|
|
25
|
+
self.device.set_nonblocking(True)
|
|
26
|
+
break # Si logra abrir el dispositivo, rompe el bucle y continúa
|
|
27
|
+
except Exception:
|
|
28
|
+
# Si falla (porque el control está apagado), espera 1 segundo y reintenta
|
|
29
|
+
print(" -> Control Derecho no detectado. Reintentando...", end="\r")
|
|
30
|
+
time.sleep(1.0)
|
|
31
|
+
|
|
32
|
+
# Inicialización de hardware una vez conectado
|
|
33
|
+
print("\n[OK] ¡Joy-Con R conectado!")
|
|
34
|
+
self._enviar_subcomando(0x48, [0x01])
|
|
35
|
+
self._enviar_subcomando(0x40, [0x01])
|
|
36
|
+
except Exception as e:
|
|
37
|
+
print('[SimpleJoycon ERROR R]:', e)
|
|
38
|
+
raise
|
|
39
|
+
|
|
40
|
+
def _next_id(self):
|
|
41
|
+
c = self.packet_counter & 0x0F
|
|
42
|
+
self.packet_counter += 1
|
|
43
|
+
return c
|
|
44
|
+
|
|
45
|
+
def _enviar_subcomando(self, subcmd_id, data_bytes):
|
|
46
|
+
packet = [0x01, self._next_id()]
|
|
47
|
+
packet.extend([0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40])
|
|
48
|
+
packet.append(subcmd_id)
|
|
49
|
+
packet.extend(data_bytes)
|
|
50
|
+
packet += [0x00] * (49 - len(packet))
|
|
51
|
+
self.device.write(bytes(packet))
|
|
52
|
+
time.sleep(0.01)
|
|
53
|
+
|
|
54
|
+
def vibracion(self, fuerza):
|
|
55
|
+
fuerza = max(0.0, min(1.0, float(fuerza)))
|
|
56
|
+
if fuerza == 0.0:
|
|
57
|
+
bytes_rumble = [0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40]
|
|
58
|
+
else:
|
|
59
|
+
freq_alta_byte, freq_baja_byte = 0x04, 0x01
|
|
60
|
+
amp_alta = int(0x02 + (fuerza * (0x15 - 0x02)))
|
|
61
|
+
amp_baja = int(0x4D | 0x80) if fuerza == 1.0 else int(0x40 + (fuerza * (0x4D - 0x40)))
|
|
62
|
+
bytes_rumble = [freq_alta_byte, amp_alta, freq_baja_byte, amp_baja] * 2
|
|
63
|
+
packet = [0x01, self._next_id()]
|
|
64
|
+
packet.extend(bytes_rumble)
|
|
65
|
+
packet += [0x00] * (49 - len(packet))
|
|
66
|
+
self.device.write(bytes(packet))
|
|
67
|
+
|
|
68
|
+
def luces(self, led1=False, led2=False, led3=False, led4=False):
|
|
69
|
+
p = 0x00
|
|
70
|
+
if led1: p |= 0x01
|
|
71
|
+
if led2: p |= 0x02
|
|
72
|
+
if led3: p |= 0x04
|
|
73
|
+
if led4: p |= 0x08
|
|
74
|
+
self._enviar_subcomando(0x30, [p])
|
|
75
|
+
|
|
76
|
+
def _procesar_estado_boton(self, nombre, esta_pulsado_ahora):
|
|
77
|
+
ahora = time.perf_counter()
|
|
78
|
+
if esta_pulsado_ahora:
|
|
79
|
+
if self._tiempos_inicio[nombre] is None:
|
|
80
|
+
self._tiempos_inicio[nombre] = ahora
|
|
81
|
+
if ahora - self._ultimo_click_tiempo[nombre] < 0.35:
|
|
82
|
+
self._conteo_clicks_interno[nombre] += 1
|
|
83
|
+
else:
|
|
84
|
+
self._conteo_clicks_interno[nombre] = 1
|
|
85
|
+
self._ultimo_click_tiempo[nombre] = ahora
|
|
86
|
+
delta_ms = int((ahora - self._tiempos_inicio[nombre]) * 1000)
|
|
87
|
+
setattr(self.botones, nombre, Boton(delta_ms, self._conteo_clicks_interno[nombre]))
|
|
88
|
+
else:
|
|
89
|
+
self._tiempos_inicio[nombre] = None
|
|
90
|
+
if ahora - self._ultimo_click_tiempo[nombre] > 0.35:
|
|
91
|
+
self._conteo_clicks_interno[nombre] = 0
|
|
92
|
+
setattr(self.botones, nombre, Boton(0, self._conteo_clicks_interno[nombre]))
|
|
93
|
+
|
|
94
|
+
def actualizar(self):
|
|
95
|
+
sol = [0x01, self._next_id(), 0x00, 0x01, 0x40, 0x40, 0x00, 0x01, 0x40, 0x40, 0x00] + [0x00]*38
|
|
96
|
+
self.device.write(bytes(sol))
|
|
97
|
+
packet = self.device.read(64)
|
|
98
|
+
if not packet or len(packet) < 48:
|
|
99
|
+
for n in self._lista_nombres:
|
|
100
|
+
self._procesar_estado_boton(n, True if getattr(self.botones, n) else False)
|
|
101
|
+
return
|
|
102
|
+
b1, b2 = packet[3], packet[4]
|
|
103
|
+
raw_y, raw_x, raw_b, raw_a = bool(b1 & 0x01), bool(b1 & 0x02), bool(b1 & 0x04), bool(b1 & 0x08)
|
|
104
|
+
raw_r, raw_zr = bool(b1 & 0x40), bool(b1 & 0x80)
|
|
105
|
+
raw_plus, raw_home = bool(b2 & 0x02), bool(b2 & 0x10)
|
|
106
|
+
rb = (packet[2] & 0xF0) >> 4
|
|
107
|
+
if rb >= 8: self.bateria.estado, self.bateria.raw = 'Llena', 100
|
|
108
|
+
elif rb >= 6: self.bateria.estado, self.bateria.raw = 'Media', 75
|
|
109
|
+
elif rb >= 4: self.bateria.estado, self.bateria.raw = 'Baja', 25
|
|
110
|
+
else: self.bateria.estado, self.bateria.raw = 'Critica', 5
|
|
111
|
+
raw_s = packet[6:9]
|
|
112
|
+
sx = raw_s[0] | ((raw_s[1] & 0x0F) << 8)
|
|
113
|
+
sy = (raw_s[1] >> 4) | (raw_s[2] << 4)
|
|
114
|
+
self.palanca.raw = [int(sx), int(sy)]
|
|
115
|
+
self.palanca.x, self.palanca.y = round((sx - 2048) / 1300.0, 2), round((sy - 2048) / 1300.0, 2)
|
|
116
|
+
if abs(self.palanca.x) < 0.25 and abs(self.palanca.y) < 0.25: self.palanca.direccion = 'CENTRO'
|
|
117
|
+
elif abs(self.palanca.x) > abs(self.palanca.y): self.palanca.direccion = 'DERECHA' if self.palanca.x > 0 else 'IZQUIERDA'
|
|
118
|
+
else: self.palanca.direccion = 'ARRIBA' if self.palanca.y > 0 else 'ABAJO'
|
|
119
|
+
off = 37
|
|
120
|
+
to_16 = lambda l, h: (l | (h << 8)) if (l | (h << 8)) < 32768 else (l | (h << 8)) - 65536
|
|
121
|
+
ax, ay, az = to_16(packet[off], packet[off+1]), to_16(packet[off+2], packet[off+3]), to_16(packet[off+4], packet[off+5])
|
|
122
|
+
self.acelerometro.raw = [ax, ay, az]
|
|
123
|
+
self.acelerometro.x, self.acelerometro.y, self.acelerometro.z = round(ax*0.000244,3), round(ay*0.000244,3), round(az*0.000244,3)
|
|
124
|
+
gx, gy, gz = to_16(packet[off+6], packet[off+7]), to_16(packet[off+8], packet[off+9]), to_16(packet[off+10], packet[off+11])
|
|
125
|
+
self.giroscopio.raw = [gx, gy, gz]
|
|
126
|
+
self.giroscopio.x, self.giroscopio.y, self.giroscopio.z = round(gx*0.061,2), round(gy*0.061,2), round(gz*0.061,2)
|
|
127
|
+
self._procesar_estado_boton('A', raw_a)
|
|
128
|
+
self._procesar_estado_boton('B', raw_b)
|
|
129
|
+
self._procesar_estado_boton('X', raw_x)
|
|
130
|
+
self._procesar_estado_boton('Y', raw_y)
|
|
131
|
+
self._procesar_estado_boton('R', raw_r)
|
|
132
|
+
self._procesar_estado_boton('ZR', raw_zr)
|
|
133
|
+
self._procesar_estado_boton('Plus', raw_plus)
|
|
134
|
+
self._procesar_estado_boton('Home', raw_home)
|
|
135
|
+
|
|
136
|
+
def cerrar(self):
|
|
137
|
+
self.luces(False,False,False,False)
|
|
138
|
+
self._enviar_subcomando(0x40, [0x00])
|
|
139
|
+
for _ in range(5): self.vibracion(0.0)
|
|
140
|
+
self.device.close()
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: SimpleJoyCon
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Una libreria de bajo nivel simplificada, estetica y sin comillas para usar Joy-Cons en Python
|
|
5
|
+
Author: Instituto Internacional de Cosas Que Se Salieron de Control (Emicicx)
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Requires-Dist: hidapi
|
|
11
|
+
Dynamic: author
|
|
12
|
+
Dynamic: classifier
|
|
13
|
+
Dynamic: requires-dist
|
|
14
|
+
Dynamic: requires-python
|
|
15
|
+
Dynamic: summary
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
setup.py
|
|
3
|
+
SimpleJoyCon/__init__.py
|
|
4
|
+
SimpleJoyCon/componentes.py
|
|
5
|
+
SimpleJoyCon/joycon_l.py
|
|
6
|
+
SimpleJoyCon/joycon_r.py
|
|
7
|
+
SimpleJoyCon.egg-info/PKG-INFO
|
|
8
|
+
SimpleJoyCon.egg-info/SOURCES.txt
|
|
9
|
+
SimpleJoyCon.egg-info/dependency_links.txt
|
|
10
|
+
SimpleJoyCon.egg-info/requires.txt
|
|
11
|
+
SimpleJoyCon.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
hidapi
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
SimpleJoyCon
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="SimpleJoyCon",
|
|
5
|
+
version="1.0.0",
|
|
6
|
+
description="Una libreria de bajo nivel simplificada, estetica y sin comillas para usar Joy-Cons en Python",
|
|
7
|
+
author="Instituto Internacional de Cosas Que Se Salieron de Control (Emicicx)",
|
|
8
|
+
packages=find_packages(),
|
|
9
|
+
install_requires=[
|
|
10
|
+
"hidapi",
|
|
11
|
+
],
|
|
12
|
+
classifiers=[
|
|
13
|
+
"Programming Language :: Python :: 3",
|
|
14
|
+
"License :: OSI Approved :: MIT License",
|
|
15
|
+
"Operating System :: Microsoft :: Windows",
|
|
16
|
+
],
|
|
17
|
+
python_requires=">=3.7",
|
|
18
|
+
)
|