async def _command_set_input_report_mode(self, sub_command_data): if sub_command_data[0] == 0x30: logger.info('Setting input report mode to 0x30...') input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0x80) input_report.reply_to_subcommand_id(0x03) await self.write(input_report) # start sending 0x30 input reports if self._input_report_mode != 0x30: self._input_report_mode = 0x30 self.transport.pause_reading() new_reader = asyncio.ensure_future( self.input_report_mode_0x30()) # We need to swap the reader in the future because this function was probably called by it async def set_reader(): await self.transport.set_reader(new_reader) self.transport.resume_reading() asyncio.ensure_future(set_reader()).add_done_callback( utils.create_error_check_callback()) else: logger.error( f'input report mode {sub_command_data[0]} not implemented - ignoring request' )
async def _command_set_input_report_mode(self, sub_command_data): if sub_command_data[0] == 0x30: logger.info('Setting input report mode to 0x30...') input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0x80) input_report.reply_to_subcommand_id(0x03) await self.write(input_report) # start sending 0x30 input reports if self._0x30_input_report_sender is None: self.transport.pause_reading() self._0x30_input_report_sender = asyncio.ensure_future( self.input_report_mode_0x30()) # create callback to check for exceptions def callback(future): try: future.result() except Exception as err: logger.exception(err) self._0x30_input_report_sender.add_done_callback(callback) else: logger.error( f'input report mode {sub_command_data[0]} not implemented - ignoring request' )
async def input_report_mode_0x30(self): """ Continuously sends 0x30 input reports containing the controller state. """ if self.transport.is_reading(): raise ValueError( 'Transport must be paused in 0x30 input report mode') self._in_0x30_input_report_mode = True input_report = InputReport() input_report.set_input_report_id(0x30) input_report.set_vibrator_input() input_report.set_misc() reader = asyncio.ensure_future(self.transport.read()) while True: await self.sig_unpause_input_report_mode_0x30.wait() if self.controller == Controller.PRO_CONTROLLER: # send state at 120Hz await asyncio.sleep(1 / 120) else: # send state at 60Hz await asyncio.sleep(1 / 60) reply_send = False if reader.done(): data = await reader if not data: # disconnect happened logger.error( 'No data received (most likely due to a disconnect).') break reader = asyncio.ensure_future(self.transport.read()) try: report = OutputReport(list(data)) output_report_id = report.get_output_report_id() if output_report_id == OutputReportID.RUMBLE_ONLY: # TODO pass elif output_report_id == OutputReportID.SUB_COMMAND: reply_send = await self._reply_to_sub_command(report) except ValueError as v_err: logger.warning(f'Report parsing error "{v_err}" - IGNORE') except NotImplementedError as err: logger.warning(err) if reply_send: # Hack: Adding a delay here to avoid flooding during pairing await asyncio.sleep(0.3) else: # write 0x30 input report. TODO: set some sensor data input_report.set_6axis_data() await self.write(input_report)
async def _command_set_shipment_state(self, sub_command_data): input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0x80) input_report.reply_to_subcommand_id(0x08) await self.write(input_report)
async def _command_enable_6axis_sensor(self, sub_command_data): input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0x80) input_report.reply_to_subcommand_id(0x40) await self.write(input_report)
async def _command_enable_vibration(self, sub_command_data): input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0x80) input_report.reply_to_subcommand_id(SubCommand.ENABLE_VIBRATION.value) await self.write(input_report)
async def _command_set_player_lights(self, sub_command_data): input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0x80) input_report.reply_to_subcommand_id(SubCommand.SET_PLAYER_LIGHTS.value) await self.write(input_report) self.sig_set_player_lights.set()
async def _command_request_device_info(self, sub_command_data): input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() address = self.transport.get_extra_info('sockname') assert address is not None bd_address = list(map(lambda x: int(x, 16), address[0].split(':'))) input_report.set_ack(0x82) input_report.sub_0x02_device_info(bd_address, controller=self.controller) await self.write(input_report)
async def write(self, input_report: InputReport): # set button and stick data input_report.set_button_status(self._controller_state.button_state) if self._controller_state.l_stick_state is None: l_stick = [0x00, 0x00, 0x00] else: l_stick = self._controller_state.l_stick_state if self._controller_state.r_stick_state is None: r_stick = [0x00, 0x00, 0x00] else: r_stick = self._controller_state.r_stick_state input_report.set_stick_status(l_stick, r_stick) await self.transport.write(input_report) self._controller_state.sig_is_send.set()
async def _command_set_input_report_mode(self, sub_command_data): if self._input_report_mode == sub_command_data[0]: logger.warning( f'Already in input report mode {sub_command_data[0]} - ignoring request' ) # Start input report reader if sub_command_data[0] in (0x30, 0x31): new_reader = asyncio.ensure_future(self.input_report_mode_full()) else: logger.error( f'input report mode {sub_command_data[0]} not implemented - ignoring request' ) return # Replace the currently running reader with the input report mode sender, # which will also handle incoming requests in the future self.transport.pause_reading() # We need to replace the reader in the future because this function was probably called by it async def set_reader(): await self.transport.set_reader(new_reader) logger.info( f'Setting input report mode to {hex(sub_command_data[0])}...') self._input_report_mode = sub_command_data[0] self.transport.resume_reading() asyncio.ensure_future(set_reader()).add_done_callback( utils.create_error_check_callback()) # Send acknowledgement input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0x80) input_report.reply_to_subcommand_id(0x03) await self.write(input_report)
async def write(self, input_report: InputReport): """ Sets timer byte and current button state in the input report and sends it. Fires sig_is_send event in the controller state afterwards. Raises NotConnected exception if the transport is not connected or the connection was lost. """ if self.transport is None: raise NotConnectedError('Transport not registered.') # set button and stick and 6-axis data of input report input_report.set_button_status(self._controller_state.button_state) if self._controller_state.l_stick_state is None: l_stick = [0x00, 0x00, 0x00] else: l_stick = self._controller_state.l_stick_state if self._controller_state.r_stick_state is None: r_stick = [0x00, 0x00, 0x00] else: r_stick = self._controller_state.r_stick_state input_report.set_stick_status(l_stick, r_stick) # set timer byte of input report input_report.set_timer(self._input_report_timer) self._input_report_timer = (self._input_report_timer + 1) % 0x100 await self.transport.write(input_report) self._controller_state.sig_is_send.set()
async def receive_data(self, hid_device, output_file=None): while True: data = await hid_device.read(size=255, timeout=3) if self._stop_reading: break elif not data: continue # add byte for input report data = b'\xa1' + data input_report = InputReport(list(data)) # check if input report is spi flash read reply if input_report.get_input_report_id() != 0x21: continue try: sub_command_id = input_report.get_reply_to_subcommand_id() if sub_command_id != SubCommand.SPI_FLASH_READ: continue except NotImplementedError: continue assert input_report.get_ack() == 0x90 reply = input_report.get_sub_command_reply_data() # parse offset offset = 0 digit = 1 for i in range(4): offset += reply[i] * digit digit *= 0x100 size = reply[4] # parse spi flash data assert len(reply) >= 5 + size spi_data = reply[5:5 + size] # check if received data is currently requested if self.pending_request is None or self.pending_request[ 0] != offset or self.pending_request[1] != size: continue # notify spi request sender that the data is received self.pending_request[2].set() logger.info(f'received offset {offset}, size {size} - {spi_data}') # write data to file if output_file is not None: output_file.write(bytes(spi_data))
async def input_report_mode_0x30(self): if self.transport.is_reading(): raise ValueError('Transport must be paused in 0x30 input report mode') input_report = InputReport() input_report.set_input_report_id(0x30) input_report.set_misc() reader = asyncio.ensure_future(self.transport.read()) while True: if self.controller == Controller.PRO_CONTROLLER: # send state at 120Hz await asyncio.sleep(1 / 120) else: # send state at 60Hz await asyncio.sleep(1 / 60) reply_send = False if reader.done(): data = await reader reader = asyncio.ensure_future(self.transport.read()) try: report = OutputReport(list(data)) output_report_id = report.get_output_report_id() if output_report_id == OutputReportID.SUB_COMMAND: reply_send = await self._reply_to_sub_command(report) except ValueError as v_err: logger.warning(f'Report parsing error "{v_err}" - IGNORE') except NotImplementedError as err: logger.warning(err) if reply_send: # Hack: Adding a delay here to avoid flooding await asyncio.sleep(0.3) else: # write 0x30 input report. TODO: set some sensor data input_report.set_6axis_data() await self.write(input_report)
async def write(self, input_report: InputReport): """ Sets timer byte and current button state in the input report and sends it. Fires sig_is_send event afterwards. """ # set button and stick data of input report input_report.set_button_status(self._controller_state.button_state) if self._controller_state.l_stick_state is None: l_stick = [0x00, 0x00, 0x00] else: l_stick = self._controller_state.l_stick_state if self._controller_state.r_stick_state is None: r_stick = [0x00, 0x00, 0x00] else: r_stick = self._controller_state.r_stick_state input_report.set_stick_status(l_stick, r_stick) # set timer byte of input report input_report.set_timer(self._input_report_timer) self._input_report_timer = (self._input_report_timer + 1) % 0x100 await self.transport.write(input_report) self._controller_state.sig_is_send.set()
async def _send_empty_input_reports(transport): report = InputReport() for i in range(10): await transport.write(report) await asyncio.sleep(1)
async def _command_set_nfc_ir_mcu_state(self, sub_command_data): input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() if sub_command_data[0] == 0x01: # 0x01 = Resume input_report.set_ack(0x80) input_report.reply_to_subcommand_id( SubCommand.SET_NFC_IR_MCU_STATE.value) elif sub_command_data[0] == 0x00: # 0x00 = Suspend input_report.set_ack(0x80) input_report.reply_to_subcommand_id( SubCommand.SET_NFC_IR_MCU_STATE.value) else: raise NotImplementedError( f'Argument {sub_command_data[0]} of {SubCommand.SET_NFC_IR_MCU_STATE} ' f'not implemented.') await self.write(input_report)
async def _command_set_nfc_ir_mcu_config(self, sub_command_data): input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0xA0) input_report.reply_to_subcommand_id( SubCommand.SET_NFC_IR_MCU_CONFIG.value) # TODO data = [ 1, 0, 255, 0, 8, 0, 27, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 200 ] for i in range(len(data)): input_report.data[16 + i] = data[i] await self.write(input_report)
def _generate_input_report(self, mode=None): input_report = InputReport() if not mode: mode = self._input_report_mode if not mode: raise ValueError("cannot generate Report without Mode") input_report.set_input_report_id(mode) if mode == 0x3F: input_report.data[1:3] = [0x28, 0xca, 0x08] input_report.data[4:11] = [ 0x40, 0x8A, 0x4F, 0x8A, 0xD0, 0x7E, 0xDF, 0x7F ] else: if self._input_report_timer_start: input_report.set_timer( round( (time.time() - self._input_report_timer_start) / 0.005) % 0x100) else: input_report.set_timer(0) input_report.set_misc() input_report.set_button_status(self._controller_state.button_state) input_report.set_stick_status(self._controller_state.l_stick_state, self._controller_state.r_stick_state) input_report.set_vibrator_input() if mode == 0x21: pass # subcommand is set outside elif mode in [0x30, 0x31, 0x32, 0x33]: input_report.set_6axis_data() if mode == 0x31: input_report.set_ir_nfc_data(self._mcu.get_data()) return input_report
async def _command_set_nfc_ir_mcu_config(self, sub_command_data): input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0xA0) input_report.reply_to_subcommand_id(SubCommand.SET_NFC_IR_MCU_CONFIG.value) self._mcu.update_status() data = list(bytes(self._mcu)[0:34]) crc = crc8() crc.update(bytes(data[:-1])) checksum = crc.digest() data[-1] = ord(checksum) for i in range(len(data)): input_report.data[16+i] = data[i] # Set MCU mode cmd if sub_command_data[1] == 0: if sub_command_data[2] == 0: self._mcu.set_state(McuState.STAND_BY) elif sub_command_data[2] == 4: self._mcu.set_state(McuState.NFC) else: logger.info(f"unknown mcu state {sub_command_data[2]}") else: logger.info(f"unknown mcu config command {sub_command_data}") await self.write(input_report)
async def _command_trigger_buttons_elapsed_time(self, sub_command_data): input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0x83) input_report.reply_to_subcommand_id( SubCommand.TRIGGER_BUTTONS_ELAPSED_TIME) # Hack: We assume this command is only used during pairing - Set values so the Switch assigns a player number if self.controller == Controller.PRO_CONTROLLER: input_report.sub_0x04_trigger_buttons_elapsed_time(L_ms=3000, R_ms=3000) elif self.controller in (Controller.JOYCON_L, Controller.JOYCON_R): # TODO: What do we do if we want to pair a combined JoyCon? input_report.sub_0x04_trigger_buttons_elapsed_time(SL_ms=3000, SR_ms=3000) else: raise NotImplementedError(self.controller) await self.write(input_report)
async def input_report_mode_0x30(self): """ Continuously sends 0x30 input reports containing the controller state. """ if self.transport.is_reading(): raise ValueError( 'Transport must be paused in 0x30 input report mode') input_report = InputReport() input_report.set_input_report_id(0x30) input_report.set_vibrator_input() input_report.set_misc() reader = asyncio.ensure_future(self.transport.read()) try: while True: # TODO: improve timing if self.controller == Controller.PRO_CONTROLLER: # send state at 120Hz await asyncio.sleep(1 / 120) else: # send state at 60Hz await asyncio.sleep(1 / 60) reply_send = False if reader.done(): data = await reader reader = asyncio.ensure_future(self.transport.read()) try: report = OutputReport(list(data)) output_report_id = report.get_output_report_id() if output_report_id == OutputReportID.RUMBLE_ONLY: # TODO pass elif output_report_id == OutputReportID.SUB_COMMAND: reply_send = await self._reply_to_sub_command( report) except ValueError as v_err: logger.warning( f'Report parsing error "{v_err}" - IGNORE') except NotImplementedError as err: logger.warning(err) if reply_send: # Hack: Adding a delay here to avoid flooding during pairing await asyncio.sleep(0.3) else: # write 0x30 input report. # TODO: set some sensor data input_report.set_6axis_data() await self.write(input_report) except NotConnectedError as err: # Stop 0x30 input report mode if disconnected. print(' [-] Probably disconnected from console.') quit() finally: # cleanup self._input_report_mode = None # cancel the reader with suppress(asyncio.CancelledError, NotConnectedError): if reader.cancel(): await reader
async def _command_spi_flash_read(self, sub_command_data): """ Replies with 0x21 input report containing requested data from the flash memory. :param sub_command_data: input report sub command data bytes """ input_report = InputReport() input_report.set_input_report_id(0x21) input_report.set_misc() input_report.set_ack(0x90) # parse offset offset = 0 digit = 1 for i in range(4): offset += sub_command_data[i] * digit digit *= 0x100 size = sub_command_data[4] if self.spi_flash is not None: spi_flash_data = self.spi_flash[offset:offset + size] input_report.sub_0x10_spi_flash_read(offset, size, spi_flash_data) else: spi_flash_data = size * [0x00] input_report.sub_0x10_spi_flash_read(offset, size, spi_flash_data) await self.write(input_report)
async def input_report_mode_full(self): """ Continuously sends: 0x30 input reports containing the controller state OR 0x31 input reports containing the controller state and nfc data """ if self.transport.is_reading(): raise ValueError('Transport must be paused in full input report mode') # send state at 66Hz send_delay = 0.015 await asyncio.sleep(send_delay) last_send_time = time.time() input_report = InputReport() input_report.set_vibrator_input() input_report.set_misc() if self._input_report_mode is None: raise ValueError('Input report mode is not set.') input_report.set_input_report_id(self._input_report_mode) reader = asyncio.ensure_future(self.transport.read()) try: while True: reply_send = False if reader.done(): data = await reader reader = asyncio.ensure_future(self.transport.read()) try: report = OutputReport(list(data)) output_report_id = report.get_output_report_id() if output_report_id == OutputReportID.RUMBLE_ONLY: # TODO pass elif output_report_id == OutputReportID.SUB_COMMAND: reply_send = await self._reply_to_sub_command(report) elif output_report_id == OutputReportID.REQUEST_IR_NFC_MCU: # TODO NFC raise NotImplementedError('NFC communictation is not implemented.') else: logger.warning(f'Report unknown output report "{output_report_id}" - IGNORE') except ValueError as v_err: logger.warning(f'Report parsing error "{v_err}" - IGNORE') except NotImplementedError as err: logger.warning(err) if reply_send: # Hack: Adding a delay here to avoid flooding during pairing await asyncio.sleep(0.3) else: # write 0x30 input report. input_report.set_6axis_data(*self._controller_state.axis_state.get_6axis()) # TODO NFC - set nfc data if input_report.get_input_report_id() == 0x31: pass await self.write(input_report) # calculate delay current_time = time.time() time_delta = time.time() - last_send_time sleep_time = send_delay - time_delta last_send_time = current_time if sleep_time < 0: # logger.warning(f'Code is running {abs(sleep_time)} s too slow!') sleep_time = 0 await asyncio.sleep(sleep_time) except NotConnectedError as err: # Stop 0x30 input report mode if disconnected. logger.error(err) finally: # cleanup self._input_report_mode = None # cancel the reader with suppress(asyncio.CancelledError, NotConnectedError): if reader.cancel(): await reader
try: start_time = None while True: # parse capture time time = struct.unpack('d', _eof_read(capture, 8))[0] if start_time is None: start_time = time # parse data size size = struct.unpack('i', _eof_read(capture, 4))[0] # parse data data = list(_eof_read(capture, size)) if data[0] == 0xA1: report = InputReport(data) # normalise time input_reports.append((time, report)) elif data[0] == 0xA2: report = OutputReport(data) # normalise time output_reports.append((time, report)) else: raise ValueError(f'Unexpected data.') except EOFError: pass dir_input_list = get_dir_inputs() rumble_timestamps = get_rumble_timestamps() print(dir_input_list) print(rumble_timestamps)
async def _send_empty_input_reports(transport): report = InputReport() while True: await transport.write(report) await asyncio.sleep(1)
with open(args.capture_file, 'rb') as capture: try: start_time = None while True: # parse capture time time = struct.unpack('d', _eof_read(capture, 8))[0] if start_time is None: start_time = time # parse data size size = struct.unpack('i', _eof_read(capture, 4))[0] # parse data data = list(_eof_read(capture, size)) if data[0] == 0xA1: report = InputReport(data) # normalise time input_reports.append((time - start_time, report)) elif data[0] == 0xA2: report = OutputReport(data) # normalise time output_reports.append((time - start_time, report)) else: raise ValueError(f'Unexpected data.') # only interested in pairing if isinstance(report, OutputReport) and report.get_sub_command( ) == SubCommand.SET_PLAYER_LIGHTS: break except EOFError: pass