Merge pull request #11 from redphx/feature/improve_tracking
Improve tracking
This commit is contained in:
		
						commit
						e1b95e5763
					
				
					 4 changed files with 92 additions and 71 deletions
				
			
		
							
								
								
									
										3
									
								
								dance.py
									
										
									
									
									
								
							
							
						
						
									
										3
									
								
								dance.py
									
										
									
									
									
								
							|  | @ -165,9 +165,6 @@ async def connect_joycon(app, ws, data): | ||||||
|         host_ip_addr=host_ip_addr, |         host_ip_addr=host_ip_addr, | ||||||
|         console_ip_addr=console_ip_addr, |         console_ip_addr=console_ip_addr, | ||||||
|         on_state_changed=on_joydance_state_changed, |         on_state_changed=on_joydance_state_changed, | ||||||
|         accel_acquisition_freq_hz=config['accel_acquisition_freq_hz'], |  | ||||||
|         accel_acquisition_latency=config['accel_acquisition_latency'], |  | ||||||
|         accel_max_range=config['accel_max_range'], |  | ||||||
|     ) |     ) | ||||||
|     app['joydance_connections'][serial] = joydance |     app['joydance_connections'][serial] = joydance | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ import random | ||||||
| import socket | import socket | ||||||
| import ssl | import ssl | ||||||
| import time | import time | ||||||
|  | import traceback | ||||||
| from enum import Enum | from enum import Enum | ||||||
| from urllib.parse import urlparse | from urllib.parse import urlparse | ||||||
| 
 | 
 | ||||||
|  | @ -11,10 +12,9 @@ import aiohttp | ||||||
| import websockets | import websockets | ||||||
| 
 | 
 | ||||||
| from .constants import (ACCEL_ACQUISITION_FREQ_HZ, ACCEL_ACQUISITION_LATENCY, | from .constants import (ACCEL_ACQUISITION_FREQ_HZ, ACCEL_ACQUISITION_LATENCY, | ||||||
|                         ACCEL_MAX_RANGE, ACCEL_SEND_RATE, JOYCON_UPDATE_RATE, |                         ACCEL_MAX_RANGE, FRAME_DURATION, SHORTCUT_MAPPING, | ||||||
|                         SHORTCUT_MAPPING, UBI_APP_ID, UBI_SKU_ID, |                         UBI_APP_ID, UBI_SKU_ID, WS_SUBPROTOCOLS, Command, | ||||||
|                         WS_SUBPROTOCOLS, Command, JoyConButton, |                         JoyConButton, WsSubprotocolVersion) | ||||||
|                         WsSubprotocolVersion) |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class PairingState(Enum): | class PairingState(Enum): | ||||||
|  | @ -68,6 +68,9 @@ class JoyDance: | ||||||
|         self.is_input_allowed = False |         self.is_input_allowed = False | ||||||
|         self.available_shortcuts = set() |         self.available_shortcuts = set() | ||||||
| 
 | 
 | ||||||
|  |         self.accel_data = [] | ||||||
|  |         self.last_accel = (0, 0, 0) | ||||||
|  | 
 | ||||||
|         self.ws = None |         self.ws = None | ||||||
|         self.disconnected = False |         self.disconnected = False | ||||||
| 
 | 
 | ||||||
|  | @ -239,52 +242,84 @@ class JoyDance: | ||||||
|         async for message in self.ws: |         async for message in self.ws: | ||||||
|             await self.on_message(message) |             await self.on_message(message) | ||||||
| 
 | 
 | ||||||
|     async def send_accelerometer_data(self): |     async def tick(self): | ||||||
|         accel_data = [] |         sleep_duration = FRAME_DURATION * 0.75 | ||||||
|         delta_time = 0 |         last_time = time.time() | ||||||
| 
 |         frames = 0 | ||||||
|         end = time.time() |  | ||||||
| 
 | 
 | ||||||
|         while True: |         while True: | ||||||
|             if self.disconnected: |             if self.disconnected: | ||||||
|                 break |                 break | ||||||
| 
 | 
 | ||||||
|  |             # Make sure it runs at exactly 60 FPS | ||||||
|  |             while True: | ||||||
|  |                 time_now = time.time() | ||||||
|  |                 dt = time_now - last_time | ||||||
|  |                 if dt >= FRAME_DURATION: | ||||||
|  |                     break | ||||||
|  |             last_time = time_now | ||||||
|  |             frames = frames + 1 if frames < 3 else 1 | ||||||
|  | 
 | ||||||
