Skip to content

Commit d536c9a

Browse files
authored
Merge pull request #52 from Open-STEM/v1.0.0
Version 1.0.0!
2 parents 4e1e3c8 + 79467dd commit d536c9a

14 files changed

+96
-159
lines changed

.pre-commit-config.yaml

-42
This file was deleted.

.readthedocs.yaml

-19
This file was deleted.

XRPLib/board.py

+9-20
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ def __init__(self, vin_pin:int, button_pin:int):
2727
self.on_switch = ADC(Pin(vin_pin))
2828

2929
self.button = Pin(button_pin, Pin.IN, Pin.PULL_UP)
30-
self.button_callback = None
3130

3231
self.led = Pin("LED", Pin.OUT)
3332
# A timer ID of -1 is a virtual timer.
@@ -43,20 +42,6 @@ def are_motors_powered(self) -> bool:
4342
"""
4443
return self.on_switch.read_u16() > 20000
4544

46-
def set_button_callback(self, trigger, callback):
47-
"""
48-
Sets an interrupt callback to be triggered on a change in button state, specified by trigger.
49-
Follow the link for more information on how to write an Interrupt Service Routine (ISR).
50-
https://docs.micropython.org/en/latest/reference/isr_rules.html
51-
52-
:param trigger: The type of trigger to be used for the interrupt
53-
:type trigger: Pin.IRQ_RISING | Pin.IRQ_FALLING
54-
:param callback: The function to be called when the interrupt is triggered
55-
:type callback: function | None
56-
"""
57-
self.button_callback = callback
58-
self.button.irq(trigger=trigger, handler=self.button_callback)
59-
6045
def is_button_pressed(self) -> bool:
6146
"""
6247
Returns the state of the button
@@ -98,9 +83,9 @@ def led_off(self):
9883
self.led.off()
9984
self._virt_timer.deinit()
10085

101-
def led_blink(self, frequency: int):
86+
def led_blink(self, frequency: int=0):
10287
"""
103-
Blinks the LED at a given frequency
88+
Blinks the LED at a given frequency. If the frequency is 0, the LED will stop blinking.
10489
10590
:param frequency: The frequency to blink the LED at (in Hz)
10691
:type frequency: int
@@ -110,6 +95,10 @@ def led_blink(self, frequency: int):
11095
self._virt_timer.deinit()
11196
# We set it to twice in input frequency so that
11297
# the led flashes on and off frequency times per second
113-
self._virt_timer.init(freq=frequency*2, mode=Timer.PERIODIC,
114-
callback=lambda t:self.led.toggle())
115-
self.is_led_blinking = True
98+
if frequency != 0:
99+
self._virt_timer.init(freq=frequency*2, mode=Timer.PERIODIC,
100+
callback=lambda t:self.led.toggle())
101+
self.is_led_blinking = True
102+
else:
103+
self._virt_timer.deinit()
104+
self.is_led_blinking = False

XRPLib/defaults.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
drivetrain = DifferentialDrive.get_default_differential_drive()
2121
rangefinder = Rangefinder.get_default_rangefinder()
2222
reflectance = Reflectance.get_default_reflectance()
23-
servo_one = Servo.get_default_servo()
23+
servo_one = Servo.get_default_servo(index=1)
24+
servo_two = Servo.get_default_servo(index=2)
2425
webserver = Webserver.get_default_webserver()
2526
board = Board.get_default_board()

XRPLib/differential_drive.py

+6-6
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,9 @@ def straight(self, distance: float, max_effort: float = 0.5, timeout: float = No
153153
main_controller = PID(
154154
kp = 0.1,
155155
ki = 0.04,
156-
kd = 0.06,
157-
min_output = 0.25,
158-
max_output = 0.5,
156+
kd = 0.04,
157+
min_output = 0.3,
158+
max_output = max_effort,
159159
max_integral = 10,
160160
tolerance = 0.25,
161161
tolerance_count = 3,
@@ -164,7 +164,7 @@ def straight(self, distance: float, max_effort: float = 0.5, timeout: float = No
164164
# Secondary controller to keep encoder values in sync
165165
if secondary_controller is None:
166166
secondary_controller = PID(
167-
kp = 0.075, kd=0.005,
167+
kp = 0.075, kd=0.001,
168168
)
169169

170170
if self.imu is not None:
@@ -241,7 +241,7 @@ def turn(self, turn_degrees: float, max_effort: float = 0.5, timeout: float = No
241241
ki = 0.001,
242242
kd = 0.00165,
243243
min_output = 0.35,
244-
max_output = 0.5,
244+
max_output = max_effort,
245245
max_integral = 75,
246246
tolerance = 1,
247247
tolerance_count = 3
@@ -250,7 +250,7 @@ def turn(self, turn_degrees: float, max_effort: float = 0.5, timeout: float = No
250250
# Secondary controller to keep encoder values in sync
251251
if secondary_controller is None:
252252
secondary_controller = PID(
253-
kp = 1.0,
253+
kp = 0.8,
254254
)
255255

256256
if use_imu and (self.imu is not None):

XRPLib/encoded_motor.py

+4-10
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class EncodedMotor:
1515
def get_default_encoded_motor(cls, index:int = 1):
1616
"""
1717
Get one of the default XRP v2 motor instances. These are singletons, so only one instance of each of these will ever exist.
18-
Left Motor is the default, so if no index (or an invalid index) is specified, the left motor will be returned.
18+
Raises an exception if an invalid index is requested.
1919
2020
:param index: The index of the motor to get; 1 for left, 2 for right, 3 for motor 3, 4 for motor 4
2121
:type index: int
@@ -68,6 +68,8 @@ def __init__(self, motor: Motor, encoder: Encoder):
6868
self.speed = 0
6969
# Use a virtual timer so we can leave the hardware timers up for the user
7070
self.updateTimer = Timer(-1)
71+
# If the update timer is not running, start it at 50 Hz (20ms updates)
72+
self.updateTimer.init(period=20, callback=lambda t:self._update())
7173

