1 | package require Thread |
---|
2 | |
---|
3 | ## |
---|
4 | #@file automated_client.tcl |
---|
5 | # |
---|
6 | #@author Richard Latimer |
---|
7 | # |
---|
8 | #@brief Includes functions for automation for sending warpnode structs and receiving structs with acks. |
---|
9 | #In addition this write stat structs to a file. The code connects with the dumb_server over port 9090 |
---|
10 | |
---|
11 | |
---|
12 | #Set Global Variables |
---|
13 | set ackSeqNum 1 |
---|
14 | set SendAck 1 |
---|
15 | set WriteStat 2 |
---|
16 | set filename "PacketData.m" |
---|
17 | set NumNodes 2; |
---|
18 | set dumbServerIP 10.0.0.6 |
---|
19 | set dumbServerPort 9090 |
---|
20 | |
---|
21 | ## |
---|
22 | #Sends data over socket, adding on the sequence number and number to specify it is a tcl string for |
---|
23 | #the dumb_server. The function can confirm acks or stat packets which are formatted for matlab by |
---|
24 | #the dumb_server. |
---|
25 | # |
---|
26 | #@param chan, the socket ID |
---|
27 | # |
---|
28 | #@param data, the string to be sent over the socket |
---|
29 | # |
---|
30 | #@param {arg}, arguement for the type of feedback to receive. Default=0 request nothing, SendAck=1 request acks, WriteStat=2 request Stats and write to a file |
---|
31 | proc senddata {chan data {arg 0}} { |
---|
32 | |
---|
33 | #Specify global variables usd by the function |
---|
34 | global ackSeqNum |
---|
35 | global SendAck |
---|
36 | global WriteStat |
---|
37 | global NumNodes |
---|
38 | |
---|
39 | |
---|
40 | #Request and ack packet from dumb_server |
---|
41 | if {$arg == $SendAck} { |
---|
42 | |
---|
43 | #Package data to be transmitted to dumb_server |
---|
44 | set val "1 $ackSeqNum $data" |
---|
45 | |
---|
46 | #Send $val over socket $chan |
---|
47 | puts $chan $val |
---|
48 | puts "Sent: $val" |
---|
49 | |
---|
50 | #Sleep 1 millsecond to help aid the cleaning of the buffer |
---|
51 | after 1 |
---|
52 | |
---|
53 | set attempts 1 |
---|
54 | #Check for an ack handshake, otherwise retransmit |
---|
55 | while {[ackHandshake $chan]} { |
---|
56 | incr attempts |
---|
57 | puts "Attempted transmission: $attempts" |
---|
58 | |
---|
59 | #Number of times to retransmit data before giving up |
---|
60 | if {$attempts == 5} { |
---|
61 | puts "Could not get ack" |
---|
62 | close $chan |
---|
63 | exit |
---|
64 | } |
---|
65 | |
---|
66 | #Resend $val over socket $chan |
---|
67 | puts $chan $val |
---|
68 | |
---|
69 | #Sleep 1 millsecond to help aid the cleaning of the buffer |
---|
70 | after 1 |
---|
71 | } |
---|
72 | |
---|
73 | incr ackSeqNum |
---|
74 | |
---|
75 | #seqNum on server and Nodes is a char with a max value of 256 |
---|
76 | if {$ackSeqNum==256} { |
---|
77 | set ackSeqNum 1 |
---|
78 | } |
---|
79 | return |
---|
80 | } |
---|
81 | |
---|
82 | #Request statistics from dumb_server |
---|
83 | if {$arg == $WriteStat} { |
---|
84 | #Package data to be transmitted to dumb_server |
---|
85 | set val "1 0 $data" |
---|
86 | |
---|
87 | #Send $val over socket $chan |
---|
88 | puts $chan $val |
---|
89 | puts "Sent: $val" |
---|
90 | |
---|
91 | |
---|
92 | set attempts 1 |
---|
93 | |
---|
94 | #Read statics and write to a file for the number of $NumNodes |
---|
95 | for {set i 0} { $i < $NumNodes } {incr i} { |
---|
96 | while {[statHandshake $chan]} { |
---|
97 | incr attempts |
---|
98 | puts "Attempted transmission: $attempts" |
---|
99 | |
---|
100 | #Number of times to retransmit data before giving up |
---|
101 | if {$attempts == 5} { |
---|
102 | puts "Could not get stat" |
---|
103 | close $chan |
---|
104 | exit |
---|
105 | } |
---|
106 | |
---|
107 | #Resend $val over socket $chan |
---|
108 | puts $chan $val |
---|
109 | |
---|
110 | #Sleep 1 millsecond to help aid the cleaning of the buffer |
---|
111 | after 1 |
---|
112 | |
---|
113 | } |
---|
114 | } |
---|
115 | return |
---|
116 | |
---|
117 | } |
---|
118 | |
---|
119 | #Default: Send data without expecting anything in return |
---|
120 | |
---|
121 | set val "1 0 $data" |
---|
122 | #Send $val over socket $chan |
---|
123 | puts $chan $val |
---|
124 | puts "Sent: $val" |
---|
125 | |
---|
126 | #Sleep 1 millsecond to help aid the cleaning of the buffer |
---|
127 | after 1 |
---|
128 | |
---|
129 | } |
---|
130 | |
---|
131 | ## |
---|
132 | #Read data from socket. |
---|
133 | # |
---|
134 | #@param chan, the channel socket ID |
---|
135 | # |
---|
136 | #@return data from socket |
---|
137 | proc receivedata {chan} { |
---|
138 | set received [gets $chan] ;# Send a string |
---|
139 | ;#puts "Received: $received" |
---|
140 | after 1 |
---|
141 | return $received |
---|
142 | } |
---|
143 | |
---|
144 | ## |
---|
145 | #Reads the socket for the parameter that signifies an ack from server. |
---|
146 | #This function checks for one second if the socket has an ack and if |
---|
147 | #the ack contains the proper seqNum. |
---|
148 | # |
---|
149 | #@param chan, the socket ID |
---|
150 | # |
---|
151 | #@return 1 if the correct ack and seqNum ARE NOT received, 0 if they ARE received |
---|
152 | proc ackHandshake {chan} { |
---|
153 | |
---|
154 | #Specify global variables usd by the function |
---|
155 | global ackSeqNum |
---|
156 | set i 0 |
---|
157 | |
---|
158 | while {1} { |
---|
159 | |
---|
160 | #Read data from channel socket |
---|
161 | set data [receivedata $chan] |
---|
162 | #Split data by whitespaces into a list of values |
---|
163 | set dataList [regexp -inline -all -- {\S+} $data] |
---|
164 | |
---|
165 | #Check if the second to last value is "_ack" |
---|
166 | if {[lindex $dataList end-1] == "_ack_"} { |
---|
167 | ;#puts "Received Ack with value: $data" |
---|
168 | ;#puts "Delay $i msec" ;#Delay between each command |
---|
169 | |
---|
170 | #Check if the last value, which is the seqNum, matches the $ackSeqNum |
---|
171 | if {[lindex $dataList end] == $ackSeqNum} { |
---|
172 | ;#puts "SeqNum Matches" ;#If we receive and ack and the SeqNum is the same |
---|
173 | return 0 |
---|
174 | } |
---|
175 | #If an _ack_ was received at precisely 1000 milliseconds, but contained the wrong seqNum |
---|
176 | if {$i==1000} { |
---|
177 | puts "SeqNum MisMatch: Expected $ackSeqNum but got [lindex $dataList end]" |
---|
178 | return 1 |
---|
179 | } |
---|
180 | |
---|
181 | } |
---|
182 | #If approximately 1000 millseconds pass without receiving an ack |
---|
183 | if {$i==1000} { |
---|
184 | puts "Didn't receive ACK" |
---|
185 | return 1 |
---|
186 | } |
---|
187 | |
---|
188 | after 1 |
---|
189 | incr i |
---|
190 | } |
---|
191 | } |
---|
192 | |
---|
193 | ## |
---|
194 | #Reads the socket for the string signifying statistics from the server. It then writes |
---|
195 | #the data to the file specified globally as $filename |
---|
196 | # |
---|
197 | #@param chan, the socket ID |
---|
198 | # |
---|
199 | #@return 1 if the statistic data IS NOT received, 0 if IS received |
---|
200 | proc statHandshake {chan} { |
---|
201 | set i 0 |
---|
202 | while {1} { |
---|
203 | |
---|
204 | #Read data from channel socket |
---|
205 | set data [receivedata $chan] |
---|
206 | #Read only values that are relevant to statistics data |
---|
207 | set data2 [regexp -inline -all -- {Node.*_statAck_} $data] |
---|
208 | #Split data by whitespaces into a list of values |
---|
209 | set dataList [regexp -inline -all -- {\S+} [join $data2]] |
---|
210 | |
---|
211 | #Check if the last value of data list signifies statistic data |
---|
212 | if {[lindex $dataList end] == "_statAck_"} { |
---|
213 | puts "Received/Writing StatAck with value: $data" |
---|
214 | puts "Delay $i" |
---|
215 | |
---|
216 | #Write statistic to file, while removing indicators that signify the data is statistic data |
---|
217 | writeStats [join [lreplace $dataList end end "\n"] " "] ;#Remove \n and _statAck_ from list, then make the list a string |
---|
218 | return 0 |
---|
219 | } |
---|
220 | |
---|
221 | #If approximately 1000 millseconds pass without receiving an ack |
---|
222 | if {$i==1000} { |
---|
223 | puts "Didn't receive Stat Packet" |
---|
224 | return 1 |
---|
225 | } |
---|
226 | |
---|
227 | after 1 |
---|
228 | incr i |
---|
229 | } |
---|
230 | } |
---|
231 | |
---|
232 | ## |
---|
233 | #Appends data to the file specified by the global variable $filename. Attaches a carriage return |
---|
234 | #after writing data |
---|
235 | # |
---|
236 | #@param data, the data to be written to file $filename |
---|
237 | proc write {data} { |
---|
238 | #Specify global variables usd by the function |
---|
239 | global filename |
---|
240 | set fileID [open $filename "a"] |
---|
241 | puts -nonewline $fileID $data |
---|
242 | puts -nonewline $fileID "\n" |
---|
243 | close $fileID |
---|
244 | } |
---|
245 | |
---|
246 | ## |
---|
247 | #Appends statistic data to the file specified by the global variable $filename. |
---|
248 | #Attaches a carriage return after writing data. The prints to the command line the statistics |
---|
249 | # |
---|
250 | #@param data, the data to be written to file $filename |
---|
251 | proc writeStats {data} { |
---|
252 | #Specify global variables usd by the function |
---|
253 | global filename |
---|
254 | set fileID [open $filename "a"] |
---|
255 | puts -nonewline $fileID $data |
---|
256 | puts -nonewline $fileID "\n" |
---|
257 | close $fileID |
---|
258 | |
---|
259 | printStats $data |
---|
260 | } |
---|
261 | |
---|
262 | ## |
---|
263 | #Prints to the command line the statistics data |
---|
264 | # |
---|
265 | #@param data, the statistics data to be printed |
---|
266 | proc printStats {data} { |
---|
267 | set dataList [regexp -inline -all -- {\S+} $data] |
---|
268 | puts "\n" |
---|
269 | puts "Stat Struct Contents:" |
---|
270 | puts "\tNode: [lindex $dataList 0]" |
---|
271 | puts "\tSec: [lindex $dataList 2]" |
---|
272 | puts "\tUsec: [lindex $dataList 3]" |
---|
273 | puts "\tGoodPackets: [lindex $dataList 4]" |
---|
274 | puts "\tOtherBadPackets: [lindex $dataList 5]" |
---|
275 | puts "\tPartnerBadPackets: [lindex $dataList 6]" |
---|
276 | puts "\tRxBytes: [lindex $dataList 7]" |
---|
277 | puts "\tTxBytes: [lindex $dataList 8]" |
---|
278 | puts "\tTimeInterval: [lindex $dataList 9]" |
---|
279 | puts "\tThroughput: [lindex $dataList 10]\n" |
---|
280 | |
---|
281 | } |
---|
282 | |
---|
283 | ###THREADED FUNCTIONS AND VALUES### |
---|
284 | |
---|
285 | #Keyboard input value shared among threads |
---|
286 | tsv::array set keyboardValue {0 0} ;#Arrays must have an even number of variables, The first argument states |
---|
287 | ;#Which value in the array is assigned, the next number is the stored value |
---|
288 | #Keyboard enable shared among threads |
---|
289 | tsv::array set keyboardEnable {0 0} |
---|
290 | |
---|
291 | |
---|
292 | #Create a thread |
---|
293 | set threadID [thread::create { |
---|
294 | |
---|
295 | ## |
---|
296 | #Reads the keyboard if $keyboardEnable is 1, write the keyboard value to $keyboardValue, then |
---|
297 | #set $keyboardEnable to 0 |
---|
298 | proc keyboardInputthread {} { |
---|
299 | global val |
---|
300 | while {1} { |
---|
301 | if {[eval tsv::get keyboardEnable 0] == 1} { |
---|
302 | puts "Input Command: " |
---|
303 | tsv::set keyboardValue 0 [gets stdin] |
---|
304 | tsv::set keyboardEnable 0 0 |
---|
305 | } |
---|
306 | } |
---|
307 | } |
---|
308 | |
---|
309 | #Keep thread alive |
---|
310 | ::thread::wait |
---|
311 | }] |
---|
312 | |
---|
313 | ## |
---|
314 | #Enable keyboard input and read keyboard input |
---|
315 | # |
---|
316 | #@return keyboardValue, the value inputted into the keyboard |
---|
317 | proc keyboardInput {} { |
---|
318 | tsv::set keyboardEnable 0 1 |
---|
319 | while {1} { |
---|
320 | if {[tsv::get keyboardEnable 0] == 0} { |
---|
321 | return [eval tsv::get keyboardValue 0] ;#Returns keyboard input |
---|
322 | } |
---|
323 | } |
---|
324 | } |
---|
325 | |
---|
326 | #Evaluate thread |
---|
327 | eval [subst {thread::send -async $threadID {keyboardInputthread } }] |
---|
328 | |
---|
329 | |
---|
330 | ### EXAMPLES OF STRUCTURES### |
---|
331 | |
---|
332 | ;# Observe Struct: structType, nodeID, partnerID, Time sec, time usec, goodPackets, partnerBadPackets, rxBytes, txBytes, otherBadPackets, throughput |
---|
333 | set ObserveStruct [list 49 0 2 30000 40000 50000 50000 50000 6000 700 10.3 ] |
---|
334 | |
---|
335 | ;# Control Struct: structType, nodeID, modOrder, txPower, coding, channel |
---|
336 | set ControlStruct0 [list 50 0 4 63 3 9 ] |
---|
337 | set ControlStruct1 [list 50 1 4 63 3 9 ] |
---|
338 | |
---|
339 | ;# Traffic Struct: structType, nodeID, trafficMode 0 = receive 1= transmit , reserved0, txInterval, txPacketLen, reserved1 |
---|
340 | set TrafficStruct0 [list 52 0 1 0 0 1470 0 ] |
---|
341 | set TrafficStruct1 [list 52 1 1 0 0 1470 0 ] |
---|
342 | |
---|
343 | ;# structType, nodeID is only relevant for ack on stop, cmdID, cmdParam |
---|
344 | set CommandStructStart [list 51 0 102 0 ] |
---|
345 | set CommandStructStop [list 51 0 103 0 ] |
---|
346 | |
---|
347 | ;# structType, nodeID, cmdID, cmdParam |
---|
348 | set CommandStructStats0 [list 51 0 104 0 ] |
---|
349 | set CommandStructStats1 [list 51 1 104 0 ] |
---|
350 | set CommandStructResetStats0 [list 51 0 97 0 ] |
---|
351 | set CommandStructResetStats1 [list 51 1 97 0 ] |
---|
352 | |
---|
353 | |
---|
354 | |
---|
355 | ### MAIN PROGRAM ### |
---|
356 | |
---|
357 | set sock [socket 10.0.0.6 9090] ;# Open the connection |
---|
358 | fconfigure $sock -buffering none -blocking 0 ;# automate flushing |
---|
359 | |
---|
360 | set TimeInterval 0 ;#Time in milliseconds |
---|
361 | set fileID [open $filename "w"] ;#Clear file if it exists |
---|
362 | |
---|
363 | for {set power 62} {$power < 64} {incr power} { |
---|
364 | for {set mod 2} {$mod<3} {incr mod} { ;#set mod |
---|
365 | |
---|
366 | |
---|
367 | senddata $sock [list 50 0 [expr {$mod*2}] $power 3 9 ] $SendAck ;#Control Structs |
---|
368 | senddata $sock [list 50 1 [expr {$mod*2}] $power 3 9 ] $SendAck |
---|
369 | senddata $sock [list 52 0 1 0 0 1470 0 ] $SendAck ;#Traffic Struct |
---|
370 | senddata $sock [list 52 1 1 0 0 1470 0 ] $SendAck |
---|
371 | |
---|
372 | senddata $sock $CommandStructResetStats0 $SendAck |
---|
373 | senddata $sock $CommandStructResetStats1 $SendAck |
---|
374 | |
---|
375 | puts "Type a number great than 0 to set a time for test in msecs" |
---|
376 | puts "Press Enter to run test for an unspecified time" |
---|
377 | set xtime [eval keyboardInput] |
---|
378 | senddata $sock $CommandStructStart |
---|
379 | if {$xtime > 0} { |
---|
380 | set TimeInterval $xtime} { |
---|
381 | puts "Press Enter when ready to stop test" |
---|
382 | set TimeInterval 0 |
---|
383 | keyboardInput |
---|
384 | } |
---|
385 | |
---|
386 | |
---|
387 | |
---|
388 | after $TimeInterval |
---|
389 | |
---|
390 | senddata $sock $CommandStructStop $SendAck |
---|
391 | senddata $sock $CommandStructStats0 $WriteStat |
---|
392 | senddata $sock $CommandStructStats1 $WriteStat |
---|
393 | |
---|
394 | } |
---|
395 | } |
---|
396 | |
---|
397 | |
---|
398 | close $sock |
---|
399 | exit |
---|