|             if not self.should_start_accelerometer: |             if not self.should_start_accelerometer: | ||||||
|                 await asyncio.sleep(0.5) |                 await asyncio.sleep(sleep_duration), | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
|             start = time.time() |             await asyncio.gather( | ||||||
|             if delta_time > ACCEL_SEND_RATE: |                 asyncio.sleep(sleep_duration), | ||||||
|                 delta_time = 0 |                 self.collect_accelerometer_data(frames), | ||||||
|                 while len(accel_data) > 0: |             ) | ||||||
|                     accels_num = min(len(accel_data), 10) |  | ||||||
| 
 | 
 | ||||||
|                     await self.send_message('JD_PhoneScoringData', { |     async def collect_accelerometer_data(self, frames): | ||||||
|                         'accelData': accel_data[:accels_num], |         if self.disconnected: | ||||||
|                         'timeStamp': self.number_of_accels_sent, |             return | ||||||
|                     }) |  | ||||||
| 
 | 
 | ||||||
|                     self.number_of_accels_sent += accels_num |         if not self.should_start_accelerometer: | ||||||
|                     accel_data = accel_data[accels_num:] |             self.accel_data = [] | ||||||
|  |             return | ||||||
| 
 | 
 | ||||||
|         try: |         try: | ||||||
|                 await asyncio.sleep(JOYCON_UPDATE_RATE) |             start = time.time() | ||||||
|                 joycon_status = self.joycon.get_status() |             max_runtime = FRAME_DURATION * 0.5 | ||||||
|  |             while time.time() - start < max_runtime: | ||||||
|  |                 # Make sure accelerometer axes are changed | ||||||
|  |                 accel = self.joycon.get_accels()  # (x, y, z) | ||||||
|  |                 if accel != self.last_accel: | ||||||
|  |                     self.last_accel = accel | ||||||
|  |                     break | ||||||
|  | 
 | ||||||
|  |             # Accelerator axes on phone & Joy-Con are different so we need to swap some axes | ||||||
|  |             # https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md | ||||||
|  |             x = accel[1] * -1 | ||||||
|  |             y = accel[0] | ||||||
|  |             z = accel[2] | ||||||
|  | 
 | ||||||
|  |             self.accel_data.append([x, y, z]) | ||||||
|  |             await self.send_accelerometer_data(frames), | ||||||
|         except OSError: |         except OSError: | ||||||
|             self.disconnect() |             self.disconnect() | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|             # Accelerator axes on phone & Joy-Con are different so we need to swap some axes |     async def send_accelerometer_data(self, frames): | ||||||
|             # https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md |         if not self.should_start_accelerometer: | ||||||
|             accel = joycon_status['accel'] |             return | ||||||
|             x = accel['y'] * -1 |  | ||||||
|             y = accel['x'] |  | ||||||
|             z = accel['z'] |  | ||||||
| 
 | 
 | ||||||
|             accel_data.append([x, y, z]) |         if frames < 3: | ||||||
|  |             return | ||||||
| 
 | 
 | ||||||
|             end = time.time() |         tmp_accel_data = [] | ||||||
|             delta_time += (end - start) * 1000 |         while len(self.accel_data): | ||||||
|  |             tmp_accel_data.append(self.accel_data.pop(0)) | ||||||
|  | 
 | ||||||
|  |         while len(tmp_accel_data) > 0: | ||||||
|  |             accels_num = min(len(tmp_accel_data), 10) | ||||||
|  | 
 | ||||||
|  |             await self.send_message('JD_PhoneScoringData', { | ||||||
|  |                 'accelData': tmp_accel_data[:accels_num], | ||||||
|  |                 'timeStamp': self.number_of_accels_sent, | ||||||
|  |             }) | ||||||
|  | 
 | ||||||
|  |             self.number_of_accels_sent += accels_num | ||||||
|  |             tmp_accel_data = tmp_accel_data[accels_num:] | ||||||
| 
 | 
 | ||||||
|     async def send_command(self): |     async def send_command(self): | ||||||
|         ''' Capture Joycon's input and send to console. Only works on protocol v2 ''' |         ''' Capture Joycon's input and send to console. Only works on protocol v2 ''' | ||||||
|  | @ -292,16 +327,14 @@ class JoyDance: | ||||||
|             return |             return | ||||||
| 
 | 
 | ||||||
|         while True: |         while True: | ||||||
|             try: |  | ||||||
|             if self.disconnected: |             if self.disconnected: | ||||||
|                 return |                 return | ||||||
| 
 | 
 | ||||||
