Skip to content

Commit f05a6bc

Browse files
authored
Merge pull request #65 from systemetric/new-shepherd
updates for new shepherd
2 parents 482163e + ea2c70d commit f05a6bc

21 files changed

Lines changed: 423 additions & 539 deletions

robocon/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from robocon.game import (
2+
TEAM,
3+
TARGET_TYPE,
4+
MARKER,
5+
TARGET_MARKER,
6+
MARKER_TYPE,
7+
BASE_MARKER,
8+
ARENA_MARKER)
9+
10+
__all__ = (
11+
"TEAM",
12+
"TARGET_TYPE",
13+
"MARKER",
14+
"TARGET_MARKER",
15+
"MARKER_TYPE",
16+
"BASE_MARKER",
17+
"ARENA_MARKER",
18+
)

robocon/brain/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import importlib.util
2+
3+
has_rpi = importlib.util.find_spec("RPi") is not None
4+
5+
if not has_rpi:
6+
import sys
7+
import fake_rpi
8+
sys.modules["RPi"] = fake_rpi.RPi
9+
sys.modules["RPi.GPIO"] = fake_rpi.RPi.GPIO
10+
sys.modules["smbus2"] = fake_rpi.smbus
11+
12+
import sys
13+
14+
# greengiant imports for users
15+
from robocon.brain.greengiant import OUTPUT, INPUT, INPUT_ANALOG, INPUT_PULLUP, PWM_SERVO
16+
from robocon.brain.io import IO
17+
18+
__all__ = (
19+
"IO",
20+
"OUTPUT",
21+
"INPUT",
22+
"INPUT_ANALOG",
23+
"INPUT_PULLUP",
24+
"PWM_SERVO",
25+
)

robocon/brain/cytron.py

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
"""
2+
An interface to the cyctron motor board. A GPIO pin is used for each motor
3+
to give direction and has a PWM signal at 100Hz giving infomation about voltage
4+
to apply
5+
"""
6+
7+
import RPi.GPIO as GPIO
8+
from .greengiant import clamp
9+
10+
_MAX_OUTPUT_VOLTAGE = 12
11+
12+
_PWM_PIN_1 = 12
13+
_PWM_PIN_2 = 13
14+
_DIR_PIN_1 = 26
15+
_DIR_PIN_2 = 24
16+
17+
class CytronBoard:
18+
def __init__(self, max_motor_voltage):
19+
"""
20+
The interface to the CytronBoard
21+
max_motor_voltage - The motors will be scaled so that this is the maxium
22+
average voltage the Cytron will output
23+
"""
24+
if not (0 <= max_motor_voltage <= 12):
25+
raise ValueError("max_motor_voltage must satisfy 0 <= "
26+
"max_motor_voltage <= 12 but instead is "
27+
f"{max_motor_voltage}")
28+
29+
# because we care about heating effects in the motors, we have to scale by
30+
# the square of the ratio
31+
self.power_scaling_factor = (
32+
max_motor_voltage / _MAX_OUTPUT_VOLTAGE) ** 2
33+
34+
self._dir = [
35+
[GPIO.LOW, _DIR_PIN_1],
36+
[GPIO.LOW, _DIR_PIN_2],
37+
]
38+
self._pwm = [
39+
[0, GPIO.PWM(_PWM_PIN_1, 100)],
40+
[0, GPIO.PWM(_PWM_PIN_2, 100)],
41+
]
42+
43+
GPIO.setmode(GPIO.BCM)
44+
GPIO.setup(_DIR_PIN_1, GPIO.OUT)
45+
GPIO.setup(_DIR_PIN_2, GPIO.OUT)
46+
GPIO.setup(_PWM_PIN_1, GPIO.OUT)
47+
GPIO.setup(_PWM_PIN_2, GPIO.OUT)
48+
49+
def __getitem__(self, index):
50+
"""Returns current motor PWM value as a percentage"""
51+
if index not in (0, 1):
52+
raise IndexError(
53+
f"Motor index must be in (0,1) but instead got {index}")
54+
55+
return self._pwm[index][0]
56+
57+
def __setitem__(self, index, percent):
58+
"""Set current motor PWM percentage value"""
59+
if index not in (0, 1):
60+
raise IndexError(
61+
f"Motor index must be in (0,1) but instead got {index}")
62+
63+
if percent < 0:
64+
self._dir[index][0] = GPIO.LOW
65+
else:
66+
self._dir[index][0] = GPIO.HIGH
67+
68+
GPIO.output(self._dir[index][1], self._dir[index][0])
69+
70+
percent = clamp(percent, -100, 100)
71+
self._pwm[index][0] = percent
72+
73+
percent = abs(percent) * self.power_scaling_factor
74+
self._pwm[index][1].start(percent)
75+
76+
def stop(self):
77+
"""Turns motors off"""
78+
for i in range(len(self._pwm)):
79+
self._pwm[i][0] = 0
80+
self._pwm[i][1].start(0)
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,14 @@ def set_5v_acc_power(self, new_state):
234234
self._bus.write_byte_data(_GG_I2C_ADDR, _GG_ENABLE_5V_ACC, int(new_state))
235235
else:
236236
# for GG versions 5v power is always enabled
237-
raise IOError(f"Attempted to set 5v power to {new_state} on an unsupported BrainBox.")
237+
print(f"WARN: Attempted to set 5v power to {new_state} on an unsupported BrainBox.")
238238

