Ejemplo n.º 1
0
async def recording_playback(controller_state: ControllerState): #This method replays saved recordings
    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')
    # waits until controller is fully connected
    await controller_state.connect()
    savedRecordings = shelve.open('savedRecs', writeback=True)
    LeftStick = controller_state.l_stick_state
    RightStick = controller_state.r_stick_state
    recList = list(savedRecordings.keys())
    print('Saved Recordings:')
    print(recList)
    print('Enter the name of the recording you want to playback')
    print('Then press <enter> to start playback.')
    recordingName = await ainput(prompt='Recording name:')
    if recordingName in recList:
        recording = savedRecordings[recordingName]
        speed_factor = 1
        last_time = None
        for event in recording:
            if speed_factor > 0 and last_time is not None:
                time.sleep((event.time - last_time) / speed_factor)
            last_time = event.time
            key = event.scan_code or event.name
            btnTrans = keyToConBtn(key)
            await directStateSet(btnTrans, controller_state) if event.event_type == keyboard.KEY_DOWN else  await directStateUNSet(btnTrans, controller_state)
        keyboard.unhook_all()
        ControllerCLI._set_stick(RightStick, 'center', None)
        ControllerCLI._set_stick(LeftStick, 'center', None)
        await controller_state.send()
    else:
        print('Recording name not recognized')
Ejemplo n.º 2
0
async def hold_b_button(controller_state: ControllerState):

    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')

    await controller_state.connect()
    await button_push(controller_state, 'b', sec=5)
Ejemplo n.º 3
0
async def buy(controller_state: ControllerState):
    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')
    await controller_state.connect()
    await ainput(
        prompt=
        '动森购买器 Make sure the  Animal crossing in shop and press <enter> to continue.'
    )
    user_input = asyncio.ensure_future(
        ainput(prompt='Buying gifts... Press <enter> to stop.'))
    while not user_input.done():
        await button_push(controller_state, 'a')
        await button_push(controller_state, 'b')
        await asyncio.sleep(0.4)
        await button_push(controller_state, 'a')
        await button_push(controller_state, 'b')
        await asyncio.sleep(0.4)
        await button_push(controller_state, 'down')
        await button_push(controller_state, 'a')
        await button_push(controller_state, 'b')
        await asyncio.sleep(1.5)
        await button_push(controller_state, 'a')
        await button_push(controller_state, 'b')
        await asyncio.sleep(0.4)
        await button_push(controller_state, 'a')
        await button_push(controller_state, 'b')
        await asyncio.sleep(0.4)
    await user_input
Ejemplo n.º 4
0
async def wait_long(controller_state: ControllerState):

    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')

    await controller_state.connect()
    await asyncio.sleep(1)
Ejemplo n.º 5
0
async def record_keyboard(
    controller_state: ControllerState
):  #this method binds keyboard to conroller and records input for later playback
    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')
    # waits until controller is fully connected
    await controller_state.connect()
    print('Using only letters and numbers, type a name for this recording')
    print('Then press <enter> to start recording keyboard control.')
    recordingName = await ainput(prompt='Recording name:')
    #pixels = neopixel.NeoPixel(board.D12, 6, auto_write=False)

    #button state handler callbacks
    savedRecordings = shelve.open('savedRecs', writeback=True)
    LeftStick = controller_state.l_stick_state
    RightStick = controller_state.r_stick_state
    bindKeyboard(controller_state)
    keyboard.start_recording()
    pixels.fill((0, 0, 0))
    pixels.fill((10, 0, 0))
    pixels.fill((10, 0, 0))
    await ainput(
        prompt='Press <enter> to stop recording and exit keyboard control.')
    recording = keyboard.stop_recording()
    pixels.fill((0, 0, 0))
    pixels.fill((0, 0, 0))

    keyboard.unhook_all()

    savedRecordings[recordingName] = recording
    savedRecordings.close()

    ControllerCLI._set_stick(RightStick, 'center', None)
    ControllerCLI._set_stick(LeftStick, 'center', None)
    await controller_state.send()
Ejemplo n.º 6
0
async def make_fish_food(controller_state: ControllerState):
    """
    Example controller script.
    Navigates to the "Test Controller Buttons" menu and presses all buttons.
    """
    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')

    # waits until controller is fully connected
    await controller_state.connect()

    await ainput(prompt='press <enter> to start.')

    user_input = asyncio.ensure_future(
        ainput(prompt='Making fish foods... Press <enter> to stop.'))

    # push all buttons consecutively until user input
    while not user_input.done():
        await button_push(controller_state, 'a')
        await asyncio.sleep(0.3)
        if user_input.done():
            break

    # await future to trigger exceptions in case something went wrong
    await user_input
Ejemplo n.º 7
0
async def wait(controller_state: ControllerState):

    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')

    await controller_state.connect()
    # TODO assuming controls will go through (no junk), this is fine
    await asyncio.sleep(.2)
Ejemplo n.º 8
0
async def run_at_start(controller_state: ControllerState):


    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')
    await controller_state.connect()
    draw.rectangle((0, 0, width, height), outline=0, fill=0)
    draw.text((x + 12, top + 32), str("Switch Connected!"), font=font, fill=255)
    draw.text((x + 6, top + 40), str("Going back to game."), font=font, fill=255)
    disp.image(image)
    disp.display()


    await button_push(controller_state, 'a')
    await asyncio.sleep(1)
    await button_push(controller_state, 'home')
    await asyncio.sleep(1)
    await button_push(controller_state, 'left', sec=2)
    await asyncio.sleep(0.3)
    await button_push(controller_state, 'a')
    await asyncio.sleep(3)

    amiibo_button = ask_button(controller_state)

    #await button_push(controller_state, 'l&&r')
    while True:
        draw.rectangle((0, 0, width, height), outline=0, fill=0)
        draw.text((x+42,top), str("SW1 = A"),font=font, fill=255)
        draw.text((x+36, top + 8), str("SW2 = L+R"), font=font, fill=255)
        draw.text((x+42, top + 16), str("SW3 = " + amiibo_button), font=font, fill=255)
        draw.text((x+40, top + 32), str("SW1+2 = EXIT"), font=font, fill=255)
        draw.text((x+30, top + 48), str("Waiting on "), font=font, fill=255)
        draw.text((x+24, top + 56), str("button press"), font=font, fill=255)
        disp.image(image)
        disp.display()
        #time.sleep(.1)
        button_state1 = GPIO.input(sw1)
        button_state2 = GPIO.input(sw2)
        button_state3 = GPIO.input(sw3)
        if button_state1 == GPIO.LOW:
            await button_push(controller_state, 'a')
        if button_state2 == GPIO.LOW:
            await button_press(controller_state, 'l')
            await button_press(controller_state, 'r')
            await asyncio.sleep(1.5)
            await button_release(controller_state, 'l')
            await button_release(controller_state, 'r')
        if button_state3 == GPIO.LOW:
            await button_push(controller_state, str(amiibo_button))
            await asyncio.sleep(3)
            draw.rectangle((0, 0, width, height), outline=0, fill=0)
            disp.image(image)
            disp.display()
            break
        if button_state1 == GPIO.LOW and button_state2 == GPIO.LOW:
            break
Ejemplo n.º 9
0
    def __init__(self, controller: Controller, spi_flash: FlashMemory = None):
        self.controller = controller
        self.spi_flash = spi_flash

        self.transport = None

        # Increases for each input report send, should overflow at 0x100
        self._input_report_timer = 0x00

        self._data_received = asyncio.Event()

        self._controller_state = ControllerState(self, controller, spi_flash=spi_flash)
        self._controller_state_sender = None

        self._mcu = IrNfcMcu()

        # None = Just answer to sub commands
        self._input_report_mode = None

        # This event gets triggered once the Switch assigns a player number to the controller and accepts user inputs
        self.sig_set_player_lights = asyncio.Event()
Ejemplo n.º 10
0
    def __init__(self, controller: Controller, spi_flash: FlashMemory = None):
        self.controller = controller
        self.spi_flash = spi_flash

        self.transport = None

        self._data_received = asyncio.Event()

        self._controller_state = ControllerState(self, controller, spi_flash=spi_flash)

        self._pending_write = None
        self._pending_input_report = None

        self._0x30_input_report_sender = None

        self.sig_set_player_lights = asyncio.Event()
Ejemplo n.º 11
0
async def keyboard_control(controller_state: ControllerState):# this method binds keyboard to controller for CLI keyboard control of switch
    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')
    # waits until controller is fully connected
    await controller_state.connect()

    await ainput(prompt='Press <enter> to start keyboard control.')

    #button state handler callbacks
    LeftStick = controller_state.l_stick_state
    RightStick = controller_state.r_stick_state
    bindKeyboard(controller_state)
    await ainput(prompt='Press <enter> to exit keyboard control.')
    keyboard.unhook_all()
    ControllerCLI._set_stick(RightStick, 'center', None)
    ControllerCLI._set_stick(LeftStick, 'center', None)
    await controller_state.send()
Ejemplo n.º 12
0
async def delete_recording(controller_state: ControllerState): #This method deletes saved recordings
    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')
    # waits until controller is fully connected
    await controller_state.connect()
    savedRecordings = shelve.open('savedRecs', writeback=True)
    recList = list(savedRecordings.keys())
    print('Saved Recordings:')
    print(recList)
    print('Enter the name of the recording you want to delete')
    print('Then press <enter> to delete.')
    recordingName = await ainput(prompt='Recording name:')
    if recordingName in recList:
        del savedRecordings[recordingName]
        print('Recording deleted')
    else:
        print('Recording name not recognized')
Ejemplo n.º 13
0
    def __init__(self,
                 controller: Controller,
                 spi_flash: FlashMemory = None,
                 reconnect=False):
        self.controller = controller
        self.spi_flash = spi_flash
        self.transport = None

        # time when the timer started.
        self._input_report_timer_start = None

        self._controller_state = ControllerState(self,
                                                 controller,
                                                 spi_flash=spi_flash)
        self._controller_state_sender = None
        self._writer_thread = None

        self._mcu = MicroControllerUnit(self._controller_state)

        self._is_pairing = not reconnect

        # input mode
        self.delay_map = {
            None: math.inf,  # subcommands only
            0x3F: 1.0,
            0x21: math.inf,  # shouldn't happen
            0x30: 1 / 60,  # this needs revising, but 120 seems too fast
            #    0x30: 1/120 if self.controller == Controller.PRO_CONTROLLER else 1/60,
            0x31: 1 / 60
        }
        self._input_report_wakeup = asyncio.Event()
        self._set_mode(None)

        # "Pausing"-mechanism.
        self._not_paused = asyncio.Event()
        self._not_paused.set()

        self.sig_input_ready = asyncio.Event()
        self.sig_data_received = asyncio.Event()
Ejemplo n.º 14
0
    def __init__(self, controller: Controller, spi_flash: FlashMemory = None):
        self.controller = controller
        self.spi_flash = spi_flash

        self.transport = None

        # Increases for each input report send, overflows at 0x100
        self._input_report_timer = 0x00

        self._data_received = asyncio.Event()

        self._controller_state = ControllerState(self,
                                                 controller,
                                                 spi_flash=spi_flash)

        self._0x30_input_report_sender = None

        # This event gets triggered once the Switch assigns a player number to the controller and accepts user inputs
        self.sig_set_player_lights = asyncio.Event()

        # Setting this events pauses the controller input report loop
        self.sig_unpause_input_report_mode_0x30 = asyncio.Event()
        self.sig_unpause_input_report_mode_0x30.set()
        self._in_0x30_input_report_mode = False