7274
def set_effort(self, effort: float):
7375
"""
@@ -123,11 +125,7 @@ def set_speed(self, speed_rpm: float = None):
123125
if speed_rpm is None or speed_rpm == 0:
124126
self.target_speed = None
125127
self.set_effort(0)
126-
self.speed = 0
127-
self.updateTimer.deinit()
128128
return
129-
# If the update timer is not running, start it at 50 Hz (20ms updates)
130-
self.updateTimer.init(period=20, callback=lambda t:self._update())
131129
# Convert from rev per min to counts per 20ms (60 sec/min, 50 Hz)
132130
self.target_speed = speed_rpm*self._encoder.resolution/(60*50)
133131
self.speedController.clear_history()
@@ -153,8 +151,4 @@ def _update(self):
153151
error = self.target_speed - self.speed
154152
effort = self.speedController.update(error)
155153
self._motor.set_effort(effort)
156-
else:
157-
self.updateTimer.deinit()
158-
159-
self.prev_position = current_position
160-
154+
self.prev_position = current_position

XRPLib/imu.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ def get_acc_rates(self):
242242

243243
def get_gyro_x_rate(self):
244244
"""
245-
Individual axis read for the Gyroscope's X-axis, in mg
245+
Individual axis read for the Gyroscope's X-axis, in mdps
246246
"""
247247
# Burst read data registers
248248
raw_bytes = self._getregs(LSM_REG_OUTX_L_G, 2)
@@ -252,7 +252,7 @@ def get_gyro_x_rate(self):
252252

253253
def get_gyro_y_rate(self):
254254
"""
255-
Individual axis read for the Gyroscope's Y-axis, in mg
255+
Individual axis read for the Gyroscope's Y-axis, in mdps
256256
"""
257257
# Burst read data registers
258258
raw_bytes = self._getregs(LSM_REG_OUTY_L_G, 2)
@@ -262,7 +262,7 @@ def get_gyro_y_rate(self):
262262

263263
def get_gyro_z_rate(self):
264264
"""
265-
Individual axis read for the Gyroscope's Z-axis, in mg
265+
Individual axis read for the Gyroscope's Z-axis, in mdps
266266
"""
267267
# Burst read data registers
268268
raw_bytes = self._getregs(LSM_REG_OUTZ_L_G, 2)

XRPLib/motor_group.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def get_position_counts(self) -> int:
5353
"""
5454
avg = 0
5555
for motor in self.motors:
56-
avg += motor.get_position_ticks()
56+
avg += motor.get_position_counts()
5757
return round(avg / len(self.motors))
5858

5959
def reset_encoder_position(self):
@@ -74,7 +74,7 @@ def get_speed(self) -> float:
7474
return avg / len(self.motors)
7575

7676

77-
def set_target_speed(self, target_speed_rpm: float = None):
77+
def set_speed(self, target_speed_rpm: float = None):
7878
"""
7979
Sets target speed (in rpm) to be maintained passively by all motors in this group
8080
Call with no parameters to turn off speed control
@@ -83,7 +83,7 @@ def set_target_speed(self, target_speed_rpm: float = None):
8383
:type target_speed_rpm: float, or None
8484
"""
8585
for motor in self.motors:
86-
motor.set_target_speed(target_speed_rpm)
86+
motor.set_speed(target_speed_rpm)
8787

