Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,10 @@ cython_debug/
#.idea/

# Webots
.arena.wbproj
.*.wbproj
.arena.jpg

/simulator/controllers/usercode_runner/runtime.ini
/simulator/controllers/competition_supervisor/runtime.ini
/zone_*
/dist
55 changes: 1 addition & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# sbot_simulator
A simulator built around Webots to use the sbot library virtually.

![sbot_simulator](assets/arena_overview.jpg)

### This is a work in progress
![sbot_simulator](assets/arena_overview.png)

## Installation

Expand Down Expand Up @@ -101,54 +99,3 @@ In order to keep the released simulator tidy and easy to use, the project is spl
Alongside these folders that are placed in the releases, the `assets` folder contains images and other resources that are used in the documentation. These are used to render an HTML page user facing readme that is included in the release archive.

Of the the folders that are not included in the release, the `tests` folder contains the unit and integration tests for the simulator that don't require running webots and the `test_simulator` folder contains a separate webots world that is used to test the simulator.

## Project Status

1. ~~device spinup~~
2. ~~debug logs~~
3. ~~test devices~~
4. ~~webots devices~~
5. ~~usercode runner~~
6. ~~vision~~
7. ~~arena~~
- ~~box~~
- ~~deck~~
- ~~triangle deck~~
- ~~floor texture~~
- ~~line~~
- ~~scoring lines~~
- ~~starting zones~~
8. ~~robot~~
9. ~~device jitter~~
- ~~in Webots~~
- ~~Ultrasound noise~~
- ~~Reflectance sensor noise~~
- ~~in python~~
- ~~motor noise~~
- ~~servo noise~~
10. sbot updates
1. ~~simulator discovery~~
2. ~~vision~~
3. ~~leds~~
4. ~~sleep & time~~
5. Windows startup performance
11. ~~keyboard robot~~
12. ~~setup script~~
13. ~~releases~~
14. documentation
1. ~~dev setup~~
2. user usage
3. ~~how it works~~
15. simulator tests
- vision position
- vision orientation
- distance sensor
- reflectance sensor
- bump sensor
- motor
- servo
16. ~~linting~~
17. ~~CI~~
18. report currents
19. supervisor
20. comp match running
Binary file removed assets/arena_overview.jpg
Binary file not shown.
Binary file added assets/arena_overview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 10 additions & 1 deletion assets/user_readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ The API for the simulator is the same as the API for the physical robot, so you


As well as the logs being displayed in the console, they are also saved to a file.
This file is saved in the `zone_0` folder and has a name in the format `log-<date>.log`.
This file is saved in the `zone_0` folder and has a name in the format `log-zone-<zone>-<date>.log`.
The date is when that simulation was run.

### Simulation of Time
Expand Down Expand Up @@ -252,6 +252,15 @@ If you see a message saying that Python cannot be found that looks similar to th
As well as the guidance above, there are a few other points to note when using the simulator.
These can help you to understand what is happening and how to get the most out of the simulator.

### Using Other Zones

If the arena has multiple starting zones, you can run multiple robots in the simulator.
To test how your robot behaves in each starting zone of the arena, you can copy your robot's code to run in each corner.

In the folder where you extracted the simulator, alongside the `zone_0` folder, you may have other `zone_<number>` folders.
Such as `zone_1`, `zone_2`, etc.
Each of these folders can contain a `robot.py` file that will be run in the corresponding starting zone of the arena.

### Performance Optimisations

The default settings work for most users however if you are using a less powerful computer or one without a dedicated graphics card (as is the case on many laptops), you may wish to adjust the graphics settings to enable the simulation to run faster.
Expand Down
20 changes: 10 additions & 10 deletions example_robots/basic_robot.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
from sbot import Robot
from sbot import arduino, motors, utils

robot = Robot()
# robot = Robot()

robot.motor_board.motors[0].power = 1
robot.motor_board.motors[1].power = 1
motors.set_power(0, 1)
motors.set_power(1, 1)

# measure the distance of the right ultrasound sensor
# pin 6 is the trigger pin, pin 7 is the echo pin
distance = robot.arduino.ultrasound_measure(6, 7)
distance = arduino.measure_ultrasound_distance(6, 7)
print(f"Right ultrasound distance: {distance / 1000} meters")

# motor board, channel 0 to half power forward
robot.motor_board.motors[0].power = 0.5
motors.set_power(0, 0.5)

# motor board, channel 1 to half power forward,
robot.motor_board.motors[1].power = 0.5
motors.set_power(1, 0.5)
# minimal time has passed at this point,
# so the robot will appear to move forward instead of turning

# sleep for 2 second
robot.sleep(2)
utils.sleep(2)

# stop both motors
robot.motor_board.motors[0].power = 0
robot.motor_board.motors[1].power = 0
motors.set_power(0, 0)
motors.set_power(1, 0)
78 changes: 45 additions & 33 deletions example_robots/keyboard_robot.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# mypy: ignore-errors
import math

from controller import Keyboard
from sbot import AnalogPins, Robot
from sbot import AnalogPin, Colour, arduino, comp, leds, motors, utils, vision

# Any keys still pressed in the following period will be handled again
# leading to rprinting sensors multiple times
Expand Down Expand Up @@ -62,17 +63,17 @@ def angle_str(angle: float) -> str:
return f"{angle:.4f} rad"


