Skip to content

Is it possible to action and control the rumble of the joycon? #16

Open

Description

@Tetsujinfr

I could not find documentation on how to make the joycon rumble, any help to do that would be great, thanks

Activity

  1. Tetsujinfr commented on Sep 18, 2020

    @Tetsujinfr
    Author

    Anyone? Pointing me to some documentation? Thanks in advance

  2. pbsds commented on Jan 29, 2021

    @pbsds
    Contributor

    The "HD Rumble" feature is somewhat complicated.
    Its been a while since i last worked on this, but from what I glanced it seems to support both sequencing and layering of multiple rumble frequencies.

  3. Tetsujinfr commented on Jan 29, 2021

    @Tetsujinfr
    Author

    Thanks for the link. Do you know how to activate the rumble, is there a boolean flag or is it done via the encoded amplitude only ?
    Also, do you know how to send the commands to the joycon via the python api?

  4. pbsds commented on Jan 30, 2021

    @pbsds
    Contributor

    We use this function to send the commands to the joycon.
    Note that every command includes rumble data which we've simply hardcoded to do nothing at the moment.
    Use this command and this subcommand to send rumble data whenever.

  5. ericries commented on Sep 1, 2022

    @ericries

    I'm interested in this as well. I have tried following your suggestions above with something like:

    joycon._write_output_report(b'\x10',` b'\x00', bytes(b'\x00\x01\x40\x40\x00\x01\x40\x40'))

    but I am not sure how to permute the argument data to get actual rumble activity. Am I on the right track? is it simply a matter of converting the math at https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md into a pythonic equivalent? would that be a helpful patch for this library, if I could get it to work?

  6. ericries commented on Sep 1, 2022

    @ericries

    For the benefit of future viewers of this issue, I was able to get this working by pulling the values (look at simple_rumble) from this pull request:
    #27

  7. ericries commented on Sep 3, 2022

    @ericries

    Again for future reference, here is how to control the rumble effect in the joycon

    import sys
    import time
    import math
    
    from pyjoycon import JoyCon, get_R_id, get_L_id
    
    def clamp(x, min, max):
        if (x < min): return min;
        if (x > max): return max;
        return x;
    
    # thanks to https://github.com/tocoteron/joycon-python/pull/27
    class RumbleJoyCon(JoyCon):
        def __init__(self, *args, **kwargs):
            JoyCon.__init__(self,*args, **kwargs)
            
        def _send_rumble(self,data=b'\x00\x00\x00\x00\x00\x00\x00\x00'):
            self._RUMBLE_DATA = data
            self._write_output_report(b'\x10', b'', b'')
    
        def enable_vibration(self,enable=True):
            """Sends enable or disable command for vibration. Seems to do nothing."""
            self._write_output_report(b'\x01', b'\x48', b'\x01' if enable else b'\x00')
            
        def rumble_simple(self):
            """Rumble for approximately 1.5 seconds (why?). Repeat sending to keep rumbling."""
            self._send_rumble(b'\x98\x1e\xc6\x47\x98\x1e\xc6\x47')
    
        def rumble_stop(self):
            """Instantly stops the rumble"""
            self._send_rumble()
    
    
    # derived from https://github.com/Looking-Glass/JoyconLib/blob/master/Packages/com.lookingglass.joyconlib/JoyconLib_scripts/Joycon.cs
    
    class RumbleData:
        def __init__(self):
            self.h_f = None
            self.amp = None
            self.l_f = None
            self.t = None
            self.timed_rumble = None
    
        def set_vals(self, low_freq, high_freq, amplitude, time = 0):
                self.h_f = high_freq;
                self.amp = amplitude;
                self.l_f = low_freq;
                self.timed_rumble = False;
                self.t = 0;
                if time != 0:
                    self.t = time / 1000.0;
                    self.timed_rumble = True;
    
        def __init__(self, low_freq, high_freq, amplitude, time = 0):
                self.set_vals(low_freq, high_freq, amplitude, time)
                
    
        def GetData(self):
                rumble_data = [None] * 8
                if (self.amp == 0.0 ):
                    rumble_data[0] = 0x0;
                    rumble_data[1] = 0x1;
                    rumble_data[2] = 0x40;
                    rumble_data[3] = 0x40;
                else:
                    l_f = clamp(self.l_f, 40.875885, 626.286133);
                    amp = clamp(self.amp, 0.0, 1.0);
                    h_f = clamp(self.h_f, 81.75177, 1252.572266);
                    hf = int((round(32.0 * math.log(h_f * 0.1, 2)) - 0x60) * 4);
                    lf = int(round(32.0* math.log(l_f * 0.1, 2)) - 0x40);
                    hf_amp = None
                    if (amp == 0):
                        hf_amp = 0;
                    elif amp < 0.117:
                        hf_amp = int(((math.log(amp * 1000, 2) * 32) - 0x60) / (5 - pow(amp, 2)) - 1);
                    elif amp < 0.23:
                        hf_amp = int(((math.log(amp * 1000, 2) * 32) - 0x60) - 0x5c)
                    else:
                        hf_amp = int((((math.log(amp * 1000, 2) * 32) - 0x60) * 2) - 0xf6);
    
                    assert hf_amp is not None
                    lf_amp = int(round(hf_amp) * .5);
                    parity = int(lf_amp % 2);
                    if (parity > 0):
                        lf_amp -= 1
    
                    lf_amp = int(lf_amp >> 1);
                    lf_amp += 0x40;
                    if (parity > 0):
                        lf_amp |= 0x8000;
                        
                    rumble_data[0] = int(hf & 0xff);
                    rumble_data[1] = int((hf >> 8) & 0xff);
                    rumble_data[2] = lf;
                    rumble_data[1] += hf_amp;
                    rumble_data[2] += int((lf_amp >> 8) & 0xff);
                    rumble_data[3] = int(lf_amp & 0xff);
                for i in range(4):
                    rumble_data[4 + i] = rumble_data[i];
                #Debug.Log(string.Format("Encoded hex freq: {0:X2}", encoded_hex_freq));
                #Debug.Log(string.Format("lf_amp: {0:X4}", lf_amp));
                #Debug.Log(string.Format("hf_amp: {0:X2}", hf_amp));
                #Debug.Log(string.Format("l_f: {0:F}", l_f));
                #Debug.Log(string.Format("hf: {0:X4}", hf));
                #Debug.Log(string.Format("lf: {0:X2}", lf));
                return bytes(rumble_data);
    
    if __name__ == "__main__":
        joycon_id_right = get_R_id()
        joycon_id_left = get_L_id()
    
        joyconR = RumbleJoyCon(*joycon_id_right)
        joyconL = RumbleJoyCon(*joycon_id_left)
    
        freq = 320
        amp = 0
        while True:
            data = RumbleData(freq/2, freq, amp)
            b = data.GetData()
            joyconR._send_rumble(b)
            time.sleep(1.5)
            joyconL._send_rumble(b)
            time.sleep(1.5)
            if amp > 0.9:
                amp = 0
            amp += 0.1
    
    
  8. LucasJoseph commented on Sep 3, 2022

    @LucasJoseph

    I'm also quiet interested by this however I still don't get how to handle the rumbling duration. I see in @ericries code a mention to self.time but this is never used. Furthermore in the [https.github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/bluetooth_hid_notes.md] info it states that there is

    A timing byte, then 4 bytes of rumble data for left Joy-Con, followed by 4 bytes for right Joy-Con.

    If I'm not mistaken I only count eight bytes in all the above examples, isn't there something missing ?

    PS: with random testing I found that sending b"\x05\x10\x10\x05\x05\x10\x10\x05" sends a short (<100ms) and soft rumble to my joycon

  9. ericries commented on Sep 4, 2022

    @ericries

    I don't understand how the bytes relate to time. What I used as a workaround is simply to turn the vibration off using enable_vibration(False) after x amount of time. The basic rumble seems to last for 1500 milliseconds or so, which is longer than I need. So I just trigger it and then turn it off to create various kinds of pulsing effects. Surely there is a better way, but I haven't seen it in any of the libraries around

  10. rgitelman commented on Jan 27, 2025

    @rgitelman

    I’m running into the following error with ericries’ script:

    Traceback (most recent call last): File "/home/romang/ocr_proj/Test/OCR_test/joycon-python.py", line 105, in <module> joyconL = RumbleJoyCon(*joycon_id_left) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/romang/ocr_proj/Test/OCR_test/joycon-python.py", line 15, in __init__ JoyCon.__init__(self, *args, **kwargs) File "/home/romang/ocr_proj/venv/lib/python3.11/site-packages/pyjoycon/joycon.py", line 25, in __init__ raise ValueError(f'vendor_id is invalid: {vendor_id!r}') ValueError: vendor_id is invalid: None

    It seems related to this issue: #15

    Am I missing something? Any help would be appreciated!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions