Add Stadia support (#8)
* Log some info * Fix pairing url and include TLS certificate in the connection request * Try to fix crashing related to shortcuts * Add print_exc() * Fix crash when pressing buttons on Joy-Con (L) * Linting * Add "Stadia" pairing method
This commit is contained in:
parent
2ebd7be378
commit
e89be3166f
4 changed files with 69 additions and 33 deletions
32
dance.py
32
dance.py
|
@ -29,6 +29,7 @@ class WsCommand(Enum):
|
||||||
class PairingMethod(Enum):
|
class PairingMethod(Enum):
|
||||||
DEFAULT = 'default'
|
DEFAULT = 'default'
|
||||||
FAST = 'fast'
|
FAST = 'fast'
|
||||||
|
STADIA = 'stadia'
|
||||||
OLD = 'old'
|
OLD = 'old'
|
||||||
|
|
||||||
|
|
||||||
|
@ -139,31 +140,29 @@ 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:
|
if pairing_method == PairingMethod.DEFAULT.value or pairing_method == PairingMethod.STADIA.value:
|
||||||
app['joycons_info'][serial]['pairing_code'] = pairing_code
|
app['joycons_info'][serial]['pairing_code'] = pairing_code
|
||||||
|
console_ip_addr = None
|
||||||
else:
|
else:
|
||||||
app['joycons_info'][serial]['pairing_code'] = ''
|
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:
|
|
||||||
console_ip_addr = None
|
|
||||||
|
|
||||||
if pairing_method == PairingMethod.OLD.value:
|
if pairing_method == PairingMethod.OLD.value:
|
||||||
protocol_version = WsSubprotocolVersion.V1
|
protocol_version = WsSubprotocolVersion.V1
|
||||||
else:
|
else:
|
||||||
protocol_version = WsSubprotocolVersion.V2
|
protocol_version = WsSubprotocolVersion.V2
|
||||||
|
|
||||||
joydance = JoyDance(
|
joydance = JoyDance(
|
||||||
joycon,
|
joycon,
|
||||||
protocol_version=protocol_version,
|
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,
|
||||||
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_freq_hz=config['accel_acquisition_freq_hz'],
|
||||||
accel_acquisition_latency=config['accel_acquisition_latency'],
|
accel_acquisition_latency=config['accel_acquisition_latency'],
|
||||||
accel_max_range=config['accel_max_range'],
|
accel_max_range=config['accel_max_range'],
|
||||||
)
|
)
|
||||||
app['joydance_connections'][serial] = joydance
|
app['joydance_connections'][serial] = joydance
|
||||||
|
|
||||||
|
@ -234,7 +233,12 @@ 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, PairingMethod.OLD.value]
|
return val in [
|
||||||
|
PairingMethod.DEFAULT.value,
|
||||||
|
PairingMethod.FAST.value,
|
||||||
|
PairingMethod.STADIA.value,
|
||||||
|
PairingMethod.OLD.value,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_host_ip():
|
def get_host_ip():
|
||||||
|
|
|
@ -5,6 +5,7 @@ import socket
|
||||||
import ssl
|
import ssl
|
||||||
import time
|
import time
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import websockets
|
import websockets
|
||||||
|
@ -56,6 +57,7 @@ class JoyDance:
|
||||||
self.host_ip_addr = host_ip_addr
|
self.host_ip_addr = host_ip_addr
|
||||||
self.console_ip_addr = console_ip_addr
|
self.console_ip_addr = console_ip_addr
|
||||||
self.host_port = self.get_random_port()
|
self.host_port = self.get_random_port()
|
||||||
|
self.tls_certificate = None
|
||||||
|
|
||||||
self.accel_acquisition_freq_hz = accel_acquisition_freq_hz
|
self.accel_acquisition_freq_hz = accel_acquisition_freq_hz
|
||||||
self.accel_acquisition_latency = accel_acquisition_latency
|
self.accel_acquisition_latency = accel_acquisition_latency
|
||||||
|
@ -114,7 +116,17 @@ class JoyDance:
|
||||||
raise Exception('ERROR: Invalid pairing code!')
|
raise Exception('ERROR: Invalid pairing code!')
|
||||||
|
|
||||||
json_body = await resp.json()
|
json_body = await resp.json()
|
||||||
self.pairing_url = json_body['pairingUrl'].replace('https://', 'wss://') + 'smartphone'
|
import pprint
|
||||||
|
pprint.pprint(json_body)
|
||||||
|
|
||||||
|
self.pairing_url = json_body['pairingUrl'].replace('https://', 'wss://')
|
||||||
|
if not self.pairing_url.endswith('/'):
|
||||||
|
self.pairing_url += '/'
|
||||||
|
self.pairing_url += 'smartphone'
|
||||||
|
|
||||||
|
self.tls_certificate = json_body['tlsCertificate']
|
||||||
|
|
||||||
|
print(self.pairing_url)
|
||||||
self.requires_punch_pairing = json_body.get('requiresPunchPairing', False)
|
self.requires_punch_pairing = json_body.get('requiresPunchPairing', False)
|
||||||
|
|
||||||
async def send_initiate_punch_pairing(self):
|
async def send_initiate_punch_pairing(self):
|
||||||
|
@ -205,7 +217,7 @@ class JoyDance:
|
||||||
await self.send_message('JD_CancelKeyboard_PhoneCommandData')
|
await self.send_message('JD_CancelKeyboard_PhoneCommandData')
|
||||||
elif __class == 'JD_PhoneUiSetupData':
|
elif __class == 'JD_PhoneUiSetupData':
|
||||||
self.is_input_allowed = True
|
self.is_input_allowed = True
|
||||||
shortcuts = set()
|
self.available_shortcuts = set()
|
||||||
if message.get('setupData', {}).get('gameplaySetup', {}).get('pauseSlider', {}):
|
if message.get('setupData', {}).get('gameplaySetup', {}).get('pauseSlider', {}):
|
||||||
self.available_shortcuts.add(Command.PAUSE)
|
self.available_shortcuts.add(Command.PAUSE)
|
||||||
|
|
||||||
|
@ -274,7 +286,7 @@ 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. Only works on procol v2 '''
|
''' Capture Joycon's input and send to console. Only works on protocol v2 '''
|
||||||
if self.protocol_version == WsSubprotocolVersion.V1:
|
if self.protocol_version == WsSubprotocolVersion.V1:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -351,12 +363,15 @@ class JoyDance:
|
||||||
await asyncio.sleep(0.01)
|
await asyncio.sleep(0.01)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
await self.disconnect()
|
await self.disconnect()
|
||||||
|
|
||||||
async def connect_ws(self):
|
async def connect_ws(self):
|
||||||
|
server_hostname = None
|
||||||
|
|
||||||
if self.protocol_version == WsSubprotocolVersion.V1:
|
if self.protocol_version == WsSubprotocolVersion.V1:
|
||||||
ssl_context = None
|
ssl_context = None
|
||||||
server_hostname = None
|
|
||||||
else:
|
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')
|
||||||
|
@ -364,7 +379,16 @@ class JoyDance:
|
||||||
ssl_context.check_hostname = False
|
ssl_context.check_hostname = False
|
||||||
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
|
if self.tls_certificate:
|
||||||
|
ssl_context.load_verify_locations(cadata=self.tls_certificate)
|
||||||
|
|
||||||
|
if '192.168' in self.pairing_url:
|
||||||
|
if self.console_conn:
|
||||||
|
server_hostname = self.console_conn.getpeername()[0]
|
||||||
|
else:
|
||||||
|
# Stadia
|
||||||
|
tmp = urlparse(self.pairing_url)
|
||||||
|
server_hostname = tmp.hostname
|
||||||
|
|
||||||
subprotocol = WS_SUBPROTOCOLS[self.protocol_version.value]
|
subprotocol = WS_SUBPROTOCOLS[self.protocol_version.value]
|
||||||
try:
|
try:
|
||||||
|
@ -387,7 +411,8 @@ class JoyDance:
|
||||||
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:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
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)
|
||||||
|
|
||||||
|
|
|
@ -129,8 +129,8 @@ SHORTCUT_MAPPING = {
|
||||||
}
|
}
|
||||||
|
|
||||||
# Same with Joy-Con (L)
|
# Same with Joy-Con (L)
|
||||||
SHORTCUT_MAPPING[JoyConButton.UP] = JoyConButton.X
|
SHORTCUT_MAPPING[JoyConButton.UP] = SHORTCUT_MAPPING[JoyConButton.X]
|
||||||
SHORTCUT_MAPPING[JoyConButton.LEFT] = JoyConButton.Y
|
SHORTCUT_MAPPING[JoyConButton.LEFT] = SHORTCUT_MAPPING[JoyConButton.Y]
|
||||||
SHORTCUT_MAPPING[JoyConButton.MINUS] = JoyConButton.PLUS
|
SHORTCUT_MAPPING[JoyConButton.MINUS] = SHORTCUT_MAPPING[JoyConButton.PLUS]
|
||||||
SHORTCUT_MAPPING[JoyConButton.L] = JoyConButton.R
|
SHORTCUT_MAPPING[JoyConButton.L] = SHORTCUT_MAPPING[JoyConButton.R]
|
||||||
SHORTCUT_MAPPING[JoyConButton.ZL] = JoyConButton.ZR
|
SHORTCUT_MAPPING[JoyConButton.ZL] = SHORTCUT_MAPPING[JoyConButton.ZR]
|
||||||
|
|
|
@ -18,6 +18,7 @@ const BATTERY_LEVEL = {
|
||||||
const PairingMethod = {
|
const PairingMethod = {
|
||||||
DEFAULT: 'default',
|
DEFAULT: 'default',
|
||||||
FAST: 'fast',
|
FAST: 'fast',
|
||||||
|
STADIA: 'stadia',
|
||||||
OLD: 'old',
|
OLD: 'old',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,8 +86,9 @@ class PairingMethodPicker extends Component {
|
||||||
<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">
|
<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 except Stadia</option>
|
||||||
<option value="${PairingMethod.FAST}">Fast: Xbox One/PlayStation/Nintendo Switch</option>
|
<option value="${PairingMethod.FAST}">Fast: Xbox One/PlayStation/Nintendo Switch</option>
|
||||||
|
<option value="${PairingMethod.STADIA}">Stadia: for Stadia, obviously</option>
|
||||||
</optgroup>
|
</optgroup>
|
||||||
<optgroup label="JD 2016-2019">
|
<optgroup label="JD 2016-2019">
|
||||||
<option value="${PairingMethod.OLD}">Old: All platforms (incl. PC)</option>
|
<option value="${PairingMethod.OLD}">Old: All platforms (incl. PC)</option>
|
||||||
|
@ -147,15 +149,19 @@ class PrivateIpAddress extends Component {
|
||||||
const addr = pairing_method == PairingMethod.DEFAULT ? state.host_ip_addr : state.console_ip_addr
|
const addr = pairing_method == PairingMethod.DEFAULT ? state.host_ip_addr : state.console_ip_addr
|
||||||
return html`
|
return html`
|
||||||
<label>
|
<label>
|
||||||
${pairing_method == PairingMethod.DEFAULT && html`Host's Private IP Address`}
|
${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) > -1 && html`Host's Private IP Address`}
|
||||||
${pairing_method != PairingMethod.DEFAULT && html`Console's Private IP Address`}
|
${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) == -1 && html`Console's Private IP Address`}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
${pairing_method == PairingMethod.DEFAULT && state.lock_host && html`
|
${(pairing_method == PairingMethod.STADIA) && html`
|
||||||
<input readonly required id="ipAddr" type="text" size="15" placeholder="${addr}" />
|
<input readonly id="ipAddr" type="text" size="15" placeholder="Not Required" />
|
||||||
`}
|
`}
|
||||||
|
|
||||||
${(pairing_method != PairingMethod.DEFAULT || !state.lock_host) && html`
|
${(pairing_method == PairingMethod.DEFAULT && state.lock_host) && html`
|
||||||
|
<input readonly id="ipAddr" type="text" size="15" placeholder="${addr}" />
|
||||||
|
`}
|
||||||
|
|
||||||
|
${([PairingMethod.FAST, PairingMethod.OLD].indexOf(pairing_method) > -1 || (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}" />
|
||||||
`}
|
`}
|
||||||
|
|
||||||
|
@ -183,12 +189,13 @@ class PairingCode extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render(props, state) {
|
render(props, state) {
|
||||||
|
const pairing_method = props.pairing_method
|
||||||
return html`
|
return html`
|
||||||
<label>Pairing Code</label>
|
<label>Pairing Code</label>
|
||||||
${props.pairing_method == PairingMethod.DEFAULT && html`
|
${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) > -1 && 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.DEFAULT && html`
|
${[PairingMethod.DEFAULT, PairingMethod.STADIA].indexOf(pairing_method) == -1 && 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…
Add table
Add a link
Reference in a new issue