Skip to content

Commit 0d33ad7

Browse files
committed
Added CLUE Robot Code from CircuitPython Day Build
1 parent f1c0dc2 commit 0d33ad7

File tree

2 files changed

+311
-0
lines changed

2 files changed

+311
-0
lines changed

CircuitPython_CLUEbot/code.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import board
2+
import neopixel
3+
from robot import Robot
4+
5+
# Ring:bit Pins
6+
UNDERLIGHT_PIXELS = board.D0
7+
LEFT_MOTOR = board.D1
8+
RIGHT_MOTOR = board.D2
9+
10+
# Set up the hardware
11+
underlight_neopixels = neopixel.NeoPixel(UNDERLIGHT_PIXELS, 2)
12+
robot = Robot(LEFT_MOTOR, RIGHT_MOTOR, underlight_neopixels)
13+
14+
while True:
15+
robot.wait_for_connection()
16+
while robot.is_connected():
17+
robot.check_for_packets()

CircuitPython_CLUEbot/robot.py

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
import time
2+
import pwmio
3+
import board
4+
import digitalio
5+
import displayio
6+
import vectorio
7+
import terminalio
8+
9+
import neopixel
10+
import adafruit_motor.servo
11+
from adafruit_ble import BLERadio
12+
from adafruit_ble.advertising.standard import ProvideServicesAdvertisement
13+
from adafruit_ble.services.nordic import UARTService
14+
from adafruit_bluefruit_connect.packet import Packet
15+
from adafruit_bluefruit_connect.color_packet import ColorPacket
16+
from adafruit_bluefruit_connect.button_packet import ButtonPacket
17+
from adafruit_display_text.label import Label
18+
19+
# Throttle Directions and Speeds
20+
FWD = 1.0
21+
REV = -1.0
22+
STOP = 0
23+
24+
# Custom Colors
25+
RED = (200, 0, 0)
26+
GREEN = (0, 200, 0)
27+
BLUE = (0, 0, 200)
28+
PURPLE = (120, 0, 160)
29+
YELLOW = (100, 100, 0)
30+
AQUA = (0, 100, 100)
31+
32+
class Robot:
33+
def __init__(self, left_pin, right_pin, underlight_neopixel):
34+
self.left_motor = self._init_motor(left_pin)
35+
self.right_motor = self._init_motor(right_pin)
36+
self._init_display()
37+
self._init_ble()
38+
self.under_pixels = underlight_neopixel
39+
self.neopixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
40+
self.direction = STOP
41+
self.release_color = None
42+
self.headlights = digitalio.DigitalInOut(board.WHITE_LEDS)
43+
self.headlights.switch_to_output()
44+
self.set_underglow(PURPLE)
45+
self.set_speed(STOP)
46+
47+
@classmethod
48+
def _init_motor(cls, pin):
49+
pwm = pwmio.PWMOut(pin, frequency=50)
50+
return adafruit_motor.servo.ContinuousServo(pwm, min_pulse=600, max_pulse=2400)
51+
52+
@classmethod
53+
def _make_palette(cls, color):
54+
palette = displayio.Palette(1)
55+
palette[0] = color
56+
return palette
57+
58+
def _init_ble(self):
59+
self.ble = BLERadio()
60+
self.uart_service = UARTService()
61+
self.advertisement = ProvideServicesAdvertisement(self.uart_service)
62+
63+
def _init_display(self):
64+
self.display = board.DISPLAY
65+
self.display_group = displayio.Group()
66+
self.display.show(self.display_group)
67+
self.shape_color = 0
68+
self.bg_color = 0xFFFF00
69+
rect = vectorio.Rectangle(
70+
pixel_shader=self._make_palette(0xFFFF00),
71+
x=0, y=0,
72+
width=self.display.width,
73+
height=self.display.height)
74+
self.display_group.append(rect)
75+
76+
def wait_for_connection(self):
77+
self.set_status_led(BLUE)
78+
self.ble.start_advertising(self.advertisement)
79+
self._set_status_waiting()
80+
while not self.ble.connected:
81+
# Wait for a connection.
82+
pass
83+
self.ble.stop_advertising()
84+
self.set_status_led(GREEN)
85+
self.set_throttle(STOP)
86+
87+
def is_connected(self):
88+
return self.ble.connected
89+
90+
def set_underglow(self, color, save_release_color = False):
91+
if save_release_color:
92+
self.release_color = self.get_underglow()
93+
for index, _ in enumerate(self.under_pixels):
94+
self.under_pixels[index] = color
95+
96+
def get_underglow(self):
97+
# Set the 2 Neopixels on the underside fo the robot
98+
return self.under_pixels[0]
99+
100+
def set_status_led(self, color):
101+
# Set the status NeoPixel on the CLUE
102+
self.neopixel[0] = color
103+
104+
def toggle_headlights(self):
105+
self.headlights.value = not self.headlights.value
106+
107+
def _set_left_throttle(self, speed):
108+
self.left_motor.throttle = speed
109+
110+
def _set_right_throttle(self, speed):
111+
# Motor is rotated 180 degrees of the left, so we invert the throttle
112+
self.right_motor.throttle = -1 * speed
113+
114+
def rotate_right(self):
115+
self.release_color = self.get_underglow()
116+
self.set_underglow(YELLOW, True)
117+
if self.direction == STOP:
118+
self._set_status_rotate_cw()
119+
else:
120+
self._set_status_right()
121+
speed = FWD if self.direction == STOP else self.direction
122+
self._set_left_throttle(speed)
123+
self._set_right_throttle(STOP if self.direction != STOP else -1 * speed)
124+
125+
def rotate_left(self):
126+
self.release_color = self.get_underglow()
127+
self.set_underglow(YELLOW, True)
128+
if self.direction == STOP:
129+
self._set_status_rotate_ccw()
130+
else:
131+
self._set_status_left()
132+
speed = FWD if self.direction == STOP else self.direction
133+
self._set_left_throttle(STOP if self.direction != STOP else -1 * speed)
134+
self._set_right_throttle(speed)
135+
136+
def set_throttle(self, speed):
137+
if speed == STOP:
138+
self._set_status_stop()
139+
elif speed > STOP:
140+
self._set_status_forward()
141+
elif speed < STOP:
142+
self._set_status_reverse()
143+
self.set_speed(speed)
144+
145+
def set_speed(self, speed):
146+
self._set_left_throttle(speed)
147+
self._set_right_throttle(speed)
148+
self.direction = speed
149+
150+
def stop(self):
151+
# Temporarily grab the current color
152+
color = self.get_underglow()
153+
self.set_underglow(RED)
154+
self.set_throttle(STOP)
155+
time.sleep(0.5)
156+
self.set_underglow(color)
157+
158+
def check_for_packets(self):
159+
if self.uart_service.in_waiting:
160+
self._process_packet(Packet.from_stream(self.uart_service))
161+
162+
def _handle_color_packet(self, packet):
163+
# Change the color
164+
self.set_underglow(packet.color)
165+
166+
def _remove_shapes(self):
167+
while len(self.display_group) > 1:
168+
self.display_group.pop()
169+
170+
def _add_centered_rect(self, width, height, x_offset=0, y_offset=0, color=None):
171+
if color is None:
172+
color = self.shape_color
173+
rectangle = vectorio.Rectangle(
174+
pixel_shader=self._make_palette(color),
175+
width=width,
176+
height=height,
177+
x=(self.display.width//2 - width//2) + x_offset - 1,
178+
y=(self.display.height//2 - height//2) + y_offset - 1
179+
)
180+
self.display_group.append(rectangle)
181+
182+
def _add_centered_polygon(self, points, x_offset=0, y_offset=0, color=None):
183+
if color is None:
184+
color = self.shape_color
185+
# Figure out the shape dimensions by using min and max
186+
width = max(points, key=lambda item:item[0])[0] - min(points, key=lambda item:item[0])[0]
187+
height = max(points, key=lambda item:item[1])[1] - min(points, key=lambda item:item[1])[1]
188+
polygon = vectorio.Polygon(
189+
pixel_shader=self._make_palette(color),
190+
points=points,
191+
x=(self.display.width // 2 - width // 2) + x_offset - 1,
192+
y=(self.display.height // 2 - height // 2) + y_offset - 1
193+
)
194+
self.display_group.append(polygon)
195+
196+
def _add_centered_circle(self, radius, x_offset=0, y_offset=0, color=None):
197+
if color is None:
198+
color = self.shape_color
199+
circle = vectorio.Circle(
200+
pixel_shader=self._make_palette(color),
201+
radius=radius,
202+
x=(self.display.width // 2) + x_offset - 1,
203+
y=(self.display.height // 2) + y_offset - 1
204+
)
205+
self.display_group.append(circle)
206+
207+
def _set_status_waiting(self):
208+
self._remove_shapes()
209+
text_area = Label(
210+
terminalio.FONT,
211+
text="Waiting for\nconnection",
212+
color=self.shape_color,
213+
scale=3,
214+
anchor_point=(0.5, 0.5),
215+
anchored_position=(self.display.width // 2, self.display.height // 2)
216+
)
217+
self.display_group.append(text_area)
218+
219+
def _set_status_reverse(self):
220+
self._remove_shapes()
221+
self._add_centered_polygon([(40, 0), (60, 0), (100, 100), (0, 100)], 0, 0)
222+
self._add_centered_polygon([(0, 40), (100, 40), (50, 0)], 0, -40)
223+
224+
def _set_status_forward(self):
225+
self._remove_shapes()
226+
self._add_centered_polygon([(20, 0), (60, 0), (80, 100), (0, 100)])
227+
self._add_centered_polygon([(0, 0), (150, 0), (75, 50)], 0, 50)
228+
229+
def _set_status_right(self):
230+
self._remove_shapes()
231+
self._add_centered_rect(100, 40)
232+
self._add_centered_polygon([(50, 0), (50, 100), (0, 50)], -50, 0)
233+
234+
def _set_status_rotate_ccw(self):
235+
self._remove_shapes()
236+
self._add_centered_circle(80)
237+
self._add_centered_circle(50, 0, 0, self.bg_color)
238+
self._add_centered_rect(160, 60, 0, 0, self.bg_color)
239+
self._add_centered_polygon([(40, 0), (80, 40), (0, 40)], 60, 10)
240+
self._add_centered_polygon([(40, 40), (80, 0), (0, 0)], -60, -10)
241+
242+
def _set_status_left(self):
243+
self._remove_shapes()
244+
self._add_centered_rect(100, 40)
245+
self._add_centered_polygon([(0, 0), (0, 100), (50, 50)], 50)
246+
247+
def _set_status_rotate_cw(self):
248+
self._remove_shapes()
249+
self._add_centered_circle(80)
250+
self._add_centered_circle(50, 0, 0, self.bg_color)
251+
self._add_centered_rect(160, 60, 0, 0, self.bg_color)
252+
self._add_centered_polygon([(40, 0), (80, 40), (0, 40)], -60, 10)
253+
self._add_centered_polygon([(40, 40), (80, 0), (0, 0)], 60, -10)
254+
255+
def _set_status_stop(self):
256+
self._remove_shapes()
257+
self._add_centered_rect(100, 100)
258+
259+
def _handle_button_press_packet(self, packet):
260+
if packet.button == ButtonPacket.UP: # UP button pressed
261+
self.set_throttle(FWD)
262+
elif packet.button == ButtonPacket.DOWN: # DOWN button
263+
self.set_throttle(REV)
264+
elif packet.button == ButtonPacket.RIGHT:
265+
self.rotate_right()
266+
elif packet.button == ButtonPacket.LEFT:
267+
self.rotate_left()
268+
elif packet.button == ButtonPacket.BUTTON_1:
269+
self.stop()
270+
elif packet.button == ButtonPacket.BUTTON_2:
271+
self.set_underglow(GREEN)
272+
elif packet.button == ButtonPacket.BUTTON_3:
273+
self.set_underglow(BLUE)
274+
elif packet.button == ButtonPacket.BUTTON_4:
275+
self.toggle_headlights()
276+
277+
def _handle_button_release_packet(self, packet):
278+
if self.release_color is not None:
279+
self.set_underglow(self.release_color)
280+
self.release_color = None
281+
if packet.button == ButtonPacket.RIGHT:
282+
self.set_throttle(self.direction)
283+
if packet.button == ButtonPacket.LEFT:
284+
self.set_throttle(self.direction)
285+
286+
def _process_packet(self, packet):
287+
if isinstance(packet, ColorPacket):
288+
self._handle_color_packet(packet)
289+
elif isinstance(packet, ButtonPacket) and packet.pressed:
290+
# do this when buttons are pressed
291+
self._handle_button_press_packet(packet)
292+
elif isinstance(packet, ButtonPacket) and not packet.pressed:
293+
# do this when some buttons are released
294+
self._handle_button_release_packet(packet)

0 commit comments

Comments
 (0)