def print_sensors(robot: Robot) -> None:
def print_sensors() -> None:
ultrasonic_sensor_names = {
(2, 3): "Front",
(4, 5): "Left",
(6, 7): "Right",
(8, 9): "Back",
}
reflectance_sensor_names = {
AnalogPins.A0: "Left",
AnalogPins.A1: "Center",
AnalogPins.A2: "Right",
AnalogPin.A0: "Left",
AnalogPin.A1: "Center",
AnalogPin.A2: "Right",
}
touch_sensor_names = {
10: "Front Left",
Expand All @@ -83,29 +84,30 @@ def print_sensors(robot: Robot) -> None:

print("Distance sensor readings:")
for (trigger_pin, echo_pin), name in ultrasonic_sensor_names.items():
dist = robot.arduino.ultrasound_measure(trigger_pin, echo_pin)
dist = arduino.measure_ultrasound_distance(trigger_pin, echo_pin)
print(f"({trigger_pin}, {echo_pin}) {name: <12}: {dist:.0f} mm")

print("Touch sensor readings:")
for pin, name in touch_sensor_names.items():
touching = robot.arduino.pins[pin].digital_value
touching = arduino.digital_read(pin)
print(f"{pin} {name: <6}: {touching}")

print("Reflectance sensor readings:")
for Apin, name in reflectance_sensor_names.items():
reflectance = robot.arduino.pins[Apin].analog_value
reflectance = arduino.analog_read(Apin)
print(f"{Apin} {name: <12}: {reflectance:.2f} V")


def print_camera_detection(robot: Robot) -> None:
markers = robot.camera.see()
def print_camera_detection() -> None:
markers = vision.detect_markers()
if markers:
print(f"Found {len(markers)} makers:")
for marker in markers:
print(f" #{marker.id}")
print(
f" Position: {marker.distance:.0f} mm, azi: {angle_str(marker.azimuth)}, "
f"elev: {angle_str(marker.elevation)}",
f" Position: {marker.position.distance:.0f} mm, "
f"{angle_str(marker.position.horizontal_angle)} right, "
f"{angle_str(marker.position.vertical_angle)} up",
)
yaw, pitch, roll = marker.orientation
print(
Expand All @@ -119,11 +121,15 @@ def print_camera_detection(robot: Robot) -> None:
print()


robot = Robot()

keyboard = KeyboardInterface()

key_sense = CONTROLS["sense"][robot.zone]
# Automatically set the zone controls based on the robot's zone
# Alternatively, you can set this manually
# ZONE_CONTROLS = 0
ZONE_CONTROLS = comp.zone

assert ZONE_CONTROLS < len(CONTROLS["forward"]), \
"No controls defined for this zone, alter the ZONE_CONTROLS variable to use in this zone."

print(
"Note: you need to click on 3D viewport for keyboard events to be picked "
Expand All @@ -138,36 +144,42 @@ def print_camera_detection(robot: Robot) -> None:
keys = keyboard.process_keys()

# Actions that are run continuously while the key is held
if CONTROLS["forward"][robot.zone] in keys["held"]:
if CONTROLS["forward"][ZONE_CONTROLS] in keys["held"]:
left_power += 0.5
right_power += 0.5

if CONTROLS["reverse"][robot.zone] in keys["held"]:
if CONTROLS["reverse"][ZONE_CONTROLS] in keys["held"]:
left_power += -0.5
right_power += -0.5

if CONTROLS["left"][robot.zone] in keys["held"]:
if CONTROLS["left"][ZONE_CONTROLS] in keys["held"]:
left_power -= 0.25
right_power += 0.25

if CONTROLS["right"][robot.zone] in keys["held"]:
if CONTROLS["right"][ZONE_CONTROLS] in keys["held"]:
left_power += 0.25
right_power -= 0.25

if CONTROLS["boost"][robot.zone] in keys["held"]:
if CONTROLS["boost"][ZONE_CONTROLS] in keys["held"]:
boost = True

# Actions that are run once when the key is pressed
if CONTROLS["sense"][robot.zone] in keys["pressed"]:
print_sensors(robot)

if CONTROLS["see"][robot.zone] in keys["pressed"]:
print_camera_detection(robot)

if CONTROLS["led"][robot.zone] in keys["pressed"]:
pass

if CONTROLS["angle_unit"][robot.zone] in keys["pressed"]:
if CONTROLS["sense"][ZONE_CONTROLS] in keys["pressed"]:
print_sensors()

if CONTROLS["see"][ZONE_CONTROLS] in keys["pressed"]:
print_camera_detection()

if CONTROLS["led"][ZONE_CONTROLS] in keys["pressed"]:
leds.set_colour(0, Colour.MAGENTA)
leds.set_colour(1, Colour.MAGENTA)
leds.set_colour(2, Colour.MAGENTA)
elif CONTROLS["led"][ZONE_CONTROLS] in keys["released"]:
leds.set_colour(0, Colour.OFF)
leds.set_colour(1, Colour.OFF)
leds.set_colour(2, Colour.OFF)

if CONTROLS["angle_unit"][ZONE_CONTROLS] in keys["pressed"]:
USE_DEGREES = not USE_DEGREES
print(f"Angle unit set to {'degrees' if USE_DEGREES else 'radians'}")

Expand All @@ -176,7 +188,7 @@ def print_camera_detection(robot: Robot) -> None:
left_power = max(min(left_power * 2, 1), -1)
right_power = max(min(right_power * 2, 1), -1)

robot.motor_board.motors[0].power = left_power
robot.motor_board.motors[1].power = right_power
motors.set_power(0, left_power)
motors.set_power(1, right_power)

robot.sleep(KEYBOARD_SAMPLING_PERIOD / 1000)
utils.sleep(KEYBOARD_SAMPLING_PERIOD / 1000)
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
sbot==2024.0.1
sbot==2025.1.0
april_vision==2.2.0
opencv-python-headless >=4,<5
opencv-python-headless >=4.8.0.76,<5
4 changes: 4 additions & 0 deletions scripts/generate_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,12 @@
logger.info("Copying helper scripts to temp directory")
shutil.copy(project_root / "scripts/setup.py", temp_dir / "setup.py")
for script in project_root.glob("scripts/run_*.py"):
if "run_comp_" in str(script):
continue
shutil.copy(script, temp_dir)

script_dir = temp_dir / "scripts"
script_dir.mkdir()
logger.info("Copying example code to temp directory")
shutil.copytree(project_root / "example_robots", temp_dir / "example_robots")

Expand Down
Loading
Loading