Creating Joystick Gremlin Plugins

Posted on 03 Nov 2020 - filed under: dcs, joystick, gaming

What is Joystick Gremlin?

No point in paraphrasing the perfectly accurate description on their own website.

Joystick Gremlin is a program that allows the configuration of joystick like devices, similar to what CH Control Manager and Thrustmaster’s T.A.R.G.E.T. do for their respectively supported joysticks. However, Joystick Gremlin works with any device be it from different manufacturers or custom devices that appear as a joystick to Windows. Joystick Gremlin uses the virtual joysticks provided by vJoy to map physical to virtual inputs and apply various other transformations such as response curves to analogue axes. In addition to customizing joysticks, Joystick Gremlin also provides powerful macro functionalities, a flexible mode system, scripting using Python, and many other features.

The key points being:

  • You don’t need to rely on the usually shitty software provided by the manufacturer
  • There is a programmable (python) API
  • It’s not tied to one application, so for example you can set your joystick curves once and they’ll be applied in all your games.

In this article I will mostly discuss the Python API, the GUI is well documented on their website; the API… not so much. I’ve struggled quite a bit, navigating github issues for snippets of working code where the documentation failed me. So I thought I would share my experience and hopefully help someone.

All of the code used here is available on Github over here.

Note: this is for Joystick Gremlin version 13.1, from what I saw the API has had a few breaking changes over time so the following may not apply for future releases.

Plugin Setup

The first thing you need to do is to fetch your devices GUIDs. This can be done directly from JG’s GUI under Tools>Device Information.

With that in hand you can now initialize the JoystickDecorator:

my_throttle1 = gremlin.input_devices.JoystickDecorator( \
		# Not sure if the name is important, but I just named
		# it exactly the same as what JG sees
                "Throttle - HOTAS Warthog ", \
		# You should replace this GUID with your own
                "{6EB24530-1896-11EB-8001-444553540000}", \
		# The name of the JG mode
                "Default")

Of course you can have as many as you want. This will allow you to access axises and buttons.

Simple Map

@my_throttle1.button(1)
def callback(event, vjoy, joy):
     vjoy[1].button(1).is_pressed = event.is_pressed

This says: “When Button 1 of my throttle is pressed, Button 1 on the virtual joystick will be pressed too”. The callback is called when the button is pressed and another time upon release. Basically both buttons are in sync.

Creating a virtual third button state

On the Warthog throttle there are two 3 states switches (the flaps and the autopilot mode). Unfortunately the middle state is OFF so no keypress is emitted when it’s in this position.

The autopilot mode 3-way switch

We can fix this with Joystick Gremlin!

def toggleSwitchMiddle(event, vjoy, joy, otherUp, otherDown, virtualButton):
    if event.is_pressed:
        vjoy[1].button(virtualButton).is_pressed = False
    else:
        j = joy[gremlin.profile.parse_guid(THROTTLE_GUID)]
        bPath = otherUp
        bAlt = otherDown
        if not j.button(bPath).is_pressed and not j.button(bAlt).is_pressed:
            vjoy[1].button(virtualButton).is_pressed = True

@my_throttle1.button(THROTTLE_BUTTONS["AP_PATH"])
def apPath(event, vjoy, joy):
     toggleSwitchMiddle(event, vjoy, joy, THROTTLE_BUTTONS["AP_PATH"], THROTTLE_BUTTONS["AP_ALT"], VJOY_BUTTONS["AP_PITCH_OFF"])

@my_throttle1.button(THROTTLE_BUTTONS["AP_ALT"])
def apAlt(event, vjoy, joy):
    toggleSwitchMiddle(event, vjoy, joy, THROTTLE_BUTTONS["AP_PATH"], THROTTLE_BUTTONS["AP_ALT"], VJOY_BUTTONS["AP_PITCH_OFF"])

Note that this uses the THROTTLE_BUTTONS variable, which is just a map referencing the button numbers by their label on the throttle, helps a bit.

Put simply what this does is simply activate a button on the virtual joystick (our output) if neither the bottom nor the top state of the switch is activated.

Startup Sync

Now since those callbacks are only called when there is a change in button state, they aren’t called when the throttle is turned on.

