Merge pull request #1 from redphx/feature/support_protocol_v1

Add support for protocol v1
This commit is contained in:
redphx 2022-04-17 15:04:41 +07:00 committed by GitHub
commit 3b4aa9ca72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 80 additions and 29 deletions

View file

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

View file

@ -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():

View file

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

View file

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

View file

@ -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" />
`} `}
` `