Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Tetsujinfr opened this issue Jul 2, 2020 · 9 comments
Open

Comments

@Tetsujinfr
Copy link

Tetsujinfr commented Jul 2, 2020

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

@Tetsujinfr
Copy link
Author

Anyone? Pointing me to some documentation? Thanks in advance

@pbsds
Copy link
Contributor

pbsds commented Jan 29, 2021

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.

@Tetsujinfr
Copy link
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?

@pbsds
Copy link
Contributor

pbsds commented Jan 30, 2021

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.

@ericries
Copy link

ericries commented Sep 1, 2022

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?

@ericries
Copy link

ericries commented Sep 1, 2022

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

@ericries
Copy link

ericries commented Sep 3, 2022

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

@LucasJoseph
Copy link

LucasJoseph commented Sep 3, 2022

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

@ericries
Copy link

ericries commented Sep 4, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants