Compare commits

..

27 Commits

Author SHA1 Message Date
e02c0a5591 Add diagnostics/ColorSensorDiagnostics.py 2025-12-15 13:35:06 +00:00
116836b86d Removed ambiguous Unicode characters 2025-12-11 14:29:13 +00:00
f58372d166 Update diagnostics/MotorDiagnostics.py 2025-12-11 14:28:52 +00:00
f688ef0cb3 Update diagnostics/MotorDiagnostics.py 2025-12-11 14:28:21 +00:00
19f735e7e2 Update diagnostics/FullDiagnostics.py 2025-12-11 14:27:56 +00:00
c2a7775d82 Update diagnostics/BatteryDiagnostics.py 2025-12-11 14:27:45 +00:00
595cfdc6eb Update README.md 2025-12-11 14:26:42 +00:00
9f94a114ae Update README.md 2025-12-09 00:22:49 +00:00
6b6cfa11b4 Update README.md 2025-12-09 00:12:26 +00:00
fee39bee77 Merge pull request 'arcmyx-dev' (#8) from arcmyx-dev into dev
Reviewed-on: Arcmyx/pynamics-pybricks-utils#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
1bd60aea24 Merge pull request 'Update README.md' (#6) from arcmyx-dev into dev
Reviewed-on: Arcmyx/pybricks-utils#6
2025-12-08 16:52:14 +00:00
1d652aff74 Update README.md 2025-12-08 16:47:10 +00:00
73d3b274b5 Merge pull request 'Update utils/FullDiagnostics.py' (#4) from arcmyx-dev into dev
Reviewed-on: Arcmyx/pybricks-utils#4
2025-12-08 13:48:28 +00:00
28375e87a8 Update utils/FullDiagnostics.py 2025-12-08 13:48:04 +00:00
alkadienePhoton
bcf128310d Add ASCII art and project description and credits to FullDiagnostics.py 2025-12-07 17:37:22 -06:00
alkadienePhoton
d92d401e2a Deleted useless stuff 2025-12-07 17:25:17 -06:00
alkadienePhoton
969f245de7 Added ASCII art 2025-12-07 17:16:55 -06:00
alkadienePhoton
086bd8841d Fixed up UI 2025-12-07 17:01:18 -06:00
alkadienePhoton
e4e9ef988b Enhance Battery and Motor Diagnostics: Add standard deviation calculation and motor diagnostics functionality 2025-12-07 16:57:28 -06:00
e00b6f1b2c Update utils/BatteryDiagnostics.py 2025-12-07 21:42:25 +00:00
9 changed files with 311 additions and 141 deletions

View File

@@ -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.
<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:
- 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).
@@ -15,4 +22,4 @@ Without the confusing legal speak, this means that you are free to:
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.
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).

View File

@@ -0,0 +1,55 @@
from pybricks.pupdevices import Motor, ColorSensor, UltrasonicSensor, ForceSensor
from pybricks.parameters import Button, Color, Direction, Port, Side, Stop
from pybricks.tools import run_task, multitask
from pybricks.tools import wait, StopWatch
from pybricks.robotics import DriveBase
from pybricks.hubs import PrimeHub
import umath
# Initialize hub and devices
hub = PrimeHub()
class BatteryDiagnostics:
def __init__(self):
self.voltage = 0
self.current = 0
def printVoltage(self):
self.voltage = hub.battery.voltage()
if self.voltage > 7800:
print(f"Battery voltage is sufficient: {self.voltage}")
elif self.voltage < 7800 :
print(f"Charging needed: {self.voltage}")
def printCurrent(self):
self.current = hub.battery.current()
print("Battery current:", self.current)
def printAll(self):
timeelapsed = 0
voltageList = []
currentList = []
while True:
print("\n\n\n\n\n")
self.printVoltage()
voltageList.append(self.voltage)
self.printCurrent()
currentList.append(self.current)
wait(50)
timeelapsed += 50
if(timeelapsed >= 3000):
break
print("--------------FINAL RESULTS OF BATTERY DIAGNOSTICS---------------")
print("Voltage deviation:", self.stdev(voltageList))
print("Current deviation:", self.stdev(currentList))
def stdev(self, vals):
data = vals
if len(data) < 2:
return 0
# Calculate the mean
mean = sum(data) / len(data)
# Calculate the variance (sum of squared differences from the mean, divided by n-1 for sample standard deviation)
variance = sum([(x - mean) ** 2 for x in data]) / float(len(data) - 1)
# Calculate the standard deviation (square root of the variance)
std_dev_manual = umath.sqrt(variance)
return (std_dev_manual)

View 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())

View File

