Пример #1
0
    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'
            )
Пример #2
0
    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'
            )
Пример #3
0
    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)
Пример #4
0
    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)
Пример #5
0
    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)
Пример #6
0
    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)
Пример #7
0
    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()
Пример #8
0
    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)
Пример #9
0
    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()
Пример #10
0
    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)
Пример #11
0
    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()
Пример #12
0
    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))
Пример #13
0
    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)
Пример #14
0
    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()
Пример #15
0
async def _send_empty_input_reports(transport):
    report = InputReport()
    for i in range(10):
        await transport.write(report)
        await asyncio.sleep(1)
Пример #16
0
    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)
Пример #17
0
    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)
Пример #18
0
    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
Пример #19
0
    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)
Пример #20
0
    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)
Пример #21
0
    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
Пример #22
0
    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)
Пример #23
0
    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)
Пример #25
0
async def _send_empty_input_reports(transport):
    report = InputReport()

    while True:
        await transport.write(report)
        await asyncio.sleep(1)
Пример #26
0
    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