Ejemplo n.º 15
0
async def test_controller_buttons(controller_state: ControllerState):
    """
    Example controller script.
    Navigates to the "Test Controller Buttons" menu and presses all buttons.
    """
    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')

    # waits until controller is fully connected
    await controller_state.connect()

    await ainput(
        prompt=
        'Make sure the Switch is in the Home menu and press <enter> to continue.'
    )
    """
    # We assume we are in the "Change Grip/Order" menu of the switch
    await button_push(controller_state, 'home')

    # wait for the animation
    await asyncio.sleep(1)
    """

    # Goto settings
    await button_push(controller_state, 'down', sec=1)
    await button_push(controller_state, 'right', sec=2)
    await asyncio.sleep(0.3)
    await button_push(controller_state, 'left')
    await asyncio.sleep(0.3)
    await button_push(controller_state, 'a')
    await asyncio.sleep(0.3)

    # go all the way down
    await button_push(controller_state, 'down', sec=4)
    await asyncio.sleep(0.3)

    # goto "Controllers and Sensors" menu
    for _ in range(2):
        await button_push(controller_state, 'up')
        await asyncio.sleep(0.3)
    await button_push(controller_state, 'right')
    await asyncio.sleep(0.3)

    # go all the way down
    await button_push(controller_state, 'down', sec=3)
    await asyncio.sleep(0.3)

    # goto "Test Input Devices" menu
    await button_push(controller_state, 'up')
    await asyncio.sleep(0.3)
    await button_push(controller_state, 'a')
    await asyncio.sleep(0.3)

    # goto "Test Controller Buttons" menu
    await button_push(controller_state, 'a')
    await asyncio.sleep(0.3)

    # push all buttons except home and capture
    button_list = controller_state.button_state.get_available_buttons()
    if 'capture' in button_list:
        button_list.remove('capture')
    if 'home' in button_list:
        button_list.remove('home')

    user_input = asyncio.ensure_future(
        ainput(prompt='Pressing all buttons... Press <enter> to stop.'))

    # push all buttons consecutively until user input
    while not user_input.done():
        for button in button_list:
            await button_push(controller_state, button)
            await asyncio.sleep(0.1)

            if user_input.done():
                break

    # await future to trigger exceptions in case something went wrong
    await user_input

    # go back to home
    await button_push(controller_state, 'home')