@@ -0,0 +1,54 @@
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()
from BatteryDiagnostics import BatteryDiagnostics
from MotorDiagnostics import MotorDiagnostics
battery = BatteryDiagnostics()
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("""
███████████ █████ █████ ██████ █████ █████████ ██████ ██████ █████ █████████ █████████
▒▒███▒▒▒▒▒███▒▒███ ▒▒███ ▒▒██████ ▒▒███ ███▒▒▒▒▒███ ▒▒██████ ██████ ▒▒███ ███▒▒▒▒▒███ ███▒▒▒▒▒███
▒███ ▒███ ▒▒███ ███ ▒███▒███ ▒███ ▒███ ▒███ ▒███▒█████▒███ ▒███ ███ ▒▒▒ ▒███ ▒▒▒
▒██████████ ▒▒█████ ▒███▒▒███▒███ ▒███████████ ▒███▒▒███ ▒███ ▒███ ▒███ ▒▒█████████
▒███▒▒▒▒▒▒ ▒▒███ ▒███ ▒▒██████ ▒███▒▒▒▒▒███ ▒███ ▒▒▒ ▒███ ▒███ ▒███ ▒▒▒▒▒▒▒▒███
▒███ ▒███ ▒███ ▒▒█████ ▒███ ▒███ ▒███ ▒███ ▒███ ▒▒███ ███ ███ ▒███
█████ █████ █████ ▒▒█████ █████ █████ █████ █████ █████ ▒▒█████████ ▒▒█████████
▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒
The free and open source diagnostics tool for the LEGO® Education SPIKE™ Prime robots, designed for FIRST Lego League.
Developed by Team 65266, Lego Dynamics.
"""
)
while True:
print("\nWhat diagnostic do you want to perform?")
print("Enter 'b' for Battery diagnostics")
print("Enter 'm' for Motor diagnostics")
print("Enter 'q' to Quit")
choice = input("Your choice: ").strip().lower()
if choice == "b":
print("-----------------------BATTERY DIAGNOSTICS-----------------------")
print("This test will check the battery voltage and current. It will measure the voltage and current over a period of 3 seconds and provide average values and deviation values. Your voltage should be above 7800 mV for optimal performance.")
input("Press Enter to begin the battery diagnostics.")
battery.printAll()
print("Battery diagnostics completed.")
elif choice == "m":
print("------------------------MOTOR DIAGNOSTICS------------------------")
motor.fullTest()
print("Motor diagnostics completed.")
elif choice == "q":
print("Diagnostics completed successfully. Exiting with code 0. Good luck in the robot game!")
break
else:
print("Invalid choice. Please enter 'b', 'm', or 'q'.")

View File

@@ -0,0 +1,142 @@
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
import umath
hub = PrimeHub()
class MotorDiagnostics:
def __init__(self):
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):
data = vals
if len(data) < 2:
return 0
# Calculate the mean
mean = sum(data) / len(data)
# Calculate the variance (sum of squared differences from the mean, divided by n-1 for sample standard deviation)
variance = sum([(x - mean) ** 2 for x in data]) / float(len(data) - 1)
# Calculate the standard deviation (square root of the variance)
std_dev_manual = umath.sqrt(variance)
return (std_dev_manual)
def health_score(self, desired, avg_speed, stdev_speed, avg_load):
# Speed accuracy: penalize % error
accuracy = max(0, 100 - abs(avg_speed - desired) / desired * 100)
# Stability: penalize deviation relative to desired
stability = max(0, 100 - (stdev_speed / desired) * 100)
# Normalize load: map 10 to 20 as baseline (around 0%), 200 as max (around 100%)
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)
# Final score: average of the three
return (accuracy + stability + load_score) / 3
# Final score: average of the three
return (accuracy + stability + load_score) / 3
def initializeMotor(self):
valid_ports = {"A", "B", "C", "D", "E", "F"}
while True:
motorinput = input(
"This test will run your motor at 3 speeds: 180, 540, and 1000 degrees per second.\n"
"Please make sure your motor is not under any load (for example, your hand) during the test.\n"
"If you want to test the wheel's load, note that this will affect the load measurements.\n"
"Enter the port for the motor you would like to test (A, B, C, D, E, or F): "
).strip().upper()
try:
# 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}.")
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):
self.testmotor.reset_angle(0)
motorspeeds = []
motorloads = []
target_angle = speed * 3
print("\n", speed, "DEGREES PER SECOND TEST")
self.testmotor.run_angle(speed, target_angle, Stop.HOLD, False)
stopwatchmotor = StopWatch()
while stopwatchmotor.time() < 3000:
wait(20)
motorspeeds.append(self.testmotor.speed())
motorloads.append(self.testmotor.load())
max_speed, max_accel, max_torque = self.testmotor.control.limits()
print("Desired motor speed: ", str(speed))
if motorspeeds:
avg = sum(motorspeeds) / len(motorspeeds)
print("Average motor speed:", avg)
print("Motor speed deviation:", str(self.stdev(motorspeeds)))
else:
print("No speed samples collected.")
avg = 0
if motorloads:
avgload = sum(motorloads) / len(motorloads)
print("Average motor load:", avgload)
print("Motor load deviation:", str(self.stdev(motorloads)))
else:
print("No load samples collected.")
avgload = 0
score = self.health_score(speed, avg, self.stdev(motorspeeds), avgload)
print("Health score for this test:", str(score) + "%")
return score
def fullTest(self):
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)
test540 = self.testSpeed(540)
test1000 = self.testSpeed(1000)
print("\n FINAL MOTOR STATISTICS")
final = (test180 + test540 + test1000) / 3
print("Final motor health score:", str(final) + "%")
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

