diff --git a/.spelling b/.spelling
index c899cddb..3cdb30e6 100644
--- a/.spelling
+++ b/.spelling
@@ -6,6 +6,7 @@ Datacamp
GPIO
Programiz
Tutorialspoint
+piezo
# Specific
PULLUP
diff --git a/docs/.pages b/docs/.pages
index 41c1b4d7..b2c58103 100644
--- a/docs/.pages
+++ b/docs/.pages
@@ -1,5 +1,5 @@
nav:
- index.md
- tutorials
- - API: api
+ - rules
- ...
diff --git a/docs/api/compass.md b/docs/api/compass.md
deleted file mode 100644
index 5998cd60..00000000
--- a/docs/api/compass.md
+++ /dev/null
@@ -1,12 +0,0 @@
----
-title: Compass
----
-
-The forklift robot has a compass unit. This allows [robots]() to determine the direction it's facing in the arena.
-
-```python
-# Get the heading of the robot.
-heading = r.compass.get_heading() # Radians
-```
-
-When called, the `get_heading` method will return the heading of the robot in radians as a float. The heading is in the range 0 to tau (2π), where 0 is the robot facing directly North, and values increasing clockwise.
diff --git a/docs/api/encoder.md b/docs/api/encoder.md
deleted file mode 100644
index 6f1e0dfe..00000000
--- a/docs/api/encoder.md
+++ /dev/null
@@ -1,31 +0,0 @@
----
-title: Motor Encoder API
----
-
-Most moving parts of the robot have encoders attached. There are two kinds of encoders, linear encoders and rotary encoders. You can tell which is which by the joint it is attached to; if the joint rotates it is a Rotary encoder, whilst if it slides, it is a linear encoder.
-
-## Linear Encoders
-
-Linear encoders are attached to joints that move in a straight line (i.e. raising/lowering the forklift).
-
-Linear encoders measure the distance the joint has moved from its start position, in *metres*.
-
-All encoders are stored in a list called `encoders`, you can access the encoder with `encoders[i]`, where `i` is the slot the encoder is plugged into. You can find this number in the [documentation for the robot]().
-
-```python
-# Get the distance from the start position
-distance_from_start = r.encoders[1].displacement # in Metres
-```
-
-## Rotary Encoders
-
-Rotary encoders are attached to joints that rotate (i.e. wheel rotations).
-
-Rotary encoders measure the total rotation the joint has moved from its start position, in *radians*.
-
-All encoders are stored in a list called `encoders`, you can access the encoder with `encoders[i]`, where `i` is the slot the encoder is plugged into. You can find this number in the [documentation for the robot]().
-
-```python
-# Get the total rotation from the start position
-rotation_from_start = r.encoders[1].rotation # in Radians
-```
diff --git a/docs/api/index.md b/docs/api/index.md
deleted file mode 100644
index 82861547..00000000
--- a/docs/api/index.md
+++ /dev/null
@@ -1,53 +0,0 @@
----
-title: API
----
-
-Programming your robot is done in [Python](https://www.python.org/),
-specifically version 3.9.6. You can learn more about Python from their
-[docs](https://docs.python.org/3/), and our whirlwind tour.
-
-## Setup
-
-The following two lines are required to complete initialisation of the
-kit:
-
-``` python
-from sbot import *
-
-r = Robot()
-```
-
-Once this has been done, this `Robot` object can be used to control the
-robot's functions.
-
-The remainder of the tutorials pages will assume your `Robot` object is
-defined as `r`.
-
-## Running your code
-
-Your code needs to be put in the `zone-0` or `zone-1` folder in a file called
-`forklift.py` or `crane.py` for the forklift and crane robots respectively.
-
-!!! tip
- If this file is missing or incorrectly named, your robot won't do anything. No log file will be created.
-
-## Logs
-
-A log file is saved in the `output/` folder so you can see what your robot did,
-what it didn't do, and any errors it raised. Each log file is in a folder based on its
-date and time, so you can look back at your previous runs.
-
-## Included Libraries
-
-Python already comes with plenty of [built-in
-libraries](https://docs.python.org/3.9/py-modindex.html) to use. We
-install some extra ones which may be of use:
-
-- [numpy 1.19.3](https://pypi.org/project/numpy/1.19.3/)
-- [matplotlib 3.3.3](https://pypi.org/project/matplotlib/3.3.3/)
-- [pandas 1.1.4](https://pypi.org/project/pandas/1.1.4/)
-- [scikit-learn 0.23.2](https://pypi.org/project/scikit-learn/0.23.2/)
-- [scipy 1.5.4](https://pypi.org/project/scipy/1.5.4/)
-
-!!! tip
- If you would like an extra library installed, go and ask a volunteer to see if we can help.
diff --git a/docs/api/servos.md b/docs/api/servos.md
deleted file mode 100644
index 6ae1a4a4..00000000
--- a/docs/api/servos.md
+++ /dev/null
@@ -1,29 +0,0 @@
----
-title: Servos
----
-
-You can control attached servo motors from the [Arduino](./arduino.md). By default, servos will be unpowered when your robot starts, and can freely rotate when turned by hand. Upon setting a value, they will hold the corresponding position. They will become unpowered again when you turn off your robot, unplug your USB stick, or set their position to the special value `None`.
-
-## Querying servos
-
-The servo assembly can interface with up to sixteen servos connected to it.
-
-```python
-servo_zero = r.arduino.servos[0]
-```
-
-!!! info
- Servo can be connected to any port, you don't have to start at 0!
-
-## Controlling servos
-
-Servos can be controlled using the `position` parameter. This should be set to a value between -1 and 1.
-
-```python
-servo_zero.position = 0.65
-print(servo_zero.position)
->>> 0.65
-```
-
-!!! tip
- When `position` is set to `None`, the servo loses power and can freely rotate. This is the same state they're in at start-up.
diff --git a/docs/api/touch.md b/docs/api/touch.md
deleted file mode 100644
index 59ffa11e..00000000
--- a/docs/api/touch.md
+++ /dev/null
@@ -1,18 +0,0 @@
----
-title: Touch Sensors
----
-
-
-
-Microswitches, or touch sensors, are used to measure when something is touching the sensor.
-
-## Reading the touch sensor
-
-The touch sensors will be connected to a specific pin in the Arduino, typically wired in such a way that the pin's voltage is changed when the switch is pressed by an object.
-
-To read this, you need to read the digital value of the correct pin. You can get the correct pin number by looking at [the documentation for the robot](), then writing the code like so:
-
-``` python
-# Get if something is touching the touch sensor.
-is_touching = r.arduino.pins[pin].digital_state # True or False
-```
diff --git a/docs/api/ultrasound.md b/docs/api/ultrasound.md
deleted file mode 100644
index bd9b3f13..00000000
--- a/docs/api/ultrasound.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-title: Ultrasound Sensors
----
-
-
-
-Ultrasound sensors can measure distance of objects from the sensor. Ultrasound sensors emit a pulse of very high frequency sound, and measure the time it takes to hear an echo, typically Ultrasound sensors have electronics on the sensor to decide when an echo is received, and reports back a number as the distance in meters.
-
-Ultrasound sensors measure distance in a 18 degree diameter cone in front of the sensor, and report the closest measured distance in that cone. Ultrasound sensors also have a maximum distance. (Ultrasound sensors typically also have a minimum distance too, but we do not simulate that in our simulator).
-
-In our robots, ultrasound sensors are connected to the ['Arduino'](./arduino.md) board, which is used to read sensors for many different purposes. Keep reading to learn how to take measurements.
-
-## Reading the ultrasound sensor
-
-### Simulator
-
-The ultrasound sensors will be connected to a specific pin in the Arduino, and will constantly measure distances. Consult the description of the [robot]() to see which pin you should use. The analogue value of the pin will be the measured distance, in metres.
-
-Ultrasound sensors have a maximum range of 2 metres, if objects are further than 2m away from the robot, it will report a distance of 2m.
-
-``` python
-# Get the closest distance to the ultrasound sensor is reading.
-distance_metres = r.arduino.pins[AnaloguePin.A0].analogue_value
-```
-
-### Physical
-
-Connect the ultrasound sensor to a pair of digital Arduino pins. You can read either the time taken for the sound pulse to reflect back or the pre-calculated distance from the API
-
-``` python
-trigger_pin = 4
-echo_pin = 5
-u = r.arduino.ultrasound_sensors[trigger_pin, echo_pin]
-
-time_taken = u.pulse()
-
-distance_metres = u.distance()
-```
-
-!!! warning
- If the ultrasound signal never returns, the sensor will timeout and return `None`.
diff --git a/docs/assets/img/api/coordinate-spaces.svg b/docs/assets/img/api/coordinate-spaces.svg
deleted file mode 100644
index 13420ab2..00000000
--- a/docs/assets/img/api/coordinate-spaces.svg
+++ /dev/null
@@ -1,262 +0,0 @@
-
-
-
-
diff --git a/docs/assets/img/api/vision/arena_marker.jpg b/docs/assets/img/api/vision/arena_marker.jpg
new file mode 100644
index 00000000..ddd08391
Binary files /dev/null and b/docs/assets/img/api/vision/arena_marker.jpg differ
diff --git a/docs/assets/img/api/vision/arena_marker_annotated.jpg b/docs/assets/img/api/vision/arena_marker_annotated.jpg
new file mode 100644
index 00000000..1d96cffd
Binary files /dev/null and b/docs/assets/img/api/vision/arena_marker_annotated.jpg differ
diff --git a/docs/assets/img/api/vision/cartesian.svg b/docs/assets/img/api/vision/cartesian.svg
new file mode 100644
index 00000000..9eff8eb3
--- /dev/null
+++ b/docs/assets/img/api/vision/cartesian.svg
@@ -0,0 +1,219 @@
+
+
diff --git a/docs/assets/img/api/vision/m-45x0y0z.png b/docs/assets/img/api/vision/m-45x0y0z.png
new file mode 100644
index 00000000..3e840b9f
Binary files /dev/null and b/docs/assets/img/api/vision/m-45x0y0z.png differ
diff --git a/docs/assets/img/api/vision/m0x-45y0z.png b/docs/assets/img/api/vision/m0x-45y0z.png
new file mode 100644
index 00000000..9c6cdaea
Binary files /dev/null and b/docs/assets/img/api/vision/m0x-45y0z.png differ
diff --git a/docs/assets/img/api/vision/m0x0y-45z.png b/docs/assets/img/api/vision/m0x0y-45z.png
new file mode 100644
index 00000000..daa4bfec
Binary files /dev/null and b/docs/assets/img/api/vision/m0x0y-45z.png differ
diff --git a/docs/assets/img/api/vision/m0x0y0z.png b/docs/assets/img/api/vision/m0x0y0z.png
new file mode 100644
index 00000000..f66084cd
Binary files /dev/null and b/docs/assets/img/api/vision/m0x0y0z.png differ
diff --git a/docs/assets/img/api/vision/m0x0y45z.png b/docs/assets/img/api/vision/m0x0y45z.png
new file mode 100644
index 00000000..84cdb274
Binary files /dev/null and b/docs/assets/img/api/vision/m0x0y45z.png differ
diff --git a/docs/assets/img/api/vision/m0x45y0z.png b/docs/assets/img/api/vision/m0x45y0z.png
new file mode 100644
index 00000000..34bb07d9
Binary files /dev/null and b/docs/assets/img/api/vision/m0x45y0z.png differ
diff --git a/docs/assets/img/api/vision/m45x0y0z.png b/docs/assets/img/api/vision/m45x0y0z.png
new file mode 100644
index 00000000..3034d941
Binary files /dev/null and b/docs/assets/img/api/vision/m45x0y0z.png differ
diff --git a/docs/assets/img/api/vision/spherical.svg b/docs/assets/img/api/vision/spherical.svg
new file mode 100644
index 00000000..6279e354
--- /dev/null
+++ b/docs/assets/img/api/vision/spherical.svg
@@ -0,0 +1,230 @@
+
+
diff --git a/docs/assets/img/api/vision/yawpitchroll.png b/docs/assets/img/api/vision/yawpitchroll.png
new file mode 100644
index 00000000..a564be9e
Binary files /dev/null and b/docs/assets/img/api/vision/yawpitchroll.png differ
diff --git a/docs/api/arduino.md b/docs/programming/arduino.md
similarity index 53%
rename from docs/api/arduino.md
rename to docs/programming/arduino.md
index e096a733..6f0a92dd 100644
--- a/docs/api/arduino.md
+++ b/docs/programming/arduino.md
@@ -1,18 +1,19 @@
----
-title: Arduino API
----
+# Arduino API
The [Arduino](https://store.arduino.cc/arduino-uno-rev3) provides a
total of 18 pins for either digital input or output (labelled 2 to 13
and A0 to A5), including 6 for analogue input (labelled A0 to A5).
+!!! warning
+ Digital pins 0 and 1 are reserved and cannot be used.
+
## Accessing the Arduino
The Arduino can be accessed using the `arduino` property of the `Robot`
object.
``` python
-my_arduino = r.arduino
+my_arduino = robot.arduino
```
You can use the GPIO *(General Purpose Input/Output)* pins for anything,
@@ -20,55 +21,7 @@ from microswitches to LEDs. GPIO is only available on pins 2 to 13 and
A0 to A5 because pins 0 and 1 are reserved for communication with the
rest of our kit.
-## Simulator
-
-In the simulator, the Arduino's pins are pre-populated and pre-configured.
-The first few digital pins are occupied by digital inputs, the next few by
-digital outputs, and the analogue pins are attached to ultrasound sensors.
-
-To find out how many inputs and outputs each type of robot has, check the
-[robot docs]().
-
-You won't be able to change pin mode like in
-a physical robot (see below), but pins 0 and 1 are still unavailable.
-
-### Digital Inputs
-
-Each robot has a number of digital inputs, starting from pin 2. If your
-robot has 5 inputs, those would occupy pins 2-6.
-
-These all have a digital state which you can read as a boolean.
-
-```python
-bumper_pressed = r.arduino.pins[5].digital_state
-```
-
-### Digital Outputs
-
-The digital outputs start the pin after the last input. If your robot has 5
-inputs and 3 outputs, the outputs would occupy pins 7-9.
-
-You can set their state similarly to reading the inputs, and you can also
-read the last value that was set.
-
-```python
-led_state = r.arduino.pins[8].digital_state
-r.arduino.pins[8].digital_state = not led_state # Toggle output
-```
-
-### Analogue Inputs
-
-Any analogue input devices (e.g. distance sensors) are connected to the
-Arduino's analogue input pins starting from pin `A0`. You can read their
-values like this:
-
-```python
-distance = r.arduino.pins[AnaloguePin.A0].analogue_value
-```
-
-The value read is returned as a float.
-
-## Pin Mode (Unavailable in Simulator)
+## Pin Mode
GPIO pins have four different modes. A pin can only have one mode at a
time, and some pins aren't compatible with certain modes. These pin
@@ -90,7 +43,7 @@ performing an action with that pin. You can read about the possible pin
modes below.
``` python
-r.arduino.pins[3].mode = GPIOPinMode.DIGITAL_INPUT_PULLUP
+robot.arduino.pins[3].mode = GPIOPinMode.DIGITAL_INPUT_PULLUP
```
### `GPIOPinMode.DIGITAL_INPUT`
@@ -99,9 +52,9 @@ In this mode, the digital state of the pin (whether it is high or low)
can be read.
``` python
-r.arduino.pins[4].mode = GPIOPinMode.DIGITAL_INPUT
+robot.arduino.pins[4].mode = GPIOPinMode.DIGITAL_INPUT
-pin_value = r.arduino.pins[4].digital_state
+pin_value = robot.arduino.pins[4].digital_state
```
### `GPIOPinMode.DIGITAL_INPUT_PULLUP`
@@ -111,9 +64,9 @@ resistor](https://learn.sparkfun.com/tutorials/pull-up-resistors)
enabled.
``` python
-r.arduino.pins[4].mode = GPIOPinMode.DIGITAL_INPUT_PULLUP
+robot.arduino.pins[4].mode = GPIOPinMode.DIGITAL_INPUT_PULLUP
-pin_value = r.arduino.pins[4].digital_state
+pin_value = robot.arduino.pins[4].digital_state
```
### `GPIOPinMode.DIGITAL_OUTPUT`
@@ -121,11 +74,11 @@ pin_value = r.arduino.pins[4].digital_state
In this mode, we can set binary values of `0V` or `5V` to the pin.
``` python
-r.arduino.pins[4].mode = GPIOPinMode.DIGITAL_OUTPUT
-r.arduino.pins[6].mode = GPIOPinMode.DIGITAL_OUTPUT
+robot.arduino.pins[4].mode = GPIOPinMode.DIGITAL_OUTPUT
+robot.arduino.pins[6].mode = GPIOPinMode.DIGITAL_OUTPUT
-r.arduino.pins[4].digital_state = True
-r.arduino.pins[6].digital_state = False
+robot.arduino.pins[4].digital_state = True
+robot.arduino.pins[6].digital_state = False
```
### `GPIOPinMode.ANALOGUE_INPUT`
@@ -143,13 +96,24 @@ cannot be used.
``` python
from sbot import AnaloguePin
-r.arduino.pins[AnaloguePin.A0].mode = GPIOPinMode.ANALOGUE_INPUT
+robot.arduino.pins[AnaloguePin.A0].mode = GPIOPinMode.ANALOGUE_INPUT
-pin_value = r.arduino.pins[AnaloguePin.A0].analogue_value
+pin_value = robot.arduino.pins[AnaloguePin.A0].analogue_value
```
!!! tip
The values are the voltages read on the pins, between 0 and 5.
+## Ultrasound Sensors
+
+You can also measure distance using an ultrasound sensor from the arduino.
+
+```python
+# Trigger pin: 4
+# Echo pin: 5
+
+distance_metres = robot.arduino.ultrasound_measure(4, 5)
+```
+
!!! warning
- Pins `A4` and `A5` are reserved and cannot be used.
+ The ultrasound sensor can measure distances up to 2 metres. If the ultrasound signal has to travel further than 2m, the sensor will timeout and return `None`.
diff --git a/docs/api/game-state.md b/docs/programming/game-state.md
similarity index 96%
rename from docs/api/game-state.md
rename to docs/programming/game-state.md
index e7d629b8..280e885d 100644
--- a/docs/api/game-state.md
+++ b/docs/programming/game-state.md
@@ -15,7 +15,7 @@ Your robot can be in 1 of 2 modes: `DEVELOPMENT` and `COMPETITION`. By
default, your robot will be in `DEVELOPMENT` mode:
``` python
-r.is_competition
+robot.is_competition
>> False
```
@@ -29,9 +29,9 @@ zone. The number of zones depends on the game. Each zone is given a
number, which you can access with the `zone` property:
``` python
-r.zone
+robot.zone
>> 1
```
During a competition match, a USB drive will be used to tell your robot
-which corner it's in. By default during development, this is `0`.
\ No newline at end of file
+which corner it's in. By default during development, this is `0`.
diff --git a/docs/programming/index.md b/docs/programming/index.md
new file mode 100644
index 00000000..3d286f9e
--- /dev/null
+++ b/docs/programming/index.md
@@ -0,0 +1,89 @@
+# Programming
+
+Programming your robot is done in [Python](https://www.python.org/),
+specifically version 3.9.6. You can learn more about Python from their
+[docs](https://docs.python.org/3/), and our [whirlwind tour](../tutorials/python-whirlwind-tour.md).
+
+Each board has an API which allows its various functionality to be controlled.
+
+## Setup
+
+The following two lines are required to complete initialisation of the
+kit:
+
+``` python
+from sbot import Robot
+
+robot = Robot()
+```
+
+Once this has been done, this `Robot` object can be used to control the
+robot's functions.
+
+The remainder of the tutorials pages will assume your `Robot` object is
+defined as `robot`.
+
+!!! note
+ In Python, variables are case-sensitive. `robot` is an instance of `Robot`.
+
+## Running your code
+
+Your code needs to be put on a USB drive in a file called `robot.py`. When connected to the robot, this file will be executed. The file is directly executed off your USB drive, with your drive as the working directory.
+
+!!! tip
+ If this file is missing or incorrectly named, your robot won't do anything. No log file will be created.
+
+To stop your code running, you can just remove the USB drive. This will also stop the motors and any other peripherals connected to the kit.
+
+You can then reinsert the USB drive into the robot, and it will run your `main.py` again (from the start). This allows you to make changes and test them quickly.
+
+## Logs
+
+A log file is saved to your USB so you can see what your robot did,
+what it didn't do, and any errors it raised. The file is saved to log.txt in the top-level directory of the USB drive.
+
+!!! warning
+ The previous log file is deleted at the start of each run, so copy it elsewhere if you need to keep hold of it!
+
+## Running Code before pressing the start button
+
+If you want to do things before the start button press, such as setting up servos or motors, you can pass `wait_for_start` to the `Robot` constructor. You will then need to wait for the start button manually using `robot.wait_start()`.
+
+```python
+robot = Robot(wait_for_start=False)
+
+# Do your setup here
+
+robot.wait_start()
+```
+
+## Debug mode
+
+It is possible to run your robot in "Debug Mode".
+
+In "Debug Mode", your robot will print more information about what it is doing.
+
+```python
+from sbot import Robot
+
+robot = Robot(debug=True)
+```
+
+!!! info
+ Debug mode is very verbose. It will print a lot of information that you may not need.
+
+## Included Libraries
+
+Python comes with plenty of [built-in
+libraries](https://docs.python.org/3.9/py-modindex.html) to use. We
+install some extra ones which may be of use:
+
+- [numpy 1.25.0](https://pypi.org/project/numpy/1.25.0/)
+- [matplotlib 3.7.1](https://pypi.org/project/matplotlib/3.7.1/)
+- [pandas 2.0.3](https://pypi.org/project/pandas/2.0.3/)
+- [scipy 1.11.1](https://pypi.org/project/scipy/1.11.1/)
+
+!!! tip
+ If you would like an extra library installed, go and ask a volunteer to see if we can help.
+
+*[API]: Application Programming Interface
diff --git a/docs/api/motor-board.md b/docs/programming/motor-board.md
similarity index 90%
rename from docs/api/motor-board.md
rename to docs/programming/motor-board.md
index 8fc63a30..dd2b7c13 100644
--- a/docs/api/motor-board.md
+++ b/docs/programming/motor-board.md
@@ -1,6 +1,4 @@
----
-title: Motor Board API
----
+# Motor Board API
The kit can control multiple motors simultaneously. One Motor Board can
control up to two motors.
@@ -11,19 +9,19 @@ If there is exactly one motor board attached to your robot, it can be
accessed using the `motor_board` property of the `Robot` object.
``` python
-my_motor_board = r.motor_board
+my_motor_board = robot.motor_board
```
!!! warning
- If there is more than one motor board on your kit, you *must* use the `motor_boards` property. `r.motor_board` *will cause an error*. This is because the kit doesn't know which motor board you want to access.
+ If there is more than one motor board on your kit, you *must* use the `motor_boards` property. `robot.motor_board` *will cause an error*. This is because the kit doesn't know which motor board you want to access.
Motor boards attached to your robot can be accessed under the
`motor_boards` property of the `Robot`. The boards are indexed by their
serial number, which is written on the board.
``` python
-my_motor_board = r.motor_boards["SRO-AAD-GBH"]
-my_other_motor_board = r.motor_boards["SR08U6"]
+my_motor_board = robot.motor_boards["SRO-AAD-GBH"]
+my_other_motor_board = robot.motor_boards["SR08U6"]
```
## Controlling the Motor Board
diff --git a/docs/programming/power-board.md b/docs/programming/power-board.md
new file mode 100644
index 00000000..07c031f8
--- /dev/null
+++ b/docs/programming/power-board.md
@@ -0,0 +1,99 @@
+# Power Board API
+
+The power board can be accessed using the `power_board` property of
+the `Robot` object.
+
+```python
+my_power_board = robot.power_board
+```
+
+## Power outputs
+
+The six outputs of the power board are grouped together as `power_board.outputs`.
+
+The power board's six outputs can be turned on and off using the
+`power_on` and `power_off` functions of the group respectively.
+
+!!! tip
+ `power_on` is called when you set up your robot, so
+ this doesn't need to be called manually. The ports will come on
+ automatically as soon as your robot is ready, before the start button is
+ pressed.
+
+```python
+robot.power_board.outputs.power_off()
+robot.power_board.outputs.power_on()
+```
+
+You can also get information about and control each output in the group.
+An output is indexed using the appropriate `PowerOutputPosition`.
+
+```python
+from sbot import PowerOutputPosition
+
+robot.power_board.outputs[PowerOutputPosition.H0].is_enabled = True
+robot.power_board.outputs[PowerOutputPosition.L3].is_enabled = False
+
+boolean_value = robot.power_board.outputs[PowerOutputPosition.L2].is_enabled
+
+current_amps = robot.power_board.outputs[PowerOutputPosition.H1].current
+```
+
+!!! warning
+ The motor and servo boards are powered through these
+ power outputs, whilst the power is off, you won't be able to control
+ your motors or servos. They will register as a missing board and your code will
+ break if you try and control them.
+
+## Battery Sensor
+
+The power board has some sensors that can monitor the status of your battery.
+This can be useful for checking the charge status of your battery.
+
+```python
+battery_voltage = robot.power_board.battery_sensor.voltage
+battery_current_amps = robot.power_board.battery_sensor.current
+```
+
+## Buzzing 🐝
+
+The power board has a piezo sounder which can buzz.
+
+The `buzz` function accepts two parameters. The first argument is the duration of the beep, in seconds.
+The second argument is either the note you want to play, or the frequency of the buzzer (in Hertz).
+
+Theoretically, the piezo buzzer will buzz at any provided frequency,
+however humans can only hear between [20Hz and
+20000Hz](https://en.wikipedia.org/wiki/Hearing_range#Humans).
+
+The `Note` enum provides notes in [scientific pitch notation](https://en.wikipedia.org/wiki/Scientific_pitch_notation) between
+`C6` and `C8`. You can play other tones by providing a frequency.
+
+!!! tip
+ Calling `buzz` is non-blocking, which means it doesn't
+ actually wait for the piezo to stop buzzing before continuing with your
+ code. If you want to wait for the buzzing to stop, add a
+ `sleep` afterwards! If you send more than 32 beeps to the robot too
+ quickly, your power board will crash!
+
+```python
+from sbot import Note
+
+# Buzz for half a second in D6.
+robot.power_board.piezo.buzz(0.5, Note.D6)
+
+# Buzz for 2 seconds at 400Hz
+robot.power_board.piezo.buzz(2, 400)
+```
+
+## Start Button
+
+You can manually wait for the start button to be pressed, not only at
+the start.
+
+```python
+robot.wait_start()
+```
+
+This may be useful for debugging, but be sure to remove it in the
+competition, as you won't be allowed to touch the start button after a match has begun!
diff --git a/docs/programming/servo-board.md b/docs/programming/servo-board.md
new file mode 100644
index 00000000..17e0337f
--- /dev/null
+++ b/docs/programming/servo-board.md
@@ -0,0 +1,56 @@
+# Servo Board API
+
+The kit can control multiple servos simultaneously. One Servo Board can
+control up to twelve servos.
+
+## Accessing the Servo Board
+
+The servo board can be accessed using the `servo_board` property of
+the `Robot` object.
+
+```python
+my_servo_board = robot.servo_board
+```
+
+This board object has an array containing the servos connected to it,
+which can be accessed as `servos[0]`, `servos[1]`, `servos[2]`, etc.
+The servo board is labelled so you know which servo is which.
+
+!!! tip
+ Remember that lists start counting at 0.
+
+## Setting servo positions
+
+The position of servos can range from `-1` to `1` inclusive:
+
+```python
+# set servo 1's position to 0.2
+robot.servo_board.servos[1].position = 0.2
+
+# Set servo 2's position to -0.55
+robot.servo_board.servos[2].position = -0.55
+```
+
+You can read the last value a servo was set to using similar code:
+
+```python
+last_position = robot.servo_board.servos[11].position
+```
+
+!!! warning
+ While it is possible to retrieve the last position a servo was set to, this does not guarantee that the servo is currently in that position.
+
+## How the set position relates to the servo angle
+
+!!! danger
+ You should be careful about forcing a servo to drive past its end stops. Some servos are very strong and it could damage the internal gears.
+
+The angle of an RC servo is controlled by the width of a pulse supplied
+to it periodically. There is no standard for the width of this pulse and
+there are differences between manufacturers as to what angle the servo
+will turn to for a given pulse width. To be able to handle the widest
+range of all servos our hardware outputs a very wide range of pulse
+widths which in some cases will force the servo to try and turn past its
+internal end-stops. You should experiment and find what the actual limit
+of your servos are (it almost certainly won't be -1 and 1) and not
+drive them past that.
diff --git a/docs/programming/vision/index.md b/docs/programming/vision/index.md
new file mode 100644
index 00000000..f1c057e1
--- /dev/null
+++ b/docs/programming/vision/index.md
@@ -0,0 +1,106 @@
+# Vision
+
+
+
+
+Your robot is able to use a webcam to detect [Fiducial Markers](https://en.wikipedia.org/wiki/Fiducial_marker).
+Specifically it will detect [AprilTags](https://april.eecs.umich.edu/software/apriltag), using the `36H11` marker set.
+
+Using [Pose Estimation](https://en.wikipedia.org/wiki/3D_pose_estimation), it can calculate the orientation and position of
+the marker relative to the webcam. Using this data, it is possible to determine the location of your robot and other objects around it.
+
+## Searching for markers
+
+Assuming you have a webcam connected, you can use `robot.camera.see()` to take a picture. The software will process the picture
+and returns a list of the markers it sees.
+
+```python
+markers = robot.camera.see()
+```
+
+!!! tip
+ Your camera will be able to process images better if they are not blurred.
+
+## Saving camera output
+
+You can also save a snapshot of what your webcam is currently seeing. This can be useful to debug your code.
+Every marker that your robot can see will have a square annotated around it, with a red dot indicating the bottom right
+corner of the marker. The ID of every marker is also written next to it.
+
+Snapshots are saved to your USB drive, and can be viewed on another computer.
+
+```python
+robot.camera.save("snapshot.jpg")
+```
+
+{ width="50%" }
+
+## Markers
+
+The marker objects in the list expose data that may be useful to your robot.
+
+### Marker ID
+
+Every marker has a numeric identifier that can be used to determine what object it represents.
+
+```python
+markers = robot.camera.see()
+
+for m in markers:
+ print(m.id)
+```
+
+### Position
+
+Each marker has a position in 3D space, relative to your webcam.
+
+You can access the position using `m.distance`, `m.cartesian` and `m.spherical`.
+
+```python
+markers = robot.camera.see()
+
+for m in markers:
+ print(m.distance) # Distance to the marker from the webcam, in metres
+ print(m.spherical.rot_y) # Bearing to the marker from the webcam, in radians
+```
+
+!!! note
+ `m.distance` is equivalent to `m.cartesian.z` or `m.spherical.dist`.
+
+For more information on position, including how to use `m.cartesian`, `m.spherical`, and the coordinate systems,
+see [Position](./position.md).
+
+It is also possible to look at the [Orientation](./orientation.md) of the marker.
+
+!!! tip
+ You can use the [`math.degrees`](https://docs.python.org/3/library/math.html#math.degrees) function to convert from radians to degrees.
+
+### Size
+
+Markers can come in different sizes.
+You can access the size of a marker using `m.size`.
+Check the rules to find out how big the different marker types are.
+
+```python
+markers = robot.camera.see()
+
+for m in markers:
+ print(m.size)
+```
+
+### Pixel Positions
+
+The positions of various points on the marker within the image are exposed over the API. This is useful
+if you would like to perform your own Computer Vision calculations.
+
+The corners are specified in clockwise order, starting from the top left corner of the
+marker. Pixels are counted from the origin of the image, which
+conventionally is in the top left corner of the image.
+
+```python
+markers = robot.camera.see()
+
+for m in markers:
+ print(m.pixel_corners) # Pixel positions of the marker corners within the image.
+ print(m.pixel_centre) # Pixel positions of the centre of the marker within the image.
+```
diff --git a/docs/programming/vision/orientation.md b/docs/programming/vision/orientation.md
new file mode 100644
index 00000000..8fb47627
--- /dev/null
+++ b/docs/programming/vision/orientation.md
@@ -0,0 +1,47 @@
+# Orientation
+
+
+
+Orientation represents the rotation of a marker around the x, y, and z axes. These can be accessed as follows:
+
+* `rot_x` / `pitch` - the angle of rotation in radians counter-clockwise about the Cartesian x axis.
+* `rot_y` / `yaw` - the angle of rotation in radians counter-clockwise about the Cartesian y axis.
+* `rot_z` / `roll` - the angle of rotation in radians counter-clockwise about the Cartesian z axis.
+
+Rotations are applied in order of z, y, x.
+
+```python
+markers = robot.camera.see()
+
+for marker in markers:
+ print(marker.orientation.rot_x) # Angle of rotation about x axis.
+ print(marker.orientation.rot_y) # Angle of rotation about y axis.
+ print(marker.orientation.rot_z) # Angle of rotation about z axis.
+```
+
+!!! note
+ In our use case the z axis always faces the camera, and thus will appear as a clockwise rotation
+
+## Examples
+
+The following table visually explains what positive and negative rotations represent.
+
+!!! example
+ 0 in all axes:
+
+ ![m0x0y0z]{ width="35%" }
+
+| | π/4 | -π/4 |
+|---:|:---:|:---:|
+| **`rot_x`** | ![m45x0y0z] | ![m-45x0y0z] |
+| **`rot_y`** | ![m0x45y0z] | ![m0x-45y0z] |
+| **`rot_z`** | ![m0x0y45z] | ![m0x0y-45z] |
+
+[m0x0y0z]: ../../assets/img/api/vision/m0x0y0z.png
+[m-45x0y0z]: ../../assets/img/api/vision/m-45x0y0z.png
+[m0x-45y0z]: ../../assets/img/api/vision/m0x-45y0z.png
+[m0x0y-45z]: ../../assets/img/api/vision/m0x0y-45z.png
+[m0x0y0z]: ../../assets/img/api/vision/m0x0y0z.png
+[m0x0y45z]: ../../assets/img/api/vision/m0x0y45z.png
+[m0x45y0z]: ../../assets/img/api/vision/m0x45y0z.png
+[m45x0y0z]: ../../assets/img/api/vision/m45x0y0z.png
diff --git a/docs/programming/vision/position.md b/docs/programming/vision/position.md
new file mode 100644
index 00000000..61c7c22a
--- /dev/null
+++ b/docs/programming/vision/position.md
@@ -0,0 +1,56 @@
+# Position
+
+Your robot supports two different coordinates systems for position:
+
+* Cartesian
+* Spherical
+
+The latter is a [Polar Coordinates system](https://en.wikipedia.org/wiki/Polar_coordinate_system).
+
+## Cartesian
+
+{ width="40%" }
+
+The [cartesian coordinates system](https://en.wikipedia.org/wiki/Cartesian_coordinate_system) has three
+_principal axes_ that are perpendicular to each other.
+
+The value of each coordinate indicates the distance travelled along the axis to the point.
+
+The camera is located at the origin, where the coordinates are ``(0, 0, 0)``.
+
+```python
+markers = robot.camera.see()
+
+for m in markers:
+ print(m.cartesian.x) # Displacement from the origin in millimetres, along x axis.
+ print(m.cartesian.y) # Displacement from the origin in millimetres, along y axis.
+ print(m.cartesian.z) # Displacement from the origin in millimetres, along z axis.
+```
+
+!!! tip
+ The `y` axis decreases as you go up. This matches convention for computer vision systems.
+
+## Spherical
+
+{ width="40%" }
+
+The [spherical coordinates system](https://en.wikipedia.org/wiki/Spherical_coordinate_system) has
+three values to specify a specific point in space.
+
+* `distance` - The _radial distance_, the distance from the origin to the point, in millimetres.
+* `rot_x` - Rotation around the X-axis, in radians, corresponding to "theta" on the diagram.
+* `rot_y` - Rotation around the Y-axis, in radians, corresponding to "phi" on the diagram.
+
+The camera is located at the origin, where the coordinates are `(0, 0, 0)`.
+
+```python
+markers = robot.camera.see()
+
+for m in markers:
+ print(m.spherical.distance) # Distance from the origin in millimetres
+ print(m.spherical.rot_x) # The angle from the azimuth to the point, in radians.
+ print(m.spherical.rot_y) # The polar angle from the plane of the camera to the point, in radians.
+```
+
+!!! tip
+ You can use the [`math.degrees`](https://docs.python.org/3/library/math.html#math.degrees) function to convert from radians to degrees.
diff --git a/docs/tutorials/.pages b/docs/tutorials/.pages
new file mode 100644
index 00000000..990f2a79
--- /dev/null
+++ b/docs/tutorials/.pages
@@ -0,0 +1,7 @@
+nav:
+ - index.md
+ - python-whirlwind-tour.md
+ - python-lab.md
+ - getting-code-on-the-robot.md
+ - basic-movement.md
+ - ...
diff --git a/docs/tutorials/basic-movement.md b/docs/tutorials/basic-movement.md
index 25dbe326..fa7cfa4e 100644
--- a/docs/tutorials/basic-movement.md
+++ b/docs/tutorials/basic-movement.md
@@ -12,33 +12,33 @@ Doing this is actually very easy; the only thing you need to realise is that a p
Here's the code:
```python
-from sbot import *
+from sbot import Robot
-r = Robot()
+robot = Robot()
while True:
- r.motor_boards[0].m0.power = 0.5
- r.motor_boards[0].m1.power = 0.5
- r.sleep(3)
+ robot.motor_boards[0].m0.power = 0.5
+ robot.motor_boards[0].m1.power = 0.5
+ robot.sleep(3)
- r.motor_boards[0].m0.power = 0
- r.motor_boards[0].m1.power = 0
- r.sleep(1.4)
+ robot.motor_boards[0].m0.power = 0
+ robot.motor_boards[0].m1.power = 0
+ robot.sleep(1.4)
- r.motor_boards[0].m0.power = -0.5
- r.motor_boards[0].m1.power = -0.5
- r.sleep(1)
+ robot.motor_boards[0].m0.power = -0.5
+ robot.motor_boards[0].m1.power = -0.5
+ robot.sleep(1)
- r.motor_boards[0].m0.power = 0
- r.motor_boards[0].m1.power = 0
- r.sleep(4)
+ robot.motor_boards[0].m0.power = 0
+ robot.motor_boards[0].m1.power = 0
+ robot.sleep(4)
```
-You're familiar with the first few lines; in fact, the only lines you may not be familiar with are the `r.motor_boards...` lines. For a comprehensive reference to the `motor` object, see [`motor` API](../api/motor-board.md) page.
+You're familiar with the first few lines; in fact, the only lines you may not be familiar with are the `robot.motor_boards...` lines. For a comprehensive reference to the `motor` object, see [`motor` API](../programming/motor-board.md) page.
But, to summarise:
-`r.motor_boards.m0.power = 0.5` sets the target power of the motor connected to output 0 on the first [motor board](../kit/motor-board.md) to half speed forwards (i.e. a duty-cycle of 0.5 forwards). As you would expect, then, `-0.5` will put the this motor into reverse at half power.
-`r.motor_boards.m0.power = 0` will brake the motor and stop it.
+`robot.motor_boards.m0.power = 0.5` sets the target power of the motor connected to output 0 on the first [motor board](../kit/motor-board.md) to half speed forwards (i.e. a duty-cycle of 0.5 forwards). As you would expect, then, `-0.5` will put the this motor into reverse at half power.
+`robot.motor_boards.m0.power = 0` will brake the motor and stop it.
So, if you put the above code on your robot, you should be able to see a motor spin forwards, stop, spin backwards, stop, and then repeat...
@@ -50,62 +50,62 @@ So, if you put the above code on your robot, you should be able to see a motor s
Now we're going to modify the program to vary the speed of the motor. Our aim is to do the forwards and backwards bit (as above), but, before we loop round again, ramp the power up to 70%, then down to -70%, and then back to 0 (all in steps of 10%). Here's the code:
```python
-from sbot import *
+from sbot import Robot
-r = Robot()
+robot = Robot()
while True:
- r.motor_boards[0].m0.power = 0.5
- r.motor_boards[0].m1.power = 0.5
- r.sleep(3)
+ robot.motor_boards[0].m0.power = 0.5
+ robot.motor_boards[0].m1.power = 0.5
+ robot.sleep(3)
- r.motor_boards[0].m0.power = 0
- r.motor_boards[0].m1.power = 0
- r.sleep(1.4)
+ robot.motor_boards[0].m0.power = 0
+ robot.motor_boards[0].m1.power = 0
+ robot.sleep(1.4)
- r.motor_boards[0].m0.power = -0.5
- r.motor_boards[0].m1.power = -0.5
- r.sleep(1)
+ robot.motor_boards[0].m0.power = -0.5
+ robot.motor_boards[0].m1.power = -0.5
+ robot.sleep(1)
- r.motor_boards[0].m0.power = 0
- r.motor_boards[0].m1.power = 0
- r.sleep(4)
+ robot.motor_boards[0].m0.power = 0
+ robot.motor_boards[0].m1.power = 0
+ robot.sleep(4)
# ^^ code from before ^^
# power up to 0.7 (from 0.1)
for pwr in range(10, 80, 10):
- r.motor_boards[0].m0.power = pwr / 100.0
- r.motor_boards[0].m1.power = pwr / 100.0
- r.sleep(0.1)
+ robot.motor_boards[0].m0.power = pwr / 100.0
+ robot.motor_boards[0].m1.power = pwr / 100.0
+ robot.sleep(0.1)
# power down from 0.7 (to 0.1)
for pwr in range(70, 0, -10):
- r.motor_boards[0].m0.power = pwr / 100.0
- r.motor_boards[0].m1.power = pwr / 100.0
- r.sleep(0.1)
+ robot.motor_boards[0].m0.power = pwr / 100.0
+ robot.motor_boards[0].m1.power = pwr / 100.0
+ robot.sleep(0.1)
# set power to 0 for a second
- r.motor_boards[0].m0.power = 0
- r.motor_boards[0].m1.power = 0
- r.sleep(1)
+ robot.motor_boards[0].m0.power = 0
+ robot.motor_boards[0].m1.power = 0
+ robot.sleep(1)
# power up to -0.7 (from -0.1)
for pwr in range(-10, -80, -10):
- r.motor_boards[0].m0.power = pwr / 100.0
- r.motor_boards[0].m1.power = pwr / 100.0
- r.sleep(0.1)
+ robot.motor_boards[0].m0.power = pwr / 100.0
+ robot.motor_boards[0].m1.power = pwr / 100.0
+ robot.sleep(0.1)
# power down to -0.1 (from -0.7)
for pwr in range(-70, 0, 10):
- r.motor_boards[0].m0.power = pwr / 100.0
- r.motor_boards[0].m1.power = pwr / 100.0
- r.sleep(0.1)
+ robot.motor_boards[0].m0.power = pwr / 100.0
+ robot.motor_boards[0].m1.power = pwr / 100.0
+ robot.sleep(0.1)
# set power to 0 for a second
- r.motor_boards[0].m0.power = 0
- r.motor_boards[0].m1.power = 0
- r.sleep(1)
+ robot.motor_boards[0].m0.power = 0
+ robot.motor_boards[0].m1.power = 0
+ robot.sleep(1)
```
## Next steps
diff --git a/docs/tutorials/getting-code-on-the-robot.md b/docs/tutorials/getting-code-on-the-robot.md
new file mode 100644
index 00000000..d4340c2f
--- /dev/null
+++ b/docs/tutorials/getting-code-on-the-robot.md
@@ -0,0 +1,25 @@
+# Getting code on the robot
+
+Getting your code on to the robot is quite simple. You will need to put your code on a USB drive which will need to be formatted with either FAT32, exFAT or ext 2-4. Upon plugging in the drive or starting up, the robot will run the `robot.py` file found in the root of the drive.
+
+To re-run your program, simply remove the USB stick from the robot and plug it back in again and it will restart automatically.
+
+## Windows
+
+1. Open your code in File Explorer
+2. Select all of your code files (++ctrl+a++ to select all files)
+3. Right-click the files and click "Copy"
+4. Open your USB drive in File Explorer
+5. Right-click in the directory and click "Paste"
+
+## macOS
+
+1. Open your code in Finder
+2. Select all of your code files (++cmd+a++ to select all files)
+3. Right-click (or Control-click) the files and click "Copy"
+4. Open your USB drive in Finder
+5. Right-click in the directory and click "Paste N items"
+
+## Robot logs
+
+When your program runs on the robot, the output of `print(...)` statements and any errors which occur are written to a log file on the USB stick as `log.txt`.
diff --git a/mkdocs.yml b/mkdocs.yml
index 7f04166b..0ae5f0d8 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -42,6 +42,7 @@ extra:
# Extensions
markdown_extensions:
- attr_list
+ - abbr
- pymdownx.highlight
- pymdownx.superfences
- footnotes