8888
def set_speed_controller(self, new_controller):
8989
"""

XRPLib/resetbot.py

+39-17
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,47 @@
1-
from XRPLib.encoded_motor import EncodedMotor
2-
from XRPLib.imu import IMU
3-
from XRPLib.board import Board
4-
from XRPLib.servo import Servo
5-
from XRPLib.webserver import Webserver
1+
import sys
62
"""
73
A simple file for shutting off all of the motors after a program gets interrupted from the REPL.
84
Run this file after interrupting a program to stop the robot by running "import XRPLib.resetbot" in the REPL.
95
"""
106

11-
# using the EncodedMotor since the default drivetrain uses the IMU and takes 3 seconds to init
12-
for i in range(4):
13-
motor = EncodedMotor.get_default_encoded_motor(i+1)
14-
motor.set_speed(0)
15-
motor.reset_encoder_position()
7+
def reset_motors():
8+
from XRPLib.encoded_motor import EncodedMotor
9+
# using the EncodedMotor since the default drivetrain uses the IMU and takes 3 seconds to init
10+
for i in range(4):
11+
motor = EncodedMotor.get_default_encoded_motor(i+1)
12+
motor.set_speed(0)
13+
motor.reset_encoder_position()
1614

17-
# Turn off the on-board LED
18-
Board.get_default_board().led_off()
15+
def reset_led():
16+
from XRPLib.board import Board
17+
# Turn off the on-board LED
18+
Board.get_default_board().led_off()
1919

20-
# Turn off both Servos
21-
Servo.get_default_servo().free()
22-
Servo(17).free()
20+
def reset_servos():
21+
from XRPLib.servo import Servo
22+
# Turn off both Servos
23+
Servo.get_default_servo(1).free()
24+
Servo.get_default_servo(2).free()
2325

24-
# Shut off the webserver and close network connections
25-
Webserver.get_default_webserver().stop_server()
26+
def reset_webserver():
27+
from XRPLib.webserver import Webserver
28+
# Shut off the webserver and close network connections
29+
Webserver.get_default_webserver().stop_server()
30+
31+
def reset_hard():
32+
reset_motors()
33+
reset_led()
34+
reset_servos()
35+
reset_webserver()
36+
37+
if "XRPLib.encoded_motor" in sys.modules:
38+
reset_motors()
39+
40+
if "XRPLib.board" in sys.modules:
41+
reset_led()
42+
43+
if "XRPLib.servo" in sys.modules:
44+
reset_servos()
45+
46+
if "XRPLib.webserver" in sys.modules:
47+
reset_webserver()

XRPLib/servo.py

+20-7
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,29 @@
22

33
class Servo:
44

5-
_DEFAULT_SERVO_INSTANCE = None
5+
_DEFAULT_SERVO_ONE_INSTANCE = None
6+
_DEFAULT_SERVO_TWO_INSTANCE = None
67

78
@classmethod
8-
def get_default_servo(cls):
9+
def get_default_servo(cls, index:int):
910
"""
10-
Get the default XRP v2 servo instance. This is a singleton, so only one instance of the servo will ever exist.
11-
"""
12-
if cls._DEFAULT_SERVO_INSTANCE is None:
13-
cls._DEFAULT_SERVO_INSTANCE = cls(16)
14-
return cls._DEFAULT_SERVO_INSTANCE
11+
Gets one of the default XRP v2 servo instances. These are singletons, so only one instance of each servo will ever exist.
12+
Raises an exception if an invalid index is requested.
13+
14+
:param index: The index of the servo to get (1 or 2)
15+
:type index: int
16+
"""
17+
if index == 1:
18+
if cls._DEFAULT_SERVO_ONE_INSTANCE is None:
19+
cls._DEFAULT_SERVO_ONE_INSTANCE = cls(16)
20+
servo = cls._DEFAULT_SERVO_ONE_INSTANCE
21+
elif index == 2:
22+
if cls._DEFAULT_SERVO_TWO_INSTANCE is None:
23+
cls._DEFAULT_SERVO_TWO_INSTANCE = cls(17)
24+
servo = cls._DEFAULT_SERVO_TWO_INSTANCE
25+
else:
26+
return Exception("Invalid servo index")
27+
return servo
1528

1629
def __init__(self, signal_pin:int):
1730
"""

XRPLib/webserver.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,8 @@ def start_network(self, ssid:str=None, robot_id:int= None, password:str=None):
6262
password = "remote.xrp"
6363
if password is not None and len(password) < 8:
6464
logging.warning("Password is less than 8 characters, this may cause issues with some devices")
65-
self.access_point = access_point(ssid, password)
65+
self.wlan = access_point(ssid, password)
6666
logging.info(f"Starting Access Point \"{ssid}\"")
67-
self.wlan = network.WLAN(network.AP_IF)
6867
self.ip = self.wlan.ifconfig()[0]
6968

7069
def connect_to_network(self, ssid:str=None, password:str=None, timeout = 10):
@@ -118,11 +117,12 @@ def stop_server(self):
118117
"""
119118
Shuts off the webserver and network and stops handling requests
120119
"""
121-
logging.enable_logging_types(logging.LOG_INFO)
122-
logging.info("Stopping Webserver and Network Connections")
123-
124-
stop()
125-
self.wlan.active(False)
120+
if self.wlan.active():
121+
logging.enable_logging_types(logging.LOG_INFO)
122+
logging.info("Stopping Webserver and Network Connections")
123+
124+
stop()
125+
self.wlan.active(False)
126126

127127
def _index_page(self, request):
128128
# Render index page and respond to form requests

0 commit comments

Comments
 (0)