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. | ||||
| - 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. | ||||
|  |  | |||
							
								
								
									
										20
									
								
								dance.py
									
										
									
									
									
								
							
							
						
						
									
										20
									
								
								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(): | ||||
|  |  | |||
|  | @ -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...') | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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` | ||||
|             <label for="stacked-state">Pairing Method</label> | ||||
|             <select id="stacked-state" onChange=${this.onChange} value=${props.pairing_method}> | ||||
|                 <option value="${PairingMethod.DEFAULT}">Default: All platforms (incl. Xbox Series/Stadia)</option> | ||||
|                 <option value="${PairingMethod.FAST}">Fast: Xbox One/PlayStation/Nintendo Switch</option> | ||||
|                 <optgroup label="JD 2020 and later"> | ||||
|                     <option value="${PairingMethod.DEFAULT}">Default: All platforms (incl. Xbox Series/Stadia)</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> | ||||
|         ` | ||||
|     } | ||||
|  | @ -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` | ||||
|             <label> | ||||
|                 ${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> | ||||
| 
 | ||||
|             ${pairing_method == PairingMethod.DEFAULT && state.lock_host && html` | ||||
|                 <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}" /> | ||||
|             `}
 | ||||
| 
 | ||||
|  | @ -181,7 +188,7 @@ class PairingCode extends Component { | |||
|             ${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} /> | ||||
|             `}
 | ||||
|             ${props.pairing_method == PairingMethod.FAST && html` | ||||
|             ${props.pairing_method != PairingMethod.DEFAULT && html` | ||||
|                 <input type="text" id="pairingCode" value="" readonly placeholder="Not Required" size="12" /> | ||||
|             `}
 | ||||
|         ` | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue