SimpleJoyCon 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,2 @@
1
+ from .joycon_r import JoyconR as R
2
+ from .joycon_l import JoyconL as L
@@ -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,8 @@
1
+ SimpleJoyCon/__init__.py,sha256=1gKz6KvfulmTRZPIaaPkQ27xVfgJXDtU_dVTkt23xaI,72
2
+ SimpleJoyCon/componentes.py,sha256=_UmKcXcSE4RajZ66vzWrID4qSSKd3QvEe5pukhb21Vo,1195
3
+ SimpleJoyCon/joycon_l.py,sha256=WaWXX72NOF-Zzv3qsBHeUuXgMnNvhiebxNLB2Pewv6w,6807
4
+ SimpleJoyCon/joycon_r.py,sha256=fB-tH0LoGYfR_G13guKHNjsQls_Jv2Uq_5OupcpyN2A,6955
5
+ simplejoycon-1.0.0.dist-info/METADATA,sha256=qL1UmyIZX3taYhLbneYdhoOs2ro1myybG5H5lA1r3dE,550
6
+ simplejoycon-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ simplejoycon-1.0.0.dist-info/top_level.txt,sha256=OjgLCN5h9dGRUu_RnV4nsSjB47l6zTL3ftJ3gOFkOhM,13
8
+ simplejoycon-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ SimpleJoyCon