Merge pull request #11 from redphx/feature/improve_tracking
Improve tracking
This commit is contained in:
commit
e1b95e5763
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
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
|
@ -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…
Reference in New Issue