Skip to content

Commit

Permalink
Merge pull request #12 from redphx/feature/merge_joycon_python
Browse files Browse the repository at this point in the history
Improve tracking, again
  • Loading branch information
redphx authored May 3, 2022
2 parents 749a2a9 + a6d66b3 commit 0f8e7f7
Show file tree
Hide file tree
Showing 10 changed files with 732 additions and 30 deletions.
4 changes: 2 additions & 2 deletions dance.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
import aiohttp
import hid
from aiohttp import WSMsgType, web
from pyjoycon import ButtonEventJoyCon, JoyCon
from pyjoycon.constants import JOYCON_PRODUCT_IDS, JOYCON_VENDOR_ID

from joydance import JoyDance, PairingState
from joydance.constants import (DEFAULT_CONFIG, JOYDANCE_VERSION,
WsSubprotocolVersion)
from pycon import ButtonEventJoyCon, JoyCon
from pycon.constants import JOYCON_PRODUCT_IDS, JOYCON_VENDOR_ID

logging.getLogger('asyncio').setLevel(logging.WARNING)

Expand Down
55 changes: 29 additions & 26 deletions joydance/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ def __init__(
self.available_shortcuts = set()

self.accel_data = []
self.last_accel = (0, 0, 0)

self.ws = None
self.disconnected = False
Expand Down Expand Up @@ -242,34 +241,46 @@ async def send_hello(self):
async for message in self.ws:
await self.on_message(message)

async def sleep_approx(self, target_duration):
tmp_duration = target_duration
x = 0.3
start = time.time()
while True:
tmp_duration = tmp_duration * x
await asyncio.sleep(tmp_duration)

dt = time.time() - start
if dt >= target_duration:
break

tmp_duration = target_duration - dt

async def tick(self):
sleep_duration = FRAME_DURATION * 0.75
last_time = time.time()
sleep_duration = FRAME_DURATION
frames = 0

while True:
if self.disconnected:
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:
await asyncio.sleep(sleep_duration),
frames = 0
await asyncio.sleep(sleep_duration)
continue

last_time = time.time()
frames = frames + 1 if frames < 3 else 1

await asyncio.gather(
asyncio.sleep(sleep_duration),
self.collect_accelerometer_data(frames),
self.sleep_approx(sleep_duration),
self.collect_accelerometer_data(),
)
await self.send_accelerometer_data(frames)

async def collect_accelerometer_data(self, frames):
dt = time.time() - last_time
sleep_duration = FRAME_DURATION - (dt - sleep_duration)

async def collect_accelerometer_data(self):
if self.disconnected:
return

Expand All @@ -278,23 +289,15 @@ async def collect_accelerometer_data(self, frames):
return

try:
start = time.time()
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
accels = self.joycon.get_accels() # (x, y, z)

# 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
accel = accels[2]
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:
self.disconnect()
return
Expand Down
3 changes: 3 additions & 0 deletions joydance/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class WsSubprotocolVersion(Enum):
WS_SUBPROTOCOLS[WsSubprotocolVersion.V2.value] = 'v2.phonescoring.jd.ubisoft.com'

FRAME_DURATION = 1 / 60
SEND_FREQ_MS = 0.005
ACCEL_ACQUISITION_FREQ_HZ = 60 # Hz
ACCEL_ACQUISITION_LATENCY = 40 # ms
ACCEL_MAX_RANGE = 8 # ±G
Expand All @@ -38,6 +39,7 @@ class Command(Enum):

BACK = 'SHORTCUT_BACK'
CHANGE_DANCERCARD = 'SHORTCUT_CHANGE_DANCERCARD'
DONT_SHOW_ANYMORE = 'SHORTCUT_DONT_SHOW_ANYMORE'
FAVORITE = 'SHORTCUT_FAVORITE'
GOTO_SONGSTAB = 'SHORTCUT_GOTO_SONGSTAB'
SKIP = 'SHORTCUT_SKIP'
Expand Down Expand Up @@ -109,6 +111,7 @@ class JoyConButton(Enum):
Command.UPLAY,
],
JoyConButton.PLUS: [
Command.DONT_SHOW_ANYMORE,
Command.FAVORITE,
Command.PAUSE,
Command.PLAYLIST_RENAME,
Expand Down
3 changes: 3 additions & 0 deletions pycon/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Simplified version of [tocoteron/joycon-python](https://github.com/tocoteron/joycon-python):
- Remove codes irrelevant to JoyDance.
- Fix bugs & improve stability.
9 changes: 9 additions & 0 deletions pycon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from .event import ButtonEventJoyCon
from .joycon import JoyCon
from .wrappers import PythonicJoyCon # as JoyCon

__all__ = [
"ButtonEventJoyCon",
"JoyCon",
"PythonicJoyCon",
]
4 changes: 4 additions & 0 deletions pycon/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
JOYCON_VENDOR_ID = 0x057E
JOYCON_L_PRODUCT_ID = 0x2006
JOYCON_R_PRODUCT_ID = 0x2007
JOYCON_PRODUCT_IDS = (JOYCON_L_PRODUCT_ID, JOYCON_R_PRODUCT_ID)
133 changes: 133 additions & 0 deletions pycon/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
from .wrappers import PythonicJoyCon


class ButtonEventJoyCon(PythonicJoyCon):
def __init__(self, *args, track_sticks=False, **kwargs):
super().__init__(*args, **kwargs)

self._events_buffer = [] # TODO: perhaps use a deque instead?

self._event_handlers = {}
self._event_track_sticks = track_sticks

self._previous_stick_l_btn = 0
self._previous_stick_r_btn = 0
self._previous_stick_r = self._previous_stick_l = (0, 0)
self._previous_r = self._previous_l = 0
self._previous_zr = self._previous_zl = 0
self._previous_plus = self._previous_minus = 0
self._previous_a = self._previous_right = 0
self._previous_b = self._previous_down = 0
self._previous_x = self._previous_up = 0
self._previous_y = self._previous_left = 0
self._previous_home = self._previous_capture = 0
self._previous_right_sr = self._previous_left_sr = 0
self._previous_right_sl = self._previous_left_sl = 0

if self.is_left():
self.register_update_hook(self._event_tracking_update_hook_left)
else:
self.register_update_hook(self._event_tracking_update_hook_right)

def joycon_button_event(self, button, state): # overridable
self._events_buffer.append((button, state))

def events(self):
while self._events_buffer:
yield self._events_buffer.pop(0)

@staticmethod
def _event_tracking_update_hook_right(self):
if self._event_track_sticks:
pressed = self.stick_r_btn
if self._previous_stick_r_btn != pressed:
self._previous_stick_r_btn = pressed
self.joycon_button_event("stick_r_btn", pressed)
pressed = self.r
if self._previous_r != pressed:
self._previous_r = pressed
self.joycon_button_event("r", pressed)
pressed = self.zr
if self._previous_zr != pressed:
self._previous_zr = pressed
self.joycon_button_event("zr", pressed)
pressed = self.plus
if self._previous_plus != pressed:
self._previous_plus = pressed
self.joycon_button_event("plus", pressed)
pressed = self.a
if self._previous_a != pressed:
self._previous_a = pressed
self.joycon_button_event("a", pressed)
pressed = self.b
if self._previous_b != pressed:
self._previous_b = pressed
self.joycon_button_event("b", pressed)
pressed = self.x
if self._previous_x != pressed:
self._previous_x = pressed
self.joycon_button_event("x", pressed)
pressed = self.y
if self._previous_y != pressed:
self._previous_y = pressed
self.joycon_button_event("y", pressed)
pressed = self.home
if self._previous_home != pressed:
self._previous_home = pressed
self.joycon_button_event("home", pressed)
pressed = self.right_sr
if self._previous_right_sr != pressed:
self._previous_right_sr = pressed
self.joycon_button_event("right_sr", pressed)
pressed = self.right_sl
if self._previous_right_sl != pressed:
self._previous_right_sl = pressed
self.joycon_button_event("right_sl", pressed)

@staticmethod
def _event_tracking_update_hook_left(self):
if self._event_track_sticks:
pressed = self.stick_l_btn
if self._previous_stick_l_btn != pressed:
self._previous_stick_l_btn = pressed
self.joycon_button_event("stick_l_btn", pressed)
pressed = self.l
if self._previous_l != pressed:
self._previous_l = pressed
self.joycon_button_event("l", pressed)
pressed = self.zl
if self._previous_zl != pressed:
self._previous_zl = pressed
self.joycon_button_event("zl", pressed)
pressed = self.minus
if self._previous_minus != pressed:
self._previous_minus = pressed
self.joycon_button_event("minus", pressed)
pressed = self.up
if self._previous_up != pressed:
self._previous_up = pressed
self.joycon_button_event("up", pressed)
pressed = self.down
if self._previous_down != pressed:
self._previous_down = pressed
self.joycon_button_event("down", pressed)
pressed = self.left
if self._previous_left != pressed:
self._previous_left = pressed
self.joycon_button_event("left", pressed)
pressed = self.right
if self._previous_right != pressed:
self._previous_right = pressed
self.joycon_button_event("right", pressed)
pressed = self.capture
if self._previous_capture != pressed:
self._previous_capture = pressed
self.joycon_button_event("capture", pressed)
pressed = self.left_sr
if self._previous_left_sr != pressed:
self._previous_left_sr = pressed
self.joycon_button_event("left_sr", pressed)
pressed = self.left_sl
if self._previous_left_sl != pressed:
self._previous_left_sl = pressed
self.joycon_button_event("left_sl", pressed)
Loading

0 comments on commit 0f8e7f7

Please sign in to comment.