Day 16 已經讓 SUMO 裡的 Vehicle 變成會在 OMNeT++ 中建立、移動與離開的動態 Node。
不過,看見兩個 Node 在 Qtenv 裡靠近,仍然不能證明它們真的交換過訊息。
要完成第一個 V2X 模擬,我們至少要把以下事件串起來:
SUMO 建立 carA 與 carB
→ Veins 建立兩個 Vehicle Node
→ Application 讀取各自的 Mobility
→ carA 產生並廣播 V2X Message
→ carB 的 Radio/MAC 收到 Frame
→ carB 的 Application 執行接收邏輯
→ carB 再送出自己的訊息給 carA
→ 結果檔留下傳送、接收與延遲證據
今天會沿用 Day 14 的雙路口路網與 Day 16 的相容環境,指定 carA 和 carB 從相反方向出發。
兩台車會各自廣播研究用狀態訊息,內容包含 SUMO Vehicle ID、序號、宣告位置、速度與傳送時間。
最後不只看動畫,還要用 Log、Scalar 與 Vector 證明訊息確實從一台車流到另一台車。
讀完這篇文章,你應該能夠:
.msg 定義研究用 V2X MessagepopulateWSM() 與 sendDown() 廣播訊息onWSM() 接收訊息並計算模擬延遲先把「看到兩台車」與「完成 V2X Message Exchange」分開。
| 觀察 | 可以證明什麼 | 還不能證明什麼 |
|---|---|---|
SUMO GUI 有 carA 與 carB |
交通需求成功載入 | OMNeT++ 已建立對應 Node |
| Qtenv 有兩個動態 Vehicle Node | Veins 已完成 Vehicle 映射 | Application 已經送出封包 |
Log 出現 SEND |
Application 已把 Message 交給下層 | 另一台車一定收到 |
Log 出現 RECV |
接收端 Application 已執行 onWSM() |
訊息內容一定真實可信 |
| Scalar 的接收數大於 0 | 這次 Run 留下接收統計 | 所有傳送訊息都成功抵達 |
| Vector 有 Reception Delay | 可以分析接收延遲分布 | 延遲數值代表所有真實道路環境 |
因此今天的合格成果不是一張「兩台車旁邊有無線圈」的截圖。
我們要留下至少一筆由 carA 送出、carB 收到的紀錄,也要留下反方向的證據。
Veins 5.3.1 的 Car 是一個 Compound Module,裡面至少包含三個和今天直接相關的 Submodule:
| Submodule | 今天負責的工作 |
|---|---|
appl |
建立研究用狀態訊息,處理 Timer 與接收事件 |
nic |
執行 IEEE 802.11p/WAVE 教學模型中的 MAC 與 PHY 行為 |
veinsmobility |
保存由 SUMO 經 TraCI 同步而來的位置、速度及道路狀態 |
訊息從 appl 送到 nic,再經無線通道抵達其他 Node 的 nic,最後往上交給接收端 appl。

