diff --git a/README.md b/README.md
index f3735fe..6646a4d 100644
--- a/README.md
+++ b/README.md
@@ -13,11 +13,14 @@ https://youtu.be/f_1IkUHFdH8
- No random disconnection.
- Support up to 6 players.
- Support all platforms (in theory):
- - Xbox Series X/S
- - Nintendo Switch
- - Xbox One (not tested)
- - Stadia (not tested)
- - PlayStation 4/5 (not tested)
+
+| | Xbox Series | Xbox One | PS4/5 | NSW | Stadia | PC |
+|-----------|-------------|----------|-------|-----|--------|----|
+| 2020-2022 | ✅ | ❓ | ❓ | ✅ | ❓ | |
+| 2016-2019 | | ❓ | ❓ | ✅ | | ❓ |
+
+✅ = confirmed working, ❓ = not tested
+
## How does it work?
It pretends to be the [Just Dance Controller app](https://play.google.com/store/apps/details?id=com.ubisoft.dance.justdance2015companion), sends movements of the Joy-Con to your game console.
@@ -105,6 +108,12 @@ sudo udevadm trigger
- Slower, but supports all platforms (including Xbox Series and Stadia).
- Requires pairing code.
- Requires host's private IP address.
+ - Old
+ - For JD 2016-2019 (including PC).
+ - Connect instantly.
+ - Doesn't require pairing code.
+ - Requires PC/console's private IP address.
+ - *Important*: Can't use buttons on Joy-Con to navigate the UI (you'll have to use controllers/keyboard).
- **Host's Private IP Address**:
- The private IP address of your PC/Mac/Linux. Find this in the Wi-Fi settings.
diff --git a/dance.py b/dance.py
index 331e1bb..1b88422 100644
--- a/dance.py
+++ b/dance.py
@@ -13,7 +13,8 @@ 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
+from joydance.constants import (DEFAULT_CONFIG, JOYDANCE_VERSION,
+ WsSubprotocolVersion)
logging.getLogger('asyncio').setLevel(logging.WARNING)
@@ -28,6 +29,7 @@ class WsCommand(Enum):
class PairingMethod(Enum):
DEFAULT = 'default'
FAST = 'fast'
+ OLD = 'old'
REGEX_PAIRING_CODE = re.compile(r'^\d{6}$')
@@ -137,14 +139,24 @@ async def connect_joycon(app, ws, data):
config_parser['joydance'] = config
save_config(config_parser)
- app['joycons_info'][serial]['pairing_code'] = pairing_code
+ if pairing_method == PairingMethod.DEFAULT.value:
+ app['joycons_info'][serial]['pairing_code'] = pairing_code
+ else:
+ app['joycons_info'][serial]['pairing_code'] = ''
+
joycon = ButtonEventJoyCon(vendor_id, product_id, serial)
if pairing_method == PairingMethod.DEFAULT.value:
console_ip_addr = None
+ if pairing_method == PairingMethod.OLD.value:
+ protocol_version = WsSubprotocolVersion.V1
+ else:
+ protocol_version = WsSubprotocolVersion.V2
+
joydance = JoyDance(
joycon,
+ protocol_version=protocol_version,
pairing_code=pairing_code,
host_ip_addr=host_ip_addr,
console_ip_addr=console_ip_addr,
@@ -162,7 +174,7 @@ async def disconnect_joycon(app, ws, data):
print(data)
serial = data['joycon_serial']
joydance = app['joydance_connections'][serial]
- app['joycons_info'][serial]['state'] = PairingState.IDLE
+ app['joycons_info'][serial]['state'] = PairingState.IDLE.value
await joydance.disconnect()
try:
@@ -222,7 +234,7 @@ def is_valid_ip_address(val):
def is_valid_pairing_method(val):
- return val in [PairingMethod.DEFAULT.value, PairingMethod.FAST.value]
+ return val in [PairingMethod.DEFAULT.value, PairingMethod.FAST.value, PairingMethod.OLD.value]
def get_host_ip():
diff --git a/joydance/__init__.py b/joydance/__init__.py
index 62286f5..0164fe5 100644
--- a/joydance/__init__.py
+++ b/joydance/__init__.py
@@ -12,7 +12,8 @@ import websockets
from .constants import (ACCEL_ACQUISITION_FREQ_HZ, ACCEL_ACQUISITION_LATENCY,
ACCEL_MAX_RANGE, ACCEL_SEND_RATE, JOYCON_UPDATE_RATE,
SHORTCUT_MAPPING, UBI_APP_ID, UBI_SKU_ID,
- WS_SUBPROTOCOL, Command, JoyConButton)
+ WS_SUBPROTOCOLS, Command, JoyConButton,
+ WsSubprotocolVersion)
class PairingState(Enum):
@@ -36,6 +37,7 @@ class JoyDance:
def __init__(
self,
joycon,
+ protocol_version,
pairing_code=None,
host_ip_addr=None,
console_ip_addr=None,
@@ -45,6 +47,7 @@ class JoyDance:
on_state_changed=None):
self.joycon = joycon
self.joycon_is_left = joycon.is_left()
+ self.protocol_version = protocol_version
if on_state_changed:
self.on_state_changed = on_state_changed
@@ -271,7 +274,10 @@ class JoyDance:
delta_time += (end - start) * 1000
async def send_command(self):
- ''' Capture Joycon's input and send to console '''
+ ''' Capture Joycon's input and send to console. Only works on procol v2 '''
+ if self.protocol_version == WsSubprotocolVersion.V1:
+ return
+
while True:
try:
if self.disconnected:
@@ -348,17 +354,23 @@ class JoyDance:
await self.disconnect()
async def connect_ws(self):
- ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
- ssl_context.set_ciphers('ALL')
- ssl_context.options &= ~ssl.OP_NO_SSLv3
- ssl_context.check_hostname = False
- ssl_context.verify_mode = ssl.CERT_NONE
+ if self.protocol_version == WsSubprotocolVersion.V1:
+ ssl_context = None
+ server_hostname = None
+ else:
+ ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
+ ssl_context.set_ciphers('ALL')
+ ssl_context.options &= ~ssl.OP_NO_SSLv3
+ ssl_context.check_hostname = False
+ ssl_context.verify_mode = ssl.CERT_NONE
- server_hostname = self.console_conn.getpeername()[0] if self.console_conn else None
+ server_hostname = self.console_conn.getpeername()[0] if self.console_conn else None
+
+ subprotocol = WS_SUBPROTOCOLS[self.protocol_version.value]
try:
async with websockets.connect(
self.pairing_url,
- subprotocols=[WS_SUBPROTOCOL],
+ subprotocols=[subprotocol],
sock=self.console_conn,
ssl=ssl_context,
ping_timeout=None,
@@ -391,7 +403,10 @@ class JoyDance:
try:
if self.console_ip_addr:
await self.on_state_changed(self.joycon.serial, PairingState.CONNECTING)
- self.pairing_url = 'wss://{}:8080/smartphone'.format(self.console_ip_addr)
+ if self.protocol_version == WsSubprotocolVersion.V1:
+ self.pairing_url = 'ws://{}:8080/smartphone'.format(self.console_ip_addr)
+ else:
+ self.pairing_url = 'wss://{}:8080/smartphone'.format(self.console_ip_addr)
else:
await self.on_state_changed(self.joycon.serial, PairingState.GETTING_TOKEN)
print('Getting authorication token...')
diff --git a/joydance/constants.py b/joydance/constants.py
index a820c0e..fdcb3e0 100644
--- a/joydance/constants.py
+++ b/joydance/constants.py
@@ -1,10 +1,18 @@
from enum import Enum
-
-JOYDANCE_VERSION = '0.1'
+JOYDANCE_VERSION = '0.2'
UBI_APP_ID = '210da0fb-d6a5-4ed1-9808-01e86f0de7fb'
UBI_SKU_ID = 'jdcompanion-android'
-WS_SUBPROTOCOL = 'v2.phonescoring.jd.ubisoft.com'
+
+
+class WsSubprotocolVersion(Enum):
+ V1 = 'v1',
+ V2 = 'v2',
+
+
+WS_SUBPROTOCOLS = {}
+WS_SUBPROTOCOLS[WsSubprotocolVersion.V1.value] = 'v1.phonescoring.jd.ubisoft.com'
+WS_SUBPROTOCOLS[WsSubprotocolVersion.V2.value] = 'v2.phonescoring.jd.ubisoft.com'
JOYCON_UPDATE_RATE = 0.02 # 50Hz
ACCEL_SEND_RATE = 40 # ms
diff --git a/static/js/app.js b/static/js/app.js
index b8146d1..d4db99d 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -17,7 +17,8 @@ const BATTERY_LEVEL = {
const PairingMethod = {
DEFAULT: 'default',
- FAST: 'fast'
+ FAST: 'fast',
+ OLD: 'old',
}
const WsCommand = {
@@ -83,8 +84,13 @@ class PairingMethodPicker extends Component {
return html`
`
}
@@ -132,7 +138,8 @@ class PrivateIpAddress extends Component {
}
componentDidMount() {
- window.mitty.emit('update_addr', this.state.host_ip_addr)
+ let addr = this.props.pairing_method == PairingMethod.DEFAULT ? this.state.host_ip_addr : this.state.console_ip_addr
+ window.mitty.emit('update_addr', addr)
}
render(props, state) {
@@ -141,14 +148,14 @@ class PrivateIpAddress extends Component {
return html`
${pairing_method == PairingMethod.DEFAULT && state.lock_host && html`
`}
- ${(pairing_method == PairingMethod.FAST || !state.lock_host) && html`
+ ${(pairing_method != PairingMethod.DEFAULT || !state.lock_host) && html`
`}
@@ -181,7 +188,7 @@ class PairingCode extends Component {
${props.pairing_method == PairingMethod.DEFAULT && html`
!/[0-9]/.test(e.key) && e.preventDefault()} onChange=${this.onChange} />
`}
- ${props.pairing_method == PairingMethod.FAST && html`
+ ${props.pairing_method != PairingMethod.DEFAULT && html`
`}
`