Ejemplo n.º 16
0
async def xbox(
    controller_state: ControllerState
):  # this method binds keyboard to controller for CLI keyboard control of switch
    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')
    # waits until controller is fully connected
    await controller_state.connect()

    lv = 0
    lh = 0
    rv = 0
    rh = 0
    #button state handler callbacks

    Working = True
    start_time = time.time()
    while Working:
        for event in pygame.event.get():
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RETURN:
                    Working = False
                    break
            if event.type == pygame.USEREVENT:
                pygame.time.set_timer(pygame.USEREVENT, 0)
                Working = False
                break

        LeftStick = controller_state.l_stick_state
        RightStick = controller_state.r_stick_state

        joysticks = [
            pygame.joystick.Joystick(x)
            for x in range(pygame.joystick.get_count())
        ]
        for i in range(pygame.joystick.get_count()):
            joysticks[i].init()

        for i in range(pygame.joystick.get_count()):
            #Left Stick

            if round(lh, -1) != round(
                    2048 + joysticks[i].get_axis(0) * 1792, -1) or round(
                        lv, -1) != round(
                            2048 + joysticks[i].get_axis(1) * 1792, -1):
                lh = (2048 + joysticks[i].get_axis(0) * 1792)
                lv = (2048 - joysticks[i].get_axis(1) * 1792)

                if lh < 2150 and lh > 1950:
                    lh = 2048
                if lv < 2150 and lv > 1950:
                    lv = 2048

                ControllerCLI._set_stick(LeftStick, 'h', lh)
                ControllerCLI._set_stick(LeftStick, 'v', lv)

            #Right Stick
            if round(rh, -1) != round(
                    2048 + joysticks[i].get_axis(3) * 1792, -1) or round(
                        rv, -1) != round(
                            2048 + joysticks[i].get_axis(4) * 1792, -1):
                rh = (2048 + joysticks[i].get_axis(3) * 1792)
                rv = (2048 - joysticks[i].get_axis(4) * 1792)

                if rh < 2150 and rh > 1950:
                    rh = 2048
                if rv < 2150 and rv > 1950:
                    rv = 2048

                ControllerCLI._set_stick(RightStick, 'h', rh)
                ControllerCLI._set_stick(RightStick, 'v', rv)

            #Triggers
            if joysticks[i].get_axis(2) >= 0.2:
                controller_state.button_state.set_button('zl')
            else:
                controller_state.button_state.set_button('zl', pushed=False)

            if joysticks[i].get_axis(5) >= -0.2:
                controller_state.button_state.set_button('zr')
            else:
                controller_state.button_state.set_button('zr', pushed=False)

            #Buttons

            if joysticks[i].get_button(0) == 1:  #B
                controller_state.button_state.set_button('b')
            else:
                controller_state.button_state.set_button('b', pushed=False)

            if joysticks[i].get_button(1) == 1:  #A
                controller_state.button_state.set_button('a')
            else:
                controller_state.button_state.set_button('a', pushed=False)

            if joysticks[i].get_button(2) == 1:  #Y
                controller_state.button_state.set_button('y')
            else:
                controller_state.button_state.set_button('y', pushed=False)

            if joysticks[i].get_button(3) == 1:  #X
                controller_state.button_state.set_button('x')
            else:
                controller_state.button_state.set_button('x', pushed=False)

            #Trigger Buttons
            if joysticks[i].get_button(4) == 1:  #Left
                controller_state.button_state.set_button('l')
            else:
                controller_state.button_state.set_button('l', pushed=False)

            if joysticks[i].get_button(5) == 1:  #Right
                controller_state.button_state.set_button('r')
            else:
                controller_state.button_state.set_button('r', pushed=False)

            #Other Buttons

            if joysticks[i].get_button(6) == 1:  #Minius
                controller_state.button_state.set_button('minus')
            else:
                controller_state.button_state.set_button('minus', pushed=False)

            if joysticks[i].get_button(7) == 1:  #plus
                controller_state.button_state.set_button('plus')
            else:
                controller_state.button_state.set_button('plus', pushed=False)
            if joysticks[i].get_button(8) == 1:  #Home
                controller_state.button_state.set_button('home')
            else:
                controller_state.button_state.set_button('home', pushed=False)
            if joysticks[i].get_button(9) == 1:  #Left Stick Click
                controller_state.button_state.set_button('l_stick')
            else:
                controller_state.button_state.set_button('l_stick',
                                                         pushed=False)
            if joysticks[i].get_button(10) == 1:  #Right Stick Click
                controller_state.button_state.set_button('r_stick')
            else:
                controller_state.button_state.set_button('r_stick',
                                                         pushed=False)

            #Dpad
            hat = joysticks[i].get_hat(0)
            if hat[0] == 1:  #Right
                controller_state.button_state.set_button('right')
            else:
                controller_state.button_state.set_button('right', pushed=False)

            if hat[0] == -1:  #Left
                controller_state.button_state.set_button('left')
            else:
                controller_state.button_state.set_button('left', pushed=False)

            if hat[1] == 1:  #Up
                controller_state.button_state.set_button('up')
            else:
                controller_state.button_state.set_button('up', pushed=False)

            if hat[1] == -1:  #Down
                controller_state.button_state.set_button('down')
            else:
                controller_state.button_state.set_button('down', pushed=False)

            await controller_state.send()
