Merge pull request #11 from redphx/feature/improve_tracking

Improve tracking
This commit is contained in:
redphx 2022-04-28 17:41:00 +07:00 committed by GitHub
commit e1b95e5763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 92 additions and 71 deletions

View File

@ -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

View File

@ -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
await asyncio.gather(
asyncio.sleep(sleep_duration),
self.collect_accelerometer_data(frames),
)
async def collect_accelerometer_data(self, frames):
if self.disconnected:
return
if not self.should_start_accelerometer:
self.accel_data = []
return
try:
start = time.time() start = time.time()
if delta_time > ACCEL_SEND_RATE: max_runtime = FRAME_DURATION * 0.5
delta_time = 0 while time.time() - start < max_runtime:
while len(accel_data) > 0: # Make sure accelerometer axes are changed
accels_num = min(len(accel_data), 10) accel = self.joycon.get_accels() # (x, y, z)
if accel != self.last_accel:
await self.send_message('JD_PhoneScoringData', { self.last_accel = accel
'accelData': accel_data[:accels_num], break
'timeStamp': self.number_of_accels_sent,
})
self.number_of_accels_sent += accels_num
accel_data = accel_data[accels_num:]
try:
await asyncio.sleep(JOYCON_UPDATE_RATE)
joycon_status = self.joycon.get_status()
except OSError:
self.disconnect()
return
# Accelerator axes on phone & Joy-Con are different so we need to swap some axes # 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 # https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md
accel = joycon_status['accel'] x = accel[1] * -1
x = accel['y'] * -1 y = accel[0]
y = accel['x'] z = accel[2]
z = accel['z']
accel_data.append([x, y, z]) self.accel_data.append([x, y, z])
await self.send_accelerometer_data(frames),
except OSError:
self.disconnect()
return
end = time.time() async def send_accelerometer_data(self, frames):
delta_time += (end - start) * 1000 if not self.should_start_accelerometer:
return
if frames < 3:
return
tmp_accel_data = []
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:
if self.disconnected:
return
try: try:
if self.disconnected: await asyncio.sleep(FRAME_DURATION)
return
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()

View File

@ -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,
} }

View File

@ -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