flame-wro-fe 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +1196 -0
- package/arduino/outputProxy/platformio.ini +16 -0
- package/arduino/outputProxy/src/main.cpp +276 -0
- package/index.js +8 -0
- package/package.json +31 -0
- package/raspySrc/main.py +12 -0
- package/raspySrc/raspy/control_panel.py +586 -0
- package/raspySrc/raspy/decision_log.py +85 -0
- package/raspySrc/raspy/drive_state.py +160 -0
- package/raspySrc/raspy/install_vehicle_service.sh +3 -0
- package/raspySrc/raspy/lap_direction.py +24 -0
- package/raspySrc/raspy/robot_runtime.py +1052 -0
- package/raspySrc/raspy/settings.json +198 -0
- package/raspySrc/raspy/settings.py +206 -0
- package/raspySrc/raspy/vehicle-runtime.service +12 -0
- package/raspySrc/raspy/vision_pipeline.py +142 -0
- package/raspySrc/raspy/vision_types.py +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,1196 @@
|
|
|
1
|
+
<a name="top"></a>
|
|
2
|
+
|
|
3
|
+
# WRO 2026: Future Engineers - Flame
|
|
4
|
+
|
|
5
|
+
<img width="745" height="381" alt="Screenshot 2026-05-19 at 11 34 00" src="https://github.com/user-attachments/assets/dca087dd-99a9-4f7e-bca0-6525fc50a922" />
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Table of Contents
|
|
9
|
+
|
|
10
|
+
1. [Overview](#overview)
|
|
11
|
+
2. [Design Process](#Design-Process)
|
|
12
|
+
3. [Car Photos](#carphoto)
|
|
13
|
+
4. [Mobility Management](#Mobility-Management)
|
|
14
|
+
- [Chassis](#Chassis)
|
|
15
|
+
- [Assembly Instructions](#assembly-instructions)
|
|
16
|
+
- [Driving Motor and Gearing](#Driving-Motor-and-Gearing)
|
|
17
|
+
- [Steering and Differential Mechanism](#Steering-Mechanism)
|
|
18
|
+
- [Battery](#Power-supply)
|
|
19
|
+
- [Controllers](#Controllers)
|
|
20
|
+
- [Sensors](#Sensors)
|
|
21
|
+
- [Camera](#camera)
|
|
22
|
+
- [Schematics](#schematics)
|
|
23
|
+
- [Components List](#components-list)
|
|
24
|
+
5. [Software](#software)
|
|
25
|
+
- [Software Development](#software-development)
|
|
26
|
+
- [Libraries Used in Runtime Files](#libraries-used-in-runtime-files)
|
|
27
|
+
- [Programming Languages](#programming-languages)
|
|
28
|
+
- [Dependencies](#dependencies)
|
|
29
|
+
- [Repository Layout](#repository-layout)
|
|
30
|
+
- [Code Installation and Run Guide](#code-installation-and-run-guide)
|
|
31
|
+
- [Open Challenge](#open-challenge)
|
|
32
|
+
- [Obstacle Challenge](#obstacle-challenge)
|
|
33
|
+
6. [System Thinking and Engineering Decisions](#system-thinking-and-engineering-decisions)
|
|
34
|
+
7. [Utilities](#utilities)
|
|
35
|
+
- [Failsafe Mechanisms](#failsafe)
|
|
36
|
+
- [Debugging Tools](#debugging-tools)
|
|
37
|
+
- [Web Debug Interface](#web-debug-interface)
|
|
38
|
+
8. [Team Photos](#team-photos)
|
|
39
|
+
9. [Demonstration Videos](#demonstration-videos)
|
|
40
|
+
10. [Contributors](#contributors)
|
|
41
|
+
11. [Resources](#sources)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
<a name="overview"></a>
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
## Overview
|
|
48
|
+
|
|
49
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/b698f968-4eef-43bb-9e2c-870156425090" />
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Welcome to the official GitHub repository for Flame from Türkiye. This project documents the development of our autonomous vehicle designed to compete in the 2026 World Robot Olympiad (WRO) Future Engineers competition.
|
|
53
|
+
|
|
54
|
+
<img width="1109" height="955" alt="Screenshot 2026-05-19 at 12 02 38" src="https://github.com/user-attachments/assets/5008d6fd-a72c-419f-aa6c-c75564019d77" />
|
|
55
|
+
|
|
56
|
+
Our objective was to develop a high-performance autonomous vehicle capable of navigating complex tracks, avoiding obstacles, and recognizing traffic signals using OpenCV. For the 2026 season, we focused on a dual-controller architecture combining the high-speed image processing of a Raspberry Pi 5 with the real-time reliability of an Arduino Nano R4.
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
<a name="Design-Process"></a>
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## Design Process
|
|
63
|
+
|
|
64
|
+
We moved through two major phases to reach our current build:
|
|
65
|
+
|
|
66
|
+
### Phase 1 (The LEGO Prototype):
|
|
67
|
+
|
|
68
|
+
<img width="1536" height="2048" alt="574065354-382b4c37-0c35-4aa0-9f4c-a3c25c82d3e3" src="https://github.com/user-attachments/assets/00c7d496-5d55-42d1-8d45-e026aee68f54" />
|
|
69
|
+
|
|
70
|
+
<img width="1152" height="2048" alt="574064939-b4bac3a6-0208-45d2-a44d-260d70b5efed-2" src="https://github.com/user-attachments/assets/d3316aa2-dcfa-4b1e-af8b-de6abf993b50" />
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
Initially, we built a frame using LEGO components and N20 Micro Gear Motor with an encoder. While excellent for rapid prototyping, the N20 motors lacked the torque required for consistent low-speed movement, and the LEGO frame exhibited structural flex under high-speed turns. Additionally, the placement of our micro servo to the lego chassis proved to be a challenge.
|
|
74
|
+
|
|
75
|
+
From this phase, we took note of what we need to improve:
|
|
76
|
+
|
|
77
|
+
- Soldering, instead of WAGO Connectors
|
|
78
|
+
- These WAGO connectors really save time and effort when connecting components, but takes up a lot of unnecessary space. It was also nearly impossible to keep the wiring organized with these connectors in place.
|
|
79
|
+
- Chassis flex and play at joints: under acceleration and cornering, small connection tolerances introduced steering drift.
|
|
80
|
+
- Limited packaging freedom: The fixed structure of LEGO pieces limited our flexibility, making it challenging to position the electronics and sensors with the precision we aimed for.
|
|
81
|
+
- Center-of-mass inconsistency: battery and board placement options were limited, which affected balance between runs.
|
|
82
|
+
- Mounting rigidity for non-LEGO parts: integrating the motor, servo, and custom electronics required adapters and workarounds.
|
|
83
|
+
|
|
84
|
+
### Phase 2 (The Custom 3D-Printed Build):
|
|
85
|
+
|
|
86
|
+
To solve the rigidity issues and to completely utilize the space we have, we designed a custom 3D-printed chassis. This allowed for a lower center of gravity and dedicated mounting points for the camera, steering, differential modules and sensors, significantly solving our problems. In short, Prototype 2 directly addressed the Phase 1 pain points: it improved rigidity, enabled cleaner wire/electronics routing, and gave us intentional component placement for better balance and repeatability. The robot is made of 3 parts: chassis, movement modules and electronics. The walls located in the center of the chassis houses the motor, while providing structual rigidity for the sandwich board of electronics mounted at the top. We designed the front and the back according to the measurements of our lego modules, resulting in a good placement. We adress the aspects of these in detail in the following chapters
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
<img width="2272" height="1704" alt="image" src="https://github.com/user-attachments/assets/1c8e5607-1823-41d0-942c-999a702d06f4" />
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
<a name="carphoto"></a>
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
### Car Photos
|
|
97
|
+
|
|
98
|
+
Here are our official photos of our car:
|
|
99
|
+
|
|
100
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/3c2c12f3-c3dc-4dd6-be22-6107e94f0ffa" width="%50" /><img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/d5569055-ca40-4b4a-bbd9-ac1e35f07774" width="%50" />
|
|
101
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/a59ad17c-30b9-4358-b857-4ac399420263" width="%50"/>
|
|
102
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/01038e13-2f4e-4801-82cf-64c40d056a4f" />
|
|
103
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/1f3a5d2b-48d5-4bf1-b1d2-fd1fdca605ed" />
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
For high resolution pictures, please visit: [Car Photos](#carphoto)
|
|
108
|
+
|
|
109
|
+
<a name="Mobility-Management"></a>
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
### Mobility Management
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
<a name="Chassis"></a>
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
### Chassis
|
|
119
|
+
|
|
120
|
+
For fast prototyping, we first used lego for our chassis. Using a lego technic crane as an example we developed a small car-like chassis. However, this proved limiting for the robot: issues with accuracy and structural sag reduced its performance, while the lack of true customizability made it difficult to position electronics as needed and prevented us from using the available space efficiently. Additionally we couldn't really place electronics where we wanted, because of the lack of real customasibility. So, we wanted to go with a 3d printed chassis. First we wanted to test the motor and differential placement, and how it holds the weight our robot. This first version was designed to have a steering system that was 3d printed. But since we changed this, we also changed our chassis. We realized that our previously built circuit—a two-layer copper “sandwich” board—needed a more stable and secure mounting. So with these in mind, we extended the motor mounting walls to the side to provide a stable place to mount the electronics, achiveing a good center of mass. These side walls also enabled us to mount the side ultrasonic sensors in Version 2.
|
|
121
|
+
|
|
122
|
+
The chassis we inspired from:
|
|
123
|
+
|
|
124
|
+
<img width="1600" height="1200" alt="WhatsApp Image 2026-02-14 at 15 19 25" src="https://github.com/user-attachments/assets/b956bc64-2b25-441b-b427-dae8f5994c9d" width="%50"/>
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
Lego Chassis:
|
|
128
|
+
|
|
129
|
+
<img width="1536" height="2048" alt="574065354-382b4c37-0c35-4aa0-9f4c-a3c25c82d3e3" src="https://github.com/user-attachments/assets/97d7723b-65d2-465f-9738-bb38828544ea" />
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
First Version - 3D Printed:
|
|
133
|
+
|
|
134
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/ad2349a1-0461-411f-b929-72f21aa2e38a" />
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
Second Version - 3D printed; with more structural stability which includes additional side walls for the electronics and the side ultrasonic sensors:
|
|
138
|
+
|
|
139
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/b74691da-ce36-4690-867a-ed94beefef7c" />
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
For more sketches of the chassis please visit the [3d-print files](./3d-print%20files/)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
### Tires:
|
|
146
|
+
|
|
147
|
+
In our tests we compared 3 different tires from 2 different manufacturers. We tested the tires that were included in the spike prime lego set, standard tires from clementoni and onother set from the EV3 set. Because of their lack of traction, we could not achieve good results with with the blue spike tires. Instead; we used the heavy-duty ev3 lego tires which have much better traction for the rear tires because of them being flexible rubber. Meanwhile, for the steering axle we used hard ones from clementino with grooves.
|
|
148
|
+
|
|
149
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/4f840071-3cad-47e8-8612-1b2ae815c41c"/>
|
|
150
|
+
|
|
151
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/14479ad4-f58e-4422-9bb2-7eb8fcbf8d45" />
|
|
152
|
+
|
|
153
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/285226ef-7e24-4ae9-8c44-9bb37ddb439f" />
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
<a name="assembly-instructions"></a>
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
### Assembly Instructions
|
|
161
|
+
|
|
162
|
+
To reproduce our robot, follow the sequence below.
|
|
163
|
+
|
|
164
|
+
1. **Prepare all files and parts**
|
|
165
|
+
- 3D print all chassis parts from [`3d-print files`](3d-print%20files):
|
|
166
|
+
- `chassis.stl`
|
|
167
|
+
- `servo_adapter.stl`
|
|
168
|
+
- `dc-motor_adapter.stl`
|
|
169
|
+
- Open the wiring design in [`Schematic/wro.fzz`](Schematic/wro.fzz) with the Fritzing desktop app.
|
|
170
|
+
- Download and prepare the LEGO module manuals from [`lego models && instructions`](lego%20models%20%26%26%20instructions):
|
|
171
|
+
- `differential.pdf`
|
|
172
|
+
- `steeringa.pdf`
|
|
173
|
+
- Confirm all electronics and mechanical items from the Components List are available.
|
|
174
|
+
|
|
175
|
+
2. **Assemble the electronics**
|
|
176
|
+
- Wire every component according to the [Schematics](#schematics) section and `Schematic/wro.fzz`.
|
|
177
|
+
- Mount the control boards, regulators, and wiring on the top sandwich board.
|
|
178
|
+
- Secure parts with screws/zip ties and use silicone supports where required.
|
|
179
|
+
- Keep wiring away from moving drivetrain and steering parts.
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
<img width="2048" height="2048" alt="image" src="https://github.com/user-attachments/assets/e0024a14-956c-45ba-843f-346e62f80cd0" />
|
|
183
|
+
|
|
184
|
+
3. **Build the LEGO modules**
|
|
185
|
+
- Build the differential module from `differential.pdf`.
|
|
186
|
+
- Build the steering module from `steeringa.pdf`.
|
|
187
|
+
- Verify both modules move smoothly before mounting.
|
|
188
|
+
|
|
189
|
+
Differential:
|
|
190
|
+
|
|
191
|
+
<img width="4032" height="3024" alt="IMG_4815" src="https://github.com/user-attachments/assets/a8d141b2-ab2a-4be5-b52c-5dfeddb3111a" />
|
|
192
|
+
|
|
193
|
+
Steering:
|
|
194
|
+
|
|
195
|
+
<img width="3024" height="4032" alt="image" src="https://github.com/user-attachments/assets/360d959e-3e3d-440a-b051-244e34ad4edb" />
|
|
196
|
+
|
|
197
|
+
4. **Final mechanical integration**
|
|
198
|
+
- Mount the JGB37-520 motor to the chassis center wall with M3 screws.
|
|
199
|
+
- Connect the motor to the differential via the printed motor adapter.
|
|
200
|
+
- Mount differential and steering modules on the chassis using silicone supports.
|
|
201
|
+
- Connect the servo horn to the LEGO steering module and check full travel.
|
|
202
|
+
- Install the sandwich board on the upper supports and route all cables cleanly.
|
|
203
|
+
- Connect the battery last and confirm emergency access to the main power switch.
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/b74691da-ce36-4690-867a-ed94beefef7c" />
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
<a name="Driving-Motor-and-Gearing"></a>
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
### Driving Motor and Gearing
|
|
213
|
+
|
|
214
|
+
In our first LEGO prototype, we used an N20 6V motor, but it proved too small and lacked sufficient torque for our robot. As a result, the robot moved very slowly and responded poorly to low-speed commands, sometimes failing to move at all when a slow motion was required. We didn't want to use a 12v motor first becuase our battery was 7.4 volts. After some research, we realized that the motor driver, the L298N, might be part of the problem. Due to its low efficiency, it was not able to deliver enough power for proper performance. It also caused some voltage sag. The L298 family dissipates about 1.4V as heat, which further reduces efficiency. In addition, the minimum PWM value for speed control did not provide a smooth or usable range for precise adjustments. So we first tried to change the driver to a much more efficent, space saving and advanced component, the DRV8874. We tried it with advanced pwm controls but we couldn't solve it. The last option for us was upgrading the motor to the JGB37-520 which is 12v. We intentionally bought a high rpm version, so we could give it the same 7.4v but it could deliver the same speed we want, while increasing the torque. This was a success. This motor really outperformed the old n20 and delivered great driving to our robot. We used two m3 screws to attach this motor to our chassis, and a lego adapter to connect to motor to the differential module. This way we achieved a 1:1 gear ratio, motor starts at much lower PWM values and the braking distance is much shorter.
|
|
215
|
+
|
|
216
|
+
Old L298N:
|
|
217
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/ef610739-66d1-4e9a-802c-a2f4ec599014" />
|
|
218
|
+
|
|
219
|
+
New DRV8874:
|
|
220
|
+
|
|
221
|
+
<img width="1200" height="993" alt="image" src="https://github.com/user-attachments/assets/b67d2d7a-ecce-448b-a1ea-6eaf317ca0d1" />
|
|
222
|
+
|
|
223
|
+
Old n20 6V
|
|
224
|
+
|
|
225
|
+
We switched from the L298N to the DRV8874 before eventually upgrading the motor itself. The DRV8874 is a significant improvement: it is far smaller, has near-zero drop voltage (vs. ~1.4 V on the L298N), and supports efficient PWM control with a much wider effective duty-cycle range at low speeds.
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
<a name="Steering-Mechanism"></a>
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
### Steering and Differential Mechanism
|
|
233
|
+
|
|
234
|
+
We wanted to use steering and differential module that were 3d printed, but after several prints the parts did not hold up and caused anomalies.
|
|
235
|
+
|
|
236
|
+
Expected outcome:
|
|
237
|
+
|
|
238
|
+
<img width="2048" height="1536" alt="image" src="https://github.com/user-attachments/assets/2e1d320c-a731-42e1-9b1b-59c08d5062dd" />
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/fea7a93e-d39c-43cf-beba-2fb83e34d0ca" />
|
|
242
|
+
|
|
243
|
+
Broken guide rail after the first try:
|
|
244
|
+
|
|
245
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/9edadec0-9a77-4dc9-af05-6325f37eee58" />
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
After this experience, we decided to stick with lego for these modules. Lego parts were much more durable due to them being injection molded. Then we designed them and mounted them on the chassis with silicone.
|
|
249
|
+
|
|
250
|
+
<img width="3024" height="4032" alt="image" src="https://github.com/user-attachments/assets/108fc1c2-c875-4384-b7be-a169b8c57ab9" />
|
|
251
|
+
|
|
252
|
+
<img width="3024" height="4032" alt="image" src="https://github.com/user-attachments/assets/08ada85c-eee9-4a12-b52f-15a9dd969e90" />
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
<a name="Power-supply"></a>
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
### Battery
|
|
259
|
+
|
|
260
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/635e711e-117f-4720-b7d1-4ec561a1775e" />
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
We use a 7.4 V-Lithium-polymer battery as our main power supply.
|
|
264
|
+
Per our research, we did not want to use a 11.1 V Lipo, as this one is very heavy, compared to the 2-cell version. We wanted to use a high-capacity one, becuase the Raspberry Pi is very Power-hungry when doing image processing.
|
|
265
|
+
The on/off switch is located directly on the top of the robot for ease of access in an emergency. The step down convertor powers all of our sensors and motor at 5v. Also to not put strain on the LM2596, we used a high capacity PD 3.0 compatible voltage regulator board to power the pi. The arduino gets its power by the usb-a to usb-c cable along with serial communication with the pi.
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
See the schematics for details on connections.
|
|
269
|
+
[Schematics](#schematics)
|
|
270
|
+
|
|
271
|
+
#### Power Budget
|
|
272
|
+
|
|
273
|
+
To ensure the battery and regulators can handle the full electrical load, we estimated the peak and average current draw for each subsystem:
|
|
274
|
+
|
|
275
|
+
| Component | Supply Rail | Avg. Current | Peak Current |
|
|
276
|
+
|---|---|---|---|
|
|
277
|
+
| Raspberry Pi 5 (8GB, image processing) | 5 V (PD 3.0 board) | ~2.0 A | ~3.0 A |
|
|
278
|
+
| Arduino Nano R4 | 5 V (via USB-C from Pi) | ~30 mA | ~100 mA |
|
|
279
|
+
| JGB37-520 Motor (at 7.4 V) | 7.4 V (direct) | ~0.8 A | ~3.0 A (stall) |
|
|
280
|
+
| MG90S Servo | 5 V (LM2596) | ~150 mA | ~600 mA |
|
|
281
|
+
| HC-SR04 × 3 Ultrasonic Sensors | 5 V (XL4016) | ~45 mA | ~45 mA |
|
|
282
|
+
| MPU6050 IMU | 3.3 V (via Arduino) | ~4 mA | ~4 mA |
|
|
283
|
+
| TCS3200 Color Sensor | 5 V (XL4016) | ~2 mA | ~2 mA |
|
|
284
|
+
| DRV8874 Motor Driver (quiescent) | 7.4 V | ~1 mA | ~1 mA |
|
|
285
|
+
|
|
286
|
+
**5 V sensor rail (XL4016):** ~200 mA average — well within the 5 A rated capacity.
|
|
287
|
+
|
|
288
|
+
**Pi supply (PD 3.0 board):** Up to 3 A peak — we selected a QC 4.0/3.0 compatible module rated for 6–35 V input so it efficiently steps down from 7.4 V without the drop-voltage penalty of a linear regulator.
|
|
289
|
+
|
|
290
|
+
**Motor rail (7.4 V direct):** Average ~0.8 A, brief stall events up to ~3 A. The DRV8874 is rated for 3.5 A continuous / 5 A peak — adequate margin.
|
|
291
|
+
|
|
292
|
+
**Battery runtime estimate:** With a 3300 mAh cell and an average total draw of approximately 3.5 A across all rails, the theoretical runtime is ~56 minutes per charge. In practice we rotate between two battery packs between rounds.
|
|
293
|
+
|
|
294
|
+
We deliberately chose the 2-cell (7.4 V) LiPo over a 3-cell (11.1 V) because the 3-cell variant weighs roughly 30–40 g more for the same capacity and would require an additional high-power step-down stage for the motor. The 2-cell voltage is sufficient for the JGB37-520 at the RPM we need (we purchased the high-RPM variant specifically to offset the lower voltage).
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
<a name="Controllers"></a>
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
### Controllers
|
|
301
|
+
|
|
302
|
+
For this season, we used a raspberry pi 5 and an arduino nano r4. We evaluated a single-controller design (Pi only), but kept a **dual-controller architecture** because actuator timing is more stable when low-level hardware control remains on a microcontroller.
|
|
303
|
+
|
|
304
|
+
#### Why two controllers?
|
|
305
|
+
|
|
306
|
+
- **Raspberry Pi 5 (high-level controller):**
|
|
307
|
+
- Captures camera frames
|
|
308
|
+
- Runs wall following, lap/section counting, and obstacle logic
|
|
309
|
+
- Hosts the Flask web dashboard for live tuning and telemetry
|
|
310
|
+
- Sends compact drive/steer commands to Arduino at fixed intervals
|
|
311
|
+
|
|
312
|
+
- **Arduino Nano R4 (real-time actuator controller):**
|
|
313
|
+
- Executes steering servo commands immediately
|
|
314
|
+
- Drives DC motor direction/PWM pins with low and predictable latency
|
|
315
|
+
- Parses serial commands safely and applies bounds before output
|
|
316
|
+
|
|
317
|
+
This split lets the Pi focus on heavy image processing while Arduino handles deterministic motor/servo control.
|
|
318
|
+
|
|
319
|
+
#### Communication design (Pi ↔ Arduino)
|
|
320
|
+
|
|
321
|
+
We tested two serial methods:
|
|
322
|
+
|
|
323
|
+
1. **GPIO UART (TX/RX pins + level shifter)**
|
|
324
|
+
Not reliable in our setup because of 3.3 V (Pi) to 5 V (Arduino) level differences and intermittent link failures.
|
|
325
|
+
|
|
326
|
+
2. **USB serial (Pi USB-A to Arduino USB-C)**
|
|
327
|
+
This proved stable and simpler to maintain, so it became our final design.
|
|
328
|
+
|
|
329
|
+
Command flow is one-way for control:
|
|
330
|
+
- Pi sends line-based commands such as `STEER:<value>` and `DRIVE:<value>`
|
|
331
|
+
- Arduino validates and applies commands to hardware outputs
|
|
332
|
+
|
|
333
|
+
The link runs at **9600 baud** with a control update cadence around **20 Hz** (`send_interval = 0.05 s`).
|
|
334
|
+
|
|
335
|
+
#### Startup behavior and reliability
|
|
336
|
+
|
|
337
|
+
When USB serial is opened, Arduino may reset. To prevent unsafe movement during this phase, we use a startup handshake and safe defaults:
|
|
338
|
+
|
|
339
|
+
- Arduino starts with motor output in stop/coast state
|
|
340
|
+
- Pi waits for serial readiness before normal command streaming
|
|
341
|
+
- Control commands are only trusted after parser/connection readiness
|
|
342
|
+
|
|
343
|
+
This sequence avoids accidental motion during boot and makes power-up behavior repeatable in competition conditions.
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
<a name="Sensors"></a>
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
### Sensors
|
|
350
|
+
|
|
351
|
+
Our car is equipped with a total of four sensors, not including the camera. Three of them are ultrasonic sensors, which allow for the detection of nearby obstacles.
|
|
352
|
+
In addition our vehicle has an IMU, the MPU6050, which is located in the top center of the robot.
|
|
353
|
+
Our IMU helps measure rotational movements, providing more precise control during straight sections and turns.
|
|
354
|
+
|
|
355
|
+
We have three ultrasonic sensors (hc-sr04) to measure distances to the left, the front and the right. In the opening race, some of the streets can be narrow. To avoid hitting a wall, the sensors detect the robot getting too close.
|
|
356
|
+
|
|
357
|
+
To run rather reliable turns and to go straight no matter which starting place, we use MPU6050 IMU sensor.
|
|
358
|
+
This sensor has a gyroscope, an accelerometer and a compass onboard, but we only utilize the gyro.
|
|
359
|
+
However, gyroscopes tend to drift, when mounted on moving vehicles, that is why we use this data combined with the camera.
|
|
360
|
+
|
|
361
|
+
#### Sensor Calibration
|
|
362
|
+
|
|
363
|
+
**Ultrasonic sensors (HC-SR04):** The HC-SR04 outputs a distance in centimeters based on the echo pulse duration. We verified the accuracy by measuring a known distance of 20 cm in a controlled environment and confirming the returned value matched within ±1 cm. No software offset was needed.
|
|
364
|
+
|
|
365
|
+
**IMU (MPU6050) — gyroscope:** We initialize the IMU at robot startup while the robot is stationary. The first 100 readings are averaged to compute the zero-rate bias for each axis, which is then subtracted from subsequent readings. This removes the static offset and reduces apparent drift during straight driving.
|
|
366
|
+
|
|
367
|
+
**Camera HSV color thresholds:** The web dashboard (Flask UI) exposes all HSV lower and upper bounds for red, green, orange, and blue in real time. We calibrate under competition lighting by placing a pillar or line marker in the camera view and adjusting bounds using the live MJPEG stream until detection is stable and noise-free across at least 30 consecutive frames. Calibrated values are saved to `config.json`.
|
|
368
|
+
|
|
369
|
+
<a name="camera"></a>
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
### Camera
|
|
373
|
+
|
|
374
|
+
We wanted to use a high quality camera, which has a wide angle of view and is compatible with OpenCV. The native Raspberry Pi Camera 3 suited all of our needs, on paper. In reality despite it being a wide angle camera, it was not enough for the obstacle round. The objects did not sometimes appear on the camera view, which causes problems.
|
|
375
|
+
|
|
376
|
+
To solve this, we added a **0.45x wide-angle 37mm clip-on lens** in front of the Raspberry Pi Camera 3 module. This lens increases the visible area in front of the robot, especially at close range where obstacle pillars were previously leaving the frame. With the wider field of view, near-edge obstacle detection became more reliable and gave the control loop earlier visual input for steering decisions.
|
|
377
|
+
|
|
378
|
+
<img width="3024" height="4032" alt="front" src="https://github.com/user-attachments/assets/4ee8eabc-229a-4434-9c35-396a4a46bb8e" />
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/0df834c9-d58f-4b4f-a553-fae66bace537" />
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
<a name="schematics"></a>
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
### Schematics
|
|
389
|
+
|
|
390
|
+
Circuit schematics and hardware layouts are available in the [Schematic folder](Schematic).
|
|
391
|
+
|
|
392
|
+
<img width="975" height="722" alt="WRO Schematic" src="https://github.com/user-attachments/assets/486fa8f0-4040-46df-8224-3d5821d0667d" />
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
<a name="components-list"></a>
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
### Components List
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
### 1. Computing & Control
|
|
402
|
+
|
|
403
|
+
Main Controller: Raspberry Pi 5 (8GB RAM)
|
|
404
|
+
|
|
405
|
+
Cooling: Raspberry Pi 5 Active Cooler + Aluminum Heatsink Set
|
|
406
|
+
|
|
407
|
+
Storage: SanDisk Extreme Pro 32GB MicroSD Card
|
|
408
|
+
|
|
409
|
+
Power Input: USB-C Power Cable with Integrated Switch (Type-C)
|
|
410
|
+
|
|
411
|
+
Display Interface: Micro HDMI to HDMI Cable (1.5m)
|
|
412
|
+
|
|
413
|
+
Second Controller: ~~Arduino Uno r3 (clone)~~ (For the drivetrain and sensors) Changed to a arduino nano r4 for latency issues.
|
|
414
|
+
|
|
415
|
+
### 2. Perception & Sensing
|
|
416
|
+
|
|
417
|
+
Primary Camera: Raspberry Pi Camera Module 3 (Wide Angle)
|
|
418
|
+
|
|
419
|
+
Camera Connection: Standard-to-Mini Camera Ribbon Cable (20cm)
|
|
420
|
+
|
|
421
|
+
Camera Lens: 0.45x 37mm clip-on Lens
|
|
422
|
+
|
|
423
|
+
Inertial Measurement: MPU6050 6-Axis Accelerometer and Gyroscope Sensor
|
|
424
|
+
|
|
425
|
+
Object Detection: HC-SR04 Ultrasonic Distance Sensors (3 units)
|
|
426
|
+
|
|
427
|
+
Color Detection: TCS3200 Color Recognition Sensor Module
|
|
428
|
+
|
|
429
|
+
### 3. Propulsion & Drive System
|
|
430
|
+
|
|
431
|
+
Main Drive Motor: JGB37-520 DC Gear Motor (12V, 1590 RPM)
|
|
432
|
+
|
|
433
|
+
Secondary/Testing Motor: JGA12-520 DC Gear Motor (6V, 1500 RPM)
|
|
434
|
+
|
|
435
|
+
Motor Driver: DRV8874 Single Brushed DC Motor Driver Carrier
|
|
436
|
+
|
|
437
|
+
### 4. Power Management
|
|
438
|
+
|
|
439
|
+
Battery: 3300mAh 7.4V 2S Li-Po Battery (2 units)
|
|
440
|
+
|
|
441
|
+
Voltage Regulation (Step-Down): XL4016 High-Power DC-DC Buck Converter (5A)
|
|
442
|
+
|
|
443
|
+
LM2596 DC-DC Buck Converter (3A)
|
|
444
|
+
|
|
445
|
+
Voltage Regulation (Step-Up): MT3608 Adjustable DC-DC Boost Converter (2 units)
|
|
446
|
+
|
|
447
|
+
Specialty Power: USB-C QC4.0/QC3.0 Fast Charging Module (6V-35V Input)
|
|
448
|
+
|
|
449
|
+
### 5. Wiring & Connectivity
|
|
450
|
+
|
|
451
|
+
Connectors: XT60 Male Connection Cables (12AWG)
|
|
452
|
+
|
|
453
|
+
XT60 to Banana Plug Adapter Cable
|
|
454
|
+
|
|
455
|
+
DC Barrel Jack with Terminal Block
|
|
456
|
+
|
|
457
|
+
Wiring: 14 AWG High-Flex Silicone Wire (Red and Black)
|
|
458
|
+
|
|
459
|
+
~~Rapid Wire Connection Kit (55 pieces)~~ We decided to ditch this in favor of soldering. These WAGO connectors really save time and effort when connecting components, but takes up a lot of space. Also, it was nearly impossible to tidy our wires with these connectores attached.
|
|
460
|
+
|
|
461
|
+
Management: Cable Tie/Zip Tie Set
|
|
462
|
+
|
|
463
|
+
### 6. Mechanical Hardware
|
|
464
|
+
|
|
465
|
+
Fasteners: Assorted Screw and Nut Set (200 pieces)
|
|
466
|
+
|
|
467
|
+
M2 Screws and Nuts (8mm)
|
|
468
|
+
|
|
469
|
+
M4 Screws (6mm & 12mm), Washers, and Nuts
|
|
470
|
+
|
|
471
|
+
### 7. 3D-Printed Parts
|
|
472
|
+
|
|
473
|
+
Our robot has a total of 3 pieces which are 3d printed. Theses are:
|
|
474
|
+
|
|
475
|
+
the chassis,
|
|
476
|
+
|
|
477
|
+
MG90s servo to lego adapter
|
|
478
|
+
|
|
479
|
+
and our JGB37-520 motor to lego adapter.
|
|
480
|
+
Developing these parts took a lot of trial and error mainly because of tolerances. The printed parts did not expecatations, mainly because of durability reasons, but we have fixed this by increasing the infill rate of our adapters. This way we could combine the high-quality and durable lego parts with our motors.
|
|
481
|
+
|
|
482
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/241b9292-bb0e-4605-a3a8-7be53221bdcd" />
|
|
483
|
+
<img width="4032" height="3024" alt="image" src="https://github.com/user-attachments/assets/e8099160-08ab-4de4-947d-09be8c982bc4" />
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
<a name="software"></a>
|
|
487
|
+
|
|
488
|
+
# Software
|
|
489
|
+
|
|
490
|
+
<a name="software-development"></a>
|
|
491
|
+
|
|
492
|
+
## Software Development
|
|
493
|
+
|
|
494
|
+
The software is split into two controllers that cooperate during every driving cycle:
|
|
495
|
+
|
|
496
|
+
1. **Raspberry Pi 5 (`raspy/robot_runtime.py`, Python)**
|
|
497
|
+
- Captures frames from the Raspberry Pi camera with `picamera2`
|
|
498
|
+
- Uses OpenCV/NumPy to crop the camera view, detect wall masks, floor markers, and red/green pillars
|
|
499
|
+
- Runs the driving state machine and computes steering/speed decisions
|
|
500
|
+
- Hosts the live preview, configuration, calibration, status, and manual-test web interface
|
|
501
|
+
- Writes decision records to one JSONL log file for review after a run
|
|
502
|
+
- Sends compact serial commands to the actuator controller
|
|
503
|
+
2. **Arduino Nano (`arduino_outputProxy/drive_bridge/src/main.cpp`, C++ / Arduino framework)**
|
|
504
|
+
- Receives `STEER`, `DRIVE`, and `STOP` commands over USB serial
|
|
505
|
+
- Drives the steering servo with bounded absolute angles
|
|
506
|
+
- Drives the DC motor through the configured H-bridge pins with ramp limiting and timeout stop
|
|
507
|
+
- Keeps low-level actuator timing separate from Linux scheduling on the Pi
|
|
508
|
+
|
|
509
|
+
The Pi makes the navigation decisions; the Arduino executes low-latency hardware actions. The WRO 2026 rules require the vehicle to drive autonomously, follow the randomly chosen round direction, complete three laps, and in the obstacle challenge pass red pillars on the right and green pillars on the left. The software is organized around those decisions.
|
|
510
|
+
|
|
511
|
+
### Libraries Used in Runtime Files
|
|
512
|
+
|
|
513
|
+
#### `arduino_outputProxy/drive_bridge/src/main.cpp` (Arduino/C++)
|
|
514
|
+
- `Arduino.h`
|
|
515
|
+
- `Servo.h`
|
|
516
|
+
|
|
517
|
+
#### `raspy/robot_runtime.py` and modules (Python)
|
|
518
|
+
- Standard library: `argparse`, `glob`, `json`, `os`, `threading`, `time`, `http.server`
|
|
519
|
+
- Third-party: `cv2` (OpenCV), `numpy`
|
|
520
|
+
- Optional/hardware-specific: `serial` (pyserial), `picamera2`
|
|
521
|
+
|
|
522
|
+
<a name="programming-languages"></a>
|
|
523
|
+
|
|
524
|
+
### Programming Languages
|
|
525
|
+
|
|
526
|
+
| Controller | Language | IDE / Toolchain |
|
|
527
|
+
|---|---|---|
|
|
528
|
+
| Raspberry Pi 5 | Python 3 | Visual Studio Code / terminal |
|
|
529
|
+
| Arduino Nano | C++ (Arduino framework) | PlatformIO / Arduino toolchain |
|
|
530
|
+
|
|
531
|
+
We chose **Python** for the Pi because OpenCV and Picamera2 make camera-based tuning fast. **C++** on the Arduino is used for bounded, deterministic servo and motor output.
|
|
532
|
+
|
|
533
|
+
<a name="dependencies"></a>
|
|
534
|
+
|
|
535
|
+
### Dependencies
|
|
536
|
+
|
|
537
|
+
**Raspberry Pi (Python):**
|
|
538
|
+
|
|
539
|
+
| Package | Version / Notes | Purpose |
|
|
540
|
+
|---|---|---|
|
|
541
|
+
| `opencv-python` (`cv2`) | 4.x | Color masks, contours, preview overlay, JPEG encoding |
|
|
542
|
+
| `numpy` | 1.x | Array operations for frame processing |
|
|
543
|
+
| `pyserial` | 3.x | USB serial communication with Arduino |
|
|
544
|
+
| `picamera2` | Raspberry Pi OS package | Raspberry Pi camera capture |
|
|
545
|
+
|
|
546
|
+
**Arduino Nano (C++):**
|
|
547
|
+
|
|
548
|
+
| Library | Source | Purpose |
|
|
549
|
+
|---|---|---|
|
|
550
|
+
| `Servo.h` | `arduino-libraries/Servo@^1.2.2` | Steering servo PWM control |
|
|
551
|
+
| `Arduino.h` | Arduino framework | Core hardware access, serial, GPIO, timers |
|
|
552
|
+
|
|
553
|
+
**Build and service tools:**
|
|
554
|
+
|
|
555
|
+
| Tool | Purpose |
|
|
556
|
+
|---|---|
|
|
557
|
+
| `platformio` | Build and upload the Arduino firmware |
|
|
558
|
+
| `systemctl` | Install, enable, start, stop, and inspect the Pi runtime service |
|
|
559
|
+
|
|
560
|
+
### Repository Layout
|
|
561
|
+
|
|
562
|
+
| Path | Purpose |
|
|
563
|
+
|---|---|
|
|
564
|
+
| `main.py` | Root launcher that adds `raspy/` to `sys.path` and calls `robot_runtime.main` |
|
|
565
|
+
| `raspy/robot_runtime.py` | Main Pi runtime, camera loop, serial link, CLI, and `RobotRunner` |
|
|
566
|
+
| `raspy/vision_pipeline.py` | HSV masks, wall mask extraction, pillar contour detection |
|
|
567
|
+
| `raspy/vision_types.py` | Shared `Pillar` data object and ROI helper |
|
|
568
|
+
| `raspy/drive_state.py` | State machine for starting, centering, turning, tracking pillars, avoiding pillars, and done state |
|
|
569
|
+
| `raspy/lap_direction.py` | Black-wall edge voting used to infer clockwise/counter-clockwise direction |
|
|
570
|
+
| `raspy/settings.py` / `raspy/settings.json` | Default settings and tuned runtime configuration |
|
|
571
|
+
| `raspy/control_panel.py` | Built-in HTTP control/configuration dashboard and MJPEG preview |
|
|
572
|
+
| `raspy/decision_log.py` | Central JSONL decision logger, replaced on each run |
|
|
573
|
+
| `raspy/install_vehicle_service.sh` | Installs the systemd service file on the Raspberry Pi |
|
|
574
|
+
| `raspy/vehicle-runtime.service` | systemd unit for starting `robot_runtime.py --pillars` on boot |
|
|
575
|
+
| `arduino_outputProxy/drive_bridge/src/main.cpp` | Arduino firmware for serial parsing and actuator output |
|
|
576
|
+
| `arduino_outputProxy/drive_bridge/platformio.ini` | PlatformIO firmware project configuration |
|
|
577
|
+
|
|
578
|
+
### Code Installation and Run Guide
|
|
579
|
+
|
|
580
|
+
Use this sequence on a Raspberry Pi 5 + Arduino Nano setup.
|
|
581
|
+
|
|
582
|
+
1. **Flash the Arduino controller**
|
|
583
|
+
- Install PlatformIO if `platformio` is not already available.
|
|
584
|
+
- From repository root:
|
|
585
|
+
- `cd arduino_outputProxy/drive_bridge`
|
|
586
|
+
- `platformio run --target upload`
|
|
587
|
+
- The checked-in PlatformIO environment and board are both `nanoatmega328new`.
|
|
588
|
+
- Serial monitor speed is `9600`. On startup the Arduino prints `READY`.
|
|
589
|
+
|
|
590
|
+
2. **Prepare Python runtime on Raspberry Pi**
|
|
591
|
+
- From repository root:
|
|
592
|
+
- `python3 -m venv .venv`
|
|
593
|
+
- `source .venv/bin/activate`
|
|
594
|
+
- `pip install --upgrade pip`
|
|
595
|
+
- `pip install opencv-python numpy pyserial`
|
|
596
|
+
- Install `picamera2` from the Raspberry Pi OS packages if it is not already present.
|
|
597
|
+
- `robot_runtime.py` imports `cv2` during startup, so even `--print-config` requires OpenCV to be installed.
|
|
598
|
+
|
|
599
|
+
3. **Connect hardware**
|
|
600
|
+
- Connect Arduino to Raspberry Pi over USB.
|
|
601
|
+
- Confirm Arduino appears as `/dev/ttyACM0` or `/dev/ttyUSB0`; auto-detect is enabled in `raspy/settings.json`.
|
|
602
|
+
- Connect and enable the Raspberry Pi camera.
|
|
603
|
+
|
|
604
|
+
4. **Run the robot software**
|
|
605
|
+
- From repository root:
|
|
606
|
+
- `python3 main.py --pillars`
|
|
607
|
+
- Or run the runtime module directly:
|
|
608
|
+
- `cd raspy`
|
|
609
|
+
- `python3 robot_runtime.py --pillars`
|
|
610
|
+
- For a camera run without Arduino output:
|
|
611
|
+
- `python3 robot_runtime.py --no-serial --max-frames 20`
|
|
612
|
+
- `--no-serial` disables USB command output only; it still needs OpenCV, Picamera2, and an enabled camera.
|
|
613
|
+
- The web dashboard is served at `http://<raspberry-pi-ip>:5000/`.
|
|
614
|
+
|
|
615
|
+
5. **Useful runtime options**
|
|
616
|
+
- `--config PATH` loads a different settings file
|
|
617
|
+
- `--set path=value` overrides one setting, for example `--set control.speed=120`
|
|
618
|
+
- `--save-config` writes overrides back to the settings file
|
|
619
|
+
- `--print-config` prints the effective configuration and exits
|
|
620
|
+
- `--fixed-dir -1|0|1` sets clockwise, auto, or counter-clockwise direction behavior
|
|
621
|
+
- `--web` / `--no-web` enables or disables the dashboard
|
|
622
|
+
|
|
623
|
+
6. **Before track runs**
|
|
624
|
+
- Verify steering direction, motor direction, and serial port.
|
|
625
|
+
- Open `/video_feed` or the dashboard preview and confirm wall, marker, and pillar overlays.
|
|
626
|
+
- Use Color Sampling in the dashboard to tune HSV thresholds, then save with **Save Config**.
|
|
627
|
+
- Check `raspy/decision_log.jsonl` after a run to review state changes and drive decisions.
|
|
628
|
+
|
|
629
|
+
7. **Optional systemd startup service**
|
|
630
|
+
- The service assumes the deployed project is at `/home/pi/program`, so the runtime path is `/home/pi/program/raspy/robot_runtime.py`.
|
|
631
|
+
- Copy or sync this repository to `/home/pi/program` before installing the service, or update `raspy/vehicle-runtime.service` to match the deployment path.
|
|
632
|
+
- Install the service from the repository checkout on the Pi:
|
|
633
|
+
- `raspy/install_vehicle_service.sh`
|
|
634
|
+
- Manage it with systemd:
|
|
635
|
+
- `sudo systemctl start vehicle-runtime.service`
|
|
636
|
+
- `sudo systemctl stop vehicle-runtime.service`
|
|
637
|
+
- `sudo systemctl enable vehicle-runtime.service`
|
|
638
|
+
- `sudo systemctl status vehicle-runtime.service`
|
|
639
|
+
- `journalctl -u vehicle-runtime.service -f`
|
|
640
|
+
- The service runs `python3 /home/pi/program/raspy/robot_runtime.py --pillars` with working directory `/home/pi/program/raspy`.
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
<a name="open-challenge"></a>
|
|
647
|
+
|
|
648
|
+
## Open Challenge
|
|
649
|
+
|
|
650
|
+
The WRO 2026 Open Challenge has no traffic signs. The vehicle must drive in the randomly selected direction, complete three laps, and stop autonomously according to the round-end rules.
|
|
651
|
+
|
|
652
|
+
### Strategy
|
|
653
|
+
|
|
654
|
+
The implemented strategy is camera-based wall following with color-marker turn detection:
|
|
655
|
+
|
|
656
|
+
1. **Frame preparation:** `PiCamera.capture_bgr` captures an RGB frame, converts it to BGR, rotates it 180 degrees when configured, and `Pipeline.crop` removes the top part of the image using `camera.crop_height`.
|
|
657
|
+
|
|
658
|
+
2. **Wall detection:** `Pipeline.filter_RG_Bl` builds a dark wall mask from grayscale intensity and low HSV saturation. It excludes detected red, green, orange, blue, and pink areas so colored markers do not become wall pixels.
|
|
659
|
+
|
|
660
|
+
3. **Line marker detection:** `Pipeline.filter_OB` builds orange and blue masks inside the configured `roi.line` rectangle. The state machine uses the orange/blue portions to schedule left/right turning states.
|
|
661
|
+
|
|
662
|
+
4. **Round direction:** If `behavior.fixed_round_dir` is `0`, `lap_direction.find_round_dir` votes on wall-edge geometry until `behavior.round_dir_vote_threshold` is reached. A non-zero fixed direction skips the search.
|
|
663
|
+
|
|
664
|
+
5. **PD steering:** In `PD-CENTER`, the controller compares the active wall portion against `pd.target_black_portion`. `pd.kp` and `pd.kd` convert the current error and previous error into a correction value.
|
|
665
|
+
|
|
666
|
+
6. **State machine:** `drive_state.StateMachine` moves through `STARTING`, `PD-CENTER`, scheduled `TURNING-L` / `TURNING-R`, and finally `DONE`. When `turns_left` reaches zero, `DONE` is scheduled after `behavior.finish_delay_seconds`.
|
|
667
|
+
|
|
668
|
+
7. **Output mapping:** The correction is clamped by `control.max_correction`, converted into a servo offset with `control.max_steering_offset`, bounded to `servo_min_deg..servo_max_deg`, and sent with the selected drive speed.
|
|
669
|
+
|
|
670
|
+
### Turn Count and Stop Behavior
|
|
671
|
+
|
|
672
|
+
The code stops through the state machine, not through a separate lap tracker. `StateMachine` initializes `turns_left` from `behavior.turns`, subtracts one when a valid direction-specific orange or blue marker schedules a turn, then schedules `DONE` once `turns_left <= 0`. In `DONE`, `RobotRunner` centers steering and sends zero drive speed.
|
|
673
|
+
|
|
674
|
+
`settings.DEFAULT_CONFIG` uses `behavior.turns = 12`, which matches three laps on a four-section track. The checked-in tuned file `raspy/settings.json` currently uses `behavior.turns = 120`. The file does not label why this value is used, so it should be treated as the current tuned/test configuration, not the WRO three-lap stop value. For an official three-lap run, set `behavior.turns` to `12` or pass `--set behavior.turns=12`.
|
|
675
|
+
|
|
676
|
+
---
|
|
677
|
+
|
|
678
|
+
<a name="obstacle-challenge"></a>
|
|
679
|
+
|
|
680
|
+
## Obstacle Challenge
|
|
681
|
+
|
|
682
|
+
The WRO 2026 Obstacle Challenge adds red and green pillars as traffic signs. The rules require the robot to pass a red pillar on the right and a green pillar on the left.
|
|
683
|
+
|
|
684
|
+
### Strategy
|
|
685
|
+
|
|
686
|
+
1. **Base navigation:** The obstacle run uses the same wall mask, line-marker logic, direction voting, and PD centering as the Open Challenge.
|
|
687
|
+
|
|
688
|
+
2. **Pillar detection:** `Pipeline.get_pillars` finds red and green contours after median blur and morphological cleanup. Small, narrow, short, or low-fill contours are discarded using the `contours` settings.
|
|
689
|
+
|
|
690
|
+
3. **Pillar stabilization:** `RobotRunner.stabilize_pillars` keeps one closest pillar track, smooths its area with an EMA, requires confirm frames, and can hold a recently lost pillar for a few frames.
|
|
691
|
+
|
|
692
|
+
4. **Ignore check:** A pillar can be ignored when orange or blue marker pixels are found inside the narrow pillar ROI. This avoids treating track markers as traffic pillars.
|
|
693
|
+
|
|
694
|
+
5. **Tracking and avoiding:** In pillar rounds, a confirmed pillar can move the state machine from `PD-CENTER` to `TRACKING-PILLAR`. A larger confirmed pillar changes the state to `AVOIDING-R` for red or `AVOIDING-G` for green.
|
|
695
|
+
|
|
696
|
+
6. **Avoidance correction:** Avoidance uses a first phase and second phase correction (`pillar_red_first_correction`, `pillar_red_second_correction`, `pillar_green_first_correction`, `pillar_green_second_correction`). The wall guard can override this if one wall portion becomes too high.
|
|
697
|
+
|
|
698
|
+
7. **Return to center:** After `behavior.pillar_avoid_seconds`, the state machine returns to `PD-CENTER` and enforces a cooldown before another avoid decision.
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
## Raspberry Pi Software (`raspy/robot_runtime.py`) — Process Sections
|
|
704
|
+
|
|
705
|
+
### Section A — Boot and initialization
|
|
706
|
+
|
|
707
|
+
- Load persisted configuration from `raspy/settings.json`
|
|
708
|
+
- Merge missing keys from `settings.DEFAULT_CONFIG`
|
|
709
|
+
- Apply CLI overrides such as `--pillars`, `--fixed-dir`, `--set`, and web options
|
|
710
|
+
- Initialize `Pipeline`, `StateMachine`, `ArduinoLink`, `PiCamera`, and `DecisionLogger`
|
|
711
|
+
- Start Arduino serial, camera capture, and the optional web server
|
|
712
|
+
|
|
713
|
+
`settings.ConfigLoader` also accepts a few older configuration keys (`ArduinoSerialPort`, `PD`, and `ROI`) and maps them into the current nested settings structure. New changes should use the current keys shown in `raspy/settings.json`.
|
|
714
|
+
|
|
715
|
+
### Section B — Frame acquisition and preprocessing
|
|
716
|
+
|
|
717
|
+
Per frame, the Pi does the following:
|
|
718
|
+
|
|
719
|
+
1. Capture a frame from Picamera2
|
|
720
|
+
2. Convert RGB to BGR
|
|
721
|
+
3. Rotate 180 degrees when `camera.flip_180 = true`
|
|
722
|
+
4. Crop using `camera.crop_height`
|
|
723
|
+
5. Convert the cropped image to HSV for color masks
|
|
724
|
+
|
|
725
|
+
### Section C — Vision section
|
|
726
|
+
|
|
727
|
+
`vision_pipeline.Pipeline` produces the lightweight inputs used by the state machine:
|
|
728
|
+
|
|
729
|
+
- Orange and blue marker portions from `roi.line`
|
|
730
|
+
- Left and right wall portions from `roi.left_wall` and `roi.right_wall`
|
|
731
|
+
- Red and green pillar candidates from contour area, width, height, and fill ratio
|
|
732
|
+
- Optional pink parking mask helper (`filter_parking`)
|
|
733
|
+
|
|
734
|
+
### Section D — State and decision section
|
|
735
|
+
|
|
736
|
+
`drive_state.StateMachine` owns the high-level driving state:
|
|
737
|
+
|
|
738
|
+
- `STARTING` waits for a direction or confirmed pillar condition
|
|
739
|
+
- `PD-CENTER` performs normal wall following
|
|
740
|
+
- `TRACKING-PILLAR` steers based on pillar x-position
|
|
741
|
+
- `AVOIDING-R` and `AVOIDING-G` use timed avoidance corrections
|
|
742
|
+
- `TURNING-L` and `TURNING-R` apply full correction for corner turns
|
|
743
|
+
- `DONE` forces speed to zero
|
|
744
|
+
|
|
745
|
+
Important behavior settings include:
|
|
746
|
+
|
|
747
|
+
- `line_marker_min_portion` for orange/blue marker detection
|
|
748
|
+
- `turn_delay_seconds`, `pillar_turn_delay_seconds`, and `turn_timeout_seconds` for turns
|
|
749
|
+
- `pillar_track_area`, `pillar_avoid_area`, and confirm-frame settings for obstacle behavior
|
|
750
|
+
- `pillar_wall_guard_threshold` and `pillar_wall_guard_correction` for wall protection during avoidance
|
|
751
|
+
|
|
752
|
+
### Section E — Serial command output
|
|
753
|
+
|
|
754
|
+
`ArduinoLink` sends line-oriented actuator commands:
|
|
755
|
+
|
|
756
|
+
- `STEER:<int>`
|
|
757
|
+
- `DRIVE:<int>`
|
|
758
|
+
- `STOP`
|
|
759
|
+
|
|
760
|
+
The port is auto-detected from `/dev/ttyACM*`, `/dev/ttyUSB*`, or pyserial port listing unless auto-detect is disabled. Commands are paced by `send_interval_seconds` and repeated only after `keepalive_interval_seconds` when unchanged.
|
|
761
|
+
|
|
762
|
+
### Section F — Web dashboard/API section
|
|
763
|
+
|
|
764
|
+
The dashboard is served by Python's built-in `ThreadingHTTPServer`:
|
|
765
|
+
|
|
766
|
+
- `GET /` dashboard
|
|
767
|
+
- `GET /api/status` runtime telemetry
|
|
768
|
+
- `GET /api/config` active config snapshot
|
|
769
|
+
- `GET /video_feed` MJPEG stream
|
|
770
|
+
- `POST /api/config` update one setting path
|
|
771
|
+
- `POST /api/save` persist settings
|
|
772
|
+
- `POST /api/command` manual `STEER`, `DRIVE`, or `STOP`
|
|
773
|
+
- `POST /api/sample`, `/api/apply_sample`, `/api/apply_samples` for color calibration
|
|
774
|
+
|
|
775
|
+
### Section G — Decision logging
|
|
776
|
+
|
|
777
|
+
`decision_log.DecisionLogger` writes newline-delimited JSON to one file. By default this is `raspy/decision_log.jsonl`. The file is opened with write mode on startup, so each run replaces the previous decision log instead of creating timestamped logs.
|
|
778
|
+
|
|
779
|
+
Logged events include:
|
|
780
|
+
|
|
781
|
+
- `session_start` and `session_stop`
|
|
782
|
+
- `runtime_start` and `runtime_error`
|
|
783
|
+
- `round_direction_selected`
|
|
784
|
+
- `state_update`
|
|
785
|
+
- sampled `drive_output` decisions
|
|
786
|
+
- `config_update` and `manual_command`
|
|
787
|
+
|
|
788
|
+
Each record stores lightweight inputs, the selected decision/action, and already-computed reasoning values such as wall portions, marker portions, pillar snapshot, correction, PD error, previous error, gain values, vote totals, and manual override status. Raw frames are not written to the decision log.
|
|
789
|
+
|
|
790
|
+
Console status output is separate from the decision log and is controlled by `debug.log_every_seconds`. Optional frame saving is controlled by `debug.save_frames`, `debug.frame_dir`, and `debug.frame_every_seconds`; when enabled, the runtime overwrites `latest.jpg` in the configured debug frame directory instead of storing a full video history.
|
|
791
|
+
|
|
792
|
+
The sections above describe responsibilities by module.
|
|
793
|
+
The next flowcharts switch perspective from static sections to **runtime execution order**. These show the steps involved from startup to each control decision.
|
|
794
|
+
|
|
795
|
+
### Raspberry Pi High-Level Flow
|
|
796
|
+
|
|
797
|
+
```mermaid
|
|
798
|
+
flowchart TD
|
|
799
|
+
A[Start robot_runtime.py] --> B[Load settings.json and CLI overrides]
|
|
800
|
+
B --> C[Init Pipeline and StateMachine]
|
|
801
|
+
B --> D[Open decision_log.jsonl replacing old file]
|
|
802
|
+
B --> E[Connect Arduino and wait for READY]
|
|
803
|
+
E --> F[Start Picamera2]
|
|
804
|
+
F --> G{Web enabled}
|
|
805
|
+
G -- Yes --> H[Start ThreadingHTTPServer]
|
|
806
|
+
G -- No --> I[Skip dashboard]
|
|
807
|
+
H --> J[Control cycle]
|
|
808
|
+
I --> J
|
|
809
|
+
J --> K[Capture crop HSV frame]
|
|
810
|
+
K --> L[Build masks and pillars]
|
|
811
|
+
L --> M[Update state machine]
|
|
812
|
+
M --> N[Compute correction speed servo]
|
|
813
|
+
N --> O[Send STEER and DRIVE]
|
|
814
|
+
O --> P[Update preview status and decision log]
|
|
815
|
+
P --> J
|
|
816
|
+
```
|
|
817
|
+
|
|
818
|
+
### `RobotRunner.cycle` Detailed Flow
|
|
819
|
+
|
|
820
|
+
```mermaid
|
|
821
|
+
flowchart TD
|
|
822
|
+
A[Capture BGR frame] --> B[Crop and convert to HSV]
|
|
823
|
+
B --> C[Measure orange blue marker ROI]
|
|
824
|
+
B --> D[Measure left right wall ROIs]
|
|
825
|
+
B --> E[Detect and stabilize pillars]
|
|
826
|
+
C --> F[StateMachine should_transition_state]
|
|
827
|
+
D --> F
|
|
828
|
+
E --> F
|
|
829
|
+
F --> G{Searching for round direction}
|
|
830
|
+
G -- Yes --> H[Add wall-edge direction vote]
|
|
831
|
+
G -- No --> I[Keep current direction]
|
|
832
|
+
H --> J[Compute correction]
|
|
833
|
+
I --> J
|
|
834
|
+
J --> K[Map correction to servo angle]
|
|
835
|
+
K --> L[Select speed for state]
|
|
836
|
+
L --> M{Manual command active}
|
|
837
|
+
M -- Yes --> N[Override servo or drive output]
|
|
838
|
+
M -- No --> O[Use automatic output]
|
|
839
|
+
N --> P[Apply serial output]
|
|
840
|
+
O --> P
|
|
841
|
+
P --> Q[Update overlay status and logs]
|
|
842
|
+
```
|
|
843
|
+
|
|
844
|
+
### `StateMachine.should_transition_state` Flow
|
|
845
|
+
|
|
846
|
+
```mermaid
|
|
847
|
+
flowchart TD
|
|
848
|
+
A[Current state and sensor portions] --> B{Scheduled state due}
|
|
849
|
+
B -- Yes --> C[Transition to scheduled state]
|
|
850
|
+
B -- Blocking wait --> Z[Hold current state]
|
|
851
|
+
B -- No --> D{STARTING}
|
|
852
|
+
D -- Pillar confirmed --> E[TRACKING-PILLAR]
|
|
853
|
+
D -- Direction known --> F[PD-CENTER]
|
|
854
|
+
D -- Otherwise --> Z
|
|
855
|
+
F --> G{Turns left <= 0}
|
|
856
|
+
G -- Yes --> H[Schedule DONE]
|
|
857
|
+
G -- No --> I{Pillar round and pillar confirmed}
|
|
858
|
+
I -- Track area --> E
|
|
859
|
+
I -- Avoid area --> J[AVOIDING-R or AVOIDING-G]
|
|
860
|
+
I -- No --> K{Line marker detected}
|
|
861
|
+
E --> L{Pillar lost}
|
|
862
|
+
L -- Yes --> F
|
|
863
|
+
L -- No --> I
|
|
864
|
+
J --> M{Avoid timer elapsed}
|
|
865
|
+
M -- Yes --> F
|
|
866
|
+
M -- No --> Z
|
|
867
|
+
K -- Blue with clockwise dir --> N[Schedule TURNING-L]
|
|
868
|
+
K -- Orange with counter-clockwise dir --> O[Schedule TURNING-R]
|
|
869
|
+
K -- Otherwise --> Z
|
|
870
|
+
N --> Z
|
|
871
|
+
O --> Z
|
|
872
|
+
```
|
|
873
|
+
|
|
874
|
+
### `compute_correction` Flow
|
|
875
|
+
|
|
876
|
+
```mermaid
|
|
877
|
+
flowchart TD
|
|
878
|
+
A[State and measured inputs] --> B{State}
|
|
879
|
+
B -- TRACKING-PILLAR --> C[Error from pillar x offset]
|
|
880
|
+
B -- PD-CENTER --> D[Error from active wall portion]
|
|
881
|
+
B -- TURNING-L --> E[Correction -1.0]
|
|
882
|
+
B -- TURNING-R --> F[Correction 1.0]
|
|
883
|
+
B -- AVOIDING-R --> G[Red avoidance phase correction]
|
|
884
|
+
B -- AVOIDING-G --> H[Green avoidance phase correction]
|
|
885
|
+
B -- STARTING or DONE --> I[Correction 0.0]
|
|
886
|
+
C --> J[PD correction kp error plus kd delta]
|
|
887
|
+
D --> J
|
|
888
|
+
G --> K{Wall guard active}
|
|
889
|
+
H --> K
|
|
890
|
+
K -- Wall too close --> L[Use guard correction]
|
|
891
|
+
K -- Clear --> M[Use phase correction]
|
|
892
|
+
E --> N[Clamp and output]
|
|
893
|
+
F --> N
|
|
894
|
+
I --> N
|
|
895
|
+
J --> N
|
|
896
|
+
L --> N
|
|
897
|
+
M --> N
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
---
|
|
901
|
+
|
|
902
|
+
## Arduino Software (`arduino_outputProxy/drive_bridge/src/main.cpp`) — Process Sections
|
|
903
|
+
|
|
904
|
+
### Section 1 — Hardware constants and limits
|
|
905
|
+
|
|
906
|
+
- `SERVO_PIN = 6`
|
|
907
|
+
- `MOTOR_IN1_PIN = 10`
|
|
908
|
+
- `MOTOR_IN2_PIN = 9`
|
|
909
|
+
- Optional `MOTOR_PWM_PIN = 11` for one-PWM/two-direction wiring
|
|
910
|
+
- Current motor mode: `USE_TWO_PWM_MOTOR_DRIVER = true` and `USE_SOFTWARE_PWM_FOR_TWO_PWM_DRIVER = true`
|
|
911
|
+
- Physical enable input is currently disabled: `USE_ENABLE_PIN = false`, with `ENABLE_PIN = 7` reserved if enabled later
|
|
912
|
+
- Steering bounds: `SERVO_MIN_DEG = 15`, `SERVO_CENTER_DEG = 90`, `SERVO_MAX_DEG = 165`
|
|
913
|
+
- Motor output bound: `MOTOR_MAX_PWM = 200`
|
|
914
|
+
- Ramp settings: `MOTOR_RAMP_INTERVAL_MS = 20`, `MOTOR_RAMP_STEP = 2`
|
|
915
|
+
- Command timeout: `COMMAND_TIMEOUT_MS = 1000`
|
|
916
|
+
- Command line buffer size: `LINE_BUF_SIZE = 64`
|
|
917
|
+
|
|
918
|
+
### Section 2 — Setup phase
|
|
919
|
+
|
|
920
|
+
`setup()` performs deterministic startup:
|
|
921
|
+
|
|
922
|
+
- Configure motor pins and optional enable pin
|
|
923
|
+
- Attach and center steering servo
|
|
924
|
+
- Stop motor output
|
|
925
|
+
- Start serial at `9600`
|
|
926
|
+
- Emit `READY` after startup delay
|
|
927
|
+
|
|
928
|
+
### Section 3 — Command receive and parse phase
|
|
929
|
+
|
|
930
|
+
`loop()` and `handleCommand` implement a strict parser:
|
|
931
|
+
|
|
932
|
+
1. Read bytes until newline
|
|
933
|
+
2. Null-terminate the line buffer
|
|
934
|
+
3. Split `NAME:VALUE` at the colon
|
|
935
|
+
4. Parse the integer payload with `parseIntStrict`
|
|
936
|
+
5. Execute only supported commands
|
|
937
|
+
|
|
938
|
+
Supported commands:
|
|
939
|
+
|
|
940
|
+
- `STOP`
|
|
941
|
+
- `STEER:<int>`
|
|
942
|
+
- `DRIVE:<int>`
|
|
943
|
+
|
|
944
|
+
### Section 4 — Actuator execution phase
|
|
945
|
+
|
|
946
|
+
- `writeSteering` clamps absolute servo angle to `15..165`
|
|
947
|
+
- `writeMotorPwm` clamps speed to `-200..200`
|
|
948
|
+
- `updateMotorRamp` moves `currentSpeed` toward `targetSpeed` in small steps
|
|
949
|
+
- If no valid command arrives for `1000 ms`, target speed returns to zero
|
|
950
|
+
- If `USE_ENABLE_PIN` is enabled and the input is not active, motor target speed is forced to zero
|
|
951
|
+
|
|
952
|
+
### Arduino Command Processing Flow
|
|
953
|
+
|
|
954
|
+
```mermaid
|
|
955
|
+
flowchart TD
|
|
956
|
+
A[Serial byte received] --> B{Is newline}
|
|
957
|
+
B -- No --> C[Append to line buffer]
|
|
958
|
+
B -- Yes --> D[Null terminate line]
|
|
959
|
+
D --> E[handleCommand]
|
|
960
|
+
E --> F{STOP command}
|
|
961
|
+
F -- Yes --> G[Center steering and stop motor]
|
|
962
|
+
F -- No --> H[Parse name and value]
|
|
963
|
+
H --> I{Valid integer value}
|
|
964
|
+
I -- No --> J[Print ERR:VALUE]
|
|
965
|
+
I -- Yes --> K{STEER or DRIVE}
|
|
966
|
+
K -- STEER --> L[Clamp and write steering angle]
|
|
967
|
+
K -- DRIVE --> M[Set bounded target speed]
|
|
968
|
+
K -- Other --> N[Print ERR:COMMAND]
|
|
969
|
+
```
|
|
970
|
+
|
|
971
|
+
### `updateMotorRamp` Flow
|
|
972
|
+
|
|
973
|
+
```mermaid
|
|
974
|
+
flowchart TD
|
|
975
|
+
A[Current speed and target speed] --> B{20 ms elapsed}
|
|
976
|
+
B -- No --> C[Keep current output]
|
|
977
|
+
B -- Yes --> D{current < target}
|
|
978
|
+
D -- Yes --> E[Increase by ramp step]
|
|
979
|
+
D -- No --> F{current > target}
|
|
980
|
+
F -- Yes --> G[Decrease by ramp step]
|
|
981
|
+
F -- No --> H[No speed change]
|
|
982
|
+
E --> I[writeMotorPwm]
|
|
983
|
+
G --> I
|
|
984
|
+
H --> I
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### `writeMotorPwm` Flow
|
|
988
|
+
|
|
989
|
+
```mermaid
|
|
990
|
+
flowchart TD
|
|
991
|
+
A[Input speed] --> B[Clamp to -200..200]
|
|
992
|
+
B --> C{Outputs enabled}
|
|
993
|
+
C -- No --> D[Force speed 0]
|
|
994
|
+
C -- Yes --> E{Two PWM driver}
|
|
995
|
+
E -- Software PWM --> F[Store outputSpeed for software PWM loop]
|
|
996
|
+
E -- Hardware PWM --> G[Analog write one motor pin]
|
|
997
|
+
E -- One PWM driver --> H[Set direction pins and PWM pin]
|
|
998
|
+
D --> I[Motor pins low]
|
|
999
|
+
F --> J[updateSoftwareMotorPwm drives duty cycle]
|
|
1000
|
+
G --> K[Motor output updated]
|
|
1001
|
+
H --> K
|
|
1002
|
+
```
|
|
1003
|
+
|
|
1004
|
+
---
|
|
1005
|
+
|
|
1006
|
+
## Raspberry Pi ↔ Arduino Runtime Interaction
|
|
1007
|
+
|
|
1008
|
+
```mermaid
|
|
1009
|
+
sequenceDiagram
|
|
1010
|
+
participant Cam as Camera
|
|
1011
|
+
participant Pi as Raspberry Pi robot_runtime.py
|
|
1012
|
+
participant Log as decision_log.jsonl
|
|
1013
|
+
participant Ard as Arduino drive_bridge
|
|
1014
|
+
Cam->>Pi: RGB frame
|
|
1015
|
+
Pi->>Pi: Vision masks, state machine, correction
|
|
1016
|
+
Pi->>Log: State and drive decision records
|
|
1017
|
+
Pi->>Ard: STEER:<angle>
|
|
1018
|
+
Pi->>Ard: DRIVE:<speed>
|
|
1019
|
+
Ard->>Ard: Servo and motor ramp execution
|
|
1020
|
+
Pi->>Pi: Update status and video feed
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
1. The camera streams frames into Pi-side perception/control code.
|
|
1024
|
+
2. Pi converts each frame into steering angle and drive speed decisions.
|
|
1025
|
+
3. Those decisions are logged as lightweight JSONL records and sent to Arduino over USB serial at `9600` baud.
|
|
1026
|
+
4. Arduino parses complete command lines, validates payloads, and updates actuator outputs.
|
|
1027
|
+
5. In parallel, Pi publishes status, configuration, calibration controls, and MJPEG video for operators.
|
|
1028
|
+
|
|
1029
|
+
---
|
|
1030
|
+
|
|
1031
|
+
## System Thinking and Engineering Decisions
|
|
1032
|
+
|
|
1033
|
+
This section documents the key software constraints, tradeoffs, and risks that shaped the current implementation.
|
|
1034
|
+
|
|
1035
|
+
### Constraints
|
|
1036
|
+
|
|
1037
|
+
| Constraint | Value / Limit | Impact |
|
|
1038
|
+
|---|---|---|
|
|
1039
|
+
| WRO 2026 start procedure | Robot is switched on, waits, then starts after one start action | Current runtime starts immediately after launch; physical start-button wait is not implemented yet |
|
|
1040
|
+
| Round direction | Randomly chosen before each challenge round | Direction can be fixed with CLI/config or inferred by wall-edge voting |
|
|
1041
|
+
| Challenge length | Open and Obstacle rounds are three minutes | Runtime keeps decisions lightweight and avoids storing raw frames by default |
|
|
1042
|
+
| Obstacle rule | Red on right, green on left | State machine has color-specific avoid states and correction values |
|
|
1043
|
+
| Actuator timing | Linux is not deterministic for PWM | Arduino owns servo and motor output |
|
|
1044
|
+
| Tuning under venue lighting | HSV thresholds move with lighting | Web sampling updates color ranges without reflashing firmware |
|
|
1045
|
+
|
|
1046
|
+
### Tradeoffs and Decisions
|
|
1047
|
+
|
|
1048
|
+
#### Pi-only control vs. Pi + Arduino split
|
|
1049
|
+
The Pi has enough processing power for camera vision, but Linux timing is not the right place to generate direct motor/servo output. We keep perception and planning on the Pi and move low-level actuator execution to Arduino over a small serial protocol.
|
|
1050
|
+
|
|
1051
|
+
#### Fixed direction vs. automatic direction voting
|
|
1052
|
+
The challenge direction is known before each round, so `--fixed-dir` can be used when we want deterministic setup. The automatic mode remains useful for testing because `lap_direction.find_round_dir` can vote from wall-edge geometry until the configured threshold is reached.
|
|
1053
|
+
|
|
1054
|
+
#### Raw-data logging vs. decision logging
|
|
1055
|
+
The system logs decisions instead of full camera frames. This keeps one readable `decision_log.jsonl` per run with the inputs and reasoning values needed to debug state transitions, without filling storage with video data.
|
|
1056
|
+
|
|
1057
|
+
#### Dashboard framework choice
|
|
1058
|
+
The dashboard uses `ThreadingHTTPServer` instead of Flask. The UI is a single embedded HTML page with JSON endpoints, which keeps the runtime dependency set small on the Raspberry Pi.
|
|
1059
|
+
|
|
1060
|
+
### Risk Identification and Mitigation
|
|
1061
|
+
|
|
1062
|
+
| Risk | Likelihood | Mitigation |
|
|
1063
|
+
|---|---|---|
|
|
1064
|
+
| Serial port changes between boots | Medium | Auto-detect `/dev/ttyACM*`, `/dev/ttyUSB*`, then pyserial port listing |
|
|
1065
|
+
| Arduino receives stale commands | Medium | Firmware forces target speed to zero after `COMMAND_TIMEOUT_MS` |
|
|
1066
|
+
| Pillar detection flickers | Medium | EMA area smoothing, confirm frames, and lost-frame hold |
|
|
1067
|
+
| Colored floor markers pollute wall mask | Medium | Wall mask excludes detected red, green, orange, blue, and pink pixels |
|
|
1068
|
+
| Venue lighting changes HSV thresholds | High | Browser-based color sampling updates filter ranges and saves settings |
|
|
1069
|
+
| Decision behavior is hard to review after a run | Medium | Central JSONL decision log records state changes and sampled drive outputs |
|
|
1070
|
+
|
|
1071
|
+
### Tests && Decisions
|
|
1072
|
+
|
|
1073
|
+
To make the current software decisions reproducible, we keep the values that most affect behavior in `raspy/settings.json`:
|
|
1074
|
+
|
|
1075
|
+
| Decision area | Setting / file | Current value or behavior | Reason |
|
|
1076
|
+
|---|---|---|---|
|
|
1077
|
+
| Camera crop | `camera.crop_height` | `190` | Process only the lower track area used for driving |
|
|
1078
|
+
| Drive speeds | `control.speed`, `turn_speed`, `avoid_speed` | `160`, `155`, `115` | Slow down during obstacle handling while keeping normal pace higher |
|
|
1079
|
+
| Steering bounds | `servo_min_deg`, `servo_center_deg`, `servo_max_deg` | `15`, `90`, `165` | Protect steering hardware and keep command mapping predictable |
|
|
1080
|
+
| Direction behavior | `behavior.fixed_round_dir` | `0` | Use automatic direction search unless set for a round |
|
|
1081
|
+
| Marker threshold | `line_marker_min_portion` | `0.25` | Require a clear orange/blue marker signal before turn scheduling |
|
|
1082
|
+
| Pillar avoid trigger | `pillar_avoid_area` | `2800` | Avoid only when the pillar is close and confirmed |
|
|
1083
|
+
| Decision logging | `decision_log.path` | `decision_log.jsonl` | Replace one log file per run for simple review |
|
|
1084
|
+
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
|
|
1088
|
+
<a name="utilities"></a>
|
|
1089
|
+
|
|
1090
|
+
|
|
1091
|
+
## Utilities
|
|
1092
|
+
|
|
1093
|
+
|
|
1094
|
+
<a name="failsafe"></a>
|
|
1095
|
+
|
|
1096
|
+
|
|
1097
|
+
### Failsafe Mechanisms
|
|
1098
|
+
|
|
1099
|
+
We implemented multiple layers of failsafe protection to prevent hardware damage and ensure safe competition runs.
|
|
1100
|
+
|
|
1101
|
+
#### Hardware Failsafes
|
|
1102
|
+
|
|
1103
|
+
**Wire color-coding:**
|
|
1104
|
+
We wrapped wires with colored tape so that every wire belonging to the same subsystem shares the same color. This reduces reconnection errors when disassembling and reassembling the robot between rounds. During early testing, an unlabeled wire became detached and was difficult to trace; after introducing color coding, this type of error has not recurred.
|
|
1105
|
+
|
|
1106
|
+
**Low-voltage indicator:**
|
|
1107
|
+
The LM2596 step-down module (5 V sensor rail) has an onboard LED that dims when input voltage falls below ~7.5 V. This gives us a visual warning that the LiPo cell is approaching its minimum safe voltage before the servo response degrades. We also keep a spare charged battery pack ready so we can swap within the pit stop window.
|
|
1108
|
+
|
|
1109
|
+
**Servo mechanical limits:**
|
|
1110
|
+
The steering servo is constrained in firmware to an absolute range of 15°–165°. This prevents a software error from issuing a command that would physically damage the servo linkage or lego steering module.
|
|
1111
|
+
|
|
1112
|
+
**Motor driver protection:**
|
|
1113
|
+
The DRV8874 provides overcurrent shutdown and thermal protection. If the motor stalls or the robot jams against a wall, the driver enters protection mode rather than burning the motor or wiring.
|
|
1114
|
+
|
|
1115
|
+
#### Software Failsafes
|
|
1116
|
+
|
|
1117
|
+
**Done-state stop:**
|
|
1118
|
+
When the state machine reaches `DONE`, the Pi centers the steering and sends zero drive speed. This keeps the vehicle from continuing after the configured turn count is complete.
|
|
1119
|
+
|
|
1120
|
+
**Arduino speed ramp limiter:**
|
|
1121
|
+
The Arduino changes motor output by `MOTOR_RAMP_STEP = 2` every `MOTOR_RAMP_INTERVAL_MS = 20`. This prevents sudden torque spikes that could cause the chassis to lose traction or cause the motor adapter to slip.
|
|
1122
|
+
|
|
1123
|
+
**Serial command validation:**
|
|
1124
|
+
The Arduino parser only executes commands that match the expected format (`NAME:VALUE` with a valid integer). Malformed or truncated packets are silently discarded, so a noise burst on the USB serial link cannot cause unsafe motor or servo movement.
|
|
1125
|
+
|
|
1126
|
+
**Serial startup handshake:**
|
|
1127
|
+
On power-on the Raspberry Pi always resets the Arduino when it opens the serial port. We solved this with a software handshake: the Arduino emits a ready signal after startup, and the Pi waits for that signal before sending any drive commands. This prevents the robot from moving before the Pi has initialized the camera and loaded configuration.
|
|
1128
|
+
|
|
1129
|
+
**Command timeout stop:**
|
|
1130
|
+
If the Arduino does not receive a valid command for `COMMAND_TIMEOUT_MS = 1000`, it forces target speed to zero. This prevents stale Pi commands from keeping the motor active after a runtime interruption.
|
|
1131
|
+
|
|
1132
|
+
<a name="debugging-tools"></a>
|
|
1133
|
+
|
|
1134
|
+
|
|
1135
|
+
### Debugging Tools
|
|
1136
|
+
|
|
1137
|
+
**Python (Raspberry Pi):** We use **Visual Studio Code** with the Pylance extension for development. The live web dashboard (`/` and `/video_feed`) allows us to observe the robot's perception state, adjust color thresholds, and test commands in real time without stopping the run. The MJPEG video feed (`/video_feed`) streams the annotated debug overlay directly from `raspy/robot_runtime.py` so we can diagnose wall detection or pillar detection issues without an HDMI cable.
|
|
1138
|
+
|
|
1139
|
+
**Arduino (C++):** We use the Arduino toolchain through PlatformIO, plus a serial monitor at `9600`, to inspect incoming command strings and verify that the Pi is sending correctly formatted `STEER` and `DRIVE` packets. During hardware bring-up we also used the serial monitor to verify servo and motor responses independently of the Pi.
|
|
1140
|
+
|
|
1141
|
+
<a name="web-debug-interface"></a>
|
|
1142
|
+
|
|
1143
|
+
#### Web Debug Interface
|
|
1144
|
+
|
|
1145
|
+
Below are screenshots of our real-time debug dashboard used during testing and competition preparation:
|
|
1146
|
+
|
|
1147
|
+
<img width="1544" height="954" alt="Web Debug Interface - Main Controls and Video" src="https://github.com/user-attachments/assets/23aca0f5-e4b5-4fd7-a334-6d787f2c4480" />
|
|
1148
|
+
|
|
1149
|
+
<img width="1547" height="962" alt="Web Debug Interface - Calibration and Runtime Status" src="https://github.com/user-attachments/assets/31be89b1-89ba-4637-bf5b-15cd3b1391df" />
|
|
1150
|
+
|
|
1151
|
+
What this interface is used for:
|
|
1152
|
+
|
|
1153
|
+
- **Live monitoring:** We watch the MJPEG stream (`/video_feed`) with overlays to confirm wall and pillar detection quality in real time.
|
|
1154
|
+
- **Fast calibration:** We tune HSV bounds and camera/control parameters from the browser, then persist them with `/api/save`.
|
|
1155
|
+
- **Runtime diagnostics:** We inspect `/api/status` and `/api/config` output to verify state, direction, active thresholds, and serial settings before each run.
|
|
1156
|
+
- **Safe iteration loop:** Instead of reflashing firmware for each small adjustment, we tune through the web UI, test immediately on track, and only commit stable parameter sets.
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
<a name="team-photos"></a>
|
|
1160
|
+
|
|
1161
|
+
|
|
1162
|
+
## Team Photos
|
|
1163
|
+
|
|
1164
|
+
[Team Photo](https://github.com/user-attachments/assets/fc466126-dbe5-4d71-9a1d-e72979701b23)
|
|
1165
|
+
|
|
1166
|
+
<a name="demonstration-videos"></a>
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
## Demonstration Videos
|
|
1170
|
+
|
|
1171
|
+
- [Open Round](https://youtu.be/vSCUsRolfGY)
|
|
1172
|
+
- [Obstacle Round](https://youtu.be/aSWpX4NWNCk)
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
<a name="contributors"></a>
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
## Contributors 👥
|
|
1181
|
+
|
|
1182
|
+
- berkemutluu
|
|
1183
|
+
- ardaberkk
|
|
1184
|
+
|
|
1185
|
+
[Team Photo](https://github.com/user-attachments/assets/fc466126-dbe5-4d71-9a1d-e72979701b23)
|
|
1186
|
+
|
|
1187
|
+
<a name="sources"></a>
|
|
1188
|
+
|
|
1189
|
+
|
|
1190
|
+
## Resources
|
|
1191
|
+
- 3D Parts Development [Onshape](https://onshape.com/)
|
|
1192
|
+
- Ackerman Steering Mechanism [Wikipedia](https://en.wikipedia.org/wiki/Ackermann_steering_geometry)
|
|
1193
|
+
- [Help with Open CV](https://learnopencv.com/)
|
|
1194
|
+
- Lego Modules && Instruction Modules Creation [Bricklink Studio](https://www.bricklink.com/v3/studio/download.page)
|
|
1195
|
+
|
|
1196
|
+
[Back to top](#top)
|