So if our 3 way switch was in the middle position at that time, the Virtual Joystick output would not be “on” (for that middle state).

We can fix this by calling a little function when Joystick Gremlin starts.

 def sync():
    gremlin.util.log("Initial sync")
    joy_proxy = gremlin.input_devices.JoystickProxy()    
    vjoy_proxy = gremlin.joystick_handling.VJoyProxy()

    # AP 3-way switch inital sync
    apPathIsPressed = joy_proxy[gremlin.profile.parse_guid(THROTTLE_GUID)].button(THROTTLE_BUTTONS["AP_PATH"]).is_pressed
    apAltIsPressed = joy_proxy[gremlin.profile.parse_guid(THROTTLE_GUID)].button(THROTTLE_BUTTONS["AP_ALT"]).is_pressed
    # If it's neither up nor down, switch the vJoy for the middle state on
    vjoy_proxy[1].button(VJOY_BUTTONS["AP_PITCH_OFF"]).is_pressed = (not apPathIsPressed) and (not apAltIsPressed)

sync()

Macros

You can also trigger macros with a press of a joystick button. I’m no python guru, but I think this is a little dirty. But hey, it works.

The following macro is used to turn on various system at once in DCS’s F-16C

First you need to define a macro (this won’t run it):

MACROS = {
    "fcr": macro.Macro(),
} 

# I hate python
## FCR/Radar/Right Hardpoint Macro
MACROS["fcr"].press("leftshift")
MACROS["fcr"].tap("f1")
MACROS["fcr"].pause(0.2)
MACROS["fcr"].tap("f2")
MACROS["fcr"].pause(0.2)
MACROS["fcr"].tap("f3")
MACROS["fcr"].release("leftshift")

Then you can call it like so:

@my_throttle1.button(THROTTLE_BUTTONS["ENG_R"])
def engLeft(event, vjoy):
    if event.is_pressed:
        macro.MacroManager().queue_macro(MACROS["mmc"])

Emulating 3 states on a push button

# Autopilot roll 3-way emulation {{{
AP_ROLL_CYCLE_STATE = 0
"""
This function make the "Enagage/Disengage" button on the throttle act as a three way switch.
Each press will cycle the switch down.
The  ing position is in the middle
Cycle between autopilot ROLL modes.
From sync, this starts in the middle position as ATT_HOLD
"""
@thrt.button(THROTTLE_BUTTONS["AP_ENGAGE_DISENGAGE"])
def apRollCycle(event, vjoy):
    global AP_ROLL_CYCLE_STATE
    if (not event.is_pressed):
        return
        
    if AP_ROLL_CYCLE_STATE == 0:
        vjoy[1].button(VJOY_BUTTONS["AP_ROLL_ATT_HOLD"]).is_pressed = False
        vjoy[1].button(VJOY_BUTTONS["AP_ROLL_STRG_SEL"]).is_pressed = True
        AP_ROLL_CYCLE_STATE = 1
    elif AP_ROLL_CYCLE_STATE == 1:
        vjoy[1].button(VJOY_BUTTONS["AP_ROLL_STRG_SEL"]).is_pressed = False
        vjoy[1].button(VJOY_BUTTONS["AP_ROLL_HDG_SEL"]).is_pressed = True
        AP_ROLL_CYCLE_STATE = 2
    elif AP_ROLL_CYCLE_STATE == 2:
        vjoy[1].button(VJOY_BUTTONS["AP_ROLL_HDG_SEL"]).is_pressed = False
        vjoy[1].button(VJOY_BUTTONS["AP_ROLL_ATT_HOLD"]).is_pressed = True
        AP_ROLL_CYCLE_STATE = 0
# }}}

This makes use of a global variable to store the current state (again this is not top notch programing I know). And cycles through 3 states as the button is pressed.

Using the plugin

Just add your python file in the plugins tab of JG’s UI.

Remarks

  • You can use Joystick Gremlin’s UI to figure out the number for each of the buttons
  • You can use gremlin.util.log("My message") to debug your scripts, the output will be shown in JG itself in the log window
  • You can find the key codes for keyboard keys in JG’s macro.py file

Comments

Comment on github