Skip to content

Commit

Permalink
PR #7: callback functions for LEDs and rumble + release 0.0.6
Browse files Browse the repository at this point in the history
  • Loading branch information
yannbouteiller committed Nov 19, 2021
1 parent 8ab8301 commit 5312acc
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 57 deletions.
65 changes: 38 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Thus far, ```vgamepad``` is compatible with Windows only.
- [Getting started](#getting-started)
- [XBox360 gamepad](#xbox360-gamepad)
- [DualShock4 gamepad](#dualshock4-gamepad)
- [Rumble and LEDs](#rumble-and-leds)
- [Contribute](#authors)

---
Expand Down Expand Up @@ -326,41 +327,51 @@ gamepad.update()
time.sleep(1.0)
```

### Receive state changes

To receive LED ring changes and rumble/vibration requests, you need to define your own callback function, then call `gamepad.register_notification(your_callback)`. If nothing is supplied, it will simply print out all state changes ([int 0-255]LargeMortor, SmallMortor and LedNumber).
---

Your callback function need to have 6 parameters: `client, target, LargeMotor, SmallMotor, LedNumber, UserData`. For more information, see [sdk/include/ViGEm/Client.h](https://github.com/ViGEm/ViGEmBus/blob/442ae3b85693b48866d0627af6f485f918b08d03/sdk/include/ViGEm/Client.h).
### Rumble and LEDs:

To unregister, call `gampad.unregister_notification()`
`vgamepad` enables registering custom callback function to handle updates of the rumble motors, and of the LED ring.

Example:
The custom callback function requires 6 parameters:
```python
def my_callback(client, target, large_motor, small_motor, led_number, user_data):
"""
Callback function triggered at each received state change
:param client: vigem bus ID
:param target: vigem device ID
:param large_motor: integer in [0, 255] representing the state of the large motor
:param small_motor: integer in [0, 255] representing the state of the small motor
:param led_number: integer in [0, 255] representing the state of the LED ring
:param user_data: placeholder, do not use
"""
# Do your things here. For instance:
print(f"Received notification for client {client}, target {target}")
print(f"large motor: {large_motor}, small motor: {small_motor}")
print(f"led number: {led_number}")
```

The callback function needs to be registered as follows:
```python
import vgamepad as vg
gamepad = vg.VX360Gamepad()
gamepad.register_notification(callback_function=my_callback)
```

def example_callback(client, target, LargeMotor, SmallMotor, LedNumber, UserData):
#Do your things here, change LED light, power a motor, or just return these value.
pass
gamepad.register_notification()
#When state changes, callbacks are made, default callback function will print out like this:
Each time the state of the gamepad is changed (for example by a video game that sends rumbling requests), the callback function will then be called.

In our example, when state changes are received, something like the following will be printed to `stdout`:
```terminal
Received notification for client 2876897124288, target 2876931874736
large motor: 255, small motor: 255
led number: 0
Received notification for client 2876897124288, target 2876931874736
large motor: 0, small motor: 0
led number: 0
```

```bash
LargeMotor: 64, SmallMotor: 30, LedNumber: 0
LargeMotor: 255, SmallMotor: 255, LedNumber: 0
LargeMotor: 67, SmallMotor: 32, LedNumber: 0
LargeMotor: 65, SmallMotor: 32, LedNumber: 0
LargeMotor: 64, SmallMotor: 32, LedNumber: 0
LargeMotor: 63, SmallMotor: 31, LedNumber: 0
LargeMotor: 63, SmallMotor: 81, LedNumber: 0
LargeMotor: 127, SmallMotor: 127, LedNumber: 0
LargeMotor: 16, SmallMotor: 83, LedNumber: 0
LargeMotor: 5, SmallMotor: 28, LedNumber: 0
LargeMotor: 0, SmallMotor: 19, LedNumber: 0
LargeMotor: 0, SmallMotor: 57, LedNumber: 0
LargeMotor: 0, SmallMotor: 51, LedNumber: 0
If not needed anymore, the callback function can be unregistered:
```python
gamepad.unregister_notification()
```

---
Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@
setup(
name='vgamepad',
packages=[package for package in find_packages()],
version='0.0.5',
version='0.0.6',
license='MIT',
description='Virtual XBox360 and DualShock4 gamepads in python',
long_description=long_description,
long_description_content_type="text/markdown",
author='Yann Bouteiller',
url='https://github.com/yannbouteiller/vgamepad',
download_url='https://github.com/yannbouteiller/vgamepad/archive/refs/tags/v0.0.5.tar.gz',
download_url='https://github.com/yannbouteiller/vgamepad/archive/refs/tags/v0.0.6.tar.gz',
keywords=['virtual', 'gamepad', 'python', 'xbox', 'dualshock', 'controller', 'emulator'],
install_requires=[],
classifiers=[
Expand Down
15 changes: 2 additions & 13 deletions vgamepad/win/vigem_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,6 @@
vigem_target_x360_get_user_index.argtypes = (c_void_p, c_void_p, c_void_p)
vigem_target_x360_get_user_index.restype = c_uint

# TODO: add the missing APIs (those with C callback functions)
"""
Registers a function which gets called, when LED index or vibration state changes
occur on the provided target device. This function fails if the provided
Expand All @@ -246,7 +245,7 @@
@param target The target device object.
"""
vigem_target_x360_unregister_notification = vigemClient.vigem_target_x360_unregister_notification
vigem_target_x360_unregister_notification.argtypes = (c_void_p)
vigem_target_x360_unregister_notification.argtypes = (c_void_p, )
vigem_target_x360_unregister_notification.restype = None

"""
Expand All @@ -268,15 +267,5 @@
@param target The target device object.
"""
vigem_target_ds4_unregister_notification = vigemClient.vigem_target_ds4_unregister_notification
vigem_target_ds4_unregister_notification.argtypes = (c_void_p)
vigem_target_ds4_unregister_notification.argtypes = (c_void_p, )
vigem_target_ds4_unregister_notification.restype = None









#
60 changes: 45 additions & 15 deletions vgamepad/win/virtual_gamepad.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,27 @@
import ctypes
from ctypes import CFUNCTYPE, c_void_p, c_ubyte
from abc import ABC, abstractmethod
from inspect import signature #Check if user defined callback function is legal
from inspect import signature # Check if user defined callback function is legal


def check_err(err):
if err != vcom.VIGEM_ERRORS.VIGEM_ERROR_NONE:
raise Exception(vcom.VIGEM_ERRORS(err).name)

def defaultCallback(client, target, LargeMotor, SmallMotor, LedNumber, UserData):
print("LargeMotor: {lm}, SmallMotor: {sm}, LedNumber: {ln}".format(lm = LargeMotor, sm = SmallMotor, ln = LedNumber))

def dummy_callback(client, target, large_motor, small_motor, led_number, user_data):
"""
Pattern for callback functions to be registered as notifications
:param client: vigem bus ID
:param target: vigem device ID
:param large_motor: integer in [0, 255] representing the state of the large motor
:param small_motor: integer in [0, 255] representing the state of the small motor
:param led_number: integer in [0, 255] representing the state of the LED ring
:param user_data: placeholder, do not use
"""
pass


class VBus:
"""
Expand Down Expand Up @@ -42,6 +55,7 @@ def __init__(self):
self._busp = self.vbus.get_busp()
self._devicep = self.target_alloc()
self.CMPFUNC = CFUNCTYPE(None, c_void_p, c_void_p, c_ubyte, c_ubyte, c_ubyte, c_void_p)
self.cmp_func = None
vcli.vigem_target_add(self._busp, self._devicep)
assert vcli.vigem_target_is_attached(self._devicep), "The virtual device could not connect to ViGEmBus."

Expand Down Expand Up @@ -208,18 +222,26 @@ def update(self):
"""
check_err(vcli.vigem_target_x360_update(self._busp, self._devicep, self.report))

def target_alloc(self):
return vcli.vigem_target_x360_alloc()
def register_notification(self, callback_function):
"""
Registers a callback function that can handle force feedback, leds, etc.
def register_notification(self, callback_func = defaultCallback):
if not signature(callback_func) == signature(defaultCallback):
raise TypeError("Needed callback function signature: {need}, but got: {got}".format(need = signature(defaultCallback), got = signature(callback_func)))
self.cmp_func = self.CMPFUNC(callback_func) #keep its reference, otherwise programe will crash when a callback is made.
:param: a function of the form: my_func(client, target, large_motor, small_motor, led_number, user_data)
"""
if not signature(callback_function) == signature(dummy_callback):
raise TypeError("Needed callback function signature: {}, but got: {}".format(signature(dummy_callback), signature(callback_function)))
self.cmp_func = self.CMPFUNC(callback_function) # keep its reference, otherwise the program will crash when a callback is made.
check_err(vcli.vigem_target_x360_register_notification(self._busp, self._devicep, self.cmp_func, None))

def unregister_notification(self):
"""
Unregisters a previously registered callback function.
"""
vcli.vigem_target_x360_unregister_notification(self._devicep)

def target_alloc(self):
return vcli.vigem_target_x360_alloc()


class VDS4Gamepad(VGamepad):
"""
Expand Down Expand Up @@ -380,14 +402,22 @@ def update_extended_report(self, extended_report):
"""
check_err(vcli.vigem_target_ds4_update_ex_ptr(self._busp, self._devicep, ctypes.byref(extended_report)))

def target_alloc(self):
return vcli.vigem_target_ds4_alloc()
def register_notification(self, callback_function):
"""
Registers a callback function that can handle force feedback, leds, etc.
def register_callback(self, callback_func = defaultCallback):
if not signature(callback_func) == signature(defaultCallback):
raise TypeError("Needed callback function signature: {need}, but got: {got}".format(need = signature(defaultCallback), got = signature(callback_func)))
self.cmp_func = self.CMPFUNC(callback_func)
:param: a function of the form: my_func(client, target, large_motor, small_motor, led_number, user_data)
"""
if not signature(callback_function) == signature(dummy_callback):
raise TypeError("Needed callback function signature: {}, but got: {}".format(signature(dummy_callback), signature(callback_function)))
self.cmp_func = self.CMPFUNC(callback_function)
check_err(vcli.vigem_target_ds4_register_notification(self._busp, self._devicep, self.cmp_func, None))

def unregister_notification(self):
"""
Unregisters a previously registered callback function.
"""
vcli.vigem_target_ds4_unregister_notification(self._devicep)

def target_alloc(self):
return vcli.vigem_target_ds4_alloc()

0 comments on commit 5312acc

Please sign in to comment.