Compare commits

...

8 Commits

Author SHA1 Message Date
fee39bee77 Merge pull request 'arcmyx-dev' (#8) from arcmyx-dev into dev
Reviewed-on: #8
2025-12-09 00:10:43 +00:00
542ab3d0af Update README.md 2025-12-09 00:10:27 +00:00
alkadienePhoton
a9636a2efd Renamed screenshot 2025-12-08 18:07:33 -06:00
d2891f9367 Upload files to "/" 2025-12-09 00:05:59 +00:00
c92f53ea53 Update utils/MotorDiagnostics.py 2025-12-09 00:03:12 +00:00
5fc9e1f125 Update utils/BatteryDiagnostics.py 2025-12-08 23:43:06 +00:00
5e91fe7697 Update utils/MotorDiagnostics.py 2025-12-08 23:42:55 +00:00
663da5ac07 Update utils/FullDiagnostics.py 2025-12-08 23:42:39 +00:00
5 changed files with 72 additions and 32 deletions

View File

@@ -2,7 +2,9 @@
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.
How to use this: <img src="https://codes.fll-65266.org/Arcmyx/pynamics-pybricks-utils/raw/branch/arcmyx-dev/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. - 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.

BIN
pynamics-screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View File

@@ -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):

View File

@@ -8,6 +8,9 @@ from BatteryDiagnostics import BatteryDiagnostics
from MotorDiagnostics import MotorDiagnostics from MotorDiagnostics import MotorDiagnostics
battery = BatteryDiagnostics() battery = BatteryDiagnostics()
motor = MotorDiagnostics() motor = MotorDiagnostics()
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 == ""):
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(""" print("""
███████████ █████ █████ ██████ █████ █████████ ██████ ██████ █████ █████████ █████████ ███████████ █████ █████ ██████ █████ █████████ ██████ ██████ █████ █████████ █████████
▒▒███▒▒▒▒▒███▒▒███ ▒▒███ ▒▒██████ ▒▒███ ███▒▒▒▒▒███ ▒▒██████ ██████ ▒▒███ ███▒▒▒▒▒███ ███▒▒▒▒▒███ ▒▒███▒▒▒▒▒███▒▒███ ▒▒███ ▒▒██████ ▒▒███ ███▒▒▒▒▒███ ▒▒██████ ██████ ▒▒███ ███▒▒▒▒▒███ ███▒▒▒▒▒███
@@ -17,20 +20,13 @@ print("""
▒███ ▒███ ▒███ ▒▒█████ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒███ ███ ███ ▒███ ▒███ ▒███ ▒███ ▒▒█████ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒███ ███ ███ ▒███
█████ █████ █████ ▒▒█████ █████ █████ █████ █████ █████ ▒▒█████████ ▒▒█████████ █████ █████ █████ ▒▒█████ █████ █████ █████ █████ █████ ▒▒█████████ ▒▒█████████
▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒
The free & open-source diagnostics tool for LEGO® Education SPIKE™ Prime The free and open source diagnostics tool for the LEGO® Education SPIKE™ Prime robots, designed for FIRST Lego League.
Designed for FIRST LEGO League teams Developed by Team 65266, Lego Dynamics.
Developed by Team 65266 — Lego Dynamics
""" """
) )
clearConfirmation = input("Do you want to clear the console before proceeding? Y/N (default: yes): ")
yeses = ["Y", "YES", "Yes", "yes", ""]
if(clearConfirmation in yeses):
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: 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")

View File

@@ -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 1020 as baseline (≈0%), 200 as max (≈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
print(f"Motor initialized on port {motorinput}.") if self.testmotor is None:
break # exit the loop once a valid port is entered self.testmotor = Motor(self.port_map[motorinput])
else: print(f"Motor initialized on port {motorinput}.")
print("Invalid port. Please enter A, B, C, D, or E, or F.") else:
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()