|  |             try: | ||||||
|  |                 await asyncio.sleep(FRAME_DURATION) | ||||||
|                 if not self.is_input_allowed and not self.should_start_accelerometer: |                 if not self.is_input_allowed and not self.should_start_accelerometer: | ||||||
|                     await asyncio.sleep(JOYCON_UPDATE_RATE * 5) |  | ||||||
|                     continue |                     continue | ||||||
| 
 | 
 | ||||||
|                 await asyncio.sleep(JOYCON_UPDATE_RATE * 5) |  | ||||||
| 
 |  | ||||||
|                 cmd = None |                 cmd = None | ||||||
|                 # Get pressed button |                 # Get pressed button | ||||||
|                 for event_type, status in self.joycon.events(): |                 for event_type, status in self.joycon.events(): | ||||||
|  | @ -309,7 +342,7 @@ class JoyDance: | ||||||
|                         continue |                         continue | ||||||
| 
 | 
 | ||||||
|                     joycon_button = JoyConButton(event_type) |                     joycon_button = JoyConButton(event_type) | ||||||
|                     if self.should_start_accelerometer:  # Can only send Pause command while playing |                     if self.should_start_accelerometer:  # Only allow to send Pause command while playing | ||||||
|                         if joycon_button == JoyConButton.PLUS or joycon_button == JoyConButton.MINUS: |                         if joycon_button == JoyConButton.PLUS or joycon_button == JoyConButton.MINUS: | ||||||
|                             cmd = Command.PAUSE |                             cmd = Command.PAUSE | ||||||
|                     else: |                     else: | ||||||
|  | @ -318,7 +351,7 @@ class JoyDance: | ||||||
|                         elif joycon_button == JoyConButton.B or joycon_button == JoyConButton.DOWN: |                         elif joycon_button == JoyConButton.B or joycon_button == JoyConButton.DOWN: | ||||||
|                             cmd = Command.BACK |                             cmd = Command.BACK | ||||||
|                         elif joycon_button in SHORTCUT_MAPPING: |                         elif joycon_button in SHORTCUT_MAPPING: | ||||||
|                             # Get command depends on which button was pressed & which shortcuts are available |                             # Get command depends on which button is being pressed & which shortcuts are available | ||||||
|                             for shortcut in SHORTCUT_MAPPING[joycon_button]: |                             for shortcut in SHORTCUT_MAPPING[joycon_button]: | ||||||
|                                 if shortcut in self.available_shortcuts: |                                 if shortcut in self.available_shortcuts: | ||||||
|                                     cmd = shortcut |                                     cmd = shortcut | ||||||
|  | @ -344,27 +377,21 @@ class JoyDance: | ||||||
| 
 | 
 | ||||||
|                 # Send command to server |                 # Send command to server | ||||||
|                 if cmd: |                 if cmd: | ||||||
|  |                     data = {} | ||||||
|                     if cmd == Command.PAUSE: |                     if cmd == Command.PAUSE: | ||||||
|                         __class = 'JD_Pause_PhoneCommandData' |                         __class = 'JD_Pause_PhoneCommandData' | ||||||
|                         data = {} |  | ||||||
|                     elif type(cmd.value) == str: |                     elif type(cmd.value) == str: | ||||||
|                         __class = 'JD_Custom_PhoneCommandData' |                         __class = 'JD_Custom_PhoneCommandData' | ||||||
|                         data = { |                         data['identifier'] = cmd.value | ||||||
|                             'identifier': cmd.value, |  | ||||||
|                         } |  | ||||||
|                     else: |                     else: | ||||||
|                         __class = 'JD_Input_PhoneCommandData' |                         __class = 'JD_Input_PhoneCommandData' | ||||||
|                         data = { |                         data['input'] = cmd.value | ||||||
|                             'input': cmd.value, |  | ||||||
|                         } |  | ||||||
| 
 | 
 | ||||||
|                     # Only send input when it's allowed to, otherwise we might get a disconnection |                     # Only send input when it's allowed to, otherwise we might get a disconnection | ||||||
|                     if self.is_input_allowed: |                     if self.is_input_allowed: | ||||||
|                         await self.send_message(__class, data) |                         await self.send_message(__class, data) | ||||||
|                         await asyncio.sleep(0.01) |                         await asyncio.sleep(FRAME_DURATION * 5) | ||||||
|             except Exception as e: |             except Exception: | ||||||
|                 print(e) |  | ||||||
|                 import traceback |  | ||||||
|                 traceback.print_exc() |                 traceback.print_exc() | ||||||
|                 await self.disconnect() |                 await self.disconnect() | ||||||
| 
 | 
 | ||||||