239239
def get_5v_acc_power(self):
240240
if self._version >= 10:
241241
return bool(self._bus.read_byte_data(_GG_I2C_ADDR, _GG_ENABLE_5V_ACC))
242242
else:
243243
# for GG versions 5v power is always enabled
244-
raise IOError(f"Attempted to get 5v power on an unsupported BrainBox.")
245-
244+
print(f"WARN: Attempted to set 5v power to {new_state} on an unsupported BrainBox.")
246245

247246
def set_user_led(self, on):
248247
self._bus.write_byte_data(_GG_I2C_ADDR, _GG_USER_LED, int(on))

robocon/brain/io.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import sys
2+
import logging
3+
from smbus2 import SMBus
4+
5+
from robocon.brain.cytron import CytronBoard
6+
from robocon.brain.greengiant import (
7+
GreenGiantInternal,
8+
GreenGiantGPIOPinList,
9+
GreenGiantMotors,
10+
_GG_SERVO_PWM_BASE,
11+
_GG_GPIO_PWM_BASE,
12+
_GG_GPIO_GPIO_BASE,
13+
_GG_SERVO_GPIO_BASE)
14+
15+
_logger = logging.getLogger("robot_io")
16+
17+
def setup_logging(level):
18+
"""Display the just the message when logging events
19+
Sets the logging level to `level`"""
20+
_logger.setLevel(level)
21+
22+
handler = logging.StreamHandler(sys.stdout)
23+
handler.setLevel(level)
24+
25+
fmt = logging.Formatter("%(message)s")
26+
handler.setFormatter(fmt)
27+
28+
_logger.addHandler(handler)
29+
30+
class IO():
31+
_initialised = False
32+
33+
def __init__(self, max_motor_voltage=6, enable_12v=True, enable_5v=True, log_level=logging.INFO):
34+
self._max_motor_voltage = max_motor_voltage
35+
36+
self._initialised = False
37+
self._warnings = []
38+
39+
setup_logging(log_level)
40+
41+
if type(self)._initialised:
42+
raise RuntimeError("IO object acquires hardware locks for its"
43+
" sole use and so can only be used once.")
44+
45+
self.bus = SMBus(1)
46+
self._green_giant = GreenGiantInternal(self.bus)
47+
self._gg_version = self._green_giant.get_version()
48+
if self._gg_version >= 10:
49+
# enable power rails
50+
self._green_giant.set_motor_power(True)
51+
self.enable_12v = enable_12v
52+
self.enable_5v = enable_5v
53+
self._adc_max = 5
54+
# configure User IO Ports
55+
self.servos = GreenGiantGPIOPinList(self.bus, self._gg_version, self._adc_max, _GG_SERVO_GPIO_BASE, _GG_SERVO_PWM_BASE)
56+
self.gpio = GreenGiantGPIOPinList(self.bus, self._gg_version, self._adc_max, _GG_GPIO_GPIO_BASE, _GG_GPIO_PWM_BASE)
57+
# configure motor drivers
58+
self.motors = GreenGiantMotors(self.bus, self._max_motor_voltage)
59+
## thinks, perhaps this should be inherrent to using the motors and
60+
## open load detection can be in there?
61+
self.motors.enable_motors(True)
62+
else:
63+
# power rails
64+
self._green_giant.set_motor_power(True)
65+
self._adc_max = self._green_giant.get_fvr_reading()
66+
# user IO
67+
self.servos = GreenGiantGPIOPinList(self.bus, self._gg_version, None, None, _GG_SERVO_PWM_BASE)
68+
self.gpio = GreenGiantGPIOPinList(self.bus, self._gg_version, self._adc_max, _GG_GPIO_GPIO_BASE , None)
69+
# configure motor drivers
70+
self.motors = CytronBoard(self._max_motor_voltage)
71+
72+
type(self)._initialised = True
73+
74+
@property
75+
def enable_motors(self):
76+
"""Return if motors are currently enabled
77+
78+
For the GG board this will be the state of the 12v line, which we cannot query,
79+
so return what it was set to.
80+
81+
For the PiLow series the Motors have both a power control and a enable. Generally
82+
the Power should not be switched on and off, just the enable bits. The power may
83+
be tripped in extreme circumstances. I guess that here we want to report any
84+
reason for the motors not working, which includes power and enable
85+
86+
"""
87+
if self._gg_version < 10:
88+
return self._green_giant.enable_12v
89+
else:
90+
return self._green_giant.get_motorpwr() and self._green_giant.get_enable()
91+
92+
@enable_motors.setter
93+
def enable_motors(self, on):
94+
"""An nice alias for set_12v"""
95+
if self._version < 10:
96+
return self._green_giant.enable_motors(on)
97+
98+
@property
99+
def enable_12v(self):
100+
return self._green_giant.get_12v_acc_power()
101+
102+
@enable_12v.setter
103+
def enable_12v(self, on):
104+
self._green_giant.set_12v_acc_power(on)
105+
106+
@property
107+
def enable_5v(self):
108+
return self._green_giant.get_5v_acc_power()
109+
110+
@enable_5v.setter
111+
def enable_5v(self, on):
112+
self._green_giant.set_5v_acc_power(on)
113+
114+
def stop(self):
115+
"""Stops the robot and cuts power to the motors.
116+
117+
does not touch the servos position.
118+
"""
119+
self.enable_12v = False
120+
self.motors.stop()
121+
122+
def set_user_led(self, val=True):
123+
self._green_giant.set_user_led(val)
124+
125+
def __del__(self):
126+
"""Frees hardware resources held by the vision object"""
127+
logging.warning("Destroying robot object")
128+
type(self)._initialised = False
129+

robot/reset.py renamed to robocon/brain/reset.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,17 @@
1010
https://stackoverflow.com/a/45799209/5006710
1111
"""
1212
from smbus2 import SMBus
13-
import robot.cytron as c
14-
import robot.greengiant as gg
13+
import .cytron as c
14+
import .greengiant as gg
1515

1616

1717
def reset():
1818
"""Resets the robot components to their default state.
1919
Used by Shepherd when the Stop button is pressed.
2020
"""
2121
bus = SMBus(1)
22-
version = gg.GreenGiantInternal(bus).get_version()
22+
internal = gg.GreenGiantInternal(bus)
23+
version = internal.get_version()
2324

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

3334
# probably should wrap this all up in a .off()
34-
internal = gg.GreenGiantInternal(bus)
3535
internal.enable_motors(False)
36-
#internal.set_motor_power(False)
37-
internal.set_12v_acc_power(False) # Not sure, should this be controlled by user?
36+
internal.set_12v_acc_power(False)
3837
internal.set_5v_acc_power(False)
3938
internal.set_user_led(False)
4039

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,8 @@
1717
BLUE = (255, 0, 0) # Blue
1818
WHITE = (255, 255, 255) # White
1919

20-
SECTOR = TEAM # 2026 ONLY, ALIAS `TEAM` AS `SECTOR`
21-
2220
__all__ = (
2321
"TEAM",
24-
"SECTOR",
2522
"TARGET_TYPE",
2623
"MARKER",
2724
"TARGET_MARKER",

0 commit comments

Comments
 (0)