diff --git a/joycontrol/protocol.py b/joycontrol/protocol.py index 00d2bf7..e41d155 100644 --- a/joycontrol/protocol.py +++ b/joycontrol/protocol.py @@ -27,6 +27,7 @@ class ControllerProtocol(BaseProtocol): self.transport = None + # Increases for each input report send, overflows at 0x100 self._input_report_timer = 0x00 self._data_received = asyncio.Event() @@ -35,6 +36,7 @@ class ControllerProtocol(BaseProtocol): 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() async def write(self, input_report: InputReport): @@ -61,10 +63,13 @@ class ControllerProtocol(BaseProtocol): await self.transport.write(input_report) self._controller_state.sig_is_send.set() - def get_controller_state(self): + def get_controller_state(self) -> ControllerState: return self._controller_state async def wait_for_output_report(self): + """ + Blocks until an output report from the Switch is received. + """ self._data_received.clear() await self._data_received.wait() @@ -79,11 +84,15 @@ class ControllerProtocol(BaseProtocol): raise NotImplementedError() 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()) @@ -110,7 +119,10 @@ class ControllerProtocol(BaseProtocol): report = OutputReport(list(data)) output_report_id = report.get_output_report_id() - if output_report_id == OutputReportID.SUB_COMMAND: + 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') @@ -118,7 +130,7 @@ class ControllerProtocol(BaseProtocol): logger.warning(err) if reply_send: - # Hack: Adding a delay here to avoid flooding + # 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 diff --git a/joycontrol/report.py b/joycontrol/report.py index 658aba3..c6db581 100644 --- a/joycontrol/report.py +++ b/joycontrol/report.py @@ -67,20 +67,26 @@ class InputReport: """ Sets the joystick status bytes """ - self.data[7:10] = bytes(left_stick) - self.data[10:13] = bytes(right_stick) + self.set_left_analog_stick(bytes(left_stick)) + self.set_right_analog_stick(bytes(right_stick)) - def set_left_analog_stick(self): + def set_left_analog_stick(self, left_stick_bytes): """ - TODO + Set left analog stick status bytes. + :param left_stick_bytes: 3 bytes """ - self.data[7:10] = [0x01, 0x18, 0x80] + if len(left_stick_bytes) != 3: + raise ValueError('Left stick status data must be exactly 3 bytes!') + self.data[7:10] = left_stick_bytes - def set_right_analog_stick(self): + def set_right_analog_stick(self, right_stick_bytes): """ - TODO + Set right analog stick status bytes. + :param right_stick_bytes: 3 bytes """ - self.data[10:13] = [0x01, 0x18, 0x80] + if len(right_stick_bytes) != 3: + raise ValueError('Right stick status data must be exactly 3 bytes!') + self.data[10:13] = right_stick_bytes def set_vibrator_input(self): """ @@ -101,6 +107,7 @@ class InputReport: def set_6axis_data(self): """ Set accelerator and gyro of 0x30 input reports + TODO """ # HACK: Set all 0 for now for i in range(14, 50): @@ -215,9 +222,8 @@ class OutputReport: if not data: data = 50 * [0x00] data[0] = 0xA2 - - if data[0] != 0xA2: - raise ValueError('Output reports must start with 0xA2') + elif data[0] != 0xA2: + raise ValueError('Output reports must start with a 0xA2 byte!') self.data = data def get_output_report_id(self): @@ -237,7 +243,7 @@ class OutputReport: def set_timer(self, timer): """ - Output report timer [0x0 - 0xF] + Output report timer in [0x0, 0xF] """ self.data[2] = timer % 0x10 @@ -252,16 +258,22 @@ class OutputReport: except ValueError: raise NotImplementedError(f'Sub command id {hex(self.data[11])} not implemented') + def set_sub_command(self, _id): + if isinstance(_id, SubCommand): + self.data[11] = _id.value + elif isinstance(_id, int): + self.data[11] = _id + else: + raise ValueError('id must be int or SubCommand') + def get_sub_command_data(self): if len(self.data) < 13: return None return self.data[12:] - def set_sub_command(self, _id): - if isinstance(_id, SubCommand): - self.data[11] = _id.value - else: - self.data[11] = _id + def set_sub_command_data(self, data): + for i, _byte in enumerate(data): + self.data[12+i] = _byte def sub_0x10_spi_flash_read(self, offset, size): """