Ejemplo n.º 17
0
async def test_controller_buttons(controller_state: ControllerState):

    if controller_state.get_controller() != Controller.PRO_CONTROLLER:
        raise ValueError('This script only works with the Pro Controller!')

    # waits until controller is fully connected
    await controller_state.connect()

    await ainput(
        prompt=
        'Make sure your character is in the right place press <enter> to continue.'
    )

    user_input = asyncio.ensure_future(
        ainput(prompt='Starting the script... Press <enter> to stop.'))

    # push all buttons consecutively until user input
    while not user_input.done():
        await button_push(controller_state, 'a')
        await asyncio.sleep(1)
        await button_push(controller_state, 'a')
        await asyncio.sleep(1)
        await button_push(controller_state, 'a')
        await asyncio.sleep(1)
        await button_push(controller_state, 'a')
        await asyncio.sleep(5)

        await button_push(controller_state, 'a')
        await asyncio.sleep(1)
        await button_push(controller_state, 'a')
        await asyncio.sleep(1)
        await button_push(controller_state, 'a')
        await asyncio.sleep(1)

        await button_push(controller_state, 'x')
        await asyncio.sleep(1)
        await button_push(controller_state, 'a')
        await asyncio.sleep(1)
        await button_push(controller_state, 'up')
        await asyncio.sleep(1)
        await button_push(controller_state, 'a')
        await asyncio.sleep(1)
        await button_push(controller_state, 'a')
        await asyncio.sleep(1)
        await button_push(controller_state, 'b')
        await asyncio.sleep(1)

        await button_push(controller_state, 'plus')
        await asyncio.sleep(1)
        await button_push(controller_state, 'up')
        await asyncio.sleep(1)
        await button_push(controller_state, 'down')
        await asyncio.sleep(0.5)
        await button_push(controller_state, 'down')
        await asyncio.sleep(0.5)
        await button_push(controller_state, 'a')
        await asyncio.sleep(0.5)
        await button_push(controller_state, 'a')
        await asyncio.sleep(20)

        if user_input.done():
            break
