Merge pull request #1 from redphx/feature/support_protocol_v1
Add support for protocol v1
This commit is contained in:
commit
3b4aa9ca72
5 changed files with 80 additions and 29 deletions
19
README.md
19
README.md
|
@ -13,11 +13,14 @@ https://youtu.be/f_1IkUHFdH8
|
||||||
- No random disconnection.
|
- No random disconnection.
|
||||||
- Support up to 6 players.
|
- Support up to 6 players.
|
||||||
- Support all platforms (in theory):
|
- Support all platforms (in theory):
|
||||||
- Xbox Series X/S
|
|
||||||
- Nintendo Switch
|
| | Xbox Series | Xbox One | PS4/5 | NSW | Stadia | PC |
|
||||||
- Xbox One (not tested)
|
|-----------|-------------|----------|-------|-----|--------|----|
|
||||||
- Stadia (not tested)
|
| 2020-2022 | ✅ | ❓ | ❓ | ✅ | ❓ | |
|
||||||
- PlayStation 4/5 (not tested)
|
| 2016-2019 | | ❓ | ❓ | ✅ | | ❓ |
|
||||||
|
|
||||||
|
✅ = confirmed working, ❓ = not tested
|
||||||
|
|
||||||
|
|
||||||
## How does it work?
|
## 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.
|
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).
|
- Slower, but supports all platforms (including Xbox Series and Stadia).
|
||||||
- Requires pairing code.
|
- Requires pairing code.
|
||||||
- Requires host's private IP address.
|
- 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**:
|
- **Host's Private IP Address**:
|
||||||
- The private IP address of your PC/Mac/Linux. Find this in the Wi-Fi settings.
|
- The private IP address of your PC/Mac/Linux. Find this in the Wi-Fi settings.
|
||||||
|
|
18
dance.py
18
dance.py
|
@ -13,7 +13,8 @@ from pyjoycon import ButtonEventJoyCon, JoyCon
|
||||||
from pyjoycon.constants import JOYCON_PRODUCT_IDS, JOYCON_VENDOR_ID
|
from pyjoycon.constants import JOYCON_PRODUCT_IDS, JOYCON_VENDOR_ID
|
||||||
|
|
||||||
from joydance import JoyDance, PairingState
|
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)
|
logging.getLogger('asyncio').setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
@ -28,6 +29,7 @@ class WsCommand(Enum):
|
||||||
class PairingMethod(Enum):
|
class PairingMethod(Enum):
|
||||||
DEFAULT = 'default'
|
DEFAULT = 'default'
|
||||||
FAST = 'fast'
|
FAST = 'fast'
|
||||||
|
OLD = 'old'
|
||||||
|
|
||||||
|
|
||||||
REGEX_PAIRING_CODE = re.compile(r'^\d{6}$')
|
REGEX_PAIRING_CODE = re.compile(r'^\d{6}$')
|
||||||
|
@ -137,14 +139,24 @@ async def connect_joycon(app, ws, data):
|
||||||
config_parser['joydance'] = config
|
config_parser['joydance'] = config
|
||||||
save_config(config_parser)
|
save_config(config_parser)
|
||||||
|
|
||||||
|
if pairing_method == PairingMethod.DEFAULT.value:
|
||||||
app['joycons_info'][serial]['pairing_code'] = pairing_code
|
app['joycons_info'][serial]['pairing_code'] = pairing_code
|
||||||
|
else:
|
||||||
|
app['joycons_info'][serial]['pairing_code'] = ''
|
||||||
|
|
||||||
joycon = ButtonEventJoyCon(vendor_id, product_id, serial)
|
joycon = ButtonEventJoyCon(vendor_id, product_id, serial)
|
||||||
|
|
||||||
if pairing_method == PairingMethod.DEFAULT.value:
|
if pairing_method == PairingMethod.DEFAULT.value:
|
||||||
console_ip_addr = None
|
console_ip_addr = None
|
||||||
|
|
||||||
|
if pairing_method == PairingMethod.OLD.value:
|
||||||
|
protocol_version = WsSubprotocolVersion.V1
|
||||||
|
else:
|
||||||
|
protocol_version = WsSubprotocolVersion.V2
|
||||||
|
|
||||||
joydance = JoyDance(
|
joydance = JoyDance(
|
||||||
joycon,
|
joycon,
|
||||||
|
protocol_version=protocol_version,
|
||||||
pairing_code=pairing_code,
|
pairing_code=pairing_code,
|
||||||
host_ip_addr=host_ip_addr,
|
host_ip_addr=host_ip_addr,
|
||||||
console_ip_addr=console_ip_addr,
|
console_ip_addr=console_ip_addr,
|
||||||
|
@ -162,7 +174,7 @@ async def disconnect_joycon(app, ws, data):
|
||||||
print(data)
|
print(data)
|
||||||
serial = data['joycon_serial']
|
serial = data['joycon_serial']
|
||||||
joydance = app['joydance_connections'][serial]
|
joydance = app['joydance_connections'][serial]
|
||||||
app['joycons_info'][serial]['state'] = PairingState.IDLE
|
app['joycons_info'][serial]['state'] = PairingState.IDLE.value
|
||||||
|
|
||||||
await joydance.disconnect()
|
await joydance.disconnect()
|
||||||
try:
|
try:
|
||||||
|
@ -222,7 +234,7 @@ def is_valid_ip_address(val):
|
||||||
|
|
||||||
|
|
||||||
def is_valid_pairing_method(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():
|
def get_host_ip():
|
||||||
|
|
|
@ -12,7 +12,8 @@ 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, ACCEL_SEND_RATE, JOYCON_UPDATE_RATE,
|
||||||
SHORTCUT_MAPPING, UBI_APP_ID, UBI_SKU_ID,
|
SHORTCUT_MAPPING, UBI_APP_ID, UBI_SKU_ID,
|
||||||
WS_SUBPROTOCOL, Command, JoyConButton)
|
WS_SUBPROTOCOLS, Command, JoyConButton,
|
||||||
|
WsSubprotocolVersion)
|
||||||
|
|
||||||
|
|
||||||
class PairingState(Enum):
|
class PairingState(Enum):
|
||||||
|
@ -36,6 +37,7 @@ class JoyDance:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
joycon,
|
joycon,
|
||||||
|
protocol_version,
|
||||||
pairing_code=None,
|
pairing_code=None,
|
||||||
host_ip_addr=None,
|
host_ip_addr=None,
|
||||||
console_ip_addr=None,
|
console_ip_addr=None,
|
||||||
|
@ -45,6 +47,7 @@ class JoyDance:
|
||||||
on_state_changed=None):
|
on_state_changed=None):
|
||||||
self.joycon = joycon
|
self.joycon = joycon
|
||||||
self.joycon_is_left = joycon.is_left()
|
self.joycon_is_left = joycon.is_left()
|
||||||
|
self.protocol_version = protocol_version
|
||||||
|
|
||||||
if on_state_changed:
|
if on_state_changed:
|
||||||
self.on_state_changed = on_state_changed
|
self.on_state_changed = on_state_changed
|
||||||
|
@ -271,7 +274,10 @@ class JoyDance:
|
||||||
delta_time += (end - start) * 1000
|
delta_time += (end - start) * 1000
|
||||||
|
|
||||||
async def send_command(self):
|
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:
|
while True:
|
||||||
try:
|
try:
|
||||||
if self.disconnected:
|
if self.disconnected:
|
||||||
|
@ -348,6 +354,10 @@ class JoyDance:
|
||||||
await self.disconnect()
|
await self.disconnect()
|
||||||
|
|
||||||
async def connect_ws(self):
|
async def connect_ws(self):
|
||||||
|
if self.protocol_version == WsSubprotocolVersion.V1:
|
||||||
|
ssl_context = None
|
||||||
|
server_hostname = None
|
||||||
|
else:
|
||||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||||
ssl_context.set_ciphers('ALL')
|
ssl_context.set_ciphers('ALL')
|
||||||
ssl_context.options &= ~ssl.OP_NO_SSLv3
|
ssl_context.options &= ~ssl.OP_NO_SSLv3
|
||||||
|
@ -355,10 +365,12 @@ class JoyDance:
|
||||||
ssl_context.verify_mode = ssl.CERT_NONE
|
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:
|
try:
|
||||||
async with websockets.connect(
|
async with websockets.connect(
|
||||||
self.pairing_url,
|
self.pairing_url,
|
||||||
subprotocols=[WS_SUBPROTOCOL],
|
subprotocols=[subprotocol],
|
||||||
sock=self.console_conn,
|
sock=self.console_conn,
|
||||||
ssl=ssl_context,
|
ssl=ssl_context,
|
||||||
ping_timeout=None,
|
ping_timeout=None,
|
||||||
|
@ -391,6 +403,9 @@ class JoyDance:
|
||||||
try:
|
try:
|
||||||
if self.console_ip_addr:
|
if self.console_ip_addr:
|
||||||
await self.on_state_changed(self.joycon.serial, PairingState.CONNECTING)
|
await self.on_state_changed(self.joycon.serial, PairingState.CONNECTING)
|
||||||
|
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)
|
self.pairing_url = 'wss://{}:8080/smartphone'.format(self.console_ip_addr)
|
||||||
else:
|
else:
|
||||||
await self.on_state_changed(self.joycon.serial, PairingState.GETTING_TOKEN)
|
await self.on_state_changed(self.joycon.serial, PairingState.GETTING_TOKEN)
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
JOYDANCE_VERSION = '0.2'
|
||||||
JOYDANCE_VERSION = '0.1'
|
|
||||||
UBI_APP_ID = '210da0fb-d6a5-4ed1-9808-01e86f0de7fb'
|
UBI_APP_ID = '210da0fb-d6a5-4ed1-9808-01e86f0de7fb'
|
||||||
UBI_SKU_ID = 'jdcompanion-android'
|
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
|
JOYCON_UPDATE_RATE = 0.02 # 50Hz
|
||||||
ACCEL_SEND_RATE = 40 # ms
|
ACCEL_SEND_RATE = 40 # ms
|
||||||
|
|
|
@ -17,7 +17,8 @@ const BATTERY_LEVEL = {
|
||||||
|
|
||||||
const PairingMethod = {
|
const PairingMethod = {
|
||||||
DEFAULT: 'default',
|
DEFAULT: 'default',
|
||||||
FAST: 'fast'
|
FAST: 'fast',
|
||||||
|
OLD: 'old',
|
||||||
}
|
}
|
||||||
|
|
||||||
const WsCommand = {
|
const WsCommand = {
|
||||||
|
@ -83,8 +84,13 @@ class PairingMethodPicker extends Component {
|
||||||
return html`
|
return html`
|
||||||
<label for="stacked-state">Pairing Method</label>
|
<label for="stacked-state">Pairing Method</label>
|
||||||
<select id="stacked-state" onChange=${this.onChange} value=${props.pairing_method}>
|
<select id="stacked-state" onChange=${this.onChange} value=${props.pairing_method}>
|
||||||
|
<optgroup label="JD 2020 and later">
|
||||||
<option value="${PairingMethod.DEFAULT}">Default: All platforms (incl. Xbox Series/Stadia)</option>
|
<option value="${PairingMethod.DEFAULT}">Default: All platforms (incl. Xbox Series/Stadia)</option>
|
||||||
<option value="${PairingMethod.FAST}">Fast: Xbox One/PlayStation/Nintendo Switch</option>
|
<option value="${PairingMethod.FAST}">Fast: Xbox One/PlayStation/Nintendo Switch</option>
|
||||||
|
</optgroup>
|
||||||
|
<optgroup label="JD 2016-2019">
|
||||||
|
<option value="${PairingMethod.OLD}">Old: All platforms (incl. PC)</option>
|
||||||
|
</optgroup>
|
||||||
</select>
|
</select>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
@ -132,7 +138,8 @@ class PrivateIpAddress extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
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) {
|
render(props, state) {
|
||||||
|
@ -141,14 +148,14 @@ class PrivateIpAddress extends Component {
|
||||||
return html`
|
return html`
|
||||||
<label>
|
<label>
|
||||||
${pairing_method == PairingMethod.DEFAULT && html`Host's Private IP Address`}
|
${pairing_method == PairingMethod.DEFAULT && html`Host's Private IP Address`}
|
||||||
${pairing_method == PairingMethod.FAST && html`Console's Private IP Address`}
|
${pairing_method != PairingMethod.DEFAULT && html`Console's Private IP Address`}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
${pairing_method == PairingMethod.DEFAULT && state.lock_host && html`
|
${pairing_method == PairingMethod.DEFAULT && state.lock_host && html`
|
||||||
<input readonly required id="ipAddr" type="text" size="15" placeholder="${addr}" />
|
<input readonly required id="ipAddr" type="text" size="15" placeholder="${addr}" />
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${(pairing_method == PairingMethod.FAST || !state.lock_host) && html`
|
${(pairing_method != PairingMethod.DEFAULT || !state.lock_host) && html`
|
||||||
<input required id="ipAddr" type="text" inputmode="decimal" size="15" maxlength="15" placeholder="192.168.x.x" pattern="^192\\.168\\.((\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.)(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])$" value=${addr} onKeyPress=${this.onKeyPress} onChange="${this.onChange}" />
|
<input required id="ipAddr" type="text" inputmode="decimal" size="15" maxlength="15" placeholder="192.168.x.x" pattern="^192\\.168\\.((\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])\\.)(\\d{1,2}|1\\d\\d|2[0-4]\\d|25[0-5])$" value=${addr} onKeyPress=${this.onKeyPress} onChange="${this.onChange}" />
|
||||||
`}
|
`}
|
||||||
|
|
||||||
|
@ -181,7 +188,7 @@ class PairingCode extends Component {
|
||||||
${props.pairing_method == PairingMethod.DEFAULT && html`
|
${props.pairing_method == PairingMethod.DEFAULT && html`
|
||||||
<input required id="pairingCode" type="text" inputmode="decimal" value=${state.pairing_code} placeholder="000000" maxlength="6" size="6" pattern="[0-9]{6}" onKeyPress=${(e) => !/[0-9]/.test(e.key) && e.preventDefault()} onChange=${this.onChange} />
|
<input required id="pairingCode" type="text" inputmode="decimal" value=${state.pairing_code} placeholder="000000" maxlength="6" size="6" pattern="[0-9]{6}" onKeyPress=${(e) => !/[0-9]/.test(e.key) && e.preventDefault()} onChange=${this.onChange} />
|
||||||
`}
|
`}
|
||||||
${props.pairing_method == PairingMethod.FAST && html`
|
${props.pairing_method != PairingMethod.DEFAULT && html`
|
||||||
<input type="text" id="pairingCode" value="" readonly placeholder="Not Required" size="12" />
|
<input type="text" id="pairingCode" value="" readonly placeholder="Not Required" size="12" />
|
||||||
`}
|
`}
|
||||||
`
|
`
|
||||||
|
|
Loading…
Reference in a new issue