Skip to content

Commit 600a411

Browse files
authored
Merge branch 'Open-STEM:main' into main
2 parents 9d8cd9a + dc6db1c commit 600a411

6 files changed

Lines changed: 166 additions & 3 deletions

File tree

XRPExamples/gamepad_example.blocks

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from XRPLib.gamepad import *
2+
from XRPLib.differential_drive import DifferentialDrive
3+
4+
gp = Gamepad.get_default_gamepad()
5+
6+
differentialDrive = DifferentialDrive.get_default_differential_drive()
7+
8+
9+
while not (gp.is_button_pressed(gp.BACK)):
10+
differentialDrive.arcade((gp.get_value(gp.Y1)), (gp.get_value(gp.X1)))
11+
12+
13+
14+
## [2025-07-13 01:36:24]
15+
##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"}}}}}}}}]}}

XRPLib/differential_drive.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ def __init__(self, left_motor: EncodedMotor, right_motor: EncodedMotor, imu: IMU
5050
self.wheel_diam = wheel_diam
5151
self.track_width = wheel_track
5252

53+
self.heading_pid = None
54+
self.current_heading = None
55+
self.reset_heading = True
56+
self.turning = False
57+
58+
if self.imu:
59+
# if the IMU is initialized, then create a PID controller that can be used
60+
# to maintain a constant heading when driving
61+
self.heading_pid = PID( kp = 0.075, kd=0.001, )
62+
5363
def set_effort(self, left_effort: float, right_effort: float) -> None:
5464
"""
5565
Set the raw effort of both motors individually
@@ -110,7 +120,33 @@ def arcade(self, straight:float, turn:float):
110120
scale = max(abs(straight), abs(turn))/(abs(straight) + abs(turn))
111121
left_speed = (straight - turn)*scale
112122
right_speed = (straight + turn)*scale
113-
self.set_effort(left_speed, right_speed)
123+
124+
if not self.heading_pid:
125+
# if not using IMU assist to maintain heading, just pass down the left and right motor
126+
# speeds to control movement
127+
self.set_effort(left_speed, right_speed)
128+
else:
129+
# else if IMU assist is enabled, then use the IMU with PID to
130+
# maintain a constant heading while driving.
131+
if turn == 0:
132+
# straight drive requested, then maintain the current heading
133+
if self.turning:
134+
# if previously turning, then clear the turn indicator and reset the course heading
135+
self.reset_heading = True
136+
self.turning = False
137+
138+
if self.reset_heading:
139+
self.reset_heading = False
140+
self.current_heading = self.imu.get_yaw()
141+
142+
# use the PID to set the heading correction based on the current heading
143+
heading_correction = self.heading_pid.update(self.current_heading - self.imu.get_yaw())
144+
145+
self.set_effort(left_speed - heading_correction, right_speed + heading_correction)
146+
else:
147+
# set the turning indicator and apply the left and right speeds
148+
self.turning = True
149+
self.set_effort(left_speed, right_speed)
114150

115151
def reset_encoder_position(self) -> None:
116152
"""

XRPLib/gamepad.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
from ble.blerepl import uart
2+
import sys
3+
from micropython import const
4+
5+
class Gamepad:
6+
7+
_DEFAULT_GAMEPAD_INSTANCE = None
8+
9+
X1 = const(0)
10+
Y1 = const(1)
11+
X2 = const(2)
12+
Y2 = const(3)
13+
BUTTON_A = const(4)
14+
BUTTON_B = const(5)
15+
BUTTON_X = const(6)
16+
BUTTON_Y = const(7)
17+
BUMPER_L = const(8)
18+
BUMPER_R = const(9)
19+
TRIGGER_L = const(10)
20+
TRIGGER_R = const(11)
21+
BACK = const(12)
22+
START = const(13)
23+
DPAD_UP = const(14)
24+
DPAD_DN = const(15)
25+
DPAD_L = const(16)
26+
DPAD_R = const(17)
27+
28+
_joyData = [
29+
0.0,
30+
0.0,
31+
0.0,
32+
0.0,
33+
0,0,0,0,0,0,0,0,0,0,0,0,0,0]
34+
35+
@classmethod
36+
def get_default_gamepad(cls):
37+
"""
38+
Get the default XRP bluetooth joystick instance. This is a singleton, so only one instance of the gamepad sensor will ever exist.
39+
"""
40+
if cls._DEFAULT_GAMEPAD_INSTANCE is None:
41+
cls._DEFAULT_GAMEPAD_INSTANCE = cls()
42+
cls._DEFAULT_GAMEPAD_INSTANCE.start()
43+
return cls._DEFAULT_GAMEPAD_INSTANCE
44+
45+
def __init__(self):
46+
"""
47+
Manages communication with gamepad data coming from a remote computer via bluetooth
48+
49+
"""
50+
def start(self):
51+
"""
52+
Signals the remote computer to begin sending gamepad data packets.
53+
"""
54+
for i in range(len(self._joyData)):
55+
self._joyData[i] = 0.0
56+
uart.set_data_callback(self._data_callback)
57+
sys.stdout.write(chr(27))
58+
sys.stdout.write(chr(101))
59+
60+
61+
def stop(self):
62+
"""
63+
Signals the remote computer to stop sending gamepad data packets.
64+
"""
65+
sys.stdout.write(chr(27))
66+
sys.stdout.write(chr(102))
67+
68+
def get_value(self, index:int) -> float:
69+
"""
70+
Get the current value of a joystick axis
71+
72+
:param index: The joystick axis index
73+
Gamepad.X1, Gamepad.Y1, Gamepad.X2, Gamepad.Y2
74+
:type int
75+
:returns: The value of the joystick between -1 and 1
76+
:rtype: float
77+
"""
78+
return -self._joyData[index] #returning the negative to make normal for user
79+
80+
def is_button_pressed(self, index:int) -> bool:
81+
"""
82+
Checks if a specific button is currently pressed.
83+
84+
:param index: The button index
85+
Gamepad.BUTTON_A, Gamepad.TRIGGER_L, Gamepad.DPAD_UP, etc
86+
:type int
87+
:returns: The value of the button 1 or 0
88+
:rtype: bool
89+
"""
90+
return self._joyData[index] > 0
91+
92+
def _data_callback(self, data):
93+
if(data[0] == 0x55 and len(data) == data[1] + 2):
94+
for i in range(2, data[1] + 2, 2):
95+
self._joyData[data[i]] = round(data[i + 1]/127.5 - 1, 2)
96+
97+

XRPLib/resetbot.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,21 @@ def reset_webserver():
3333
# Shut off the webserver and close network connections
3434
Webserver.get_default_webserver().stop_server()
3535

36+
def reset_gamepad():
37+
from XRPLib.gamepad import Gamepad
38+
# Stop the browser from sending more gamepad data
39+
Gamepad.get_default_gamepad().stop()
40+
3641
def reset_hard():
42+
reset_gamepad()
3743
reset_motors()
3844
reset_led()
3945
reset_servos()
4046
reset_webserver()
4147

48+
if "XRPLib.gamepad" in sys.modules:
49+
reset_gamepad()
50+
4251
if "XRPLib.encoded_motor" in sys.modules:
4352
reset_motors()
4453

@@ -50,3 +59,4 @@ def reset_hard():
5059

5160
if "XRPLib.webserver" in sys.modules:
5261
reset_webserver()
62+

XRPLib/timeout.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ def __init__(self, timeout: float):
88
:param timeout: The timeout, in seconds
99
:type timeout: float
1010
"""
11-
self.timeout = timeout*1000
11+
self.timeout = timeout
12+
if self.timeout != None:
13+
self.timeout = timeout*1000
14+
1215
self.start_time = time.ticks_ms()
1316

1417
def is_done(self):

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
["XRPLib/differential_drive.py", "github:Open-STEM/XRP_Micropython/XRPLib/differential_drive.py"],
88
["XRPLib/encoded_motor.py", "github:Open-STEM/XRP_Micropython/XRPLib/encoded_motor.py"],
99
["XRPLib/encoder.py", "github:Open-STEM/XRP_Micropython/XRPLib/encoder.py"],
10+
["XRPLib/gamepad.py", "github:Open-STEM/XRP_Micropython/XRPLib/gamepad.py"],
1011
["XRPLib/imu_defs.py", "github:Open-STEM/XRP_Micropython/XRPLib/imu_defs.py"],
1112
["XRPLib/imu.py", "github:Open-STEM/XRP_Micropython/XRPLib/imu.py"],
1213
["XRPLib/motor_group.py", "github:Open-STEM/XRP_Micropython/XRPLib/motor_group.py"],
@@ -20,6 +21,7 @@
2021
["XRPLib/webserver.py", "github:Open-STEM/XRP_Micropython/XRPLib/webserver.py"],
2122
["XRPExamples/__init__.py", "github:Open-STEM/XRP_Micropython/XRPExamples/__init__.py"],
2223
["XRPExamples/drive_examples.py", "github:Open-STEM/XRP_Micropython/XRPExamples/drive_examples.py"],
24+
["XRPExamples/gamepad_example.blocks", "github:Open-STEM/XRP_Micropython/XRPExamples/gamepad_example.blocks"],
2325
["XRPExamples/installation_verification.py", "github:Open-STEM/XRP_Micropython/XRPExamples/installation_verification.py"],
2426
["XRPExamples/led_example.py", "github:Open-STEM/XRP_Micropython/XRPExamples/led_example.py"],
2527
["XRPExamples/sensor_examples.py", "github:Open-STEM/XRP_Micropython/XRPExamples/sensor_examples.py"],
@@ -28,5 +30,5 @@
2830
"deps": [
2931
["github:pimoroni/phew", "latest"]
3032
],
31-
"version": "2.0.1"
33+
"version": "2.1.3"
3234
}

0 commit comments

Comments
 (0)