Skip to content

Commit 6be357a

Browse files
committed
new file: src/v2i-hub/PriorityPlugin/CMakeLists.txt
new file: src/v2i-hub/PriorityPlugin/README.md new file: src/v2i-hub/PriorityPlugin/manifest.json new file: src/v2i-hub/PriorityPlugin/src/PriorityPlugin.cpp new file: src/v2i-hub/PriorityPlugin/src/PriorityPlugin.hpp
1 parent 2b75f6c commit 6be357a

File tree

5 files changed

+503
-0
lines changed

5 files changed

+503
-0
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
PROJECT ( PriorityPlugin VERSION 7.11.0 LANGUAGES CXX )
2+
3+
SET (TMX_PLUGIN_NAME "Priority")
4+
5+
FIND_PACKAGE (XercesC REQUIRED)
6+
FIND_PACKAGE (NetSNMP REQUIRED)
7+
8+
BuildTmxPlugin ()
9+
10+
TARGET_INCLUDE_DIRECTORIES ( ${PROJECT_NAME} PUBLIC ${XercesC_INCLUDE_DIRS} ${NETSNMP_INCLUDE_DIRS})
11+
TARGET_LINK_LIBRARIES ( ${PROJECT_NAME} tmxutils ${XercesC_LIBRARY} ${NETSNMP_LIBRARIES})

src/v2i-hub/PriorityPlugin/README.md

Whitespace-only changes.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"name":"PriorityPlugin",
3+
"description":"Plugin to handle Signal Request Messages and place priority requests.",
4+
"version":"@PROJECT_VERSION@",
5+
"exeLocation":"/bin/PriorityPlugin",
6+
"coreIpAddr":"127.0.0.1",
7+
"corePort":24601,
8+
"messageTypes":[
9+
],
10+
"configuration":[
11+
{
12+
"key":"LogLevel",
13+
"default":"INFO",
14+
"description":"The log level for this plugin."
15+
},
16+
{
17+
"key":"IP",
18+
"default":"192.168.55.92",
19+
"description":"The IPv4 address of the destination Traffic Signal Controller (TSC)."
20+
},
21+
{
22+
"key":"Port",
23+
"default":"161",
24+
"description":"The destination port on the Traffic Signal Controller (TSC) for SNMP communication."
25+
},
26+
{
27+
"key":"TSC_SNMP_Community",
28+
"default":"public",
29+
"description":"The SNMP Community used for sending SNMP NTCIP communication to Traffic Signal Controller (TSC)."
30+
},
31+
{
32+
"key":"VehicleClassLevel",
33+
"default":"10",
34+
"description":"The default NTCIP 1211 priorityRequestVehicleClassLevel (1..10). 1=highest precedence within class, 10=lowest."
35+
},
36+
{
37+
"key":"ServiceStrategyNumber",
38+
"default":"1",
39+
"description":"The default NTCIP 1211 priorityRequestServiceStrategyNumber (1..255) sent with each priority request. Priority Request Generator (PRG) shall never set 0."
40+
},
41+
{
42+
"key":"TimeOfServiceDesired",
43+
"default":"5",
44+
"description":"The priorityRequestTimeOfServiceDesired (TSD) (1..65535). Desired time in seconds to arrive at the intersection's stopping point, relative to the receipt of the message."
45+
},
46+
{
47+
"key":"TimeOfEstimatedDeparture",
48+
"default":"8",
49+
"description":"The priorityRequestTimeOfEstimatedDeparture (TED) (1..65535). Estimated time in seconds to clear the intersection, relative to the receipt of the message."
50+
}
51+
]
52+
}
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
1+
/**
2+
* Copyright (C) 2026 LEIDOS.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this except in compliance with the License. You may obtain a copy of
6+
* the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
#include "PriorityPlugin.hpp"
17+
18+
using namespace tmx::messages;
19+
using namespace tmx::utils;
20+
21+
namespace PriorityPlugin {
22+
PriorityPlugin::PriorityPlugin(const std::string &name) : TmxMessageManager(name)
23+
{
24+
AddMessageFilter <SrmMessage> (this, &PriorityPlugin::HandleSRM);
25+
SubscribeToMessages();
26+
}
27+
28+
PriorityPlugin::~PriorityPlugin()
29+
{
30+
}
31+
32+
void PriorityPlugin::OnStateChange(IvpPluginState state)
33+
{
34+
TmxMessageManager::OnStateChange(state);
35+
if (state == IvpPluginState_registered) {
36+
UpdateConfigSettings();
37+
}
38+
}
39+
40+
void PriorityPlugin::OnConfigChanged(const char *key, const char *value)
41+
{
42+
TmxMessageManager::OnConfigChanged(key, value);
43+
UpdateConfigSettings();
44+
}
45+
46+
void PriorityPlugin::OnMessageReceived(tmx::routeable_message &msg)
47+
{
48+
PLOG(logDEBUG1) << "Routable Message: " << msg.to_string();
49+
if (PluginClient::IsJ2735Message(msg))
50+
{
51+
}
52+
}
53+
54+
void PriorityPlugin::UpdateConfigSettings()
55+
{
56+
GetConfigValue<std::string>("IP", _tscIP);
57+
GetConfigValue<uint16_t>("Port", _tscPort);
58+
GetConfigValue<std::string>("TSC_SNMP_Community", _snmpCommunity);
59+
60+
GetConfigValue<uint8_t>("VehicleClassLevel", _classLevelStr);
61+
GetConfigValue<uint8_t>("ServiceStrategyNumber", _strategyStr);
62+
GetConfigValue<uint16_t>("TimeOfServiceDesired", _tsd);
63+
GetConfigValue<uint16_t>("TimeOfEstimatedDeparture", _ted);
64+
65+
try {
66+
_snmpClient = std::make_shared<snmp_client>(
67+
_tscIP, _tscPort, _snmpCommunity,
68+
"", "", "",
69+
SNMP_VERSION_1);
70+
} catch (const snmp_client_exception &e) {
71+
PLOG(logERROR) << "Failed to create SNMP client: " << e.what();
72+
_snmpClient.reset();
73+
}
74+
75+
PLOG(logINFO) << "PriorityPlugin configured: TSC=" << _tscIP << ":" << _tscPort
76+
<< " Community=" << _snmpCommunity
77+
<< " ClassLevel=" << _classLevelStr
78+
<< " Strategy=" << _strategyStr
79+
<< " TimeOfServiceDesired=" << _tsd
80+
<< " TimeOfEstimatedDeparture=" << _ted;
81+
}
82+
83+
void PriorityPlugin::HandleSRM(SrmMessage &msg, tmx::routeable_message &routeableMsg)
84+
{
85+
PLOG(logINFO) << "Received Signal Request Message (SRM)";
86+
87+
auto srm = msg.get_j2735_data();
88+
if (!srm) {
89+
PLOG(logWARNING) << "SRM decode returned null, skipping.";
90+
_skippedMessages++;
91+
SetStatus(_keySkippedMessages, _skippedMessages);
92+
return;
93+
}
94+
95+
// Validate that the SRM contains requests
96+
if (!srm->requests || srm->requests->list.count <= 0) {
97+
PLOG(logWARNING) << "SRM contains no signal request packages, skipping.";
98+
_skippedMessages++;
99+
SetStatus(_keySkippedMessages, _skippedMessages);
100+
return;
101+
}
102+
103+
// Extract the vehicle ID from the requestor
104+
const uint8_t *vehicleIDBytes = nullptr;
105+
size_t vehicleIDLen = 0;
106+
if (srm->requestor.id.present == VehicleID_PR_entityID) {
107+
vehicleIDBytes = srm->requestor.id.choice.entityID.buf;
108+
vehicleIDLen = srm->requestor.id.choice.entityID.size;
109+
} else if (srm->requestor.id.present == VehicleID_PR_stationID) {
110+
// stationID is a 4-byte value; treat its memory as bytes
111+
vehicleIDBytes = reinterpret_cast<const uint8_t *>(&srm->requestor.id.choice.stationID);
112+
vehicleIDLen = sizeof(srm->requestor.id.choice.stationID);
113+
}
114+
115+
// Determine vehicle class type from the requestor role
116+
long role = 0;
117+
if (srm->requestor.type != nullptr) {
118+
role = srm->requestor.type->role;
119+
}
120+
uint8_t classType = MapVehicleClassType(role);
121+
122+
// Current epoch time for priorityRequestTimeOfRequest
123+
uint32_t timeOfRequest = static_cast<uint32_t>(std::time(nullptr));
124+
125+
// Current time for computing relative offsets from "now"
126+
time_t nowEpoch = std::time(nullptr);
127+
struct tm utcNow;
128+
gmtime_r(&nowEpoch, &utcNow);
129+
// Compute current minute-of-year and second-within-minute
130+
int currentDayOfYear = utcNow.tm_yday;
131+
long currentMinuteOfYear = static_cast<long>(currentDayOfYear) * 1440L
132+
+ static_cast<long>(utcNow.tm_hour) * 60L
133+
+ static_cast<long>(utcNow.tm_min);
134+
long currentMsInMinute = static_cast<long>(utcNow.tm_sec) * 1000L;
135+
136+
// Process each SignalRequestPackage in the SRM
137+
for (int i = 0; i < srm->requests->list.count; i++) {
138+
auto *pkg = srm->requests->list.array[i];
139+
if (!pkg) {
140+
continue;
141+
}
142+
143+
uint8_t requestID = static_cast<uint8_t>(pkg->request.requestID);
144+
145+
// Compute priorityRequestTimeOfServiceDesired:
146+
// NTCIP 1211 5.1.1.1.7 — relative seconds to arrive at the intersection
147+
// stopping point from receipt of the message.
148+
// SRM provides MinuteOfTheYear (absolute) and DSecond (ms within minute).
149+
// Convert the absolute ETA to a relative offset from "now".
150+
uint16_t timeOfService = _tsd; // default per configuration
151+
long etaOffsetMs = 0;
152+
if (pkg->minute) {
153+
long etaMinuteOfYear = static_cast<long>(*pkg->minute);
154+
long etaMs = 0;
155+
if (pkg->second) {
156+
etaMs = static_cast<long>(*pkg->second);
157+
}
158+
// Total ms from start-of-year for ETA
159+
long etaTotalMs = etaMinuteOfYear * 60L * 1000L + etaMs;
160+
// Total ms from start-of-year for now
161+
long nowTotalMs = currentMinuteOfYear * 60L * 1000L + currentMsInMinute;
162+
etaOffsetMs = etaTotalMs - nowTotalMs;
163+
// Handle year wrap-around
164+
if (etaOffsetMs < 0) {
165+
etaOffsetMs += 525960L * 60L * 1000L; // MinuteOfTheYear max ≈ 365.25 days
166+
}
167+
long etaOffsetSec = etaOffsetMs / 1000L;
168+
timeOfService = static_cast<uint16_t>(
169+
std::min(65535L, std::max(1L, etaOffsetSec)));
170+
}
171+
172+
// Compute priorityRequestTimeOfEstimatedDeparture:
173+
// NTCIP 1211 5.1.1.1.8 — relative seconds of estimated departure
174+
// from the intersection from receipt of the message.
175+
uint16_t timeOfDepart = _ted; // default per configuration
176+
if (pkg->duration) {
177+
// Duration extends past the ETA
178+
long departOffsetMs = etaOffsetMs + static_cast<long>(*pkg->duration);
179+
long departOffsetSec = departOffsetMs / 1000L;
180+
timeOfDepart = static_cast<uint16_t>(
181+
std::min(65535L, std::max(1L, departOffsetSec)));
182+
}
183+
184+
// Encode the 29-byte NTCIP 1211 priority request
185+
std::vector<uint8_t> encoded = EncodePriorityRequest(
186+
requestID,
187+
vehicleIDBytes,
188+
vehicleIDLen,
189+
classType,
190+
_classLevelStr,
191+
_strategyStr,
192+
timeOfService,
193+
timeOfDepart,
194+
timeOfRequest);
195+
196+
PLOG(logINFO) << "Sending NTCIP 1211 priority request for requestID="
197+
<< static_cast<int>(requestID)
198+
<< " intersectionID=" << pkg->request.id.id
199+
<< " to " << _tscIP << ":" << _tscPort;
200+
201+
bool success = SendPriorityRequest(NTCIP1211_PRIORITY_REQUEST_ABSOLUTE_OID, encoded);
202+
if (success) {
203+
_priorityRequestsSent++;
204+
SetStatus(_keyPriorityRequestsSent, _priorityRequestsSent);
205+
PLOG(logINFO) << "Priority request sent successfully.";
206+
} else {
207+
PLOG(logERROR) << "Failed to send priority request via SNMP.";
208+
}
209+
}
210+
}
211+
212+
uint8_t PriorityPlugin::MapVehicleClassType(long role) const
213+
{
214+
switch (role) {
215+
case 5: // roadRescue
216+
case 6: // emergency
217+
case 7: // safetyCar
218+
case 11: // roadSideSource
219+
case 12: // police
220+
case 13: // fire
221+
case 14: // ambulance
222+
return 1; // Highest priority — emergency vehicles
223+
case 1: // publicTransport
224+
case 16: // transit
225+
return 3; // Transit priority
226+
case 4: // roadWork
227+
case 15: // dot
228+
return 5; // Maintenance/supervisor
229+
case 3: // dangerousGoods
230+
case 9: // truck
231+
case 17: // slowMoving
232+
case 18: // stopNgo
233+
return 7; // Commercial/freight
234+
default:
235+
return 10; // Lowest priority
236+
}
237+
}
238+
239+
std::vector<uint8_t> PriorityPlugin::EncodePriorityRequest(uint8_t requestID, const uint8_t *vehicleID, size_t vehicleIDLen, uint8_t classType, uint8_t classLevel, uint8_t strategyNum, uint16_t timeOfService, uint16_t timeOfDepart, uint32_t timeOfRequest) const
240+
{
241+
std::vector<uint8_t> buf(PRIORITY_REQUEST_SIZE, 0);
242+
243+
// Byte 0: priorityRequestID (1 byte)
244+
buf[0] = requestID;
245+
246+
// Bytes 1-17: priorityRequestVehicleID (17 bytes, zero-padded)
247+
if (vehicleID && vehicleIDLen > 0)
248+
{
249+
size_t copyLen = std::min(vehicleIDLen, VEHICLE_ID_FIELD_SIZE);
250+
std::memcpy(&buf[1], vehicleID, copyLen);
251+
}
252+
253+
// Byte 18: priorityRequestVehicleClassType (1 byte)
254+
buf[18] = classType;
255+
256+
// Byte 19: priorityRequestVehicleClassLevel (1 byte)
257+
buf[19] = classLevel;
258+
259+
// Byte 20: priorityRequestServiceStrategyNumber (1 byte)
260+
buf[20] = strategyNum;
261+
262+
// Bytes 21-22: priorityRequestTimeOfServiceDesired (2 bytes, big-endian)
263+
buf[21] = static_cast<uint8_t>((timeOfService >> 8) & 0xFF);
264+
buf[22] = static_cast<uint8_t>(timeOfService & 0xFF);
265+
266+
// Bytes 23-24: priorityRequestTimeOfEstimatedDeparture (2 bytes, big-endian)
267+
buf[23] = static_cast<uint8_t>((timeOfDepart >> 8) & 0xFF);
268+
buf[24] = static_cast<uint8_t>(timeOfDepart & 0xFF);
269+
270+
// Bytes 25-28: priorityRequestTimeOfRequest (4 bytes, big-endian)
271+
buf[25] = static_cast<uint8_t>((timeOfRequest >> 24) & 0xFF);
272+
buf[26] = static_cast<uint8_t>((timeOfRequest >> 16) & 0xFF);
273+
buf[27] = static_cast<uint8_t>((timeOfRequest >> 8) & 0xFF);
274+
buf[28] = static_cast<uint8_t>(timeOfRequest & 0xFF);
275+
276+
return buf;
277+
}
278+
279+
bool PriorityPlugin::SendPriorityRequest(const std::string &oidStr, const std::vector<uint8_t> &data)
280+
{
281+
if (!_snmpClient) {
282+
PLOG(logERROR) << "SNMP client not initialized, cannot send priority request.";
283+
return false;
284+
}
285+
286+
// Build the snmp_response_obj with the raw bytes as a STRING for SET
287+
snmp_response_obj val;
288+
val.type = snmp_response_obj::response_type::STRING;
289+
val.val_string.assign(data.begin(), data.end());
290+
291+
bool success = _snmpClient->process_snmp_request(
292+
oidStr, request_type::SET, val);
293+
294+
if (success) {
295+
PLOG(logDEBUG) << "SNMP SET successful for OID: " << oidStr;
296+
} else {
297+
PLOG(logERROR) << "SNMP SET failed for OID: " << oidStr;
298+
}
299+
return success;
300+
}
301+
302+
} /* namespace PriorityPlugin */
303+
304+
int main(int argc, char *argv[])
305+
{
306+
return run_plugin<PriorityPlugin::PriorityPlugin>("Priority Plugin", argc, argv);
307+
}

0 commit comments

Comments
 (0)