G18: Emergency Vehicle Traffic Preemption System - shalan/CSCE4301-WiKi GitHub Wiki
| Name | GitHub |
|---|---|
| Ziad Gaballah | ZiadGaballah |
| Hamza Kamel | hamzaabbas17 |
| Khadeejah Iraky | KoukiXia |
Github Repo: https://github.com/KoukiXia/Emergency-Vehicle-Traffic-Preemption-System
Link to Proposal Slides: https://github.com/KoukiXia/Emergency-Vehicle-Traffic-Preemption-System/blob/main/Slides%201.pdf
Link to Progress Slides: https://github.com/KoukiXia/Emergency-Vehicle-Traffic-Preemption-System/blob/main/Slides%202.pdf
Link to Final Slides: https://github.com/KoukiXia/Emergency-Vehicle-Traffic-Preemption-System/blob/main/Slides%201.pdf
Figure 1. Physical deployment concept. The Ambulance Unit (foreground) carries the GPS antenna, emergency-mode toggle, OLED link-status display, and LoRa radio. The Intersection Unit (mounted on the signal pole, cutaway shown inset) hosts the second ESP32, LoRa receiver, OLED, and buzzer, and drives a standard 4-approach signal head. The two communicate over LoRa at 868 MHz.
This wiki documents GreenWave's full lifecycle: the original proposal, the M3 simulation-based progress demo, the as-built hardware system delivered at M4, and an honest accounting of what was and wasn't implemented relative to the original scope.
- Β§1 is the proposal as submitted. Some details (ESP32, 868 MHz, OLEDs, GPS, 4-approach signal head) were later revised β see Β§2 and Β§4 for the as-built design.
- Β§2 is the as-built architecture (the truthful version).
- Β§3 is the M3 simulation progress checkpoint.
- Β§4 is the Checkpoint B hardware integration update.
- Β§5 is the final implementation: firmware architecture, RTOS task design, protocol, state machine.
- Β§6 is testing and validation, including the honest FR status table.
- Β§7 is project management.
- Β§8 is lessons learned and known limitations.
- Β§9 is media: photos, video, and serial captures.
In Cairo and most Egyptian cities, ambulances lose critical minutes stuck in gridlock. Published figures put the average ambulance response time in Egypt at 8 to 15 minutes, and it is common to see emergency vehicles trapped behind traffic with sirens blaring. For conditions like cardiac arrest, stroke, or major trauma, every minute of delay measurably reduces survival rate. Meanwhile, cities like Dubai, several US metros, and parts of Germany and the UK already deploy Emergency Vehicle Preemption (EVP) systems (Opticom, EMTRAC, Commsignia V2X) that turn intersections green for approaching emergency vehicles, cutting response times by 14 to 23 percent. No comparable system is deployed in Egypt.
GreenWave is a prototype Emergency Vehicle Preemption system built around two microcontrollers running FreeRTOS, connected by a LoRa radio link. An "ambulance unit" carries a physical "Emergency Mode" switch. When the driver asserts the switch, it broadcasts a signed preemption request packet over LoRa every two seconds. A stationary "intersection unit" runs a multi-phase traffic light state machine with red/yellow/green LEDs for two approaches (priority direction and cross-traffic). On receiving a valid preemption request, the intersection controller safely aborts its current phase (respecting yellow-clearance and all-red intervals), grants a green to the priority approach, holds it while packets keep arriving, then returns to normal cycle when the ambulance has passed.
The project demonstrates hardware-software co-design (microcontrollers + LoRa + actuated signal head), non-trivial real-time behavior (phase safety, clearance timing, preemption arbitration), an RTOS architecture with multiple concurrent tasks communicating through binary semaphores, and authenticated wireless V2I (vehicle-to-infrastructure) communication.
[See the original proposal for the full literature scan and motivation.]
Briefly: Egypt has no deployed EVP system; international precedent exists (Opticom in US since the 1970s, modern GPS-radio EVP in Seattle/Minneapolis showing 14β23% response-time reduction, Dubai RTA's smart-ambulance program). We address the gap with a student-scale, open, inexpensive prototype.
Two ESP32 nodes running FreeRTOS, LoRa as the V2I radio, GPS on the vehicle, a 4-approach signal head with 12 LEDs. The ambulance unit was intended to fuse GPS speed, heading, and distance-to-intersection to gate preemption requests. The intersection unit was to run a 4-phase signal FSM with directional filtering (Β±45Β° heading match) and GPS-based clearance detection.
The original FR list (FR1βFR11) defined RTOS architecture, GPS-gated transmission, directional filtering, safe phase transitions, HMAC authentication, replay protection, fail-safe flashing red, manual override, and OLED observability.
Several of these were revised, partially completed, or descoped during implementation β see Β§2 (as-built) and Β§6.4 (honest FR status table) for the current state.
This section documents the actual delivered system. Where the proposal differs, the as-built version takes precedence and the divergence is noted.
flowchart LR
subgraph AMBULANCE["π AMBULANCE UNIT"]
direction TB
BTN_A["Emergency<br>Button (PA8)"]
MCU_A["STM32L432KC<br>Nucleo-L432KC<br><br>FreeRTOS<br>β’ buttonTask<br>β’ radioTask (2s)<br>β’ sirenTask (2Hz)<br>β’ defaultTask (1Hz)"]
LED_A["LD3<br>Siren Indicator<br>(PB3)"]
LORA_A["SX1278 LoRa<br>433 MHz"]
ANT_A["Ξ»/4 Antenna<br>17 cm wire"]
BTN_A -->|EXTI falling<br>+ semaphore| MCU_A
MCU_A -->|GPIO toggle| LED_A
MCU_A <-->|SPI1| LORA_A
LORA_A --- ANT_A
end
subgraph INTERSECTION["π¦ INTERSECTION UNIT"]
direction TB
ANT_I["Ξ»/4 Antenna<br>17 cm wire"]
LORA_I["SX1278 LoRa<br>433 MHz"]
MCU_I["STM32L432KC<br>Nucleo-L432KC<br><br>FreeRTOS<br>β’ radioRxTask (sem)<br>β’ stateMachineTask (10Hz)<br>β’ buttonTask (sem + long-press)<br>β’ defaultTask (1Hz)"]
BTN_I["Test Button<br>(PA8)<br>short=preempt<br>long=override"]
LED_LD3["LD3<br>RX Indicator"]
LEDS_NS["NS LEDs<br>RED Β· YEL Β· GRN<br>PA0 PA1 PA4"]
LEDS_EW["EW LEDs<br>RED Β· YEL Β· GRN<br>PA11 PA12 PB5"]
ANT_I --- LORA_I
LORA_I <-->|SPI1| MCU_I
LORA_I -.->|DIO0 EXTI<br>+ semaphore| MCU_I
BTN_I -->|EXTI + semaphore| MCU_I
MCU_I -->|GPIO| LED_LD3
MCU_I -->|GPIO| LEDS_NS
MCU_I -->|GPIO| LEDS_EW
end
ANT_A <==>|"LoRa 433 MHz<br>SF7 Β· BW 125 kHz<br>HMAC-signed<br>24-byte packet<br>every 2 seconds"| ANT_I
style AMBULANCE fill:#fff5e6,stroke:#cc7700,stroke-width:2px
style INTERSECTION fill:#e6f3ff,stroke:#0066cc,stroke-width:2px
style LORA_A fill:#ffe6e6
style LORA_I fill:#ffe6e6
style MCU_A fill:#f0f0f0
style MCU_I fill:#f0f0f0
=========== AMBULANCE UNIT ============
| |
+-----------+ EXTI| +--------------------------------+ |
| Emergency |---->|-->| | |
| Switch | | | STM32L432KC (Nucleo board) | |
+-----------+ | | FreeRTOS Kernel | |
+-----------+ GPIO| | |SPI| LoRa 433 MHz
| LD3 LED |<----|---| Tasks (4): |---| ===========>>
| (siren) | | | - buttonTask (sem-driven) | | (SX1278 TX)
+-----------+ | | - radioTask (every 2 s) | | HMAC-signed
| | - sirenTask (2 Hz blink) | | preempt pkt
| | - defaultTask (1 Hz hb) | | while EMRG on
| +--------------------------------+ |
| Power: USB 5V -> Nucleo onboard 3V3 |
==========================================
| ~ a few meters of free space (demo)
v
========== INTERSECTION UNIT ==========
| |
| +--------------------------------+ | GPIO +------+
| | |-->| NS R/Y/G |
LoRa SPI | | STM32L432KC (Nucleo board) | | (3 LEDs) |
>>===============>|-->| FreeRTOS Kernel | +------+
(SX1278 RX) | | | | GPIO +------+
| | Tasks (4): |-->| EW R/Y/G |
| | - radioRxTask (sem-driven) | | (3 LEDs) |
| | - stateMachine (10 Hz) | +------+
| | - buttonTask (sem-driven, |GPIO| +------+
| | long-press) |--->| LD3 |
| | - defaultTask (1 Hz hb) | | (RX) |
| +--------------------------------+ | +------+
| ^ |
| | EXTI |
| (test button) |
| Power: USB 5V -> Nucleo onboard 3V3 |
===========================================
^
|
FR9: Failsafe β both reds flash 1 Hz on
LoRa init failure or manual override
| Element | Proposal | As Built | Reason |
|---|---|---|---|
| MCU | ESP32 Γ 2 | STM32 Nucleo-L432KC Γ 2 | Course/lecturer requirement was STM32 |
| LoRa chip | SX1276 | SX1278 (Ra-02 module) | Locally available; same protocol family |
| LoRa frequency | 868 MHz | 433 MHz | 433 MHz is the license-free ISM band in Egypt |
| Signal head | 4 approaches Γ R/Y/G (12 LEDs) | 2 directions Γ R/Y/G (6 LEDs) | Scope cut: tabletop demo crossroads only needs 2 perpendicular phases |
| GPS | NEO-6M on ambulance | None | Removed at M3 (see Β§3.3) |
| OLED displays | SSD1306 on both nodes | None | Scope cut at Checkpoint B; UART over ST-Link replaced |
| Buzzer | Active buzzer on intersection | None | Scope cut |
| Direction selection | DIP switch + RSSI-trend detection | Single hard-coded priority direction (EW) | Scope cut |
| Antennas | Tuned Ξ»/4 stub | 17 cm wire pressed into ANT through-hole | No soldering iron available |
LoRa 433 MHz, SF7, BW 125 kHz, CR 4/5, sync word 0xF3, TX power 2 dBm. Packet format:
emergency_packet_t {
uint32_t magic; // 0x47573138 ("GW18")
uint8_t ambulance_id; // 0x01
uint8_t direction; // hardcoded DIR_EAST
uint8_t flags; // 0x01 = emergency active
uint8_t reserved;
uint32_t sequence; // monotonically increasing
uint32_t timestamp_ms; // ambulance HAL_GetTick()
uint8_t hmac[8]; // truncated HMAC-SHA256
}
Total 24 bytes. Signed with HMAC-SHA256 on the ambulance side using a 32-byte shared secret compiled into both nodes' firmware. Note: HMAC verification on the intersection side is implemented but has an integration issue β see Β§6.4 (FR7, FR8 status) for honest details.
Both nodes are powered over USB (Nucleo's onboard ST-LINK USB) at 5 V, with the Nucleo's LDO providing 3.3 V to the LoRa module.
Firmware for both nodes was complete and validated end-to-end in the Wokwi simulator at M3. Hardware procurement was running behind schedule, so M3 was delivered as a Wokwi-based simulation demo. Every safety-critical behavior β phase FSM with min-green/yellow/all-red clearance, preemption arbitration, manual override, fail-safe β was exercised in simulation.
Figure 4. Ambulance unit in Wokwi. ESP32 with the EMERGENCY momentary switch and the STATUS LED. With the switch asserted, the firmware enters emergency state, lights the status LED, and begins broadcasting authenticated preemption packets at the configured rate.
Figure 5. Intersection unit in Wokwi. Twelve LEDs implement the 4-approach signal head (N/S/E/W Γ R/Y/G). The four blue buttons (N/S/E/W AMBULANCE) inject a simulated preempt request from each approach, replacing the LoRa receive path during simulation. The red OVERRIDE button forces flashing-red fail-safe; the green CLEAR button releases an active preempt. The buzzer chirps while preempt is active. The screenshot captures a yellow-clearance phase mid-transition β proof that the FSM honors the mandatory clearance interval before changing approaches.
Ambulance Unit https://github.com/user-attachments/assets/7c5acce0-8f6f-4bad-ae81-6abd9b0f873b
Intersection Unit https://github.com/user-attachments/assets/3bd3108f-365d-47e8-95f4-dbacfad1dd52
- Serial-log capture of one full preempt event (entry β hold β release), with timestamps.
- Wokwi capture of the manual-override flashing-red fail-safe.
- *Wokwi capture of the all-red clearance interval between phases.
Original (proposal): Ambulance carried a NEO-6M GPS module; preempt packets included lat/lon/heading/speed; the intersection rejected requests whose heading did not match an approach within Β±45Β° and released the green when GPS showed the vehicle had cleared.
Revised (M3): GPS removed entirely. Approach identification was originally going to be replaced with packet-carried approach ID + RSSI-trend detection on the intersection.
Status post-implementation: The GPS removal stuck; that was the right call. The RSSI-trend replacement, however, was never implemented β the system instead uses a fixed 15-second preempt-hold timer that refreshes on every received packet, which is simpler and adequate for a tabletop demo but isn't the proximity-aware design we described at M3.
The simulation proved the phase FSM, the preempt arbitration, the HMAC verification (in simulation only), and the safe transitions through yellow and all-red clearances. The firmware was structured for FreeRTOS-style task separation even though the M3 build was on ESP-IDF and we later ported to STM32 + ARM Compiler 6 + the FreeRTOS provided by STM32CubeMX. The architecture survived the port; the file structure and task names changed.
Between M3 and Checkpoint B we migrated from ESP32 to STM32 Nucleo-L432KC (lecturer-mandated). The migration consisted of:
- Re-implementing the LoRa driver to talk to SX1278 over STM32 HAL SPI rather than ESP-IDF SPI.
- Re-implementing HMAC-SHA256 in plain C without depending on mbedTLS (to avoid Keil pack dependencies β the workshop's Pack Installer had broken vendor PDSC files).
- Switching the build toolchain to Keil Β΅Vision MDK with ARM Compiler 6.24.
- Switching CubeMX to generate FreeRTOS scaffolding (CMSIS-RTOS v2).
The application logic β phase FSM, preempt arbitration, packet format, task structure β was preserved.
- Ambulance unit: Nucleo-L432KC + SX1278 (Ra-02) + tactile button + onboard LD3 LED + 17 cm wire antenna, all on a breadboard mounted to a wooden laser-cut box.
- Intersection unit: Same Nucleo + SX1278 + 6 LEDs (R/Y/G Γ 2) + tactile button + onboard LD3 + 17 cm wire antenna, breadboard on a wooden laser-cut box.
- LED wiring: Each direction group (NS and EW) shares one current-limiting resistor on its common cathode β safe because the FSM guarantees only one LED in each group is lit at any time. NS group uses 330 Ξ©; EW group uses 100 Ξ©.
| Issue | Cause | Resolution |
|---|---|---|
| Initial LoRa init hang on ambulance | Loose SPI wiring after handling breadboard | Reseated all 8 LoRa wires (3V3, GND, SCK, MISO, MOSI, NSS, RST, DIO0) |
| Intersection received no packets after FreeRTOS port | A spurious lora_receive_continuous() re-arm call in the RX task was resetting the radio mid-operation |
Removed the re-arm. Continuous RX mode self-maintains |
| Keil free version 32 KB binary limit hit after adding FreeRTOS | FreeRTOS added ~6 KB to the image | Enabled -Oz size optimization in the compiler. Final image: ~28 KB |
| Compile errors against the CubeMX-generated FreeRTOS RVDS port | ARM Compiler 6 (armclang) does not support the RVDS port's __forceinline / __asm syntax |
Replaced portable/RVDS/ARM_CM4F/port.c and portmacro.h with the GCC-port equivalents from FreeRTOS V10.3.1, and updated the include path |
traceISR_ENTER/traceISR_EXIT undefined |
The downloaded GCC portmacro.h was from a newer FreeRTOS version than the CubeMX-bundled headers | Added empty-macro definitions for the three trace hooks in FreeRTOSConfig.h
|
| LED layout swapped during demo build | Physical LED wires were attached to the opposite GPIO groups vs. firmware mapping | Re-mapped the firmware so the priority direction (EW) corresponds to the actually-wired LEDs |
| HMAC verification rejecting valid packets |
packet_verify() integration issue between TX and RX HMAC paths β root cause not isolated in time |
Bypassed HMAC verification in RX path while leaving HMAC signing active on TX side; documented as FR7/FR8 partial β see Β§6.4 |
We added a counter for DIO0 interrupts (dio0_irq_count) and a counter for failed packet reads (read_failures) to the intersection's heartbeat output. These were instrumental in diagnosing the FreeRTOS re-arm bug: we could see that DIO0 was firing (so RF link worked) but reads were failing (so it was a software-side bug, not an antenna/hardware issue).
Both nodes run FreeRTOS (CMSIS-RTOS v2 API) on the STM32L432KC with the SysTick reserved for the kernel and TIM6 used as the HAL timebase source. Each node has four concurrent tasks plus the FreeRTOS idle task.
| Task | Period / Trigger | Priority | Job |
|---|---|---|---|
buttonTask |
Semaphore from EXTI ISR | High | Receives button press signal, toggles emergency_active, logs state change |
radioTask |
2 s periodic | Normal | If emergency_active, builds a signed packet and transmits via LoRa |
sirenTask |
500 ms periodic | Low | If emergency_active, toggles LD3 to produce a visual siren blink |
defaultTask (watchdog) |
1 s periodic | Normal | Prints status heartbeat to UART every 10 seconds |
| Task | Period / Trigger | Priority | Job |
|---|---|---|---|
radioRxTask |
Semaphore from DIO0 EXTI ISR | AboveNormal | Reads packet from LoRa FIFO, validates magic + ID, applies replay check, calls activate_preempt()
|
stateMachineTask |
100 ms periodic | High | Runs phase transitions, updates LED outputs, handles failsafe flashing |
buttonTask |
Semaphore from EXTI ISR + polling for long-press | Normal | Short press = simulate preempt; long press (β₯2 s) = toggle failsafe override |
defaultTask (watchdog) |
1 s periodic | Normal | Prints status heartbeat with phase, preempt state, packet counts, replay count, IRQ counts |
-
ISR β task signaling: binary semaphores (
packetSemHandle,buttonSemHandle). The ISR callsosSemaphoreRelease()(CMSIS-RTOS v2's ISR-safe API); the task blocks onosSemaphoreAcquire(..., osWaitForever). Zero CPU usage while idle, microsecond-latency wakeup on event. -
Task β task shared state:
volatileflags and counters for state read by multiple tasks (e.g.,emergency_active,current_phase,last_packet_ms,failsafe_active). On Cortex-M4 these are atomic for word-sized accesses, so no mutex was needed. - SPI bus: shared between tasks and the EXTI ISR. The LoRa driver's ISR work is brief; in practice the timing does not collide. A production version would add a mutex.
The intersection runs a 6-phase cycle:
PHASE_NS_GREEN (10 s)
β
PHASE_NS_YELLOW (3 s)
β
PHASE_ALLRED_1 (1.5 s)
β
PHASE_EW_GREEN (10 s normal, 15 s during preempt)
β
PHASE_EW_YELLOW (3 s)
β
PHASE_ALLRED_2 (1.5 s)
β
[back to PHASE_NS_GREEN]
Full cycle: 29 seconds (normal), or up to 34 seconds with active preemption.
When failsafe_active is set (LoRa init failure OR manual override via long-press), this normal state machine is bypassed: both red LEDs flash at 1 Hz; all other LEDs are forced off; preempt requests (LoRa or button) are ignored until failsafe is cleared.
stateDiagram-v2
direction TB
[*] --> NS_GREEN : Power-on (default)
[*] --> FAILSAFE : LoRa init fail
state "NS_GREEN<br>(10 s)" as NS_GREEN
state "NS_YELLOW<br>(3 s)" as NS_YELLOW
state "ALLRED_1<br>(1.5 s)" as ALLRED_1
state "EW_GREEN<br>(10 s normal<br>15 s preempt)" as EW_GREEN
state "EW_YELLOW<br>(3 s)" as EW_YELLOW
state "ALLRED_2<br>(1.5 s)" as ALLRED_2
state "FAILSAFE<br>both reds<br>flash 1 Hz" as FAILSAFE
NS_GREEN --> NS_YELLOW : timeout
NS_YELLOW --> ALLRED_1 : timeout
ALLRED_1 --> EW_GREEN : timeout
EW_GREEN --> EW_YELLOW : timeout AND no preempt
EW_YELLOW --> ALLRED_2 : timeout
ALLRED_2 --> NS_GREEN : timeout
NS_GREEN --> NS_YELLOW : β‘ preempt packet<br>(cut early)
EW_YELLOW --> EW_GREEN : β‘ preempt packet<br>(snap back)
ALLRED_2 --> EW_GREEN : β‘ preempt packet<br>(snap back)
EW_GREEN --> EW_GREEN : β‘ preempt packet<br>(refresh 15s timer)
NS_GREEN --> FAILSAFE : long-press button
NS_YELLOW --> FAILSAFE : long-press button
ALLRED_1 --> FAILSAFE : long-press button
EW_GREEN --> FAILSAFE : long-press button
EW_YELLOW --> FAILSAFE : long-press button
ALLRED_2 --> FAILSAFE : long-press button
FAILSAFE --> NS_GREEN : long-press button<br>(clean reset)
When a valid packet arrives (or the test button is short-pressed), activate_preempt() decides what to do based on the current phase:
| Current phase | Action |
|---|---|
NS_GREEN |
Cut to NS_YELLOW immediately (gives NS traffic the mandatory yellow warning) |
NS_YELLOW, ALLRED_1
|
Let them finish naturally β they're already heading to EW_GREEN |
EW_GREEN |
Restart the EW_GREEN timer (extends to PREEMPT_DURATION = 15 s) |
EW_YELLOW, ALLRED_2
|
Snap back to EW_GREEN (no second cycle through NS) |
Each new packet refreshes the 15-second timer, so as long as the ambulance keeps transmitting, EW stays green.
| Parameter | Value |
|---|---|
| Frequency | 433 MHz |
| Bandwidth | 125 kHz |
| Spreading Factor | SF7 |
| Coding Rate | 4/5 |
| Sync word | 0xF3 |
| TX Power | 2 dBm |
| Header mode | Explicit |
| CRC | Enabled |
| Mode | Continuous RX (intersection) / single-shot TX (ambulance) |
| Signal | STM32 pin | Nucleo header |
|---|---|---|
| LoRa SCK | PA5 | A4 |
| LoRa MISO | PA6 | A5 |
| LoRa MOSI | PA7 | A6 |
| LoRa NSS | PB0 | D3 |
| LoRa RST | PB1 | D6 |
| LoRa DIO0 | PA10 | D0 |
| Emergency button | PA8 | D9 (EXTI falling, internal pull-up) |
| Status LED (siren) | PB3 (LD3 onboard) | β |
| Signal | STM32 pin | Nucleo header |
|---|---|---|
| LoRa SCK, MISO, MOSI, NSS, RST, DIO0 | Same as ambulance | Same as ambulance |
| NS_RED | PA0 | A0 |
| NS_YELLOW | PA1 | A1 |
| NS_GREEN | PA4 | A3 |
| EW_RED | PA11 | D10 |
| EW_YELLOW | PA12 | D2 |
| EW_GREEN | PB5 | D11 |
| Test button (preempt / override) | PA8 | D9 (EXTI falling, internal pull-up) |
| LD3 (RX indicator) | PB3 (onboard) | β |
/Ambulance/
Core/Src/main.c β tasks + init
Core/Inc/main.h β pin defines
Core/Inc/FreeRTOSConfig.h
Drivers/lora.c, lora.h β SX1278 driver
Drivers/packet.c, packet.h β packet struct, HMAC, signing
Middlewares/FreeRTOS/...
/Intersection/
[same structure]
| Test | Method | Result |
|---|---|---|
| Ambulance button debounce | Pressed button 20 times in succession at various speeds | Each press toggles emergency exactly once (150 ms debounce in EXTI ISR confirmed) |
| Ambulance TX rate | Watched serial output during emergency mode | Exactly one TX seq=N every 2 s for >5 minutes; seq# strictly monotonic |
| Ambulance LD3 blink | Visual + measured with oscilloscope | 2 Hz square wave (500 ms on / 500 ms off) while emergency active, off otherwise |
| Intersection phase timing | Stopwatch comparison over multiple cycles | Each phase within Β±50 ms of nominal; total cycle 29.0 s as designed |
| Intersection LED correctness | Visual verification at each phase | Correct R/Y/G pattern at each phase; no overlap in same direction |
| LoRa link basic test | Powered both, watched intersection UART |
RX OK seq=N within 2 s of first ambulance TX (with HMAC verify bypassed) |
| Failsafe activation on LoRa init failure | Powered up intersection with LoRa NSS wire disconnected | Both red LEDs flashed at 1 Hz; ENTERING FAILSAFE MODE logged |
| Manual override toggle | Held test button for 2+ seconds | Failsafe toggled on; LEDs flashed red; MANUAL OVERRIDE ON logged. Second long-press cleared it |
| Scenario | Expected | Observed |
|---|---|---|
| Ambulance stationary in emergency mode, intersection running normal cycle | Intersection enters preempt within 2 s, EW goes green, holds for 15 s after ambulance turns off, then resumes | Passed |
| Ambulance briefly toggles emergency (single button press, then immediate second press) | Single packet transmitted, intersection preempts, then times out after 15 s | Passed |
| Press intersection's test button (short press, < 1 s) while no LoRa packets coming | Local preempt activated as if a packet had arrived | Passed |
| Hold intersection's test button (long press, β₯ 2 s) | Failsafe activated; both reds flash; LoRa-triggered preempt blocked | Passed |
| Preempt during NS_GREEN | NS cut to yellow immediately | Passed |
| Preempt during EW_YELLOW | Snap back to EW_GREEN | Passed |
| Multiple consecutive preempts | 15 s timer refreshes on each packet, EW stays green continuously | Passed |
| LoRa init failure (NSS disconnected) | Intersection enters failsafe automatically; flashing red | Passed |
End-to-end demo:
- Both nodes powered up. Both report
LoRa initialized OK(ambulance) /LoRa receiver active(intersection) on serial. - Intersection begins normal phase cycle.
- Operator presses ambulance button. Ambulance's LD3 blinks. Serial shows
TX seq=1, 2, 3, .... - Within 2 seconds the intersection's serial shows
RX OK seq=1,PREEMPT ACTIVATED, and the EW direction transitions to green via the appropriate clearance. - Intersection's LD3 lights solid (RX indicator).
- EW stays green while ambulance continues to transmit.
- Operator presses ambulance button again to disable emergency. Ambulance LD3 stops blinking. Transmissions stop.
- After 15 seconds of no packets, intersection logs
PREEMPT DEACTIVATED. Intersection's LD3 turns off. Normal cycle resumes.
| FR# | Requirement | Status | Notes |
|---|---|---|---|
| FR1 | FreeRTOS with β₯4 concurrent tasks | β Implemented | 4 tasks per node, CMSIS-RTOS v2, binary semaphores for ISR signaling |
| FR2 | Emergency switch gates transmission | β Implemented | TX only fires while emergency_active
|
| FR3 | GPS-gated 2 Hz transmission within 1 km | GPS removed at M3. TX is fixed 2-second interval (not 2 Hz). Geofence not implemented | |
| FR4 | Directional filtering Β±45Β° | GPS-based directional filter removed; one priority direction (EW) is hardcoded | |
| FR5 | Safe phase transitions | β Implemented | All preempt entries go through yellow + all-red clearance |
| FR6 | Preempt release on clear or timeout | β Implemented | 15 s of no packets = release |
| FR7 | HMAC-SHA256 authentication | HMAC signing is active on the TX (ambulance) side and packets are correctly signed. RX-side verification (packet_verify) was implemented in code but had an integration issue that caused valid packets to be rejected. For the demo, verification was bypassed in the RX path; magic number + ambulance ID checks remain active. The signing infrastructure is in place and the verification call site is wired up β only the verification function's internal logic needs the final fix |
|
| FR8 | Replay protection via sequence number | Sequence-number check code (last_seq_seen tracking, packets_replayed counter, reject branch) is implemented in StartRadioRxTask. However, because HMAC verification is currently bypassed (FR7), the replay check sits after a check that always passes β meaning it functions correctly in the code path but cannot be claimed as a defense-in-depth security feature until FR7 is fully working |
|
| FR9 | Fail-safe flashing red on fault | β Implemented | Both red LEDs flash at 1 Hz when failsafe_active is set. Triggered automatically on LoRa init failure, or manually via long-press of the test button (FR10). Preempt requests are ignored while failsafe is active |
| FR10 | Manual override β flashing red | β Implemented | Long-press (β₯2 s) of the intersection's test button toggles failsafe mode on/off. Short-press (< 2 s) still acts as a LoRa-fallback preempt trigger. Releasing failsafe cleanly resets the state machine to NS_GREEN |
| FR11 | OLED + serial observability | Serial logging is comprehensive (phase transitions, RX events, IRQ counts, replay rejections, override toggles). OLED was descoped |
Honest reading: of 11 FRs, 6 are fully implemented, 5 are partial or revised (FR3, FR4, FR7, FR8, FR11), and 0 are missing entirely. The two safety-related partials (FR7 HMAC verification, FR8 replay protection) are blocked by a single integration issue in the packet_verify function β a known target for post-demo cleanup. All other safety mechanisms (FR5 safe transitions, FR6 timeout release, FR9 failsafe, FR10 override) are fully working.
| Member | Primary Role | Responsibilities |
|---|---|---|
| Ziad Gaballah (Firmware & RTOS Lead) | Embedded software | FreeRTOS task architecture on both STM32s, traffic light state machine with clearance intervals, preemption arbitration logic, HMAC implementation, FreeRTOS port migration (RVDS β GCC), Keil toolchain bring-up, semaphore-based ISR signaling, failsafe and override implementation |
| Khadeejah Iraky (Hardware & Integration Lead) | Electronics and mechanical | Schematic and wiring, breadboard builds for both units, LoRa interfacing, signal-head LED driver, antenna fabrication, wooden enclosures, bring-up debugging, workshop component sourcing |
| Hamza Kamel (Connectivity & UX Lead) | Wireless and user interface | LoRa link bring-up, packet protocol design, SX1278 driver, wiki documentation throughout the semester, presentation slides, demo video |
All three members share responsibility for integration testing, the proposal and progress presentations, the final demo, and the wiki.
| Date | Milestone | Planned Deliverable | Actual |
|---|---|---|---|
| Tue, Apr 14, 2026 | M1: Team Formation | Team submitted, project title registered | β Done |
| Wed, Apr 15, 2026 | M2: Proposal Presentation | 5β10 slide proposal in class | β Done |
| Mon, Apr 20, 2026 | Checkpoint A: Wiki Setup | Wiki populated with approved proposal | β Done |
| Apr 20 β Apr 28 | Build Sprint 1 | Subsystems running independently | β Done in simulation; HW slipped |
| Wed, Apr 29, 2026 | M3: Progress Demo | Live demo of preempt | β Done as Wokwi simulation demo |
| Apr 29 β May 5 | Build Sprint 2 | Hardware bring-up; ESP32βSTM32 migration; FreeRTOS port debugging | β Done |
| Wed, May 6, 2026 | Checkpoint B: Integration Update | Wiki updated; integration status documented | β Done |
| May 6 β May 12 | Polish Sprint | RTOS semaphores, HMAC re-enable attempt, FR8/9/10 implementation, final wiki, rehearsal | β Done |
| Wed, May 13, 2026 | M4: Final Demo | Polished presentation, live demo on hardware, clean repo, complete wiki | β Done |
- The state machine design held up. The 6-phase FSM with hard-enforced yellow and all-red clearance survived every test, every preempt-arrival timing, and the entire platform migration from ESP32-Wokwi to STM32 hardware.
- The pivot to remove GPS at M3 was the right call. Indoor GPS was always going to be a demo-day liability.
- Splitting the project into ambulance + intersection let us debug in isolation. When the intersection wasn't receiving packets after the FreeRTOS port, we could confirm the ambulance was transmitting before suspecting the RF link.
-
Diagnostic counters paid for themselves.
dio0_irq_countandread_failureslocalized the post-RTOS RX bug to software in minutes. - FreeRTOS conversion taught us real RTOS primitives. The binary-semaphore design for ISR-to-task signaling is genuinely better than polling and gave us something defensible to show.
- Failsafe and override (FR9, FR10) shipped fully working. These are the safety promises the proposal made; getting them done in the polish sprint matters.
- Start with the real hardware platform from day one. ESP32-Wokwi was educationally useful but every hour spent on it had to be re-spent on STM32.
- Use Keil MDK Community Edition from the start. The free MDK-Lite's 32 KB limit forced a late optimization scramble.
-
Test HMAC verification end-to-end earlier. We had HMAC working in simulation, but the TX/RX integration bug in
packet_verifyonly surfaced in the polish sprint when there wasn't time to fully debug it. A simple loopback test between the two firmware images at M3 would have caught it. - Use a mutex for SPI bus access. The current design works because the ISR's SPI use happens not to collide with task SPI use. This is fragile; a FreeRTOS mutex would make it correct.
- HMAC verification is bypassed in the RX path (see FR7 / FR8 in Β§6.4). The signing infrastructure is fully in place β only the verifier function needs the final integration fix. This is a documented, time-boxed scope cut, not an architectural decision.
- No OLED display β observability is via UART through the ST-Link USB. Fine for development but unusual for a fielded device.
- No buzzer β visual-only signaling.
- Single hardcoded priority direction β the system assumes EW is the priority approach. A real system would receive direction from the ambulance and switch which approach is preempted.
-
No replay protection across power cycles β if the ambulance is reset mid-session (seq goes back to 1) while the intersection retains
last_seq_seen = 50, all post-reset packets are rejected as replays. Workaround: reset both devices together. - Antennas are wire pressed into through-hole β no SMA connectors or soldered joints. Works at room scale but flaky for any longer range.
- No hardware watchdog β the "watchdog task" is a heartbeat for human observability, not an IWDG-backed fault recovery mechanism.
-
Fix
packet_verifyto close out FR7 and unblock FR8. - Implement RSSI-trend proximity detection (the M3 pivot promised).
- Add a second intersection for a real "green wave" along a corridor.
- Add an OLED display so the demo doesn't require a serial console.
- Solder antennas instead of wire-in-through-hole.
- Power from batteries to free the demo from the lab bench.
- Field test at >100 m line-of-sight to characterize actual range.
GreenWave proves that a useful subset of an emergency vehicle preemption system can be built from $30 of parts and a few weeks of student work. The honest gaps in the implementation, documented in Β§6.4, are real and we don't paper over them. But the core demonstration β press a button on the ambulance, watch the intersection respond authentically with yellow-clearance before stopping cross traffic, watch the failsafe activate on fault β is fully working, and is a faithful tabletop model of what Opticom and EMTRAC do at city scale.
- Opticom Infrared and GPS Preemption Systems (Global Traffic Technologies)
- EMTRAC GPS preemption documentation
- "Effects of Emergency Vehicle Preemption on Crash Frequency" (FHWA studies, 2010s)
- Dubai RTA SCOOT and UTC-UX Fusion announcements
- C-V2X / ITS-G5 emergency preemption pilots (Ford / Commsignia 2021β2024)
- FreeRTOS Real-Time Kernel β Reference Manual (Amazon Web Services)
- Semtech SX1278 datasheet
- STM32L4xx HAL reference manual