From 77d276c2f7e6ffe9a0189ef62a41845c2b8334fb Mon Sep 17 00:00:00 2001 From: fgrossman Date: Sun, 13 Jul 2025 01:21:28 -0400 Subject: [PATCH 1/3] Add in Gamepad support Gameoad support and up the version to 2.1.3 --- XRPLib/gamepad.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++ XRPLib/resetbot.py | 10 +++++ package.json | 3 +- 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 XRPLib/gamepad.py diff --git a/XRPLib/gamepad.py b/XRPLib/gamepad.py new file mode 100644 index 0000000..9a6e661 --- /dev/null +++ b/XRPLib/gamepad.py @@ -0,0 +1,97 @@ +from ble.blerepl import uart +import sys +from micropython import const + +class Gamepad: + + _DEFAULT_GAMEPAD_INSTANCE = None + + X1 = const(0) + Y1 = const(1) + X2 = const(2) + Y2 = const(3) + BUTTON_A = const(4) + BUTTON_B = const(5) + BUTTON_X = const(6) + BUTTON_Y = const(7) + BUMPER_L = const(8) + BUMPER_R = const(9) + TRIGGER_L = const(10) + TRIGGER_R = const(11) + BACK = const(12) + START = const(13) + DPAD_UP = const(14) + DPAD_DN = const(15) + DPAD_L = const(16) + DPAD_R = const(17) + + _joyData = [ + 0.0, + 0.0, + 0.0, + 0.0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0] + + @classmethod + def get_default_gamepad(cls): + """ + Get the default XRP bluetooth joystick instance. This is a singleton, so only one instance of the gamepad sensor will ever exist. + """ + if cls._DEFAULT_GAMEPAD_INSTANCE is None: + cls._DEFAULT_GAMEPAD_INSTANCE = cls() + cls._DEFAULT_GAMEPAD_INSTANCE.start() + return cls._DEFAULT_GAMEPAD_INSTANCE + + def __init__(self): + """ + Manages communication with gamepad data coming from a remote computer via bluetooth + + """ + def start(self): + """ + Signals the remote computer to begin sending gamepad data packets. + """ + for i in range(len(self._joyData)): + self._joyData[i] = 0.0 + uart.set_data_callback(self._data_callback) + sys.stdout.write(chr(27)) + sys.stdout.write(chr(101)) + + + def stop(self): + """ + Signals the remote computer to stop sending gamepad data packets. + """ + sys.stdout.write(chr(27)) + sys.stdout.write(chr(102)) + + def get_value(self, index:int) -> float: + """ + Get the current value of a joystick axis + + :param index: The joystick axis index + Gamepad.X1, Gamepad.Y1, Gamepad.X2, Gamepad.Y2 + :type int + :returns: The value of the joystick between -1 and 1 + :rtype: float + """ + return -self._joyData[index] #returning the negative to make normal for user + + def is_button_pressed(self, index:int) -> bool: + """ + Checks if a specific button is currently pressed. + + :param index: The button index + Gamepad.BUTTON_A, Gamepad.TRIGGER_L, Gamepad.DPAD_UP, etc + :type int + :returns: The value of the button 1 or 0 + :rtype: bool + """ + return self._joyData[index] > 0 + + def _data_callback(self, data): + if(data[0] == 0x55 and len(data) == data[1] + 2): + for i in range(2, data[1] + 2, 2): + self._joyData[data[i]] = round(data[i + 1]/127.5 - 1, 2) + + diff --git a/XRPLib/resetbot.py b/XRPLib/resetbot.py index de80714..1c8f3ce 100644 --- a/XRPLib/resetbot.py +++ b/XRPLib/resetbot.py @@ -33,12 +33,21 @@ def reset_webserver(): # Shut off the webserver and close network connections Webserver.get_default_webserver().stop_server() +def reset_gamepad(): + from XRPLib.gamepad import Gamepad + # Stop the browser from sending more gamepad data + Gamepad.get_default_gamepad.stop() + def reset_hard(): + reset_gamepad() reset_motors() reset_led() reset_servos() reset_webserver() +if "XRPLib.gamepad" in sys.modules: + reset_gamepad() + if "XRPLib.encoded_motor" in sys.modules: reset_motors() @@ -50,3 +59,4 @@ def reset_hard(): if "XRPLib.webserver" in sys.modules: reset_webserver() + diff --git a/package.json b/package.json index 02bf4e9..4ecc704 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ ["XRPLib/differential_drive.py", "github:Open-STEM/XRP_Micropython/XRPLib/differential_drive.py"], ["XRPLib/encoded_motor.py", "github:Open-STEM/XRP_Micropython/XRPLib/encoded_motor.py"], ["XRPLib/encoder.py", "github:Open-STEM/XRP_Micropython/XRPLib/encoder.py"], + ["XRPLib/gamepad.py", "github:Open-STEM/XRP_Micropython/XRPLib/gamepad.py"], ["XRPLib/imu_defs.py", "github:Open-STEM/XRP_Micropython/XRPLib/imu_defs.py"], ["XRPLib/imu.py", "github:Open-STEM/XRP_Micropython/XRPLib/imu.py"], ["XRPLib/motor_group.py", "github:Open-STEM/XRP_Micropython/XRPLib/motor_group.py"], @@ -28,5 +29,5 @@ "deps": [ ["github:pimoroni/phew", "latest"] ], - "version": "2.0.1" + "version": "2.1.3" } From ed19c3b2715a427549afa7220bb3b5e57bf22a33 Mon Sep 17 00:00:00 2001 From: fgrossman Date: Sun, 13 Jul 2025 01:43:07 -0400 Subject: [PATCH 2/3] gamepad_example added Add a blocks example program for the gamepad support --- XRPExamples/gamepad_example.blocks | 15 +++++++++++++++ package.json | 1 + 2 files changed, 16 insertions(+) create mode 100644 XRPExamples/gamepad_example.blocks diff --git a/XRPExamples/gamepad_example.blocks b/XRPExamples/gamepad_example.blocks new file mode 100644 index 0000000..d5e3795 --- /dev/null +++ b/XRPExamples/gamepad_example.blocks @@ -0,0 +1,15 @@ +from XRPLib.gamepad import * +from XRPLib.differential_drive import DifferentialDrive + +gp = Gamepad.get_default_gamepad() + +differentialDrive = DifferentialDrive.get_default_differential_drive() + + +while not (gp.is_button_pressed(gp.BACK)): + differentialDrive.arcade((gp.get_value(gp.Y1)), (gp.get_value(gp.X1))) + + + +## [2025-07-13 01:36:24] +##XRPBLOCKS {"blocks":{"languageVersion":0,"blocks":[{"type":"controls_whileUntil","id":"#/DDnbE2JxVsLQo`C`e^","x":-363,"y":16,"fields":{"MODE":"UNTIL"},"inputs":{"BOOL":{"block":{"type":"xrp_gp_button_pressed","id":"DtLypJc;SU2xT4A~S3nV","fields":{"GPBUTTON":"BACK"}}},"DO":{"block":{"type":"xrp_arcade","id":"(|]Iln#JYa9hzgEz+(4g","inputs":{"STRAIGHT":{"shadow":{"type":"math_number","id":"FOj1uvC$:~x/+cX}d(:|","fields":{"NUM":0.8}},"block":{"type":"xrp_gp_get_value","id":"s#GS_Dt)B8Cb9+4ctpwk","fields":{"GPVALUE":"Y1"}}},"TURN":{"shadow":{"type":"math_number","id":"n1jAKrjST!+3#bfChh$d","fields":{"NUM":0.2}},"block":{"type":"xrp_gp_get_value","id":"*]`U9XHyYnA?Gp%U;$NF","fields":{"GPVALUE":"X1"}}}}}}}}]}} \ No newline at end of file diff --git a/package.json b/package.json index 4ecc704..fb9c107 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ ["XRPLib/webserver.py", "github:Open-STEM/XRP_Micropython/XRPLib/webserver.py"], ["XRPExamples/__init__.py", "github:Open-STEM/XRP_Micropython/XRPExamples/__init__.py"], ["XRPExamples/drive_examples.py", "github:Open-STEM/XRP_Micropython/XRPExamples/drive_examples.py"], + ["XRPExamples/gamepad_example.blocks", "github:Open-STEM/XRP_Micropython/XRPExamples/gamepad_example.blocks"], ["XRPExamples/installation_verification.py", "github:Open-STEM/XRP_Micropython/XRPExamples/installation_verification.py"], ["XRPExamples/led_example.py", "github:Open-STEM/XRP_Micropython/XRPExamples/led_example.py"], ["XRPExamples/sensor_examples.py", "github:Open-STEM/XRP_Micropython/XRPExamples/sensor_examples.py"], From c045240efb54be0ac84e2e2d04f4ab6860a3308a Mon Sep 17 00:00:00 2001 From: fgrossman Date: Sun, 13 Jul 2025 16:03:03 -0400 Subject: [PATCH 3/3] fixed bug in gamepad stop Forgot to turn get_default_gamepad into a function call --- XRPLib/resetbot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/XRPLib/resetbot.py b/XRPLib/resetbot.py index 1c8f3ce..7c9d321 100644 --- a/XRPLib/resetbot.py +++ b/XRPLib/resetbot.py @@ -36,7 +36,7 @@ def reset_webserver(): def reset_gamepad(): from XRPLib.gamepad import Gamepad # Stop the browser from sending more gamepad data - Gamepad.get_default_gamepad.stop() + Gamepad.get_default_gamepad().stop() def reset_hard(): reset_gamepad()