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