圖 1:SUMO 提供兩台車的 Ground Truth Mobility,Veins Vehicle Node 再由 Application、MAC 與 PHY 完成研究用訊息的廣播及接收
這張圖有兩條不同的資料流:
Mobility Flow
SUMO → TraCI → TraCIMobility → Application
Packet Flow
Sender Application → MAC → PHY → Wireless Channel
→ Receiver PHY → MAC → Receiver Application
Mobility 同步成功,不代表 Packet Flow 已經成功。
相反地,固定位置的 Node 能互傳封包,也不代表交通移動已經正確接入。
今天要同時驗證兩條路徑。
不是。
我們會建立一個名為 Day17StatusMessage 的研究用訊息,欄位概念和 Day 12 的車輛狀態訊息相似,但它不是 SAE J2735 BSM,也不是 ETSI CAM 的 ASN.1 Payload。
| 欄位 | 今天的用途 | 不代表什麼 |
|---|---|---|
senderId |
保存 SUMO Vehicle ID,例如 carA |
憑證身分或真實車牌 |
sequenceNumber |
協助對照同一來源的訊息順序 | 完整的防重放機制 |
senderPosition |
保存傳送時 Application 看到的位置 | 經過 GNSS 誤差模型的真實座標 |
senderSpeed |
保存傳送時 SUMO 提供的速度 | 經過獨立感測驗證的速度 |
sentAt |
保存 OMNeT++ Simulation Time | 具備可信時間來源的安全時間戳記 |
這樣設計有兩個目的。
第一,先把「產生、傳送、接收與記錄」的 Packet Flow 跑通,不讓 ASN.1、憑證與完整標準 Profile 同時增加實作複雜度。
第二,預先保留 Ground Truth 與宣告內容的邊界。
今天的正常節點會把目前位置寫進 Message。
到了 Day 21 與 Day 30,惡意節點可以只修改 senderPosition,SUMO 中真正的車輛位置仍保持不變,這樣才能量測假位置造成的差異。
Day17StatusMessage只用於隔離模擬環境,不是可直接部署到道路設備的 V2X 協定。
今天的 populateWSM() 沒有傳入特定接收位址,因此會使用 L2 Broadcast Address。
這代表 carA 不是先知道 carB 的位址,再建立一條點對點連線。
它會把 Message 交給無線下層,位於可接收條件內的節點都有機會處理這筆廣播。
今天的場景只有兩台車,因此我們可以很容易看見互傳效果。
若之後加入十台背景車,同一筆廣播可能被多個 Node 收到,接收數就不再等於傳送數。
還要注意,Broadcast 不應被當成「可靠群播」:
如果應用需要知道特定對象是否收到,還要另外設計回覆、逾時與重試機制。
今天先讓兩台車各自週期廣播,因此反方向訊息是另一筆狀態更新,不是針對前一筆封包的 ACK。
Day 16 已經提醒,一台概念車輛可能同時有多種身分。
今天會實際看到:
| ID | 範例 | 主要用途 |
|---|---|---|
| SUMO Vehicle ID | carA |
對應 Route、Position、Speed 與 TraCI Vehicle |
| OMNeT++ Module Path | Day17Scenario.node[0] |
找到 Runtime 動態 Module |
| L2 Address | Veins 在 Runtime 指派的 MAC Address | 無線資料鏈結層的來源與接收處理 |
Message senderId |
carA |
今天為了實驗對照放入 Payload 的字串 |
node[0] 不應被寫死成 carA。
動態建立順序可能隨出發時間與場景改變,重新執行後也不應把 Module Index 當成長期 V2X 身分。
今天的 Log 會同時列出接收端 SUMO ID 與 Message 裡的 senderId,用來確認映射,而不是只看 node[0] 或 node[1] 猜測。
假設 carA 的第一個 Timer 在第 1 秒到期,概念流程如下:
t = 1.000 s
→ sendStatusTimer 到達 carA Application
→ Application 讀取 carA 的 Position 與 Speed
→ 建立 Day17StatusMessage,sequenceNumber = 0
→ populateWSM() 填入 WAVE 教學模型需要的欄位
→ sendDown() 把 Message 交給 MAC
t > 1.000 s
→ MAC 取得傳送機會
→ PHY 建立無線傳輸
→ carB PHY 嘗試解碼
→ carB MAC 將成功收到的 Frame 往上交付
→ carB Application 執行 onWSM()
→ 記錄 senderId、sequenceNumber 與 receptionDelay
實際接收時間不應先寫死。
它會受到 Radio、MAC、Channel 與事件排程影響,應從這次 Run 的結果讀取。
今天讓兩台車錯開第一個傳送 Timer,避免它們在同一個模擬時間刻意同步產生第一筆封包:

圖 2:carA 與 carB 分別在建立後 1.0 秒及 1.5 秒送出第一筆訊息,之後每秒各自更新;接收時間仍由無線模型與事件排程決定
sendDown() 成功,為什麼不等於對方收到?Application 呼叫 sendDown() 時,只是把 Message Ownership 交給較低層 Module。
接下來仍要經過:
只有接收端的 onWSM() 被呼叫,才能說這筆 Message 已到達接收端 Application。
這和 Day 03、Day 04 對 CAN ACK 的提醒相似:不同層次的「成功」不能混為一談。
| 事件 | 所在層次 | 可以判斷的事 |
|---|---|---|
sendDown() |
Sender Application | Message 已交給下層處理 |
| PHY Transmission | Sender Radio | 無線傳輸事件已建立 |
| Receiver 解碼成功 | Receiver PHY/MAC | Frame 通過模型中的接收判定 |
onWSM() |
Receiver Application | Application 已取得 Message |
| Application 採用內容 | 應用與安全邏輯 | 還要驗證來源、新鮮度與合理性 |
今天只做到第四層,並把第五層保留為後續攻擊與防禦篇章的研究問題。
carA 與 carB 互傳研究用狀態訊息本次實作沿用 Day 16 已驗證的版本組合:
| 元件 | 本篇基準版本 |
|---|---|
| Veins | 5.3.1 |
| OMNeT++ | 6.1.0 |
| SUMO | 1.22.0 |
如果你的版本不同,先回到 Day 16 跑通官方 Example,再移植今天的 Application。
不要在未驗證的版本組合上同時除錯 Build、TraCI 與 Packet Flow。
在 Day 16 使用的 OMNeT++ Workspace 建立 Empty Project:
File
→ New
→ OMNeT++ Project
→ Project Name:day17-v2x
→ Empty Project
接著開啟:
Project
→ Properties
→ Project References
→ 勾選 veins
Project Reference 讓今天的程式可以引用 Veins 的 NED Type、Header 與 Library。
完成後在 Project Root 建立:
Day17Scenario.ned
Day17V2XApp.ned
Day17V2XApp.cc
Day17StatusMessage.msg
omnetpp.ini
day17.rou.xml
day17.sumocfg
day17.launchd.xml
day17.net.xml
config.xml
antenna.xml
day17.net.xml 來自 Day 14,config.xml 與 antenna.xml 則沿用 Veins 5.3.1 官方 Example。
如果 Day 14 的工作目錄仍在 ~/sumo-day14,可以複製路網:
cp ~/sumo-day14/day14.net.xml \
~/omnetpp-workspace/day17-v2x/day17.net.xml
再從 Veins 5.3.1 官方 Example 複製兩份 Radio 設定:
cp ~/src/veins-5.3.1/examples/veins/config.xml \
~/omnetpp-workspace/day17-v2x/config.xml
cp ~/src/veins-5.3.1/examples/veins/antenna.xml \
~/omnetpp-workspace/day17-v2x/antenna.xml
上面的路徑沿用 Day 15 與 Day 16 的目錄安排。
如果你的 Workspace 放在其他位置,先以 IDE 的 Project Properties 確認實際 Project Location,再調整目的路徑。
這一步沿用 Day 14 的道路幾何,但不沿用原本六股 Flow。
今天只建立兩台可辨識的測試車,讓 Packet Flow 容易對照。
在 day17.rou.xml 填入:
<?xml version="1.0" encoding="UTF-8"?>
<routes>
<vType id="passenger"
vClass="passenger"
accel="2.6"
decel="4.5"
sigma="0"
length="5"
maxSpeed="13.89"/>
<route id="west_to_east"
edges="west_j1 j1_j2 j2_east"/>
<route id="east_to_west"
edges="east_j2 j2_j1 j1_west"/>
<vehicle id="carA"
type="passenger"
route="west_to_east"
depart="0"
departSpeed="max"
color="1,0,0"/>
<vehicle id="carB"
type="passenger"
route="east_to_west"
depart="0"
departSpeed="max"
color="0,0,1"/>
</routes>
carA 從西向東行駛,carB 則從東向西行駛。
sigma="0" 只用來減少本篇教學場景中的跟車隨機性,不代表正式研究永遠應關閉 Driver Imperfection。
在 day17.sumocfg 填入:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<input>
<net-file value="day17.net.xml"/>
<route-files value="day17.rou.xml"/>
</input>
<time>
<begin value="0"/>
<end value="120"/>
<step-length value="0.1"/>
</time>
<report>
<xml-validation value="never"/>
<xml-validation.net value="never"/>
<no-step-log value="true"/>
</report>
</configuration>
SUMO Step Length 設為 100 ms,後面的 Veins updateInterval 也會使用 100 ms。
兩者採用相同數值讓今天的時間關係容易理解,但這不是所有研究都必須遵守的固定規則。
正式實驗仍要依車速、Radio Model 與量測問題評估同步粒度。
先單獨確認場景:
cd ~/omnetpp-workspace/day17-v2x
sumo -c day17.sumocfg
正常結束只能證明 SUMO 場景可載入,還不能證明 Veins Packet Flow 已接通。
在 day17.launchd.xml 填入:
<?xml version="1.0"?>
<launch>
<copy file="day17.net.xml"/>
<copy file="day17.rou.xml"/>
<copy file="day17.sumocfg" type="config"/>
</launch>
veins_launchd 會把三份檔案放進本次 Run 的暫存目錄,再啟動獨立 SUMO Instance。
在 Day17StatusMessage.msg 填入:
import veins.base.utils.Coord;
import veins.modules.messages.BaseFrame1609_4;
packet Day17StatusMessage extends BaseFrame1609_4
{
string senderId;
int sequenceNumber = 0;
Coord senderPosition;
double senderSpeed = 0;
simtime_t sentAt;
}
OMNeT++ Message Compiler 會依這份 .msg 產生 Day17StatusMessage_m.h 與對應實作,讓 C++ 程式可以使用 setSenderId()、getSenderPosition() 等方法。
BaseFrame1609_4 提供 Veins WAVE 教學模型需要的 Channel、User Priority、PSID 與 Recipient Address 等欄位。
今天新增的欄位則屬於研究用 Application Payload。
在 Day17V2XApp.ned 填入:
package day17;
import org.car2x.veins.modules.application.ieee80211p.DemoBaseApplLayer;
simple Day17V2XApp extends DemoBaseApplLayer
{
parameters:
@class(Day17V2XApp);
double messageInterval @unit(s) = default(1s);
}
這個 Module 繼承 DemoBaseApplLayer,因此可以使用:
mobility 讀取 TraCI MobilitypopulateWSM() 填入下層需要的欄位sendDown() 把 Message 交給 MAConWSM() 處理收到的研究用 Frame在 Day17V2XApp.cc 填入:
#include <omnetpp.h>
#include "Day17StatusMessage_m.h"
#include "veins/modules/application/ieee80211p/DemoBaseApplLayer.h"
using namespace omnetpp;
using namespace veins;
class Day17V2XApp : public DemoBaseApplLayer
{
private:
cMessage* sendTimer = nullptr;
int nextSequenceNumber = 0;
int sentCount = 0;
int receivedCount = 0;
cOutVector receptionDelayVector;
protected:
void initialize(int stage) override;
void handleSelfMsg(cMessage* msg) override;
void onWSM(BaseFrame1609_4* frame) override;
void finish() override;
public:
~Day17V2XApp() override;
};
Define_Module(Day17V2XApp);
void Day17V2XApp::initialize(int stage)
{
DemoBaseApplLayer::initialize(stage);
if (stage == 0) {
sendTimer = new cMessage("sendStatusTimer");
receptionDelayVector.setName("receptionDelay");
}
else if (stage == 1) {
const std::string vehicleId = mobility->getExternalId();
if (vehicleId == "carA") {
scheduleAt(simTime() + SimTime(1, SIMTIME_S), sendTimer);
}
else if (vehicleId == "carB") {
scheduleAt(simTime() + SimTime(1500, SIMTIME_MS), sendTimer);
}
}
}
void Day17V2XApp::handleSelfMsg(cMessage* msg)
{
if (msg != sendTimer) {
DemoBaseApplLayer::handleSelfMsg(msg);
return;
}
auto* status = new Day17StatusMessage("day17Status");
populateWSM(status);
status->setSenderId(mobility->getExternalId().c_str());
status->setSequenceNumber(nextSequenceNumber++);
status->setSenderPosition(curPosition);
status->setSenderSpeed(mobility->getSpeed());
status->setSentAt(simTime());
sentCount++;
EV_INFO << "SEND sender=" << status->getSenderId()
<< " seq=" << status->getSequenceNumber()
<< " position=" << status->getSenderPosition()
<< " speed=" << status->getSenderSpeed()
<< " time=" << status->getSentAt() << "\n";
sendDown(status);
scheduleAt(simTime() + par("messageInterval"), sendTimer);
}
void Day17V2XApp::onWSM(BaseFrame1609_4* frame)
{
auto* status = check_and_cast<Day17StatusMessage*>(frame);
const simtime_t delay = simTime() - status->getSentAt();
receivedCount++;
receptionDelayVector.record(delay);
EV_INFO << "RECV receiver=" << mobility->getExternalId()
<< " sender=" << status->getSenderId()
<< " seq=" << status->getSequenceNumber()
<< " declaredPosition=" << status->getSenderPosition()
<< " receiverPosition=" << curPosition
<< " delay=" << delay << "\n";
findHost()->getDisplayString().setTagArg("i", 1, "green");
}
void Day17V2XApp::finish()
{
recordScalar("day17Sent", sentCount);
recordScalar("day17Received", receivedCount);
DemoBaseApplLayer::finish();
}
Day17V2XApp::~Day17V2XApp()
{
cancelAndDelete(sendTimer);
}
這段程式有五個重要細節。
initialize()DemoBaseApplLayer::initialize(stage) 會準備 Mobility、MAC 與統計欄位。
如果跳過 Parent Initialization,mobility 或 mac 可能尚未可用。
carA 在建立後 1 秒送出第一筆,carB 則在 1.5 秒送出。
後續都使用 messageInterval = 1s。
這些時間是教學設定,不是 BSM 或 CAM 的標準傳送週期。
populateWSM() 預設建立 Broadcast沒有提供特定 Recipient 時,populateWSM() 會使用 L2 Broadcast Address,並依 dataOnSch 等參數設定 Channel 與 User Priority。
sendDown() 後不再操作 Message呼叫 sendDown(status) 後,Message Ownership 已交給 OMNeT++/下層 Module。
目前 Application 不應再讀寫或刪除 status。
onWSM() 不刪除收到的 FrameVeins 的 DemoBaseApplLayer::handleLowerMsg() 會在 onWSM() 返回後刪除收到的 Message。
因此這個 Callback 只讀取欄位與記錄結果,不自行 delete frame,避免 Double Free。
在 Day17Scenario.ned 填入:
package day17;
import org.car2x.veins.nodes.Scenario;
network Day17Scenario extends Scenario
{
}
Scenario 已經包含:
TraCIScenarioManagerLaunchd
ConnectionManager
ObstacleControl
AnnotationManager
Car Module今天不加入 RSU,讓場景只保留兩台 Vehicle Node。
在 omnetpp.ini 填入:
[General]
network = day17.Day17Scenario
sim-time-limit = 35s
debug-on-errors = true
print-undisposed = true
**.scalar-recording = true
**.vector-recording = true
*.playgroundSizeX = 1200m
*.playgroundSizeY = 800m
*.playgroundSizeZ = 50m
*.obstacles.obstacles = xmldoc("config.xml", "//AnalogueModel[@type='SimpleObstacleShadowing']/obstacles")
*.manager.updateInterval = 0.1s
*.manager.host = "localhost"
*.manager.port = 9999
*.manager.autoShutdown = true
*.manager.launchConfig = xmldoc("day17.launchd.xml")
*.connectionManager.sendDirect = true
*.connectionManager.maxInterfDist = 1000m
*.connectionManager.drawMaxIntfDist = false
*.**.nic.mac1609_4.useServiceChannel = false
*.**.nic.mac1609_4.txPower = 20mW
*.**.nic.mac1609_4.bitrate = 6Mbps
*.**.nic.phy80211p.minPowerLevel = -110dBm
*.**.nic.phy80211p.useNoiseFloor = true
*.**.nic.phy80211p.noiseFloor = -98dBm
*.**.nic.phy80211p.decider = xmldoc("config.xml")
*.**.nic.phy80211p.analogueModels = xmldoc("config.xml")
*.**.nic.phy80211p.usePropagationDelay = true
*.**.nic.phy80211p.antenna = xmldoc("antenna.xml", "/root/Antenna[@id='monopole']")
*.node[*].applType = "day17.Day17V2XApp"
*.node[*].appl.headerLength = 80bit
*.node[*].appl.sendBeacons = false
*.node[*].appl.dataOnSch = false
*.node[*].appl.dataLengthBits = 320bit
*.node[*].appl.dataUserPriority = 5
*.node[*].appl.messageInterval = 1s
*.node[*].veinsmobility.x = 0
*.node[*].veinsmobility.y = 0
*.node[*].veinsmobility.z = 0
*.node[*].veinsmobility.setHostSpeed = false
上面的 PHY、MAC 與 Antenna 設定沿用 Veins 5.3.1 官方 Example 作為教學基準。
它們不是經過特定城市、車款或天線量測校正的參數。
正式研究必須說明模型選擇、參數來源、敏感度分析與限制,不能因為官方 Example 能執行,就直接把結果當成道路實測效能。
sendBeacons = false 也很重要。
今天不用 Veins 內建的週期 DemoSafetyMessage,而是由 sendStatusTimer 建立自訂 Day17StatusMessage,避免兩種週期訊息同時出現而混淆結果。
在 IDE 執行:
Project
→ Clean
→ 選擇 day17-v2x
Project
→ Build Project
Build 過程會先由 Message Compiler 產生 Day17StatusMessage_m.*,再編譯 Day17V2XApp.cc。
若出現找不到 Veins Header 或 NED Type,依序確認:
veins
Day17StatusMessage.msg 的 Import 路徑是否和 Veins 5.3.1 一致不要只修最後一行 Linker Error,最前面的 Message Compiler 或 Header Error 通常才是根因。
veins_launchd在另一個 Terminal 進入 Veins Root:
cd ~/src/veins-5.3.1
./bin/veins_launchd -vv -c "$(command -v sumo)"
確認畫面出現:
Listening on port 9999
保持 Terminal 開啟。
今天仍使用 localhost,不把 veins_launchd 暴露到外部網路。
在 IDE 選取 omnetpp.ini,執行:
Run As
→ OMNeT++ Simulation
→ Qtenv
模擬開始後先觀察:
node[0] 與 node[1] 是否由 Manager 動態建立veinsmobility Position 是否每 100 ms 更新SEND sender=carA
RECV receiver=carB sender=carA
SEND sender=carB
RECV receiver=carA sender=carB
Log 的實際 Module Path 與小數時間會依 Runtime Event 決定,不需要和某一張教學畫面完全相同。
核心關係應該是:
SEND sender=carA seq=0
→ RECV receiver=carB sender=carA seq=0
SEND sender=carB seq=0
→ RECV receiver=carA sender=carB seq=0
如果只看到 SEND 而沒有 RECV,先檢查兩台車的距離、Radio Parameter、config.xml、Antenna 設定與 PHY Log。
不要因為 Coupling 正常就把問題歸咎於 TraCI。
Qtenv 適合理解事件,但正式保存結果應再用 Cmdenv 執行一次。
在 Project Run Configuration 選擇 Cmdenv,或從已設定好 Project Library 與 NED Path 的環境執行對應 Run Command。
模擬正常到達第 35 秒後,results/ 應包含 .sca 與 .vec 等結果檔。
在 OMNeT++ Result Analysis 中檢查:
| Result | 預期觀察 |
|---|---|
day17Sent |
carA 與 carB 都大於 0 |
day17Received |
兩台車都至少收到對方一筆訊息 |
generatedWSMs |
Veins Base Application 記錄的研究用 WSM 傳送數 |
receivedWSMs |
Veins Base Application 記錄的研究用 WSM 接收數 |
receptionDelay |
每筆到達 Application 的模擬延遲 Vector |
不要先寫死精確收件數。
兩台車在不同時間位置不同,無線接收也受到模型參數影響。
今天真正要驗證的是:兩個方向都有接收事件,傳送與接收序號能對上,結果檔也保存了延遲資料。
查看兩個 Application Module 的 day17Sent 與 generatedWSMs。
如果兩者都大於 0,並且 Log 分別出現 sender=carA 與 sender=carB,可以確認兩台車都執行過傳送邏輯。
查看 day17Received、receivedWSMs 與 RECV Log。
RECV receiver=carB sender=carA 表示 carA 的 Message 已走到 carB Application。
反方向紀錄則證明 carB 的 Message 到達 carA。
declaredPosition 是傳送端建立 Message 時寫入的座標,receiverPosition 則是接收端處理 Message 時自己的目前座標。
這兩個欄位本來就不應相等。
可以進一步計算兩車距離,也能比較相鄰序號的宣告位置是否形成連續軌跡。
但今天的延遲通常遠小於 100 ms Mobility Update Interval,因此不要從單一短 Run 過度推論真實車輛動態與無線延遲關係。
| 現象 | 優先檢查 | 不要立刻下的結論 |
|---|---|---|
| SUMO 無法載入 Route | Day 14 Edge ID、day17.net.xml 與 Route 連續性 |
C++ Application 寫錯 |
| Qtenv 沒有動態 Node | veins_launchd、Launch Config 與 Vehicle depart |
Radio Range 太小 |
Build 找不到 _m.h |
.msg 是否成功編譯與 Makefile 是否更新 |
TraCI 版本不相容 |
有 Node、沒有 SEND |
applType、Vehicle ID 與 Timer Scheduling |
PHY 無法解碼 |
有 SEND、沒有 RECV |
兩車距離、MAC/PHY、Decider 與 Antenna 設定 | SUMO Mobility 沒同步 |
有 RECV、沒有 Vector |
receptionDelayVector.setName() 與 Vector Recording |
Message 沒有到達 |
node[0] 突然消失 |
SUMO Vehicle 是否已抵達或離開 | Application 一定 Crash |
除錯時先判斷問題位於:
Traffic
→ Coupling
→ Dynamic Node
→ Application Timer
→ MAC/PHY
→ Receiver Application
→ Result Recording
一次只往下一層,會比同時修改 Route、Radio 與 C++ 更容易找到根因。
完成 Message Exchange 後,仍要主動列出限制:
Day17StatusMessage 不是標準 BSM/CAM 編碼sequenceNumber 還沒有形成完整防重放視窗因此今天的結論只能是:
在這組版本、路網與模型設定下,兩個由 SUMO 驅動的 Veins Vehicle Node 完成了雙向研究用 Message Exchange,並留下 Application 接收與延遲結果。
不能把它改寫成「真實 V2X 在道路上保證可靠」,也不能說「訊息收到,所以位置一定正確」。
| 項目 | 本篇任務 |
|---|---|
| 主線概念 | 找出 Sender Timer、MAC/PHY 傳輸與 Receiver onWSM() 的事件鏈 |
| 隔離工具 | SUMO、OMNeT++、Veins 與本機 veins_launchd |
| 測試節點 | carA 與 carB,不連接真實車輛或 RSU |
| 輸出證據 | Version Tuple、場景檔、雙向 SEND/RECV Log、Scalar 與 Delay Vector |
完成後保存:
Veins/OMNeT++/SUMO Version Tuple
day17.net.xml/day17.rou.xml/day17.sumocfg
day17.launchd.xml/omnetpp.ini
Day17StatusMessage.msg
Day17V2XApp.ned/Day17V2XApp.cc
Random Seed 與 sim-time-limit
Scalar/Vector Result
這些檔案會成為後續 Baseline。
Day 21 可以只修改 Payload 的宣告位置,Day 22 再加入合理性檢查,Day 30 則比較 Baseline、Attack 與 Mitigation。
安全與法律提醒: 本篇所有訊息只在本機模擬環境中傳送,不會對道路車輛、公共 RSU、行動網路或他人的設備發送資料。
veins_launchd維持在localhost或受控隔離網路,不任意暴露到公開網路。
今天的 Message 沒有任何安全驗證,因此接收端只是把 senderId 當成 Payload 字串顯示。
它不能證明:
carA
senderPosition 和 SUMO Ground Truth 相同sequenceNumber 沒有被重設或重送這也正是後續 V2X 攻擊面會從哪裡開始。
可以先把今天的信任鏈拆成:
SUMO Ground Truth
→ Sender Application 讀取位置
→ Payload 宣告 senderId/position/speed/time
→ 無線模型傳送
→ Receiver Application 收到 Payload
→ 尚未進行安全驗證與合理性檢查
未來加入簽章後,也只能提高來源與受保護內容完整性的信心。
接收端仍要比較時間、位置、速度、道路與其他觀察,判斷內容是否合理。
今天留下的 Ground Truth 與 Payload 欄位,正是進行這項比較的基礎。
Car Compound Module 由 Application、NIC 與 TraCIMobility 共同完成移動節點通訊Day17StatusMessage 是研究用 Payload,不是標準 BSM 或 CAMpopulateWSM() 準備下層欄位,sendDown() 將 Message 交給 MAConWSM() 被呼叫,才代表 Message 已到達接收端 ApplicationsendDown() 後 Message Ownership 已交給下層,接收 Callback 也要遵守 Veins 的刪除流程今天完成了第一筆雙向 V2X Message Exchange,也知道成功接收不代表訊息內容可信。
Day 18 將把視野從這兩台模擬車拉回整個聯網車輛,盤點 CAN、OBD、Bluetooth、Wi-Fi、Cellular、V2X 與 OTA 等常見攻擊面,並分清楚入口、弱點與攻擊路徑。
Car.ned Vehicle Node 結構
DemoBaseApplLayer Header
DemoBaseApplLayer Message 處理與統計實作
BaseFrame1609_4.msg
omnetpp.ini
cMessage API