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