View File

@@ -1,33 +0,0 @@
from pybricks.pupdevices import Motor, ColorSensor, UltrasonicSensor, ForceSensor
from pybricks.parameters import Button, Color, Direction, Port, Side, Stop
from pybricks.tools import run_task, multitask
from pybricks.tools import wait, StopWatch
from pybricks.robotics import DriveBase
from pybricks.hubs import PrimeHub
# Initialize hub and devices
hub = PrimeHub()
left_motor = Motor(Port.A, Direction.COUNTERCLOCKWISE)
right_motor = Motor(Port.B,Direction.CLOCKWISE) # Specify default direction
left_arm = Motor(Port.C, Direction.CLOCKWISE) # Specify default direction
right_arm = Motor(Port.D, Direction.CLOCKWISE,[[12,36],[12,20,24]]) #Added gear train list for gear ration
lazer_ranger = UltrasonicSensor(Port.E)
color_sensor = ColorSensor(Port.F)
# DriveBase configuration
WHEEL_DIAMETER = 68.8 # mm (adjust for your wheels)
AXLE_TRACK = 180 # mm (distance between wheels)
drive_base = DriveBase(left_motor, right_motor, WHEEL_DIAMETER, AXLE_TRACK)
drive_base.settings(600, 500, 300, 200)
drive_base.use_gyro(True)
WALL_DISTANCE = 200 # mm
if hub.battery.voltage() > 7800:
print(f"Battery voltage is sufficient: {hub.battery.voltage()}")
elif hub.battery.voltage < 7800 :
print(f"Charging needed: {hub.battery.voltage()}")
print(hub.battery.current())
print(hub.imu.heading())
print(hub.system.info())

View File

@@ -1,69 +0,0 @@
from pybricks.pupdevices import Motor, ColorSensor, UltrasonicSensor, ForceSensor
from pybricks.parameters import Button, Color, Direction, Port, Side, Stop
from pybricks.tools import run_task, multitask
from pybricks.tools import wait, StopWatch
from pybricks.robotics import DriveBase
from pybricks.hubs import PrimeHub
# Initialize hub and devices
hub = PrimeHub()
color_sensor = ColorSensor(Port.F)
# Color Settings
# https://docs.pybricks.com/en/latest/parameters/color.html
print("Default Detected Colors:", color_sensor.detectable_colors())
# Custom color Hue, Saturation, Brightness value for Lego bricks
Color.MAGENTA = Color(315,100,60)
Color.BLUE = Color(240,100,100)
Color.CYAN = Color(180,100,100)
Color.RED = Color(350, 100, 100)
LEGO_BRICKS_COLOR = [
Color.BLUE,
Color.GREEN,
Color.WHITE,
Color.RED,
Color.YELLOW,
Color.MAGENTA,
Color.NONE
]
magenta_counter = 0
stable_color = None
real_color = None
#Update Detectable colors
color_sensor.detectable_colors(LEGO_BRICKS_COLOR)
print(f'Yellow:{Color.YELLOW} : {Color.YELLOW.h}, {Color.YELLOW.s}, {Color.YELLOW.v}')
print("Updated Detected Colors:", color_sensor.detectable_colors())
async def main():
while True:
global magenta_counter, stable_color, real_color
color_reflected_percent = await color_sensor.reflection()
print("Reflection: ", color_reflected_percent)
if color_reflected_percent > 15:
color_detected = await color_sensor.color()
if color_detected == Color.MAGENTA:
magenta_counter += 1
else:
magenta_counter = 0
stable_color = color_detected
# Only accept magenta if it's been stable for a while - usually triggers before other colors so we gotta do this :|
if magenta_counter > 10:
stable_color = Color.MAGENTA
if stable_color != Color.MAGENTA:
stable_color = await color_sensor.color()
real_color = stable_color
#if(color_detected != Color.NONE):
# return
print("Magenta counter: ", magenta_counter)
if real_color is not None:
print(f'Detected color: {real_color} : {real_color.h}, {real_color.s}, {real_color.v}')
else:
print("No valid color detected yet.")
await wait(50)
run_task(main())

View File

@@ -1,35 +0,0 @@
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()
from BatteryDiagnostics import BatteryDiagnostics
battery = BatteryDiagnostics()
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")
while True:
print("\nWhat diagnostic do you want to perform?")
print("Enter 'b' for Battery diagnostics")
print("Enter 'm' for Motor diagnostics")
print("Enter 'q' to Quit")
choice = input("Your choice: ").strip().lower()
if choice == "b":
print("-----------------------BATTERY DIAGNOSTICS-----------------------")
battery.printAll()
elif choice == "m":
print("------------------------MOTOR DIAGNOSTICS------------------------")
# motor.printAll() # call your motor diagnostics here
print("Motor diagnostics would run here.")
elif choice == "q":
print("Diagnostics completed successfully. Exiting with code 0. Good luck in the robot game!")
break
else:
print("Invalid choice. Please enter 'b', 'm', or 'q'.")