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