Ejemplo n.º 18
0
class ControllerProtocol(BaseProtocol):
    def __init__(self, controller: Controller, spi_flash: FlashMemory = None):
        self.controller = controller
        self.spi_flash = spi_flash

        self.transport = None

        # Increases for each input report send, should overflow at 0x100
        self._input_report_timer = 0x00

        self._data_received = asyncio.Event()

        self._controller_state = ControllerState(self, controller, spi_flash=spi_flash)
        self._controller_state_sender = None

        self._mcu = IrNfcMcu()

        # None = Just answer to sub commands
        self._input_report_mode = None

        # This event gets triggered once the Switch assigns a player number to the controller and accepts user inputs
        self.sig_set_player_lights = asyncio.Event()

    async def send_controller_state(self):
        """
        Waits for the controller state to be send.

        Raises NotConnected exception if the transport is not connected or the connection was lost.
        """
        # TODO: Call write directly if in continuously sending input report mode

        if self.transport is None:
            raise NotConnectedError('Transport not registered.')

        self._controller_state.sig_is_send.clear()

        # wrap into a future to be able to set an exception in case of a disconnect
        self._controller_state_sender = asyncio.ensure_future(self._controller_state.sig_is_send.wait())
        await self._controller_state_sender
        self._controller_state_sender = None

    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 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()

    def get_controller_state(self) -> ControllerState:
        return self._controller_state

    async def wait_for_output_report(self):
        """
        Waits until an output report from the Switch is received.
        """
        self._data_received.clear()
        await self._data_received.wait()

    def connection_made(self, transport: BaseTransport) -> None:
        logger.debug('Connection established.')
        self.transport = transport

    def connection_lost(self, exc: Optional[Exception] = None) -> None:
        if self.transport is not None:
            logger.error('Connection lost.')
            asyncio.ensure_future(self.transport.close())
            self.transport = None

            if self._controller_state_sender is not None:
                self._controller_state_sender.set_exception(NotConnectedError)

    def error_received(self, exc: Exception) -> None:
        # TODO?
        raise NotImplementedError()

    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: This does not reply anything
                            # reply_send = await self._reply_to_ir_nfc_mcu(report)
                            await self._reply_to_ir_nfc_mcu(report)
                        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.
                    # TODO: set some sensor data
                    input_report.set_6axis_data()

                    # set nfc data
                    if input_report.get_input_report_id() == 0x31:
                        self._mcu.set_nfc(self._controller_state.get_nfc())
                        self._mcu.update_nfc_report()
                        input_report.set_ir_nfc_data(bytes(self._mcu))

                    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

    async def report_received(self, data: Union[bytes, Text], addr: Tuple[str, int]) -> None:
        self._data_received.set()

        try:
            report = OutputReport(list(data))
        except ValueError as v_err:
            logger.warning(f'Report parsing error "{v_err}" - IGNORE')
            return

        try:
            output_report_id = report.get_output_report_id()
        except NotImplementedError as err:
            logger.warning(err)
            return

        if output_report_id == OutputReportID.SUB_COMMAND:
            await self._reply_to_sub_command(report)
        # elif output_report_id == OutputReportID.RUMBLE_ONLY:
        #    pass
        else:
            logger.warning(f'Output report {output_report_id} not implemented - ignoring')

    async def _reply_to_ir_nfc_mcu(self, report):
        """
        TODO: Cleanup
              We aren't replying to anything here, do we need to?
        """
        sub_command = report.data[11]
        sub_command_data = report.data[12:]

        # logging.info(f'received output report - Request MCU sub command {sub_command}')

        if self._mcu.get_action() in (Action.READ_TAG, Action.READ_TAG_2, Action.READ_FINISHED):
            return

        # Request mcu state
        if sub_command == 0x01:
            # 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(0x21)

            self._mcu.set_action(Action.REQUEST_STATUS)
            # input_report.set_mcu(self._mcu)

            # await self.write(input_report)
        # Send Start tag discovery
        elif sub_command == 0x02:
            # 0: Cancel all, 4: StartWaitingReceive
            if sub_command_data[0] == 0x04:
                self._mcu.set_action(Action.START_TAG_DISCOVERY)
            # 1: Start polling
            elif sub_command_data[0] == 0x01:
                self._mcu.set_action(Action.START_TAG_POLLING)
            # 2: stop polling
            elif sub_command_data[0] == 0x02:
                self._mcu.set_action(Action.NON)
            elif sub_command_data[0] == 0x06:
                self._mcu.set_action(Action.READ_TAG)
            else:
                logging.info(f'Unknown sub_command_data arg {sub_command_data}')
        else:
            logging.info(f'Unknown MCU sub command {sub_command}')

    async def _reply_to_sub_command(self, report):
        # classify sub command
        try:
            sub_command = report.get_sub_command()
        except NotImplementedError as err:
            logger.warning(err)
            return False

        if sub_command is None:
            raise ValueError('Received output report does not contain a sub command')

        logging.info(f'received output report - Sub command {sub_command}')

        sub_command_data = report.get_sub_command_data()
        assert sub_command_data is not None

        try:
            # answer to sub command
            if sub_command == SubCommand.REQUEST_DEVICE_INFO:
                await self._command_request_device_info(sub_command_data)

            elif sub_command == SubCommand.SET_SHIPMENT_STATE:
                await self._command_set_shipment_state(sub_command_data)

            elif sub_command == SubCommand.SPI_FLASH_READ:
                await self._command_spi_flash_read(sub_command_data)

            elif sub_command == SubCommand.SET_INPUT_REPORT_MODE:
                await self._command_set_input_report_mode(sub_command_data)

            elif sub_command == SubCommand.TRIGGER_BUTTONS_ELAPSED_TIME:
                await self._command_trigger_buttons_elapsed_time(sub_command_data)

            elif sub_command == SubCommand.ENABLE_6AXIS_SENSOR:
                await self._command_enable_6axis_sensor(sub_command_data)

            elif sub_command == SubCommand.ENABLE_VIBRATION:
                await self._command_enable_vibration(sub_command_data)

            elif sub_command == SubCommand.SET_NFC_IR_MCU_CONFIG:
                await self._command_set_nfc_ir_mcu_config(sub_command_data)

            elif sub_command == SubCommand.SET_NFC_IR_MCU_STATE:
                await self._command_set_nfc_ir_mcu_state(sub_command_data)

            elif sub_command == SubCommand.SET_PLAYER_LIGHTS:
                await self._command_set_player_lights(sub_command_data)
            else:
                logger.warning(f'Sub command 0x{sub_command.value:02x} not implemented - ignoring')
                return False
        except NotImplementedError as err:
            logger.error(f'Failed to answer {sub_command} - {err}')
            return False
        return True

    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 _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_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 _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 _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 _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_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_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)
            self._mcu.set_action(Action.NON)
            self._mcu.set_state(McuState.STAND_BY)
        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)
            self._mcu.set_state(McuState.STAND_BY)
        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_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()