|  | @ -403,17 +430,18 @@ class JoyDance: | ||||||
|             ) as websocket: |             ) as websocket: | ||||||
|                 try: |                 try: | ||||||
|                     self.ws = websocket |                     self.ws = websocket | ||||||
|  | 
 | ||||||
|                     await asyncio.gather( |                     await asyncio.gather( | ||||||
|                         self.send_hello(), |                         self.send_hello(), | ||||||
|                         self.send_accelerometer_data(), |                         self.tick(), | ||||||
|                         self.send_command(), |                         self.send_command(), | ||||||
|                     ) |                     ) | ||||||
| 
 | 
 | ||||||
|                 except websockets.ConnectionClosed: |                 except websockets.ConnectionClosed: | ||||||
|                     await self.on_state_changed(self.joycon.serial, PairingState.ERROR_CONSOLE_CONNECTION) |                     await self.on_state_changed(self.joycon.serial, PairingState.ERROR_CONSOLE_CONNECTION) | ||||||
|                     await self.disconnect(close_ws=False) |                     await self.disconnect(close_ws=False) | ||||||
|         except Exception as e: |         except Exception: | ||||||
|             print(e) |             traceback.print_exc() | ||||||
|             await self.on_state_changed(self.joycon.serial, PairingState.ERROR_CONSOLE_CONNECTION) |             await self.on_state_changed(self.joycon.serial, PairingState.ERROR_CONSOLE_CONNECTION) | ||||||
|             await self.disconnect(close_ws=False) |             await self.disconnect(close_ws=False) | ||||||
| 
 | 
 | ||||||
|  | @ -449,6 +477,6 @@ class JoyDance: | ||||||
|                     await self.hole_punching() |                     await self.hole_punching() | ||||||
| 
 | 
 | ||||||
|             await self.connect_ws() |             await self.connect_ws() | ||||||
|         except Exception as e: |         except Exception: | ||||||
|             await self.disconnect() |             await self.disconnect() | ||||||
|             print(e) |             traceback.print_exc() | ||||||
|  |  | ||||||
|  | @ -14,10 +14,9 @@ WS_SUBPROTOCOLS = {} | ||||||
| WS_SUBPROTOCOLS[WsSubprotocolVersion.V1.value] = 'v1.phonescoring.jd.ubisoft.com' | WS_SUBPROTOCOLS[WsSubprotocolVersion.V1.value] = 'v1.phonescoring.jd.ubisoft.com' | ||||||
| WS_SUBPROTOCOLS[WsSubprotocolVersion.V2.value] = 'v2.phonescoring.jd.ubisoft.com' | WS_SUBPROTOCOLS[WsSubprotocolVersion.V2.value] = 'v2.phonescoring.jd.ubisoft.com' | ||||||
| 
 | 
 | ||||||
| JOYCON_UPDATE_RATE = 0.02  # 50Hz | FRAME_DURATION = 1 / 60 | ||||||
| ACCEL_SEND_RATE = 40  # ms | ACCEL_ACQUISITION_FREQ_HZ = 60  # Hz | ||||||
| ACCEL_ACQUISITION_FREQ_HZ = 50  # Hz | ACCEL_ACQUISITION_LATENCY = 40  # ms | ||||||
| ACCEL_ACQUISITION_LATENCY = 20  # ms |  | ||||||
| ACCEL_MAX_RANGE = 8  # ±G | ACCEL_MAX_RANGE = 8  # ±G | ||||||
| 
 | 
 | ||||||
| DEFAULT_CONFIG = { | DEFAULT_CONFIG = { | ||||||
|  | @ -25,9 +24,6 @@ DEFAULT_CONFIG = { | ||||||
|     'host_ip_addr': '', |     'host_ip_addr': '', | ||||||
|     'console_ip_addr': '', |     'console_ip_addr': '', | ||||||
|     'pairing_code': '', |     'pairing_code': '', | ||||||
|     'accel_acquisition_freq_hz': ACCEL_ACQUISITION_FREQ_HZ, |  | ||||||
|     'accel_acquisition_latency': ACCEL_ACQUISITION_LATENCY, |  | ||||||
|     'accel_max_range': ACCEL_MAX_RANGE, |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,4 +1,4 @@ | ||||||
| https://github.com/redphx/joycon-python/archive/refs/heads/master.zip#egg=joycon-python | https://github.com/redphx/joycon-python/archive/refs/tags/0.3.zip#egg=joycon-python | ||||||
| websockets==10.2 | websockets==10.2 | ||||||
| aiohttp==3.8.1 | aiohttp==3.8.1 | ||||||
| hidapi==0.11.2 | hidapi==0.11.2 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue