|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +#---------------------------------------------------------------------------- |
| 4 | +# Created By : Bryson Schiel - @schielb (GitHub) |
| 5 | +# Created Date: May 18, 2023 |
| 6 | +# Latest Update: May 18, 2023 |
| 7 | +# version ='1.1' |
| 8 | +# --------------------------------------------------------------------------- |
| 9 | +""" |
| 10 | + This file tries to "zoom in" on the knee for each RSU during testing. |
| 11 | +""" |
| 12 | +# --------------------------------------------------------------------------- |
| 13 | + |
| 14 | +import pyshark |
| 15 | +from yaml import safe_load |
| 16 | +from resources.mesh_class import MeshClass |
| 17 | +import copy |
| 18 | +from threading import Event, Thread |
| 19 | +import time |
| 20 | +import os |
| 21 | +from datetime import datetime |
| 22 | +import matplotlib.pyplot as plt |
| 23 | + |
| 24 | +def call_repeatedly(interval, func, *args): |
| 25 | + stopped = Event() |
| 26 | + def loop(): |
| 27 | + while not stopped.wait(interval): # the first call is in `interval` secs |
| 28 | + func(*args) |
| 29 | + Thread(target=loop).start() |
| 30 | + return stopped.set |
| 31 | + |
| 32 | + |
| 33 | +with open("./cv2x.yml") as f: |
| 34 | + yaml_data = safe_load(f) |
| 35 | + |
| 36 | +mesh = MeshClass(yaml_data["mesh_ip"]) |
| 37 | + |
| 38 | +rsus : dict = copy.deepcopy(yaml_data["rsus"]) |
| 39 | + |
| 40 | +data = {} |
| 41 | +cur_time : int |
| 42 | + |
| 43 | +def display_data(): |
| 44 | + global cur_time |
| 45 | + cur_time += 1 |
| 46 | + print("\r Time: %d // " % cur_time, end='') |
| 47 | + for rsu in data.keys(): |
| 48 | + print(rsu + " ≈ " + str(data[rsu]) + " // ", end='') |
| 49 | + |
| 50 | + |
| 51 | +def clear_data(): |
| 52 | + global cur_time |
| 53 | + cur_time = 0 |
| 54 | + for name in rsus.keys(): |
| 55 | + data[name] = 0 |
| 56 | + |
| 57 | +def handle_paket(block): |
| 58 | + #print("Got something!") |
| 59 | + i = 0 |
| 60 | + for rsu in rsus.keys(): |
| 61 | + if (hasattr(block, 'ip') |
| 62 | + and str(block['ip'].src) == rsus[rsu]['ip'] # RSU IP Address |
| 63 | + and str(block['ip'].dst) == yaml_data['host_ip'] # This IP address ### CHANGE MANUALLY IN YAML! ### |
| 64 | + and str(block['ip'].proto) == '17' # UDP Protocol number |
| 65 | + and hasattr(block, 'udp') |
| 66 | + and str(block['udp'].dstport) == str(rsus[rsu]['dst_port']) # This UDP reception port |
| 67 | + and hasattr(block, 'DATA') |
| 68 | + ): |
| 69 | + # If we got in here, then this packet is a forwarded C-V2X packet from the specified RSU |
| 70 | + data[rsu] += 1 |
| 71 | + #print ("Packet received: ", rsu) |
| 72 | + |
| 73 | +def uniquify(path): |
| 74 | + filename, extension = os.path.splitext(path) |
| 75 | + counter = 1 |
| 76 | + |
| 77 | + while os.path.exists(path): |
| 78 | + path = filename + "(" + str(counter) + ")" + extension |
| 79 | + counter += 1 |
| 80 | + |
| 81 | + return path |
| 82 | + |
| 83 | +cap_folder_name = "Packet_Captures/" + datetime.now().strftime("%b-%d-%H:%M:%S") |
| 84 | +res_folder_name = "Results/" + datetime.now().strftime("%b-%d-%H:%M:%S") |
| 85 | + |
| 86 | +#See if we need to create a new folder and csv file for today for today |
| 87 | +for newpath in [cap_folder_name, res_folder_name]: |
| 88 | + if not os.path.exists(newpath): |
| 89 | + os.makedirs(newpath) |
| 90 | + |
| 91 | +# This is where we see some tricky stuff. We set a bottom and a top range for attenuation values, and then |
| 92 | +# we need to sort through each one for each RSU to find the knee of the RSU (where, for one dB value, we |
| 93 | +# get a reception rate above the critical safrety limit, and for the next dB, that RSU performs below the |
| 94 | +# critical safety limit). |
| 95 | +# |
| 96 | +# I think that in this case, it is fine to keep gathering from each RSU, even if we have already found a |
| 97 | +# knee for the other RSUs. I think the best way is to find a middle sort option for each RSU, and in |
| 98 | +# reality we need to focus on 1 RSU at a time. We will probably wind up having to go back and forth quite |
| 99 | +# a bit to make it work out, but that should be okay. |
| 100 | +bottom_att = yaml_data["att_low"] |
| 101 | +top_att = yaml_data["att_high"] |
| 102 | +attenuation = bottom_att |
| 103 | + |
| 104 | +critical_safety_limit = 0.9 |
| 105 | + |
| 106 | +knee_finders = {} |
| 107 | +for rsu in rsus: |
| 108 | + knee_finders[rsu] = {"bottom": bottom_att, "top": top_att, "knee_found": False} |
| 109 | + |
| 110 | +# This set of numbers lets us make sure that we find knees iteratively, focusing on one and then the other |
| 111 | +num_knees_found = 0 |
| 112 | +num_rsus = len(rsus) |
| 113 | + |
| 114 | +results = {} |
| 115 | + |
| 116 | + |
| 117 | +def midway(bottom, top): |
| 118 | + return int(((top - bottom) / 2) + bottom) |
| 119 | + |
| 120 | +while num_knees_found < num_rsus: |
| 121 | + # Clear the data and declare the start of the trial |
| 122 | + clear_data() |
| 123 | + print("\x1B[32mSetting up trial on attenuation value\x1B[35m %d\x1B[32m db for\x1B[35m %d\x1B[32m seconds...\x1B[37m" |
| 124 | + % (attenuation, yaml_data["trial_length"])) |
| 125 | + |
| 126 | + cap_file_name = uniquify(cap_folder_name + "/attenuation_%d.pcap" % attenuation) |
| 127 | + |
| 128 | + cap = pyshark.LiveCapture(interface=yaml_data["wireshark_interface"], |
| 129 | + bpf_filter="udp and not src host %s" % yaml_data['host_ip'], |
| 130 | + output_file=cap_file_name) |
| 131 | + |
| 132 | + |
| 133 | + |
| 134 | + # Go through and set all the attenuation values we need |
| 135 | + for tx_port in yaml_data["static_mesh_ports"]: |
| 136 | + for rsu in rsus.keys(): |
| 137 | + rx_port = rsus[rsu]["mesh_port"] |
| 138 | + partial_att = rsus[rsu]["att_offset"] |
| 139 | + diff_att = attenuation - partial_att |
| 140 | + diff_att = round(diff_att * 4) / 4 # needs a multiple of 0.25 |
| 141 | + |
| 142 | + #print('dbg: mesh.set_att(%s, %s, %f)' % (tx_port, rx_port, diff_att)) |
| 143 | + mesh.set_att(tx_port, rx_port, diff_att) |
| 144 | + |
| 145 | + time.sleep(10) # Delay to allow new setup to settle |
| 146 | + print("Starting trial:") |
| 147 | + |
| 148 | + # Start the repeating timer |
| 149 | + end_timer = call_repeatedly(1, display_data) |
| 150 | + |
| 151 | + # Start the packet capture |
| 152 | + try: |
| 153 | + cap.apply_on_packets(handle_paket, timeout=int(yaml_data["trial_length"])) |
| 154 | + |
| 155 | + except Exception as e: |
| 156 | + if e is TimeoutError: |
| 157 | + # This just means that a packet was caught mid-exit; not fatal to the experiment |
| 158 | + pass |
| 159 | + finally: |
| 160 | + end_timer() |
| 161 | + #timer.cancel() |
| 162 | + print() |
| 163 | + print("\x1B[32mEnding trial for\x1B[35m %d\x1B[32m db\x1B[37m" % attenuation, end="\n") |
| 164 | + |
| 165 | + print("Saving data from trial...") |
| 166 | + # Save the results in their own files |
| 167 | + cap.close() |
| 168 | + |
| 169 | + results[attenuation] = {} |
| 170 | + |
| 171 | + for rsu in rsus: |
| 172 | + |
| 173 | + |
| 174 | + total_time_gap = yaml_data["trial_length"] |
| 175 | + num_packets = data[rsu] |
| 176 | + |
| 177 | + estimated_num_spaced = int(total_time_gap * 10) |
| 178 | + percent_reception = float(float(num_packets) / float(estimated_num_spaced)) |
| 179 | + |
| 180 | + summaries_file_name = '%s/summaries_%s.txt' % (res_folder_name, rsu) |
| 181 | + |
| 182 | + print("Saving %s data in %s" % (rsu, summaries_file_name)) |
| 183 | + |
| 184 | + print(file=open(summaries_file_name, 'a')) |
| 185 | + print("Attenuation ", attenuation, file=open(summaries_file_name, 'a')) |
| 186 | + print("Number of packets: ", num_packets, file=open(summaries_file_name, 'a')) |
| 187 | + print('Total time gap: ', total_time_gap, file=open(summaries_file_name, 'a')) |
| 188 | + print('Total expected packets: ', estimated_num_spaced, file=open(summaries_file_name, 'a')) |
| 189 | + print("Calculated missed packets: ", estimated_num_spaced - num_packets, file=open(summaries_file_name, 'a')) |
| 190 | + print("Percent reception: ", percent_reception, file=open(summaries_file_name, 'a')) |
| 191 | + |
| 192 | + # Now we focus on taking the current recpetion rate and trying to find the knee |
| 193 | + if percent_reception > critical_safety_limit: |
| 194 | + if attenuation > knee_finders[rsu]["bottom"]: |
| 195 | + knee_finders[rsu]["bottom"] = attenuation |
| 196 | + else: |
| 197 | + # Crucially, we don't want to record a slightly low value if a higher attenuation, for |
| 198 | + # whatever reason, gives us a better recpetion rate. |
| 199 | + if attenuation < knee_finders[rsu]["top"] and attenuation > knee_finders[rsu]["bottom"]: |
| 200 | + knee_finders[rsu]["top"] = attenuation |
| 201 | + |
| 202 | + results[attenuation][rsu] = percent_reception |
| 203 | + |
| 204 | + |
| 205 | + |
| 206 | + print("Data saved 👍\n") |
| 207 | + # Here, we reset each knee as needed |
| 208 | + for rsu in list(rsus)[num_knees_found:]: |
| 209 | + if knee_finders[rsu]["top"] - knee_finders[rsu]["bottom"] == 1: |
| 210 | + knee_finders[rsu]["knee_found"] = True |
| 211 | + num_knees_found += 1 |
| 212 | + else: |
| 213 | + if knee_finders[rsu]["top"] - knee_finders[rsu]["bottom"] < 0: |
| 214 | + # Error, top and bottom are switched, move top back |
| 215 | + knee_finders[rsu]["top"] = top_att |
| 216 | + # No matter what, if the current index doesn't check out, break |
| 217 | + break |
| 218 | + |
| 219 | + # Here, we need to set the "attenuation" variable for the next loop |
| 220 | + if num_knees_found < num_rsus: |
| 221 | + nkf = list(knee_finders)[num_knees_found] |
| 222 | + attenuation = midway(knee_finders[nkf]["bottom"], knee_finders[nkf]["top"]) |
| 223 | + |
| 224 | + print("New attenuation: %d" % attenuation) |
| 225 | + |
| 226 | +############################################################################################## |
| 227 | +# At this point, all attenuations have been gathered, and we are ready to display the results. |
| 228 | +############################################################################################## |
| 229 | +for rsu in rsus: |
| 230 | + |
| 231 | + values = [] |
| 232 | + |
| 233 | + num_vals = 0 |
| 234 | + att = 0 |
| 235 | + rec = 0.0 |
| 236 | + |
| 237 | + for attenuation in results: |
| 238 | + values.append((attenuation, results[attenuation][rsu])) |
| 239 | + values.sort() |
| 240 | + |
| 241 | + attenuations = [] |
| 242 | + reception_rates = [] |
| 243 | + for i in values: |
| 244 | + attenuations.append(i[0]) |
| 245 | + reception_rates.append(i[1] * 100.0) |
| 246 | + |
| 247 | + data['att'] = attenuations |
| 248 | + data[rsu] = {} |
| 249 | + data[rsu]['rate'] = reception_rates |
| 250 | + |
| 251 | + |
| 252 | + plt.plot(attenuations, reception_rates, marker = 'o') |
| 253 | + plt.xlabel("Attenuations (dB)") |
| 254 | + plt.ylabel("Packet Reception Rate (%)") |
| 255 | + plt.title("Reception Rate per Attenuation (%s)" % rsu) |
| 256 | + plt.ylim(-4, 104) |
| 257 | + plt.axhline(y=90, color='red', linestyle='--', label='Critical Safety Limit: 90%') |
| 258 | + plt.show() |
| 259 | + plt.savefig(res_folder_name + '/Attenuations-%s.png' % rsu) |
| 260 | + plt.clf() |
| 261 | + |
| 262 | +for rsu in rsus: |
| 263 | + plt.plot(data['att'], data[rsu]['rate'], label=rsu, marker = 'o') |
| 264 | + |
| 265 | +plt.xlabel("Attenuation (db)") |
| 266 | +plt.ylabel("Packet Reception Rate (%)") |
| 267 | +plt.ylim(-4, 104) |
| 268 | +plt.title("Reception Rate per Attenuation (All RSU Comparison)") |
| 269 | +plt.axhline(y=90, color='red', linestyle='--', label='Critical Safety Limit: 90%') |
| 270 | +plt.legend() |
| 271 | +plt.savefig(res_folder_name + '/Comparison-Attenuations.png') |
| 272 | +plt.show() |
| 273 | +plt.clf() |
| 274 | + |
0 commit comments