Skip to content

Commit

Permalink
Merge pull request #52 from Open-STEM/v1.0.0
Browse files Browse the repository at this point in the history
Version 1.0.0!
  • Loading branch information
ksiegall authored Aug 20, 2023
2 parents 4e1e3c8 + 79467dd commit d536c9a
Show file tree
Hide file tree
Showing 14 changed files with 96 additions and 159 deletions.
42 changes: 0 additions & 42 deletions .pre-commit-config.yaml

This file was deleted.

19 changes: 0 additions & 19 deletions .readthedocs.yaml

This file was deleted.

29 changes: 9 additions & 20 deletions XRPLib/board.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ def __init__(self, vin_pin:int, button_pin:int):
self.on_switch = ADC(Pin(vin_pin))

self.button = Pin(button_pin, Pin.IN, Pin.PULL_UP)
self.button_callback = None

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

def set_button_callback(self, trigger, callback):
"""
Sets an interrupt callback to be triggered on a change in button state, specified by trigger.
Follow the link for more information on how to write an Interrupt Service Routine (ISR).
https://docs.micropython.org/en/latest/reference/isr_rules.html
:param trigger: The type of trigger to be used for the interrupt
:type trigger: Pin.IRQ_RISING | Pin.IRQ_FALLING
:param callback: The function to be called when the interrupt is triggered
:type callback: function | None
"""
self.button_callback = callback
self.button.irq(trigger=trigger, handler=self.button_callback)

