Compare commits
21 Commits
bcf128310d
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| e02c0a5591 | |||
| 116836b86d | |||
| f58372d166 | |||
| f688ef0cb3 | |||
| 19f735e7e2 | |||
| c2a7775d82 | |||
| 595cfdc6eb | |||
| 9f94a114ae | |||
| 6b6cfa11b4 | |||
| fee39bee77 | |||
| 542ab3d0af | |||
|
|
a9636a2efd | ||
| d2891f9367 | |||
| c92f53ea53 | |||
| 5fc9e1f125 | |||
| 5e91fe7697 | |||
| 663da5ac07 | |||
| 1bd60aea24 | |||
| 1d652aff74 | |||
| 73d3b274b5 | |||
| 28375e87a8 |
15
README.md
15
README.md
@@ -1,10 +1,17 @@
|
|||||||
# Team 65266 Lego Dynamics - Pybricks Utils
|
# Team 65266 Lego Dynamics - PYNAMICS - Pybricks Utilities
|
||||||
|
|
||||||
A collection of Pybricks utilities to assist in your FLL robot programming with Python. Created by FLL team 65266, Lego Dynamics.
|
A collection of Pybricks utilities to assist in your FLL robot programming with Python. Created by FLL team 65266, Lego Dynamics.
|
||||||
|
|
||||||
|
<img src="https://codes.fll-65266.org/Arcmyx/pynamics-pybricks-utils/raw/branch/main/pynamics-screenshot.png" alt="Pynamics screenshot" width="670">
|
||||||
|
|
||||||
|
How to use this:
|
||||||
|
|
||||||
|
- Download the repository by clicking on the "Code" tab, clicking the "< > Code" button, then downloading as a ZIP. Additionally, you can also use ```git clone https://codes.fll-65266.org/Arcmyx/pybricks-utils.git```. Unzip the archive and open code.pybricks.com. Then choose which folder you'd like to use, and open each file in pybricks by using the import button. For example, to use the diagnostics tool, simply open each program in the ```diagnostics``` folder into Pybricks. Then, follow the instructions for each utility.
|
||||||
|
|
||||||
Included utilities:
|
Included utilities:
|
||||||
- Diagnostics - a program that prints out useful information like battery life, etc.
|
|
||||||
- Color Sensor Tests - a program that identifies what color the sensor is detecting. If you'd like, you can use our color ranges in your own programs.
|
- Diagnostics - a program that allows you to diagnose issues and test parts of your robot, such as battery, motor, and color sensor. Open each program in the ```diagnostics``` folder in Pybricks, connect your robot, switch to the ```FullDiagnostics.py``` file and press run.
|
||||||
|
- Color Sensor Tests (UPCOMING) - a program that identifies what color the sensor is detecting. If you'd like, you can use our color ranges in your own programs.
|
||||||
|
|
||||||
This code is licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0).
|
This code is licensed under the Creative Commons Attribution 4.0 International License (CC BY 4.0).
|
||||||
|
|
||||||
@@ -15,4 +22,4 @@ Without the confusing legal speak, this means that you are free to:
|
|||||||
Under the following condition:
|
Under the following condition:
|
||||||
- Attribution (BY) — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. Essentially, give us credit, link this repository, and don't take the credit for our work, because that's just sad.
|
- Attribution (BY) — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. Essentially, give us credit, link this repository, and don't take the credit for our work, because that's just sad.
|
||||||
|
|
||||||
For the full legal details, please review the CC BY 4.0 Legal Code.
|
For the full legal details, please review the CC BY 4.0 Legal Code (read the [LICENSE](LICENSE) file here).
|
||||||
@@ -35,6 +35,7 @@ class BatteryDiagnostics:
|
|||||||
|
|
||||||
if(timeelapsed >= 3000):
|
if(timeelapsed >= 3000):
|
||||||
break
|
break
|
||||||
|
print("--------------FINAL RESULTS OF BATTERY DIAGNOSTICS---------------")
|
||||||
print("Voltage deviation:", self.stdev(voltageList))
|
print("Voltage deviation:", self.stdev(voltageList))
|
||||||
print("Current deviation:", self.stdev(currentList))
|
print("Current deviation:", self.stdev(currentList))
|
||||||
def stdev(self, vals):
|
def stdev(self, vals):
|
||||||
49
diagnostics/ColorSensorDiagnostics.py
Normal file
49
diagnostics/ColorSensorDiagnostics.py
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
from pybricks.hubs import PrimeHub
|
||||||
|
from pybricks.pupdevices import Motor, ColorSensor, UltrasonicSensor, ForceSensor
|
||||||
|
from pybricks.parameters import Button, Color, Direction, Port, Side, Stop
|
||||||
|
from pybricks.robotics import DriveBase
|
||||||
|
from pybricks.tools import wait, StopWatch
|
||||||
|
|
||||||
|
hub = PrimeHub()
|
||||||
|
class ColorSensorDiagnostics:
|
||||||
|
def __init__(self):
|
||||||
|
self.colorsensor = None
|
||||||
|
self.port_map = {
|
||||||
|
"A": Port.A,
|
||||||
|
"B": Port.B,
|
||||||
|
"C": Port.C,
|
||||||
|
"D": Port.D,
|
||||||
|
"E": Port.E,
|
||||||
|
"F": Port.F,
|
||||||
|
}
|
||||||
|
def initializeColorSensor(self):
|
||||||
|
valid_ports = {"A", "B", "C", "D", "E", "F"}
|
||||||
|
while True:
|
||||||
|
colorinput = input(
|
||||||
|
"This will test your color sensor.\n"
|
||||||
|
"Enter the port for the color sensor you would like to test (A, B, C, D, E, or F): "
|
||||||
|
).strip().upper()
|
||||||
|
if colorinput not in valid_ports:
|
||||||
|
print("Invalid port. Please enter A-F.")
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
if self.colorsensor is None:
|
||||||
|
self.colorsensor = ColorSensor(self.port_map[colorinput])
|
||||||
|
print(f"Color Sensor initialized on port {colorinput}.")
|
||||||
|
else:
|
||||||
|
print(f"Reusing existing color sensor on port {colorinput}.")
|
||||||
|
break
|
||||||
|
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == 16: # EBUSY
|
||||||
|
print(f"Port {colorinput} is already in use. Reusing existing color sensor.")
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(f"Error initializing color sensor on port {colorinput}: {e}")
|
||||||
|
print("Make sure a color sensor is actually connected to this port.")
|
||||||
|
self.colorsensor = None
|
||||||
|
self.colorsensor.detectable_colors(Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE, Color.WHITE, Color.NONE)
|
||||||
|
def printOutput(self):
|
||||||
|
while True:
|
||||||
|
print("HSV output:", self.colorsensor.hsv)
|
||||||
|
print("Detected color:", self.colorsensor.color())
|
||||||
@@ -11,8 +11,6 @@ motor = MotorDiagnostics()
|
|||||||
clearConfirmation = input("Do you want to clear the console before proceeding? Y/N (default: yes): ")
|
clearConfirmation = input("Do you want to clear the console before proceeding? Y/N (default: yes): ")
|
||||||
if(clearConfirmation == "Y" or clearConfirmation == "y" or clearConfirmation == "yes" or clearConfirmation == ""):
|
if(clearConfirmation == "Y" or clearConfirmation == "y" or clearConfirmation == "yes" or clearConfirmation == ""):
|
||||||
print("Clearing console... \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
|
print("Clearing console... \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n")
|
||||||
|
|
||||||
while True:
|
|
||||||
print("""
|
print("""
|
||||||
███████████ █████ █████ ██████ █████ █████████ ██████ ██████ █████ █████████ █████████
|
███████████ █████ █████ ██████ █████ █████████ ██████ ██████ █████ █████████ █████████
|
||||||
▒▒███▒▒▒▒▒███▒▒███ ▒▒███ ▒▒██████ ▒▒███ ███▒▒▒▒▒███ ▒▒██████ ██████ ▒▒███ ███▒▒▒▒▒███ ███▒▒▒▒▒███
|
▒▒███▒▒▒▒▒███▒▒███ ▒▒███ ▒▒██████ ▒▒███ ███▒▒▒▒▒███ ▒▒██████ ██████ ▒▒███ ███▒▒▒▒▒███ ███▒▒▒▒▒███
|
||||||
@@ -27,6 +25,8 @@ The free and open source diagnostics tool for the LEGO® Education SPIKE™ Prim
|
|||||||
Developed by Team 65266, Lego Dynamics.
|
Developed by Team 65266, Lego Dynamics.
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
while True:
|
||||||
|
|
||||||
print("\nWhat diagnostic do you want to perform?")
|
print("\nWhat diagnostic do you want to perform?")
|
||||||
print("Enter 'b' for Battery diagnostics")
|
print("Enter 'b' for Battery diagnostics")
|
||||||
print("Enter 'm' for Motor diagnostics")
|
print("Enter 'm' for Motor diagnostics")
|
||||||
@@ -9,6 +9,15 @@ hub = PrimeHub()
|
|||||||
class MotorDiagnostics:
|
class MotorDiagnostics:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.testmotor = None
|
self.testmotor = None
|
||||||
|
self.port_map = {
|
||||||
|
"A": Port.A,
|
||||||
|
"B": Port.B,
|
||||||
|
"C": Port.C,
|
||||||
|
"D": Port.D,
|
||||||
|
"E": Port.E,
|
||||||
|
"F": Port.F,
|
||||||
|
}
|
||||||
|
|
||||||
def stdev(self, vals):
|
def stdev(self, vals):
|
||||||
data = vals
|
data = vals
|
||||||
if len(data) < 2:
|
if len(data) < 2:
|
||||||
@@ -31,11 +40,18 @@ class MotorDiagnostics:
|
|||||||
|
|
||||||
# Stability: penalize deviation relative to desired
|
# Stability: penalize deviation relative to desired
|
||||||
stability = max(0, 100 - (stdev_speed / desired) * 100)
|
stability = max(0, 100 - (stdev_speed / desired) * 100)
|
||||||
max_speed, max_accel, max_torque = self.testmotor.control.limits()
|
|
||||||
# Load: penalize load percentage directly
|
# Normalize load: map 10 to 20 as baseline (around 0%), 200 as max (around 100%)
|
||||||
load_pct = (avg_load / max_torque) * 100 if max_torque else 0
|
baseline = 15 # midpoint of idle range
|
||||||
|
max_observed = 200 # heavy load/stall
|
||||||
|
normalized_load = max(0, avg_load - baseline)
|
||||||
|
load_pct = min(100, (normalized_load / (max_observed - baseline)) * 100)
|
||||||
|
|
||||||
load_score = max(0, 100 - load_pct)
|
load_score = max(0, 100 - load_pct)
|
||||||
|
|
||||||
|
# Final score: average of the three
|
||||||
|
return (accuracy + stability + load_score) / 3
|
||||||
|
|
||||||
|
|
||||||
# Final score: average of the three
|
# Final score: average of the three
|
||||||
return (accuracy + stability + load_score) / 3
|
return (accuracy + stability + load_score) / 3
|
||||||
@@ -49,12 +65,24 @@ class MotorDiagnostics:
|
|||||||
"Enter the port for the motor you would like to test (A, B, C, D, E, or F): "
|
"Enter the port for the motor you would like to test (A, B, C, D, E, or F): "
|
||||||
).strip().upper()
|
).strip().upper()
|
||||||
|
|
||||||
if motorinput in valid_ports:
|
try:
|
||||||
self.testmotor = Motor(Port[motorinput])
|
# Only create a new Motor if we don't already have one
|
||||||
|
if self.testmotor is None:
|
||||||
|
self.testmotor = Motor(self.port_map[motorinput])
|
||||||
print(f"Motor initialized on port {motorinput}.")
|
print(f"Motor initialized on port {motorinput}.")
|
||||||
break # exit the loop once a valid port is entered
|
|
||||||
else:
|
else:
|
||||||
print("Invalid port. Please enter A, B, C, D, or E, or F.")
|
print(f"Reusing existing motor on port {motorinput}.")
|
||||||
|
break
|
||||||
|
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == 16: # EBUSY
|
||||||
|
print(f"Port {motorinput} is already in use. Reusing existing motor.")
|
||||||
|
# Do not overwrite self.testmotor here — keep the existing reference
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print(f"Error initializing motor on port {motorinput}: {e}")
|
||||||
|
print("Make sure a motor is actually connected to this port.")
|
||||||
|
self.testmotor = None
|
||||||
|
|
||||||
def testSpeed(self, speed):
|
def testSpeed(self, speed):
|
||||||
self.testmotor.reset_angle(0)
|
self.testmotor.reset_angle(0)
|
||||||
@@ -62,40 +90,53 @@ class MotorDiagnostics:
|
|||||||
motorspeeds = []
|
motorspeeds = []
|
||||||
motorloads = []
|
motorloads = []
|
||||||
target_angle = speed * 3
|
target_angle = speed * 3
|
||||||
|
print("\n", speed, "DEGREES PER SECOND TEST")
|
||||||
self.testmotor.run_angle(speed, target_angle, Stop.HOLD, False)
|
self.testmotor.run_angle(speed, target_angle, Stop.HOLD, False)
|
||||||
while abs(self.testmotor.speed()) > 5:
|
stopwatchmotor = StopWatch()
|
||||||
|
while stopwatchmotor.time() < 3000:
|
||||||
wait(20)
|
wait(20)
|
||||||
motorspeeds.append(self.testmotor.speed())
|
motorspeeds.append(self.testmotor.speed())
|
||||||
motorloads.append(self.testmotor.load())
|
motorloads.append(self.testmotor.load())
|
||||||
|
|
||||||
max_speed, max_accel, max_torque = self.testmotor.control.limits()
|
max_speed, max_accel, max_torque = self.testmotor.control.limits()
|
||||||
print("Maximum motor speed (deg/s):", max_speed)
|
|
||||||
|
|
||||||
print("Desired motor speed (degrees per second): ", str(speed))
|
|
||||||
|
print("Desired motor speed: ", str(speed))
|
||||||
if motorspeeds:
|
if motorspeeds:
|
||||||
avg = sum(motorspeeds) / len(motorspeeds)
|
avg = sum(motorspeeds) / len(motorspeeds)
|
||||||
print("Average motor speed (degrees per second):", avg)
|
print("Average motor speed:", avg)
|
||||||
print("Motor speed deviation (this measures how much your motor deviates from the average speed):", str(self.stdev(motorspeeds)))
|
print("Motor speed deviation:", str(self.stdev(motorspeeds)))
|
||||||
else:
|
else:
|
||||||
print("No speed samples collected.")
|
print("No speed samples collected.")
|
||||||
avg = 0
|
avg = 0
|
||||||
if motorloads:
|
if motorloads:
|
||||||
avgload = sum(motorloads) / len(motorloads)
|
avgload = sum(motorloads) / len(motorloads)
|
||||||
print("Average motor load (mNm):", avgload)
|
|
||||||
print("Motor load deviation (this measures how much your motor load deviates from the average load):", str(self.stdev(motorloads)))
|
print("Average motor load:", avgload)
|
||||||
|
print("Motor load deviation:", str(self.stdev(motorloads)))
|
||||||
else:
|
else:
|
||||||
print("No load samples collected.")
|
print("No load samples collected.")
|
||||||
avgload = 0
|
avgload = 0
|
||||||
|
|
||||||
score = self.health_score(speed, avg, self.stdev(motorspeeds), avgload)
|
score = self.health_score(speed, avg, self.stdev(motorspeeds), avgload)
|
||||||
print("Health score for this test:", score)
|
print("Health score for this test:", str(score) + "%")
|
||||||
return score
|
return score
|
||||||
def fullTest(self):
|
def fullTest(self):
|
||||||
self.initializeMotor()
|
self.initializeMotor()
|
||||||
|
print("Load measurements are in mNm. Speed measurements are in degrees per second.")
|
||||||
|
max_speed, max_accel, max_torque = self.testmotor.control.limits()
|
||||||
|
print("Maximum motor speed:", max_speed)
|
||||||
test180 = self.testSpeed(180)
|
test180 = self.testSpeed(180)
|
||||||
test540 = self.testSpeed(540)
|
test540 = self.testSpeed(540)
|
||||||
test1000 = self.testSpeed(1000)
|
test1000 = self.testSpeed(1000)
|
||||||
|
print("\n FINAL MOTOR STATISTICS")
|
||||||
final = (test180 + test540 + test1000) / 3
|
final = (test180 + test540 + test1000) / 3
|
||||||
print("Final motor health score:", final)
|
print("Final motor health score:", str(final) + "%")
|
||||||
# print final health scores here as a combination of the 3 speeds, include speeds individual scores and total score
|
if final < 80:
|
||||||
|
print("Your motor is in need of attention. Make sure to clean it regularly and charge the Prime Hub.")
|
||||||
|
elif final < 90:
|
||||||
|
print("Your motor is in OK condition. Make sure to clean it regularly and charge the Prime Hub.")
|
||||||
|
elif final < 97:
|
||||||
|
print("Your motor is in great condition!")
|
||||||
|
else:
|
||||||
|
print("Your motor is in AMAZING condition!!!")
|
||||||
|
self.testmotor.stop()
|
||||||
BIN
pynamics-screenshot.png
Normal file
BIN
pynamics-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 253 KiB |
Reference in New Issue
Block a user