/* * Smart Emergency Alert Broadcasting in VANET using ns-3 (version 2) * Network Programming AAT 2026-27, B.M.S. College of Engineering * * Scenario * A 4-lane highway (2 lanes each way). At t = accidentTime one eastbound * vehicle (Vehicle 0) crashes and blocks the eastbound carriageway. It then * broadcasts an ACCIDENT ALERT every alertInterval seconds. Every vehicle * runs a UDP socket application that receives the alert and decides whether * to re-broadcast it (multi-hop dissemination). Eastbound vehicles brake and * queue behind the crash; we record whether each driver was warned in time. * * Dissemination schemes compared (--scheme=) * flood : blind flooding. Every vehicle re-broadcasts on first reception. * Simple, but causes the "broadcast storm" (redundancy, contention, * collisions). * gossip : probabilistic flooding. Re-broadcast with probability gossipProb. * smart : proposed Smart Relay. Distance-based contention with directional * suppression. A receiver waits a time that is SHORTER the FARTHER * it is from the sender, so the vehicle that extends coverage the * most relays first. Any vehicle that then hears a duplicate from a * vehicle further along the propagation direction cancels its own * re-broadcast. * * Network stack * IEEE 802.11p (10 MHz, 6 Mb/s) ad hoc, log-distance + Nakagami fading, * IPv4, UDP broadcast to 10.1.255.255:4040. Background CAM-like beacons on * UDP port 4041 load the channel the way periodic safety beacons do. * * Outputs * console summary, --csv (one row of metrics per run), --trace (JSON used by * the browser visualizer), --netanim (NetAnim XML). */ #include "ns3/core-module.h" #include "ns3/internet-module.h" #include "ns3/mobility-module.h" #include "ns3/netanim-module.h" #include "ns3/network-module.h" #include "ns3/propagation-module.h" #include "ns3/wifi-module.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ns3; NS_LOG_COMPONENT_DEFINE("VanetSmartAlert"); static const uint16_t ALERT_PORT = 4040; static const uint16_t BEACON_PORT = 4041; static const char* ALERT_TEXT = "ACCIDENT ALERT: crash ahead on eastbound carriageway. Reduce speed now."; enum Scheme { FLOODING, GOSSIP, SMART }; static std::string SchemeName(Scheme s) { switch (s) { case FLOODING: return "flood"; case GOSSIP: return "gossip"; case SMART: return "smart"; } return "?"; } // --------------------------------------------------------------------------- // AlertHeader: the application-layer header carried in every alert datagram. // Positions are sent in centimetres as signed 32-bit integers (network order). // --------------------------------------------------------------------------- class AlertHeader : public Header { public: uint8_t type{1}; // 1 = accident uint8_t severity{3}; // 1 low .. 3 high uint16_t hops{0}; // hops travelled so far (0 at the origin) uint32_t originId{0}; uint32_t seq{0}; uint32_t senderId{0}; double originX{0}; double originY{0}; double senderX{0}; double senderY{0}; int64_t genTimeNs{0}; // generation time at the origin static TypeId GetTypeId() { static TypeId tid = TypeId("ns3::AlertHeader").SetParent
().AddConstructor(); return tid; } TypeId GetInstanceTypeId() const override { return GetTypeId(); } uint32_t GetSerializedSize() const override { return 40; } void Serialize(Buffer::Iterator i) const override { i.WriteU8(type); i.WriteU8(severity); i.WriteHtonU16(hops); i.WriteHtonU32(originId); i.WriteHtonU32(seq); i.WriteHtonU32(senderId); i.WriteHtonU32(ToCm(originX)); i.WriteHtonU32(ToCm(originY)); i.WriteHtonU32(ToCm(senderX)); i.WriteHtonU32(ToCm(senderY)); i.WriteHtonU64(static_cast(genTimeNs)); } uint32_t Deserialize(Buffer::Iterator i) override { type = i.ReadU8(); severity = i.ReadU8(); hops = i.ReadNtohU16(); originId = i.ReadNtohU32(); seq = i.ReadNtohU32(); senderId = i.ReadNtohU32(); originX = FromCm(i.ReadNtohU32()); originY = FromCm(i.ReadNtohU32()); senderX = FromCm(i.ReadNtohU32()); senderY = FromCm(i.ReadNtohU32()); genTimeNs = static_cast(i.ReadNtohU64()); return GetSerializedSize(); } void Print(std::ostream& os) const override { os << "alert seq=" << seq << " origin=" << originId << " sender=" << senderId << " hops=" << hops; } private: static uint32_t ToCm(double m) { return static_cast(static_cast(std::lround(m * 100.0))); } static double FromCm(uint32_t v) { return static_cast(v) / 100.0; } }; // --------------------------------------------------------------------------- // AlertTag: simulation-only marker so PHY traces can tell alert frames apart // from beacon frames. It is not part of the transmitted bytes. // --------------------------------------------------------------------------- class AlertTag : public Tag { public: uint32_t seq{0}; static TypeId GetTypeId() { static TypeId tid = TypeId("ns3::AlertTag").SetParent().AddConstructor(); return tid; } TypeId GetInstanceTypeId() const override { return GetTypeId(); } uint32_t GetSerializedSize() const override { return 4; } void Serialize(TagBuffer i) const override { i.WriteU32(seq); } void Deserialize(TagBuffer i) override { seq = i.ReadU32(); } void Print(std::ostream& os) const override { os << "seq=" << seq; } }; // --------------------------------------------------------------------------- // Global simulation state: road layout, vehicles, statistics and trace log. // --------------------------------------------------------------------------- struct Lane { double y; int dir; // +1 eastbound, -1 westbound double speed; }; // Lanes 0 and 1 are eastbound (towards the crash), 2 and 3 westbound. static const std::vector LANES = {{1.75, +1, 30.0}, {5.25, +1, 25.0}, {-1.75, -1, 31.0}, {-5.25, -1, 26.0}}; struct Vehicle { uint32_t id{0}; uint32_t lane{0}; Ptr mob; bool crashed{false}; bool queued{false}; // has been given a stopping slot behind the crash bool stopped{false}; // standing still in the queue double firstAlertRx{-1}; double brakeStart{-1}; bool warnedAtBrake{false}; bool eyewitness{false}; // close enough to see the crash; needs no radio warning }; struct SimConfig { Scheme scheme{SMART}; uint32_t nVehicles{60}; double roadLength{2000}; double simTime{20}; double accidentTime{3}; double accidentX{1600}; double alertInterval{1}; double gossipProb{0.6}; double nominalRange{300}; uint32_t numSlots{5}; double slotTime{0.002}; double slotJitter{0.0005}; uint32_t maxHops{20}; double beaconHz{5}; uint32_t beaconSize{200}; double txPowerDbm{23}; double pathLossExp{2.5}; bool fading{true}; bool verbose{false}; double queueGap{10}; double brakeDecel{6}; double sightDistance{150}; }; static SimConfig g_cfg; static std::vector g_veh; static std::vector g_laneTail; // next free stopping position per eastbound lane static bool g_crashHappened = false; static AnimationInterface* g_anim = nullptr; struct AlertRecord { double genTime{0}; uint32_t tx{0}; uint32_t rxError{0}; std::map delay; // node -> first-reception delay (s) std::map hops; std::set targets; // vehicles inside the region of interest at generation time }; struct Stats { std::map alerts; // by sequence number uint64_t originTx{0}; uint64_t relayTx{0}; uint64_t duplicates{0}; uint64_t suppressed{0}; uint64_t gossipSkipped{0}; uint64_t alertRxError{0}; uint64_t alertRxDrop{0}; uint64_t beaconTx{0}; uint64_t beaconRxError{0}; uint32_t alertBytes{0}; }; static Stats g_stats; // Compact event log for the browser visualizer. // Event codes: 0 tx, 1 first rx, 2 relay scheduled, 3 relay suppressed, // 4 corrupted alert frame, 5 crash, 6 brake start struct TraceLog { bool enabled{false}; std::ostringstream moves; std::ostringstream events; bool firstMove{true}; bool firstEvent{true}; void Move(double t, uint32_t id, const Vector& p, const Vector& v) { if (!enabled) { return; } moves << (firstMove ? "" : ",") << std::fixed << std::setprecision(6) << "[" << t << "," << id << "," << std::setprecision(2) << p.x << "," << p.y << "," << v.x << "," << v.y << "]"; firstMove = false; } void Event(double t, int code, uint32_t id, double a = 0, double b = 0, double c = 0) { if (!enabled) { return; } events << (firstEvent ? "" : ",") << std::fixed << std::setprecision(6) << "[" << t << "," << code << "," << id << "," << std::setprecision(4) << a << "," << b << "," << c << "]"; firstEvent = false; } }; static TraceLog g_trace; static Vector PosOf(uint32_t id) { return g_veh[id].mob->GetPosition(); } static void Recolour(uint32_t id, uint8_t r, uint8_t g, uint8_t b) { if (g_anim) { g_anim->UpdateNodeColor(id, r, g, b); } } // --------------------------------------------------------------------------- // Mobility: braking, queueing behind the crash, and keeping traffic flowing. // --------------------------------------------------------------------------- // Decelerate uniformly from the current speed to a stop at stopX. The // constant-velocity model is driven with mid-point speeds over short steps, // which reproduces the exact braking distance v^2 / (2a). static void BrakeTo(uint32_t id, double stopX) { Vehicle& v = g_veh[id]; double x = PosOf(id).x; double speed = std::abs(v.mob->GetVelocity().x); double dist = stopX - x; double now = Simulator::Now().GetSeconds(); v.brakeStart = now; v.warnedAtBrake = v.firstAlertRx >= 0; g_trace.Event(now, 6, id, v.eyewitness ? 2 : (v.warnedAtBrake ? 1 : 0), stopX); if (dist <= 0.5 || speed < 0.1) { v.mob->SetVelocity(Vector(0, 0, 0)); v.stopped = true; return; } double decel = speed * speed / (2 * dist); double total = speed / decel; uint32_t steps = std::max(1, static_cast(std::ceil(total / 0.1))); double dt = total / steps; for (uint32_t k = 0; k < steps; ++k) { double vk = speed - decel * (k + 0.5) * dt; Simulator::Schedule(Seconds(k * dt), [id, vk]() { g_veh[id].mob->SetVelocity(Vector(vk, 0, 0)); }); } Simulator::Schedule(Seconds(total), [id, stopX]() { Vehicle& veh = g_veh[id]; veh.mob->SetVelocity(Vector(0, 0, 0)); Vector p = veh.mob->GetPosition(); veh.mob->SetPosition(Vector(stopX, p.y, 0)); veh.stopped = true; }); } // Give an eastbound vehicle the next free slot in its lane's queue and // schedule the moment it has to start braking. static void AssignQueueSlot(uint32_t id) { Vehicle& v = g_veh[id]; if (v.queued || v.crashed) { return; } v.queued = true; double x = PosOf(id).x; double& tail = g_laneTail[v.lane]; // A vehicle already inside the blocked zone stops where it is. double stopX = x >= tail ? x : tail; tail = stopX - g_cfg.queueGap; double speed = LANES[v.lane].speed; double brakeDist = speed * speed / (2 * g_cfg.brakeDecel); double startIn = (stopX - brakeDist - x) / speed; Simulator::Schedule(Seconds(std::max(0.0, startIn)), &BrakeTo, id, stopX); } static void Crash() { Vehicle& v = g_veh[0]; v.crashed = true; v.stopped = true; v.mob->SetVelocity(Vector(0, 0, 0)); g_crashHappened = true; double now = Simulator::Now().GetSeconds(); double cx = PosOf(0).x; g_trace.Event(now, 5, 0, cx, PosOf(0).y); Recolour(0, 220, 30, 30); // Both eastbound lanes are blocked a little behind the wreck. g_laneTail = {cx - 20, cx - 20, 0, 0}; // Vehicles still behind the crash queue up, nearest first. std::vector behind; for (auto& veh : g_veh) { if (!veh.crashed && LANES[veh.lane].dir > 0 && PosOf(veh.id).x < cx) { behind.push_back(veh.id); veh.eyewitness = PosOf(veh.id).x > cx - g_cfg.sightDistance; } } std::sort(behind.begin(), behind.end(), [](uint32_t a, uint32_t b) { return PosOf(a).x > PosOf(b).x; }); for (uint32_t id : behind) { AssignQueueSlot(id); } std::cout << "t=" << std::fixed << std::setprecision(3) << now << "s | *** Vehicle 0 CRASHED at x=" << std::setprecision(1) << cx << " m, eastbound carriageway blocked ***" << std::endl; } // Vehicles that leave one end of the 2 km section re-enter at the other end, // so traffic density stays constant (models a steady stream of new traffic). static void RecycleTraffic() { const double margin = 150; for (auto& v : g_veh) { if (v.crashed || v.queued) { continue; } Vector p = PosOf(v.id); if (LANES[v.lane].dir < 0 && p.x < -margin) { v.mob->SetPosition(Vector(g_cfg.roadLength + margin, p.y, 0)); } else if (LANES[v.lane].dir > 0 && p.x > g_cfg.roadLength + margin) { v.mob->SetPosition(Vector(-margin, p.y, 0)); if (g_crashHappened) { AssignQueueSlot(v.id); } } } Simulator::Schedule(Seconds(0.1), &RecycleTraffic); } // --------------------------------------------------------------------------- // VehicleApp: the emergency-alert socket application installed on every car. // --------------------------------------------------------------------------- class VehicleApp : public Application { public: void Setup(uint32_t id, Ptr rng) { m_id = id; m_rng = rng; } // Called on the crashed vehicle: start the periodic alert. void StartAlerting() { SendOriginAlert(); } private: struct RelayState { AlertHeader hdr; uint16_t myHop{0}; int dir{1}; bool pending{false}; EventId event; }; using Key = std::pair; // (origin, seq) void StartApplication() override { m_socket = Socket::CreateSocket(GetNode(), UdpSocketFactory::GetTypeId()); m_socket->SetAllowBroadcast(true); if (m_socket->Bind(InetSocketAddress(Ipv4Address::GetAny(), ALERT_PORT)) == -1) { NS_FATAL_ERROR("Vehicle " << m_id << ": could not bind alert socket"); } m_socket->SetRecvCallback(MakeCallback(&VehicleApp::HandleRead, this)); } void StopApplication() override { for (auto& [key, st] : m_state) { st.event.Cancel(); } m_alertEvent.Cancel(); if (m_socket) { m_socket->Close(); m_socket = nullptr; } } void Broadcast(const AlertHeader& h) { Ptr p = Create(reinterpret_cast(ALERT_TEXT), static_cast(std::strlen(ALERT_TEXT))); p->AddHeader(h); AlertTag tag; tag.seq = h.seq; p->AddPacketTag(tag); g_stats.alertBytes = p->GetSize(); m_socket->SendTo(p, 0, InetSocketAddress(Ipv4Address("10.1.255.255"), ALERT_PORT)); double now = Simulator::Now().GetSeconds(); g_stats.alerts[h.seq].tx++; g_trace.Event(now, 0, m_id, h.seq, h.hops, h.senderId == h.originId ? 0 : 1); } void SendOriginAlert() { Vector p = PosOf(m_id); AlertHeader h; h.originId = m_id; h.senderId = m_id; h.seq = ++m_seq; h.originX = h.senderX = p.x; h.originY = h.senderY = p.y; h.hops = 0; h.genTimeNs = Simulator::Now().GetNanoSeconds(); AlertRecord& rec = g_stats.alerts[h.seq]; rec.genTime = Simulator::Now().GetSeconds(); // Region of interest: every other vehicle on the highway section. for (auto& v : g_veh) { double x = PosOf(v.id).x; if (v.id != m_id && x >= 0 && x <= g_cfg.roadLength) { rec.targets.insert(v.id); } } g_stats.originTx++; Broadcast(h); std::cout << "t=" << std::fixed << std::setprecision(3) << Simulator::Now().GetSeconds() << "s | Vehicle " << m_id << " broadcasts ACCIDENT ALERT #" << h.seq << " to " << "10.1.255.255:" << ALERT_PORT << std::endl; m_alertEvent = Simulator::Schedule(Seconds(g_cfg.alertInterval), &VehicleApp::SendOriginAlert, this); } void HandleRead(Ptr socket) { Address from; while (Ptr packet = socket->RecvFrom(from)) { AlertHeader h; if (packet->GetSize() < h.GetSerializedSize()) { continue; // malformed datagram } packet->RemoveHeader(h); if (h.originId == m_id || h.senderId == m_id) { continue; // our own alert echoed back by a relay } OnAlert(h, packet); } } void OnAlert(const AlertHeader& h, Ptr payload) { double now = Simulator::Now().GetSeconds(); Key key{h.originId, h.seq}; Vector me = PosOf(m_id); auto it = m_state.find(key); if (it == m_state.end()) { // First copy of this alert: deliver it to the "driver". RelayState st; st.hdr = h; st.myHop = h.hops + 1; double delay = now - h.genTimeNs * 1e-9; AlertRecord& rec = g_stats.alerts[h.seq]; rec.delay[m_id] = delay; rec.hops[m_id] = st.myHop; Vehicle& v = g_veh[m_id]; if (v.firstAlertRx < 0) { v.firstAlertRx = now; Recolour(m_id, 0, 170, 120); } g_trace.Event(now, 1, m_id, h.seq, st.myHop, h.senderId); if (g_cfg.verbose) { std::vector buf(payload->GetSize()); payload->CopyData(buf.data(), buf.size()); std::cout << "t=" << std::fixed << std::setprecision(4) << now << "s | Vehicle " << m_id << " got alert #" << h.seq << " from Vehicle " << h.senderId << " (hop " << st.myHop << ", delay " << std::setprecision(2) << delay * 1000 << " ms): " << std::string(buf.begin(), buf.end()) << std::endl; } if (st.myHop < g_cfg.maxHops) { ScheduleRelay(st, me); } m_state[key] = st; return; } // Duplicate copy. g_stats.duplicates++; RelayState& st = it->second; if (g_cfg.scheme == SMART && st.pending) { // Someone at or beyond our position (in the direction the alert is // travelling) has already re-broadcast: our transmission would add // almost no new coverage, so cancel it. double ahead = (h.senderX - me.x) * st.dir; if (ahead >= 0) { st.event.Cancel(); st.pending = false; g_stats.suppressed++; g_trace.Event(now, 3, m_id, h.seq, h.senderId); } } } void ScheduleRelay(RelayState& st, const Vector& me) { double now = Simulator::Now().GetSeconds(); switch (g_cfg.scheme) { case FLOODING: st.pending = true; st.event = Simulator::ScheduleNow(&VehicleApp::Relay, this, st.hdr.originId, st.hdr.seq); break; case GOSSIP: if (m_rng->GetValue() < g_cfg.gossipProb) { st.pending = true; st.event = Simulator::ScheduleNow(&VehicleApp::Relay, this, st.hdr.originId, st.hdr.seq); } else { g_stats.gossipSkipped++; g_trace.Event(now, 3, m_id, st.hdr.seq, st.hdr.senderId); } break; case SMART: { double dx = me.x - st.hdr.senderX; double dy = me.y - st.hdr.senderY; double d = std::sqrt(dx * dx + dy * dy); st.dir = dx >= 0 ? 1 : -1; double frac = 1.0 - std::min(d, g_cfg.nominalRange) / g_cfg.nominalRange; uint32_t slot = std::min(g_cfg.numSlots - 1, static_cast(frac * g_cfg.numSlots)); double wait = slot * g_cfg.slotTime + m_rng->GetValue(0, g_cfg.slotJitter); st.pending = true; st.event = Simulator::Schedule(Seconds(wait), &VehicleApp::Relay, this, st.hdr.originId, st.hdr.seq); g_trace.Event(now, 2, m_id, st.hdr.seq, wait, d); break; } } } void Relay(uint32_t originId, uint32_t seq) { RelayState& st = m_state[{originId, seq}]; st.pending = false; Vector me = PosOf(m_id); AlertHeader h = st.hdr; h.senderId = m_id; h.senderX = me.x; h.senderY = me.y; h.hops = st.myHop; g_stats.relayTx++; Broadcast(h); } uint32_t m_id{0}; uint32_t m_seq{0}; Ptr m_socket; Ptr m_rng; std::map m_state; EventId m_alertEvent; }; // --------------------------------------------------------------------------- // BeaconApp: periodic cooperative-awareness beacons (background channel load). // --------------------------------------------------------------------------- class BeaconApp : public Application { public: void Setup(Ptr rng) { m_rng = rng; } private: void StartApplication() override { m_socket = Socket::CreateSocket(GetNode(), UdpSocketFactory::GetTypeId()); m_socket->SetAllowBroadcast(true); m_socket->Bind(); double period = 1.0 / g_cfg.beaconHz; m_event = Simulator::Schedule(Seconds(m_rng->GetValue(0, period)), &BeaconApp::Send, this); } void StopApplication() override { m_event.Cancel(); if (m_socket) { m_socket->Close(); } } void Send() { m_socket->SendTo(Create(g_cfg.beaconSize), 0, InetSocketAddress(Ipv4Address("10.1.255.255"), BEACON_PORT)); g_stats.beaconTx++; double period = 1.0 / g_cfg.beaconHz; // Small jitter avoids beacons from different cars staying synchronised. m_event = Simulator::Schedule(Seconds(period * m_rng->GetValue(0.95, 1.05)), &BeaconApp::Send, this); } Ptr m_socket; Ptr m_rng; EventId m_event; }; // --------------------------------------------------------------------------- // Trace sinks // --------------------------------------------------------------------------- static void OnCourseChange(uint32_t id, Ptr m) { g_trace.Move(Simulator::Now().GetSeconds(), id, m->GetPosition(), m->GetVelocity()); } static void CountLostAlert(uint32_t id, Ptr p, bool decodeError) { AlertTag tag; if (p->PeekPacketTag(tag)) { if (decodeError) { g_stats.alertRxError++; } else { g_stats.alertRxDrop++; } g_stats.alerts[tag.seq].rxError++; g_trace.Event(Simulator::Now().GetSeconds(), 4, id, tag.seq); } else if (decodeError) { g_stats.beaconRxError++; } } // Payload could not be decoded (SINR too low, typically because two frames // overlapped at this receiver: a collision). static void OnRxError(uint32_t id, Ptr p, double /*snr*/) { CountLostAlert(id, p, true); } // Frame dropped before decoding because the receiver was busy with another // frame or transmitting itself. Weak/out-of-range frames are not counted. static void OnPhyRxDrop(uint32_t id, Ptr p, WifiPhyRxfailureReason reason) { switch (reason) { case RXING: case TXING: case BUSY_DECODING_PREAMBLE: case RECEPTION_ABORTED_BY_TX: case PREAMBLE_DETECTION_PACKET_SWITCH: case FRAME_CAPTURE_PACKET_SWITCH: CountLostAlert(id, p, false); break; default: break; } } // --------------------------------------------------------------------------- // Metrics // --------------------------------------------------------------------------- struct Summary { uint32_t alerts{0}; double pdr{0}; double fullCoverage{0}; double meanDelayMs{0}; double p95DelayMs{0}; double maxDelayMs{0}; double jitterMs{0}; double meanHops{0}; uint32_t maxHops{0}; double txPerAlert{0}; double rebroadcastRatio{0}; double dupPerVehicle{0}; double lostPerAlert{0}; double goodputKbps{0}; double alertLoadKbps{0}; uint32_t queued{0}; uint32_t unwarned{0}; double avgLeadS{0}; }; static Summary ComputeSummary() { Summary s; std::vector delays; double pdrSum = 0; uint32_t full = 0; uint64_t roiRx = 0; // first receptions by vehicles in the region of interest uint64_t allRx = 0; // first receptions by any vehicle uint64_t hopSum = 0; uint64_t txSum = 0; uint64_t lostSum = 0; // Only alerts that had a full interval to spread before the end are scored. std::vector scored; for (auto& [seq, rec] : g_stats.alerts) { if (rec.genTime + g_cfg.alertInterval <= g_cfg.simTime + 1e-9 && !rec.targets.empty()) { scored.push_back(seq); } } // Delays restricted to the region of interest, one map per scored alert. std::vector> roiDelay; for (uint32_t seq : scored) { AlertRecord& rec = g_stats.alerts[seq]; std::map got; for (auto& [node, d] : rec.delay) { if (rec.targets.count(node)) { got[node] = d; delays.push_back(d * 1000); hopSum += rec.hops[node]; s.maxHops = std::max(s.maxHops, rec.hops[node]); } } pdrSum += static_cast(got.size()) / rec.targets.size(); full += got.size() == rec.targets.size() ? 1 : 0; roiRx += got.size(); allRx += rec.delay.size(); txSum += rec.tx; lostSum += rec.rxError; roiDelay.push_back(std::move(got)); } s.alerts = scored.size(); if (s.alerts > 0) { s.pdr = pdrSum / s.alerts; s.fullCoverage = 100.0 * full / s.alerts; s.txPerAlert = static_cast(txSum) / s.alerts; s.lostPerAlert = static_cast(lostSum) / s.alerts; s.dupPerVehicle = static_cast(g_stats.duplicates) / std::max(1, g_stats.alerts.size() * (g_cfg.nVehicles - 1)); double active = s.alerts * g_cfg.alertInterval; s.goodputKbps = roiRx * g_stats.alertBytes * 8.0 / active / 1000.0; s.alertLoadKbps = txSum * (g_stats.alertBytes + 64) * 8.0 / active / 1000.0; } if (roiRx > 0) { s.meanHops = static_cast(hopSum) / roiRx; } if (allRx > 0) { // Fraction of receivers that re-broadcast (relays / receptions). s.rebroadcastRatio = static_cast(txSum - s.alerts) / allRx; } if (!delays.empty()) { std::sort(delays.begin(), delays.end()); s.meanDelayMs = std::accumulate(delays.begin(), delays.end(), 0.0) / delays.size(); s.p95DelayMs = delays[static_cast(0.95 * (delays.size() - 1))]; s.maxDelayMs = delays.back(); } // Jitter: mean variation of a vehicle's delay between consecutive alerts // (RFC 3550 style |D(i) - D(i-1)|), averaged over all vehicles. double jSum = 0; uint64_t jCount = 0; for (size_t i = 1; i < roiDelay.size(); ++i) { for (auto& [node, d] : roiDelay[i]) { auto prev = roiDelay[i - 1].find(node); if (prev != roiDelay[i - 1].end()) { jSum += std::abs(d - prev->second) * 1000; jCount++; } } } s.jitterMs = jCount ? jSum / jCount : 0; double leadSum = 0; uint32_t warned = 0; for (auto& v : g_veh) { if (v.brakeStart < 0 || v.eyewitness) { continue; } s.queued++; if (v.warnedAtBrake) { warned++; leadSum += v.brakeStart - v.firstAlertRx; } else { s.unwarned++; } } s.avgLeadS = warned ? leadSum / warned : 0; return s; } static void WriteCsv(const std::string& path, const Summary& s) { bool exists = std::ifstream(path).good(); std::ofstream f(path, std::ios::app); if (!f) { std::cerr << "Could not open CSV file " << path << std::endl; return; } if (!exists) { f << "scheme,vehicles,run,alerts,pdr,full_coverage_pct,mean_delay_ms,p95_delay_ms," "max_delay_ms,jitter_ms,mean_hops,max_hops,tx_per_alert,rebroadcast_ratio," "dup_per_vehicle,lost_rx_per_alert,goodput_kbps,alert_load_kbps,queued,unwarned," "avg_lead_s\n"; } f << std::fixed << std::setprecision(4) << SchemeName(g_cfg.scheme) << "," << g_cfg.nVehicles << "," << RngSeedManager::GetRun() << "," << s.alerts << "," << s.pdr << "," << s.fullCoverage << "," << s.meanDelayMs << "," << s.p95DelayMs << "," << s.maxDelayMs << "," << s.jitterMs << "," << s.meanHops << "," << s.maxHops << "," << s.txPerAlert << "," << s.rebroadcastRatio << "," << s.dupPerVehicle << "," << s.lostPerAlert << "," << s.goodputKbps << "," << s.alertLoadKbps << "," << s.queued << "," << s.unwarned << "," << s.avgLeadS << "\n"; } static void WriteTrace(const std::string& path, const Summary& s) { std::ofstream f(path); if (!f) { std::cerr << "Could not open trace file " << path << std::endl; return; } f << std::fixed << std::setprecision(4); f << "{\"meta\":{\"scheme\":\"" << SchemeName(g_cfg.scheme) << "\",\"vehicles\":" << g_cfg.nVehicles << ",\"run\":" << RngSeedManager::GetRun() << ",\"roadLength\":" << g_cfg.roadLength << ",\"simTime\":" << g_cfg.simTime << ",\"accidentTime\":" << g_cfg.accidentTime << ",\"alertInterval\":" << g_cfg.alertInterval << ",\"range\":" << g_cfg.nominalRange << ",\"gossipProb\":" << g_cfg.gossipProb << ",\"lanes\":["; for (size_t i = 0; i < LANES.size(); ++i) { f << (i ? "," : "") << "{\"y\":" << LANES[i].y << ",\"dir\":" << LANES[i].dir << ",\"speed\":" << LANES[i].speed << "}"; } f << "]},\"vehicleLane\":["; for (size_t i = 0; i < g_veh.size(); ++i) { f << (i ? "," : "") << g_veh[i].lane; } f << "],\"alerts\":["; bool first = true; for (auto& [seq, rec] : g_stats.alerts) { double maxD = 0; for (auto& [n, d] : rec.delay) { maxD = std::max(maxD, d); } f << (first ? "" : ",") << "{\"seq\":" << seq << ",\"gen\":" << rec.genTime << ",\"reached\":" << rec.delay.size() << ",\"targets\":" << rec.targets.size() << ",\"tx\":" << rec.tx << ",\"lost\":" << rec.rxError << ",\"maxDelay\":" << maxD << "}"; first = false; } f << "],\"summary\":{\"pdr\":" << s.pdr << ",\"meanDelayMs\":" << s.meanDelayMs << ",\"p95DelayMs\":" << s.p95DelayMs << ",\"jitterMs\":" << s.jitterMs << ",\"txPerAlert\":" << s.txPerAlert << ",\"rebroadcastRatio\":" << s.rebroadcastRatio << ",\"lostPerAlert\":" << s.lostPerAlert << ",\"meanHops\":" << s.meanHops << ",\"goodputKbps\":" << s.goodputKbps << ",\"unwarned\":" << s.unwarned << ",\"queued\":" << s.queued << "},"; f << "\"moves\":[" << g_trace.moves.str() << "],"; f << "\"events\":[" << g_trace.events.str() << "]}\n"; } // --------------------------------------------------------------------------- // Scenario construction // --------------------------------------------------------------------------- static void PlaceVehicles(NodeContainer& nodes) { Ptr rng = CreateObject(); rng->SetStream(1000); // placement is identical for every scheme with the same --RngRun const double minGap = 15; std::vector> laneXs(LANES.size()); g_veh.resize(g_cfg.nVehicles); // Vehicle 0 drives in lane 1 and reaches accidentX exactly at accidentTime. double x0 = g_cfg.accidentX - LANES[1].speed * g_cfg.accidentTime; g_veh[0].lane = 1; laneXs[1].push_back(x0); for (uint32_t i = 1; i < g_cfg.nVehicles; ++i) { uint32_t lane = (i - 1) % LANES.size(); auto& xs = laneXs[lane]; double x = 0; bool ok = false; for (int attempt = 0; attempt < 2000 && !ok; ++attempt) { x = rng->GetValue(0, g_cfg.roadLength); ok = std::all_of(xs.begin(), xs.end(), [&](double o) { return std::abs(o - x) >= minGap; }); } if (!ok) { // Road is crowded: drop the car in the middle of the largest gap. std::vector sorted = xs; sorted.push_back(-minGap); sorted.push_back(g_cfg.roadLength + minGap); std::sort(sorted.begin(), sorted.end()); double best = 0; for (size_t k = 1; k < sorted.size(); ++k) { if (sorted[k] - sorted[k - 1] > best) { best = sorted[k] - sorted[k - 1]; x = (sorted[k] + sorted[k - 1]) / 2; } } } xs.push_back(x); g_veh[i].lane = lane; } // Hand out positions in creation order. std::vector next(LANES.size(), 0); MobilityHelper mobility; mobility.SetMobilityModel("ns3::ConstantVelocityMobilityModel"); mobility.Install(nodes); for (uint32_t i = 0; i < g_cfg.nVehicles; ++i) { Vehicle& v = g_veh[i]; v.id = i; const Lane& lane = LANES[v.lane]; double x = laneXs[v.lane][next[v.lane]++]; v.mob = nodes.Get(i)->GetObject(); v.mob->TraceConnectWithoutContext("CourseChange", MakeBoundCallback(&OnCourseChange, i)); v.mob->SetPosition(Vector(x, lane.y, 0)); v.mob->SetVelocity(Vector(lane.dir * lane.speed, 0, 0)); } } static bool ParseScheme(const std::string& name, Scheme& out) { if (name == "flood" || name == "flooding") { out = FLOODING; } else if (name == "gossip") { out = GOSSIP; } else if (name == "smart") { out = SMART; } else { return false; } return true; } int main(int argc, char* argv[]) { std::string scheme = "smart"; std::string csvPath; std::string tracePath; std::string netanimPath; CommandLine cmd(__FILE__); cmd.AddValue("scheme", "Dissemination scheme: flood | gossip | smart", scheme); cmd.AddValue("vehicles", "Number of vehicles (including the crashed one)", g_cfg.nVehicles); cmd.AddValue("roadLength", "Length of the highway section (m)", g_cfg.roadLength); cmd.AddValue("simTime", "Simulation duration (s)", g_cfg.simTime); cmd.AddValue("accidentTime", "Time at which Vehicle 0 crashes (s)", g_cfg.accidentTime); cmd.AddValue("accidentX", "Crash position along the road (m)", g_cfg.accidentX); cmd.AddValue("alertInterval", "Period of repeated alerts from the crash site (s)", g_cfg.alertInterval); cmd.AddValue("gossipProb", "Re-broadcast probability for the gossip scheme", g_cfg.gossipProb); cmd.AddValue("range", "Nominal radio range R used by Smart Relay (m)", g_cfg.nominalRange); cmd.AddValue("slots", "Number of contention slots used by Smart Relay", g_cfg.numSlots); cmd.AddValue("slotTime", "Length of one contention slot (s)", g_cfg.slotTime); cmd.AddValue("beaconHz", "Background beacon rate per vehicle (Hz, 0 = off)", g_cfg.beaconHz); cmd.AddValue("beaconSize", "Background beacon payload (bytes)", g_cfg.beaconSize); cmd.AddValue("txPower", "Transmit power (dBm)", g_cfg.txPowerDbm); cmd.AddValue("pathLossExp", "Log-distance path-loss exponent", g_cfg.pathLossExp); cmd.AddValue("fading", "Enable Nakagami fading", g_cfg.fading); cmd.AddValue("verbose", "Print every alert reception", g_cfg.verbose); cmd.AddValue("csv", "Append a row of metrics to this CSV file", csvPath); cmd.AddValue("trace", "Write a JSON trace for the visualizer to this file", tracePath); cmd.AddValue("netanim", "Write a NetAnim XML animation to this file", netanimPath); cmd.Parse(argc, argv); // ---- Validate parameters ------------------------------------------------ if (!ParseScheme(scheme, g_cfg.scheme)) { std::cerr << "Unknown --scheme=" << scheme << " (use flood, gossip or smart)" << std::endl; return 1; } if (g_cfg.nVehicles < 2) { std::cerr << "At least 2 vehicles are required." << std::endl; return 1; } if (g_cfg.accidentTime >= g_cfg.simTime || g_cfg.accidentTime < 0) { std::cerr << "accidentTime must be within the simulation time." << std::endl; return 1; } if (g_cfg.accidentX <= 0 || g_cfg.accidentX >= g_cfg.roadLength) { std::cerr << "accidentX must lie on the road (0 < accidentX < roadLength)." << std::endl; return 1; } if (g_cfg.gossipProb < 0 || g_cfg.gossipProb > 1 || g_cfg.numSlots == 0 || g_cfg.alertInterval <= 0 || g_cfg.nominalRange <= 0) { std::cerr << "Invalid relay parameters." << std::endl; return 1; } g_trace.enabled = !tracePath.empty(); std::cout << "\n=== NS-3 Smart Emergency Alert Broadcasting (VANET) ===\n" << "Scheme : " << SchemeName(g_cfg.scheme) << "\n" << "Vehicles : " << g_cfg.nVehicles << " on a " << g_cfg.roadLength << " m, 4-lane highway\n" << "Radio : IEEE 802.11p ad hoc, 10 MHz, 6 Mb/s, " << g_cfg.txPowerDbm << " dBm\n" << "Transport : UDP broadcast 10.1.255.255:" << ALERT_PORT << "\n" << "Crash : t=" << g_cfg.accidentTime << " s at x=" << g_cfg.accidentX << " m, alert every " << g_cfg.alertInterval << " s\n" << "Run (seed) : " << RngSeedManager::GetRun() << "\n\n"; // ---- 1. Vehicles and mobility -------------------------------------------- NodeContainer vehicles; vehicles.Create(g_cfg.nVehicles); PlaceVehicles(vehicles); // ---- 2. IEEE 802.11p radio in ad hoc mode -------------------------------- YansWifiChannelHelper channel; channel.SetPropagationDelay("ns3::ConstantSpeedPropagationDelayModel"); channel.AddPropagationLoss("ns3::LogDistancePropagationLossModel", "Exponent", DoubleValue(g_cfg.pathLossExp), "ReferenceLoss", DoubleValue(47.86)); // 1 m at 5.9 GHz if (g_cfg.fading) { channel.AddPropagationLoss("ns3::NakagamiPropagationLossModel"); } Ptr wifiChannel = channel.Create(); YansWifiPhyHelper phy; phy.SetChannel(wifiChannel); phy.Set("ChannelSettings", StringValue("{0, 10, BAND_5GHZ, 0}")); phy.Set("TxPowerStart", DoubleValue(g_cfg.txPowerDbm)); phy.Set("TxPowerEnd", DoubleValue(g_cfg.txPowerDbm)); WifiHelper wifi; wifi.SetStandard(WIFI_STANDARD_80211p); wifi.SetRemoteStationManager("ns3::ConstantRateWifiManager", "DataMode", StringValue("OfdmRate6MbpsBW10MHz"), "ControlMode", StringValue("OfdmRate6MbpsBW10MHz"), "NonUnicastMode", StringValue("OfdmRate6MbpsBW10MHz")); WifiMacHelper mac; mac.SetType("ns3::AdhocWifiMac"); NetDeviceContainer devices = wifi.Install(phy, mac, vehicles); // Fix every random stream so that, for the same --RngRun, all schemes see // exactly the same traffic and channel conditions. wifi.AssignStreams(devices, 10000); channel.AssignStreams(wifiChannel, 20000); // ---- 3. Internet stack ---------------------------------------------------- InternetStackHelper internet; internet.Install(vehicles); Ipv4AddressHelper address; address.SetBase("10.1.0.0", "255.255.0.0"); address.Assign(devices); // ---- 4. Applications ------------------------------------------------------- std::vector> apps; for (uint32_t i = 0; i < g_cfg.nVehicles; ++i) { Ptr rng = CreateObject(); rng->SetStream(2000 + i); Ptr app = CreateObject(); app->Setup(i, rng); vehicles.Get(i)->AddApplication(app); app->SetStartTime(Seconds(0)); app->SetStopTime(Seconds(g_cfg.simTime)); apps.push_back(app); if (g_cfg.beaconHz > 0) { Ptr brng = CreateObject(); brng->SetStream(5000 + i); Ptr beacon = CreateObject(); beacon->Setup(brng); vehicles.Get(i)->AddApplication(beacon); beacon->SetStartTime(Seconds(0)); beacon->SetStopTime(Seconds(g_cfg.simTime)); } } // ---- 5. PHY instrumentation (lost / collided alert frames) -------------- for (uint32_t i = 0; i < devices.GetN(); ++i) { Ptr wphy = DynamicCast(devices.Get(i))->GetPhy(); wphy->TraceConnectWithoutContext("PhyRxDrop", MakeBoundCallback(&OnPhyRxDrop, i)); wphy->GetState()->TraceConnectWithoutContext("RxError", MakeBoundCallback(&OnRxError, i)); } // ---- 6. Scenario events ---------------------------------------------------- Simulator::Schedule(Seconds(g_cfg.accidentTime), &Crash); Simulator::Schedule(Seconds(g_cfg.accidentTime), &VehicleApp::StartAlerting, apps[0]); Simulator::Schedule(Seconds(0.1), &RecycleTraffic); std::unique_ptr anim; if (!netanimPath.empty()) { anim = std::make_unique(netanimPath); anim->SetMobilityPollInterval(Seconds(0.2)); anim->SetMaxPktsPerTraceFile(500000); for (uint32_t i = 0; i < g_cfg.nVehicles; ++i) { anim->UpdateNodeDescription(i, "V" + std::to_string(i)); anim->UpdateNodeColor(i, 120, 130, 150); anim->UpdateNodeSize(i, 6, 3); } g_anim = anim.get(); } // ---- 7. Run ------------------------------------------------------------------ Simulator::Stop(Seconds(g_cfg.simTime)); Simulator::Run(); Summary s = ComputeSummary(); std::cout << "\n--- Results (" << SchemeName(g_cfg.scheme) << ", " << g_cfg.nVehicles << " vehicles, run " << RngSeedManager::GetRun() << ") ---\n" << std::fixed << std::setprecision(2) << "Alerts scored : " << s.alerts << "\n" << "Packet delivery ratio : " << s.pdr * 100 << " %\n" << "Alerts reaching every car : " << s.fullCoverage << " %\n" << "Mean / 95th pct delay : " << s.meanDelayMs << " / " << s.p95DelayMs << " ms\n" << "Jitter : " << s.jitterMs << " ms\n" << "Mean / max hops : " << s.meanHops << " / " << s.maxHops << "\n" << "Transmissions per alert : " << s.txPerAlert << "\n" << "Re-broadcast ratio : " << s.rebroadcastRatio * 100 << " %\n" << "Lost alert frames / alert : " << s.lostPerAlert << "\n" << "Alert goodput : " << s.goodputKbps << " kb/s\n" << "Queued, not eyewitnesses : " << s.queued << " (" << s.unwarned << " braked without a warning)\n" << "Average warning lead time : " << s.avgLeadS << " s\n"; if (!csvPath.empty()) { WriteCsv(csvPath, s); } if (!tracePath.empty()) { WriteTrace(tracePath, s); std::cout << "Trace written to " << tracePath << "\n"; } g_anim = nullptr; anim.reset(); Simulator::Destroy(); return 0; }