def is_button_pressed(self) -> bool:
"""
Returns the state of the button
Expand Down Expand Up @@ -98,9 +83,9 @@ def led_off(self):
self.led.off()
self._virt_timer.deinit()

def led_blink(self, frequency: int):
def led_blink(self, frequency: int=0):
"""
Blinks the LED at a given frequency
Blinks the LED at a given frequency. If the frequency is 0, the LED will stop blinking.
:param frequency: The frequency to blink the LED at (in Hz)
:type frequency: int
Expand All @@ -110,6 +95,10 @@ def led_blink(self, frequency: int):
self._virt_timer.deinit()
# We set it to twice in input frequency so that
# the led flashes on and off frequency times per second
self._virt_timer.init(freq=frequency*2, mode=Timer.PERIODIC,
callback=lambda t:self.led.toggle())
self.is_led_blinking = True
if frequency != 0:
self._virt_timer.init(freq=frequency*2, mode=Timer.PERIODIC,
callback=lambda t:self.led.toggle())
self.is_led_blinking = True
else:
self._virt_timer.deinit()
self.is_led_blinking = False
3 changes: 2 additions & 1 deletion XRPLib/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
drivetrain = DifferentialDrive.get_default_differential_drive()
rangefinder = Rangefinder.get_default_rangefinder()
reflectance = Reflectance.get_default_reflectance()
servo_one = Servo.get_default_servo()
servo_one = Servo.get_default_servo(index=1)
servo_two = Servo.get_default_servo(index=2)
webserver = Webserver.get_default_webserver()
board = Board.get_default_board()
12 changes: 6 additions & 6 deletions XRPLib/differential_drive.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,9 @@ def straight(self, distance: float, max_effort: float = 0.5, timeout: float = No
main_controller = PID(
kp = 0.1,
ki = 0.04,
kd = 0.06,
min_output = 0.25,
max_output = 0.5,
kd = 0.04,
min_output = 0.3,
max_output = max_effort,
max_integral = 10,
tolerance = 0.25,
tolerance_count = 3,
Expand All @@ -164,7 +164,7 @@ def straight(self, distance: float, max_effort: float = 0.5, timeout: float = No
# Secondary controller to keep encoder values in sync
if secondary_controller is None:
secondary_controller = PID(
kp = 0.075, kd=0.005,
kp = 0.075, kd=0.001,
)

if self.imu is not None:
Expand Down Expand Up @@ -241,7 +241,7 @@ def turn(self, turn_degrees: float, max_effort: float = 0.5, timeout: float = No
ki = 0.001,
kd = 0.00165,
min_output = 0.35,
max_output = 0.5,
max_output = max_effort,
max_integral = 75,
tolerance = 1,
tolerance_count = 3
Expand All @@ -250,7 +250,7 @@ def turn(self, turn_degrees: float, max_effort: float = 0.5, timeout: float = No
# Secondary controller to keep encoder values in sync
if secondary_controller is None:
secondary_controller = PID(
kp = 1.0,
kp = 0.8,
)

if use_imu and (self.imu is not None):
Expand Down
14 changes: 4 additions & 10 deletions XRPLib/encoded_motor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class EncodedMotor:
def get_default_encoded_motor(cls, index:int = 1):
"""
Get one of the default XRP v2 motor instances. These are singletons, so only one instance of each of these will ever exist.
Left Motor is the default, so if no index (or an invalid index) is specified, the left motor will be returned.
Raises an exception if an invalid index is requested.
:param index: The index of the motor to get; 1 for left, 2 for right, 3 for motor 3, 4 for motor 4
:type index: int
Expand Down Expand Up @@ -68,6 +68,8 @@ def __init__(self, motor: Motor, encoder: Encoder):
self.speed = 0
# Use a virtual timer so we can leave the hardware timers up for the user
self.updateTimer = Timer(-1)
# If the update timer is not running, start it at 50 Hz (20ms updates)
self.updateTimer.init(period=20, callback=lambda t:self._update())

def set_effort(self, effort: float):
"""
Expand Down Expand Up @@ -123,11 +125,7 @@ def set_speed(self, speed_rpm: float = None):
if speed_rpm is None or speed_rpm == 0:
self.target_speed = None
self.set_effort(0)
self.speed = 0
self.updateTimer.deinit()
return
# If the update timer is not running, start it at 50 Hz (20ms updates)
self.updateTimer.init(period=20, callback=lambda t:self._update())
# Convert from rev per min to counts per 20ms (60 sec/min, 50 Hz)
self.target_speed = speed_rpm*self._encoder.resolution/(60*50)
self.speedController.clear_history()
Expand All @@ -153,8 +151,4 @@ def _update(self):
error = self.target_speed - self.speed
effort = self.speedController.update(error)
self._motor.set_effort(effort)
else:
self.updateTimer.deinit()

self.prev_position = current_position

self.prev_position = current_position
6 changes: 3 additions & 3 deletions XRPLib/imu.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ def get_acc_rates(self):

def get_gyro_x_rate(self):
"""
Individual axis read for the Gyroscope's X-axis, in mg
Individual axis read for the Gyroscope's X-axis, in mdps
"""
# Burst read data registers
raw_bytes = self._getregs(LSM_REG_OUTX_L_G, 2)
Expand All @@ -252,7 +252,7 @@ def get_gyro_x_rate(self):

def get_gyro_y_rate(self):
"""
Individual axis read for the Gyroscope's Y-axis, in mg
Individual axis read for the Gyroscope's Y-axis, in mdps
"""
# Burst read data registers
raw_bytes = self._getregs(LSM_REG_OUTY_L_G, 2)
Expand All @@ -262,7 +262,7 @@ def get_gyro_y_rate(self):

def get_gyro_z_rate(self):
"""
Individual axis read for the Gyroscope's Z-axis, in mg
Individual axis read for the Gyroscope's Z-axis, in mdps
"""
# Burst read data registers
raw_bytes = self._getregs(LSM_REG_OUTZ_L_G, 2)
Expand Down
6 changes: 3 additions & 3 deletions XRPLib/motor_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def get_position_counts(self) -> int:
"""
avg = 0
for motor in self.motors:
avg += motor.get_position_ticks()
avg += motor.get_position_counts()
return round(avg / len(self.motors))

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


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

def set_speed_controller(self, new_controller):
"""
Expand Down
56 changes: 39 additions & 17 deletions XRPLib/resetbot.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,47 @@
from XRPLib.encoded_motor import EncodedMotor
from XRPLib.imu import IMU
from XRPLib.board import Board
from XRPLib.servo import Servo
from XRPLib.webserver import Webserver
import sys
"""
A simple file for shutting off all of the motors after a program gets interrupted from the REPL.
Run this file after interrupting a program to stop the robot by running "import XRPLib.resetbot" in the REPL.
"""

# using the EncodedMotor since the default drivetrain uses the IMU and takes 3 seconds to init
for i in range(4):
motor = EncodedMotor.get_default_encoded_motor(i+1)
motor.set_speed(0)
motor.reset_encoder_position()
def reset_motors():
from XRPLib.encoded_motor import EncodedMotor
# using the EncodedMotor since the default drivetrain uses the IMU and takes 3 seconds to init
for i in range(4):
motor = EncodedMotor.get_default_encoded_motor(i+1)
motor.set_speed(0)
motor.reset_encoder_position()

# Turn off the on-board LED
Board.get_default_board().led_off()
def reset_led():
from XRPLib.board import Board
# Turn off the on-board LED
Board.get_default_board().led_off()

# Turn off both Servos
Servo.get_default_servo().free()
Servo(17).free()
def reset_servos():
from XRPLib.servo import Servo
# Turn off both Servos
Servo.get_default_servo(1).free()
Servo.get_default_servo(2).free()

# Shut off the webserver and close network connections
Webserver.get_default_webserver().stop_server()
def reset_webserver():
from XRPLib.webserver import Webserver
# Shut off the webserver and close network connections
Webserver.get_default_webserver().stop_server()

def reset_hard():
reset_motors()
reset_led()
reset_servos()
reset_webserver()

if "XRPLib.encoded_motor" in sys.modules:
reset_motors()

if "XRPLib.board" in sys.modules:
reset_led()

if "XRPLib.servo" in sys.modules:
reset_servos()

if "XRPLib.webserver" in sys.modules:
reset_webserver()
27 changes: 20 additions & 7 deletions XRPLib/servo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,29 @@

class Servo:

_DEFAULT_SERVO_INSTANCE = None
_DEFAULT_SERVO_ONE_INSTANCE = None
_DEFAULT_SERVO_TWO_INSTANCE = None

@classmethod
def get_default_servo(cls):
def get_default_servo(cls, index:int):
"""
Get the default XRP v2 servo instance. This is a singleton, so only one instance of the servo will ever exist.
"""
if cls._DEFAULT_SERVO_INSTANCE is None:
cls._DEFAULT_SERVO_INSTANCE = cls(16)
return cls._DEFAULT_SERVO_INSTANCE
Gets one of the default XRP v2 servo instances. These are singletons, so only one instance of each servo will ever exist.
Raises an exception if an invalid index is requested.
:param index: The index of the servo to get (1 or 2)
:type index: int
"""
if index == 1:
if cls._DEFAULT_SERVO_ONE_INSTANCE is None:
cls._DEFAULT_SERVO_ONE_INSTANCE = cls(16)
servo = cls._DEFAULT_SERVO_ONE_INSTANCE
elif index == 2:
if cls._DEFAULT_SERVO_TWO_INSTANCE is None:
cls._DEFAULT_SERVO_TWO_INSTANCE = cls(17)
servo = cls._DEFAULT_SERVO_TWO_INSTANCE
else:
return Exception("Invalid servo index")
return servo

def __init__(self, signal_pin:int):
"""
Expand Down
14 changes: 7 additions & 7 deletions XRPLib/webserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,8 @@ def start_network(self, ssid:str=None, robot_id:int= None, password:str=None):
password = "remote.xrp"
if password is not None and len(password) < 8:
logging.warning("Password is less than 8 characters, this may cause issues with some devices")
self.access_point = access_point(ssid, password)
self.wlan = access_point(ssid, password)
logging.info(f"Starting Access Point \"{ssid}\"")
self.wlan = network.WLAN(network.AP_IF)
self.ip = self.wlan.ifconfig()[0]

def connect_to_network(self, ssid:str=None, password:str=None, timeout = 10):
Expand Down Expand Up @@ -118,11 +117,12 @@ def stop_server(self):
"""
Shuts off the webserver and network and stops handling requests
"""
logging.enable_logging_types(logging.LOG_INFO)
logging.info("Stopping Webserver and Network Connections")

stop()
self.wlan.active(False)
if self.wlan.active():
logging.enable_logging_types(logging.LOG_INFO)
logging.info("Stopping Webserver and Network Connections")

stop()
self.wlan.active(False)

def _index_page(self, request):
# Render index page and respond to form requests
Expand Down
Loading

0 comments on commit d536c9a

Please sign in to comment.