pythonjoystickgamepaddevice-emulationnintendo

How do I emulate Xbox 360 joysticks using Vgamepad with joycons?


I've managed to map all buttons and triggers from joycons using joycon-python to an emulated Xbox 360 controller using vgamepad. However, I cannot figure out how to map the joysticks. I've tried tons of different ways. In the worst cases I get no output at all, while in the best cases the emulated output goes strictly and strongly into one direction, which I can't noticeably influence by moving the sticks.

import vgamepad as vg
import time
from pyjoycon import JoyCon, get_R_id, get_L_id

right_joycon_id = get_R_id()
left_joycon_id = get_L_id()

right_joycon = JoyCon(*right_joycon_id)
left_joycon = JoyCon(*left_joycon_id)

gamepad = vg.VX360Gamepad()

while True:
    right_report = right_joycon.get_status()
    left_report = left_joycon.get_status()

    right_stick_x = right_report['analog-sticks']['right']['horizontal']
    right_stick_y = right_report['analog-sticks']['right']['vertical']
    left_stick_x = left_report['analog-sticks']['left']['horizontal']
    left_stick_y = left_report['analog-sticks']['left']['vertical']

    mapped_right_stick_x = int((right_stick_x - 32768) * 32767 / 32768)
    mapped_right_stick_y = int((right_stick_y - 32768) * 32767 / 32768)
    mapped_left_stick_x = int((left_stick_x - 32768) * 32767 / 32768)
    mapped_left_stick_y = int((left_stick_y - 32768) * 32767 / 32768)

    gamepad.right_joystick(x_value=mapped_right_stick_x, y_value=mapped_right_stick_y)
    gamepad.left_joystick(x_value=mapped_left_stick_x, y_value=mapped_left_stick_y)

    gamepad.update()
    
    time.sleep(0.005)

With this code, the output just continuously points the emulated joysticks sharply to the lower left regardless of the physical state of the sticks.

Indeed I barely know what I'm doing and am probably overlooking something obvious, but I got quite far mapping all other functions (aside from rumble), so I hope someone can explain what I'm missing


Solution

  • joycon-python outputs stick axes as 12-bit unsigned integer values (0 to 4096) and vgamepad wants 16-bit signed integer values (-32768 to 32767).

    Naively:

        x360_axis = int((joycon_axis - 2048) * (65536 / 4096))
    

    Note that the values you'll get from this are not calibrated. Switch controllers have calibration data that needs to be read from the device and applied to the raw thumbstick axis values.

    The factory calibration data is stored at 0x603d (left thumbstick) and 0x6046 (right thumbstick):

    https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/spi_flash_notes.md#x6000-factory-configuration-and-calibration

    The thumbstick calibration data contains min, max, and center values for each axis:

    https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/spi_flash_notes.md#analog-stick-factory-and-user-calibration

    There's also deadzone information at 0x6086:

    https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/imu_sensor_notes.md#stick-parameters-1--2