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/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)