Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ca104c5
use newer `importlib.util.find_spec` function (other was removed)
OldUser101 Feb 7, 2026
dc4d620
use relative imports
OldUser101 Feb 7, 2026
c6811a1
add dependency packages to `setup.py`
OldUser101 Feb 7, 2026
40319ca
fix minor syntax issue
OldUser101 Feb 7, 2026
322f988
fix `opencv` dependency name and version
OldUser101 Feb 7, 2026
c0ea79b
fix yet another typo
OldUser101 Feb 7, 2026
270934f
remove `wiringpi` from dependency list
OldUser101 Feb 18, 2026
8a4086b
refactor(cytron): replace `wiringpi` with `RPi.GPIO`
OldUser101 Feb 28, 2026
e841d49
fix relative imports
OldUser101 Feb 28, 2026
b977693
Revert "fix relative imports"
OldUser101 Mar 12, 2026
9cfcbbf
remove weird `game_config` import in wrapper
OldUser101 Mar 12, 2026
61a6124
export `game_config` submodule properly?
OldUser101 Mar 14, 2026
fe140a5
fix relative imports (yet again)
OldUser101 Mar 14, 2026
d24f249
force numpy < 2.0.0 for opencv
OldUser101 Mar 14, 2026
e0bf369
add mock picamera2 module
OldUser101 Mar 14, 2026
f73abd7
feat(wrapper): use new hopper modules for start fifo
OldUser101 Mar 21, 2026
5eeb855
declare `hopper` dependency, add `pyproject.toml` file, fix cytron issue
OldUser101 Mar 21, 2026
ec18504
fix: change various things for testing on non-brain hardware
OldUser101 Mar 21, 2026
bdb4cc1
feat: write b64 encoded images to hopper image pipe
OldUser101 Apr 25, 2026
eb843c0
fix(vision): pass `image_pipe` to `PostProcessor`
OldUser101 Apr 26, 2026
6ec5584
fix: actually open hopper pipes before using
OldUser101 Apr 26, 2026
db908e6
fix: open image pipe in input mode
OldUser101 Apr 26, 2026
b6a2346
fix(vision): use JPEG images, not PNG
OldUser101 Apr 29, 2026
3723073
fix(vision): use `logging.ERROR` instead of `ERROR`
OldUser101 Apr 29, 2026
ea2c70d
module restructuring
OldUser101 May 6, 2026
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
18 changes: 18 additions & 0 deletions robocon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from robocon.game import (
TEAM,
TARGET_TYPE,
MARKER,
TARGET_MARKER,
MARKER_TYPE,
BASE_MARKER,
ARENA_MARKER)

__all__ = (
"TEAM",
"TARGET_TYPE",
"MARKER",
"TARGET_MARKER",
"MARKER_TYPE",
"BASE_MARKER",
"ARENA_MARKER",
)
25 changes: 25 additions & 0 deletions robocon/brain/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import importlib.util

has_rpi = importlib.util.find_spec("RPi") is not None

if not has_rpi:
import sys
import fake_rpi
sys.modules["RPi"] = fake_rpi.RPi
sys.modules["RPi.GPIO"] = fake_rpi.RPi.GPIO
sys.modules["smbus2"] = fake_rpi.smbus

import sys

# greengiant imports for users
from robocon.brain.greengiant import OUTPUT, INPUT, INPUT_ANALOG, INPUT_PULLUP, PWM_SERVO
from robocon.brain.io import IO

__all__ = (
"IO",
"OUTPUT",
"INPUT",
"INPUT_ANALOG",
"INPUT_PULLUP",
"PWM_SERVO",
)
80 changes: 80 additions & 0 deletions robocon/brain/cytron.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
An interface to the cyctron motor board. A GPIO pin is used for each motor
to give direction and has a PWM signal at 100Hz giving infomation about voltage
to apply
"""

import RPi.GPIO as GPIO
from .greengiant import clamp

_MAX_OUTPUT_VOLTAGE = 12

_PWM_PIN_1 = 12
_PWM_PIN_2 = 13
_DIR_PIN_1 = 26
_DIR_PIN_2 = 24

class CytronBoard:
def __init__(self, max_motor_voltage):
"""
The interface to the CytronBoard
max_motor_voltage - The motors will be scaled so that this is the maxium
average voltage the Cytron will output
"""
if not (0 <= max_motor_voltage <= 12):
raise ValueError("max_motor_voltage must satisfy 0 <= "
"max_motor_voltage <= 12 but instead is "
f"{max_motor_voltage}")

# because we care about heating effects in the motors, we have to scale by
# the square of the ratio
self.power_scaling_factor = (
max_motor_voltage / _MAX_OUTPUT_VOLTAGE) ** 2

self._dir = [
[GPIO.LOW, _DIR_PIN_1],
[GPIO.LOW, _DIR_PIN_2],
]
self._pwm = [
[0, GPIO.PWM(_PWM_PIN_1, 100)],
[0, GPIO.PWM(_PWM_PIN_2, 100)],
]

GPIO.setmode(GPIO.BCM)
GPIO.setup(_DIR_PIN_1, GPIO.OUT)
GPIO.setup(_DIR_PIN_2, GPIO.OUT)
GPIO.setup(_PWM_PIN_1, GPIO.OUT)
GPIO.setup(_PWM_PIN_2, GPIO.OUT)

def __getitem__(self, index):
"""Returns current motor PWM value as a percentage"""
if index not in (0, 1):
raise IndexError(
f"Motor index must be in (0,1) but instead got {index}")

return self._pwm[index][0]

def __setitem__(self, index, percent):
"""Set current motor PWM percentage value"""
if index not in (0, 1):
raise IndexError(
f"Motor index must be in (0,1) but instead got {index}")

if percent < 0:
self._dir[index][0] = GPIO.LOW
else:
self._dir[index][0] = GPIO.HIGH

GPIO.output(self._dir[index][1], self._dir[index][0])

percent = clamp(percent, -100, 100)
self._pwm[index][0] = percent

percent = abs(percent) * self.power_scaling_factor
self._pwm[index][1].start(percent)

def stop(self):
"""Turns motors off"""
for i in range(len(self._pwm)):
self._pwm[i][0] = 0
self._pwm[i][1].start(0)
5 changes: 2 additions & 3 deletions robot/greengiant.py → robocon/brain/greengiant.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,15 +234,14 @@ def set_5v_acc_power(self, new_state):
self._bus.write_byte_data(_GG_I2C_ADDR, _GG_ENABLE_5V_ACC, int(new_state))
else:
# for GG versions 5v power is always enabled
raise IOError(f"Attempted to set 5v power to {new_state} on an unsupported BrainBox.")
print(f"WARN: Attempted to set 5v power to {new_state} on an unsupported BrainBox.")

def get_5v_acc_power(self):
if self._version >= 10:
return bool(self._bus.read_byte_data(_GG_I2C_ADDR, _GG_ENABLE_5V_ACC))
else:
# for GG versions 5v power is always enabled
raise IOError(f"Attempted to get 5v power on an unsupported BrainBox.")

print(f"WARN: Attempted to set 5v power to {new_state} on an unsupported BrainBox.")

def set_user_led(self, on):
self._bus.write_byte_data(_GG_I2C_ADDR, _GG_USER_LED, int(on))
Expand Down
129 changes: 129 additions & 0 deletions robocon/brain/io.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import sys
import logging
from smbus2 import SMBus

from robocon.brain.cytron import CytronBoard
from robocon.brain.greengiant import (
GreenGiantInternal,
GreenGiantGPIOPinList,
GreenGiantMotors,
_GG_SERVO_PWM_BASE,
_GG_GPIO_PWM_BASE,
_GG_GPIO_GPIO_BASE,
_GG_SERVO_GPIO_BASE)

_logger = logging.getLogger("robot_io")

def setup_logging(level):
"""Display the just the message when logging events
Sets the logging level to `level`"""
_logger.setLevel(level)

handler = logging.StreamHandler(sys.stdout)
handler.setLevel(level)

fmt = logging.Formatter("%(message)s")
handler.setFormatter(fmt)

_logger.addHandler(handler)

class IO():
_initialised = False

def __init__(self, max_motor_voltage=6, enable_12v=True, enable_5v=True, log_level=logging.INFO):
self._max_motor_voltage = max_motor_voltage

self._initialised = False
self._warnings = []

setup_logging(log_level)

if type(self)._initialised:
raise RuntimeError("IO object acquires hardware locks for its"
" sole use and so can only be used once.")

self.bus = SMBus(1)
self._green_giant = GreenGiantInternal(self.bus)
self._gg_version = self._green_giant.get_version()
if self._gg_version >= 10:
# enable power rails
self._green_giant.set_motor_power(True)
self.enable_12v = enable_12v
self.enable_5v = enable_5v
self._adc_max = 5
# configure User IO Ports
self.servos = GreenGiantGPIOPinList(self.bus, self._gg_version, self._adc_max, _GG_SERVO_GPIO_BASE, _GG_SERVO_PWM_BASE)
self.gpio = GreenGiantGPIOPinList(self.bus, self._gg_version, self._adc_max, _GG_GPIO_GPIO_BASE, _GG_GPIO_PWM_BASE)
# configure motor drivers
self.motors = GreenGiantMotors(self.bus, self._max_motor_voltage)
## thinks, perhaps this should be inherrent to using the motors and
## open load detection can be in there?
self.motors.enable_motors(True)
else:
# power rails
self._green_giant.set_motor_power(True)
self._adc_max = self._green_giant.get_fvr_reading()
# user IO
self.servos = GreenGiantGPIOPinList(self.bus, self._gg_version, None, None, _GG_SERVO_PWM_BASE)
self.gpio = GreenGiantGPIOPinList(self.bus, self._gg_version, self._adc_max, _GG_GPIO_GPIO_BASE , None)
# configure motor drivers
self.motors = CytronBoard(self._max_motor_voltage)

type(self)._initialised = True

@property
def enable_motors(self):
"""Return if motors are currently enabled

For the GG board this will be the state of the 12v line, which we cannot query,
so return what it was set to.

For the PiLow series the Motors have both a power control and a enable. Generally
the Power should not be switched on and off, just the enable bits. The power may
be tripped in extreme circumstances. I guess that here we want to report any
reason for the motors not working, which includes power and enable

"""
if self._gg_version < 10:
return self._green_giant.enable_12v
else:
return self._green_giant.get_motorpwr() and self._green_giant.get_enable()

@enable_motors.setter
def enable_motors(self, on):
"""An nice alias for set_12v"""
if self._version < 10:
return self._green_giant.enable_motors(on)

@property
def enable_12v(self):
return self._green_giant.get_12v_acc_power()

@enable_12v.setter
def enable_12v(self, on):
self._green_giant.set_12v_acc_power(on)

@property
def enable_5v(self):
return self._green_giant.get_5v_acc_power()

@enable_5v.setter
def enable_5v(self, on):
self._green_giant.set_5v_acc_power(on)

def stop(self):
"""Stops the robot and cuts power to the motors.

does not touch the servos position.
"""
self.enable_12v = False
self.motors.stop()

def set_user_led(self, val=True):
self._green_giant.set_user_led(val)

def __del__(self):
"""Frees hardware resources held by the vision object"""
logging.warning("Destroying robot object")
type(self)._initialised = False

11 changes: 5 additions & 6 deletions robot/reset.py → robocon/brain/reset.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@
https://stackoverflow.com/a/45799209/5006710
"""
from smbus2 import SMBus
import robot.cytron as c
import robot.greengiant as gg
import .cytron as c
import .greengiant as gg


def reset():
"""Resets the robot components to their default state.
Used by Shepherd when the Stop button is pressed.
"""
bus = SMBus(1)
version = gg.GreenGiantInternal(bus).get_version()
internal = gg.GreenGiantInternal(bus)
version = internal.get_version()

if version < 10:
c.CytronBoard(1).stop()
Expand All @@ -31,10 +32,8 @@ def reset():
gg.GreenGiantGPIOPinList(bus, version, 5, gg._GG_GPIO_GPIO_BASE, gg._GG_GPIO_PWM_BASE).off()

# probably should wrap this all up in a .off()
internal = gg.GreenGiantInternal(bus)
internal.enable_motors(False)
#internal.set_motor_power(False)
internal.set_12v_acc_power(False) # Not sure, should this be controlled by user?
internal.set_12v_acc_power(False)
internal.set_5v_acc_power(False)
internal.set_user_led(False)

Expand Down
3 changes: 0 additions & 3 deletions robot/game_config/__init__.py → robocon/game/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,8 @@
BLUE = (255, 0, 0) # Blue
WHITE = (255, 255, 255) # White

SECTOR = TEAM # 2026 ONLY, ALIAS `TEAM` AS `SECTOR`

__all__ = (
"TEAM",
"SECTOR",
"TARGET_TYPE",
"MARKER",
"TARGET_MARKER",
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
25 changes: 25 additions & 0 deletions robocon/vision/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import importlib.util

has_picamera2 = importlib.util.find_spec("picamera2") is not None

if not has_picamera2:
import sys
import robocon.vision.mock_picamera2 as mock_picamera2
sys.modules["picamera2"] = mock_picamera2

import sys

from robocon.vision.camera import Camera, NoCameraPresent
from robocon.vision.vision import RoboConUSBCamera

MINIUM_VERSION = (3, 6)
if sys.version_info <= MINIUM_VERSION:
raise ImportError(
"Expected python {} but instead got {}".format(MINIUM_VERSION, sys.version_info)
)

__all__ = (
"Camera",
"NoCameraPresent",
"RoboConUSBCamera",
)
3 changes: 1 addition & 2 deletions robot/apriltags3.py → robocon/vision/apriltags3.py
100755 → 100644
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#!/usr/bin/env python
"""Python wrapper for C version of apriltags. This program creates two
classes that are used to detect apriltags and extract information from
them. Using this module, you can identify all apriltags visible in an
Expand All @@ -21,7 +20,7 @@
import numpy as np
import scipy.spatial.transform as transform

from robot.game_config import MARKER, MARKER_TYPE
from robocon.game import MARKER, MARKER_TYPE


######################################################################
Expand Down
Loading
Loading