-
Notifications
You must be signed in to change notification settings - Fork 3
/
V2VService.cpp
295 lines (271 loc) · 11.2 KB
/
V2VService.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
#include "V2VService.hpp"
int main() {
std::shared_ptr<V2VService> v2vService = std::make_shared<V2VService>();
while (1) {
int choice;
std::string groupId;
std::cout << "Which message would you like to send?" << std::endl;
std::cout << "(1) AnnouncePresence" << std::endl;
std::cout << "(2) FollowRequest" << std::endl;
std::cout << "(3) FollowResponse" << std::endl;
std::cout << "(4) StopFollow" << std::endl;
std::cout << "(5) LeaderStatus" << std::endl;
std::cout << "(6) FollowerStatus" << std::endl;
std::cout << "(#) Nothing, just quit." << std::endl;
std::cout << ">> ";
std::cin >> choice;
switch (choice) {
case 1: v2vService->announcePresence(); break;
case 2: {
std::cout << "Which group do you want to follow?" << std::endl;
std::cin >> groupId;
if (v2vService->presentCars.find(groupId) != v2vService->presentCars.end())
v2vService->followRequest(v2vService->presentCars[groupId]);
else std::cout << "Sorry, unable to locate that groups vehicle!" << std::endl;
break;
}
case 3: v2vService->followResponse(); break;
case 4: {
std::cout << "Which group do you want to stop follow?" << std::endl;
std::cin >> groupId;
if (v2vService->presentCars.find(groupId) != v2vService->presentCars.end())
v2vService->stopFollow(v2vService->presentCars[groupId]);
else std::cout << "Sorry, unable to locate that groups vehicle!" << std::endl;
break;
}
case 5: v2vService->leaderStatus(50, 0, 100); break;
case 6: v2vService->followerStatus(); break;
default: exit(0);
}
}
}
/**
* Implementation of the V2VService class as declared in V2VService.hpp
*/
V2VService::V2VService() {
/*
* The broadcast field contains a reference to the broadcast channel which is an OD4Session. This is where
* AnnouncePresence messages will be received.
*/
broadcast =
std::make_shared<cluon::OD4Session>(BROADCAST_CHANNEL,
[this](cluon::data::Envelope &&envelope) noexcept {
std::cout << "[OD4] ";
switch (envelope.dataType()) {
case ANNOUNCE_PRESENCE: {
AnnouncePresence ap = cluon::extractMessage<AnnouncePresence>(std::move(envelope));
std::cout << "received 'AnnouncePresence' from '"
<< ap.vehicleIp() << "', GroupID '"
<< ap.groupId() << "'!" << std::endl;
presentCars[ap.groupId()] = ap.vehicleIp();
break;
}
default: std::cout << "¯\\_(ツ)_/¯" << std::endl;
}
});
/*
* Each car declares an incoming UDPReceiver for messages directed at them specifically. This is where messages
* such as FollowRequest, FollowResponse, StopFollow, etc. are received.
*/
incoming =
std::make_shared<cluon::UDPReceiver>("0.0.0.0", DEFAULT_PORT,
[this](std::string &&data, std::string &&sender, std::chrono::system_clock::time_point &&ts) noexcept {
std::cout << "[UDP] ";
std::pair<int16_t, std::string> msg = extract(data);
switch (msg.first) {
case FOLLOW_REQUEST: {
FollowRequest followRequest = decode<FollowRequest>(msg.second);
std::cout << "received '" << followRequest.LongName()
<< "' from '" << sender << "'!" << std::endl;
// After receiving a FollowRequest, check first if there is currently no car already following.
if (followerIp.empty()) {
unsigned long len = sender.find(':'); // If no, add the requester to known follower slot
followerIp = sender.substr(0, len); // and establish a sending channel.
toFollower = std::make_shared<cluon::UDPSender>(followerIp, DEFAULT_PORT);
followResponse();
}
break;
}
case FOLLOW_RESPONSE: {
FollowResponse followResponse = decode<FollowResponse>(msg.second);
std::cout << "received '" << followResponse.LongName()
<< "' from '" << sender << "'!" << std::endl;
break;
}
case STOP_FOLLOW: {
StopFollow stopFollow = decode<StopFollow>(msg.second);
std::cout << "received '" << stopFollow.LongName()
<< "' from '" << sender << "'!" << std::endl;
// Clear either follower or leader slot, depending on current role.
unsigned long len = sender.find(':');
if (sender.substr(0, len) == followerIp) {
followerIp = "";
toFollower.reset();
}
else if (sender.substr(0, len) == leaderIp) {
leaderIp = "";
toLeader.reset();
}
break;
}
case FOLLOWER_STATUS: {
FollowerStatus followerStatus = decode<FollowerStatus>(msg.second);
std::cout << "received '" << followerStatus.LongName()
<< "' from '" << sender << "'!" << std::endl;
/* TODO: implement lead logic (if applicable) */
break;
}
case LEADER_STATUS: {
LeaderStatus leaderStatus = decode<LeaderStatus>(msg.second);
std::cout << "received '" << leaderStatus.LongName()
<< "' from '" << sender << "'!" << std::endl;
/* TODO: implement follow logic */
break;
}
default: std::cout << "¯\\_(ツ)_/¯" << std::endl;
}
});
}
/**
* This function sends an AnnouncePresence (id = 1001) message on the broadcast channel. It will contain information
* about the sending vehicle, including: IP, port and the group identifier.
*/
void V2VService::announcePresence() {
if (!followerIp.empty()) return;
AnnouncePresence announcePresence;
announcePresence.vehicleIp(YOUR_CAR_IP);
announcePresence.groupId(YOUR_GROUP_ID);
broadcast->send(announcePresence);
}
/**
* This function sends a FollowRequest (id = 1002) message to the IP address specified by the parameter vehicleIp. And
* sets the current leaderIp field of the sending vehicle to that of the target of the request.
*
* @param vehicleIp - IP of the target for the FollowRequest
*/
void V2VService::followRequest(std::string vehicleIp) {
if (!leaderIp.empty()) return;
leaderIp = vehicleIp;
toLeader = std::make_shared<cluon::UDPSender>(leaderIp, DEFAULT_PORT);
FollowRequest followRequest;
toLeader->send(encode(followRequest));
}
/**
* This function send a FollowResponse (id = 1003) message and is sent in response to a FollowRequest (id = 1002).
* This message will contain the NTP server IP for time synchronization between the target and the sender.
*/
void V2VService::followResponse() {
if (followerIp.empty()) return;
FollowResponse followResponse;
toFollower->send(encode(followResponse));
}
/**
* This function sends a StopFollow (id = 1004) request on the ip address of the parameter vehicleIp. If the IP address is neither
* that of the follower nor the leader, this function ends without sending the request message.
*
* @param vehicleIp - IP of the target for the request
*/
void V2VService::stopFollow(std::string vehicleIp) {
StopFollow stopFollow;
if (vehicleIp == leaderIp) {
toLeader->send(encode(stopFollow));
leaderIp = "";
toLeader.reset();
}
if (vehicleIp == followerIp) {
toFollower->send(encode(stopFollow));
followerIp = "";
toFollower.reset();
}
}
/**
* This function sends a FollowerStatus (id = 3001) message on the leader channel.
*
* @param speed - current velocity
* @param steeringAngle - current steering angle
* @param distanceFront - distance to nearest object in front of the car sending the status message
* @param distanceTraveled - distance traveled since last reading
*/
void V2VService::followerStatus() {
if (leaderIp.empty()) return;
FollowerStatus followerStatus;
toLeader->send(encode(followerStatus));
}
/**
* This function sends a LeaderStatus (id = 2001) message on the follower channel.
*
* @param speed - current velocity
* @param steeringAngle - current steering angle
* @param distanceTraveled - distance traveled since last reading
*/
void V2VService::leaderStatus(float speed, float steeringAngle, uint8_t distanceTraveled) {
if (followerIp.empty()) return;
LeaderStatus leaderStatus;
leaderStatus.timestamp(getTime());
leaderStatus.speed(speed);
leaderStatus.steeringAngle(steeringAngle);
leaderStatus.distanceTraveled(distanceTraveled);
toFollower->send(encode(leaderStatus));
}
/**
* Gets the current time.
*
* @return current time in milliseconds
*/
uint32_t V2VService::getTime() {
timeval now;
gettimeofday(&now, nullptr);
return (uint32_t ) now.tv_usec / 1000;
}
/**
* The extraction function is used to extract the message ID and message data into a pair.
*
* @param data - message data to extract header and data from
* @return pair consisting of the message ID (extracted from the header) and the message data
*/
std::pair<int16_t, std::string> V2VService::extract(std::string data) {
if (data.length() < 10) return std::pair<int16_t, std::string>(-1, "");
int id, len;
std::stringstream ssId(data.substr(0, 4));
std::stringstream ssLen(data.substr(4, 10));
ssId >> std::hex >> id;
ssLen >> std::hex >> len;
return std::pair<int16_t, std::string> (
data.length() -10 == len ? id : -1,
data.substr(10, data.length() -10)
);
};
/**
* Generic encode function used to encode a message before it is sent.
*
* @tparam T - generic message type
* @param msg - message to encode
* @return encoded message
*/
template <class T>
std::string V2VService::encode(T msg) {
cluon::ToProtoVisitor v;
msg.accept(v);
std::stringstream buff;
buff << std::hex << std::setfill('0')
<< std::setw(4) << msg.ID()
<< std::setw(6) << v.encodedData().length()
<< v.encodedData();
return buff.str();
}
/**
* Generic decode function used to decode an incoming message.
*
* @tparam T - generic message type
* @param data - encoded message data
* @return decoded message
*/
template <class T>
T V2VService::decode(std::string data) {
std::stringstream buff(data);
cluon::FromProtoVisitor v;
v.decodeFrom(buff);
T tmp = T();
tmp.accept(v);
return tmp;
}