G18: Emergency Vehicle Traffic Preemption System - shalan/CSCE4301-WiKi GitHub Wiki

Project Title: GreenWave β€” Emergency Vehicle Traffic Preemption System

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

greenwave-hero

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.


Reader's Guide

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.

1. The Proposal

Abstract / Elevator Pitch

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.

Problem Statement

[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.

Original Solution (as proposed)

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.

Functional Requirements (proposed)

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.


2. System Architecture (As Built)

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
Loading

2.1 Top-Level Block Diagram

                    ===========  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

2.2 As-Built vs. Proposal β€” Hardware Deltas

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
IMG_3637

2.3 Communication

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.

2.4 Power

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.


3. Milestone 3 β€” Progress Update (April 29, 2026)

3.1 Headline Status

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.

Screenshot 2026-05-03 at 09 17 38

3.2 Wokwi Simulation Evidence

Screenshot 2026-05-03 at 09 32 01

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.

Screenshot 2026-05-03 at 09 31 23

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.

3.3 Pivot β€” GPS Removed

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.

3.4 What Worked 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.


4. Checkpoint B β€” Hardware Integration Update (May 6, 2026)

4.1 Platform Change: ESP32 β†’ STM32

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.

4.2 Hardware Build

  • 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 Ξ©.
IMG_3630 IMG_3636

4.3 Integration Issues Encountered

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

4.4 Diagnostic Approach

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


5. Final Implementation

5.1 Firmware Architecture

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.

Ambulance Tasks

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

Intersection Tasks

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

Inter-Task Communication

  • ISR β†’ task signaling: binary semaphores (packetSemHandle, buttonSemHandle). The ISR calls osSemaphoreRelease() (CMSIS-RTOS v2's ISR-safe API); the task blocks on osSemaphoreAcquire(..., osWaitForever). Zero CPU usage while idle, microsecond-latency wakeup on event.
  • Task β†’ task shared state: volatile flags 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.

5.2 State Machine

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

Preemption Logic

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.

5.3 LoRa Configuration

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)

5.4 Pin Assignments

Ambulance (Nucleo-L432KC)

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) β€”

Intersection (Nucleo-L432KC)

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) β€”

5.5 Code Repository Layout

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

6. Testing & Validation

6.1 Bench Tests (Subsystem)

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

6.2 Integration Tests (Full Behavior)

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

6.3 Demo-Day Behavior

End-to-end demo:

  1. Both nodes powered up. Both report LoRa initialized OK (ambulance) / LoRa receiver active (intersection) on serial.
  2. Intersection begins normal phase cycle.
  3. Operator presses ambulance button. Ambulance's LD3 blinks. Serial shows TX seq=1, 2, 3, ....
  4. 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.
  5. Intersection's LD3 lights solid (RX indicator).
  6. EW stays green while ambulance continues to transmit.
  7. Operator presses ambulance button again to disable emergency. Ambulance LD3 stops blinking. Transmissions stop.
  8. After 15 seconds of no packets, intersection logs PREEMPT DEACTIVATED. Intersection's LD3 turns off. Normal cycle resumes.

6.4 Honest Accounting β€” FR Status vs. Final Implementation

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 ⚠️ Revised GPS removed at M3. TX is fixed 2-second interval (not 2 Hz). Geofence not implemented
FR4 Directional filtering ±45° ⚠️ Revised 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 ⚠️ Partial 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 ⚠️ Partial 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 ⚠️ Partial 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.


7. Project Management

7.1 Division of Labor

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.

7.2 Timeline (actual)

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

8. Lessons Learned & Known Limitations

8.1 What Went Well

  • 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_count and read_failures localized 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.

8.2 What We'd Do Differently

  • 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_verify only 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.

8.3 Known Limitations of the Delivered System

  • 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.

8.4 Future Work

  • Fix packet_verify to 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.

8.5 Closing Note

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.


9. Media

9.1 Hardware Photos

IMG_3637 IMG_3636 IMG_3635 IMG_3634 IMG_3633 IMG_3632 IMG_3631 IMG_3630 IMG_3629 IMG_3628

9.2 Serial Capture Examples

Tera

9.3 References

  • 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
⚠️ **GitHub.com Fallback** ⚠️