Skip to content

Commit ef3dbe1

Browse files
committed
lf_interop_real_browser.py : Refactor report flow with bandsteering support
Signed-off-by: sivakondri-CT <kondru.sankar@candelatech.com>
1 parent 7a37910 commit ef3dbe1

1 file changed

Lines changed: 244 additions & 15 deletions

File tree

py-scripts/real_application_tests/real_browser/lf_interop_real_browser_test.py

Lines changed: 244 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,8 +1338,6 @@ def collect_bandsteering_values(self):
13381338
"channel": channel_map.get(sta, "NA"),
13391339
"bssid": bssid
13401340
})
1341-
1342-
logger.info("[BANDSTEERING] Rows collected this tick: %d", len(rows))
13431341
logger.info("[DEBUG] CX BATCH: %s", self.bandsteering_cx_list)
13441342
return rows
13451343

@@ -1635,7 +1633,7 @@ def run_test(self, available_resources):
16351633
matched, abort = self.robo_obj.move_to_coordinate(coord=coordinate)
16361634
if not matched:
16371635
logging.warning(f"Failed to move to coordinate {coordinate}")
1638-
continue
1636+
continue
16391637
self.current_cord = coordinate
16401638
if self.rotations_enabled:
16411639
for angle in self.angles_list:
@@ -2373,6 +2371,15 @@ def create_report(self, iot_summary=None):
23732371
test_setup_info = with_iot_params_in_table(test_setup_info, iot_summary)
23742372
report.test_setup_table(
23752373
test_setup_data=test_setup_info, value='Test Parameters')
2374+
# Ensure real_time_data.csv is included before graph generation
2375+
if os.path.isfile("real_time_data.csv") and \
2376+
"real_time_data.csv" not in self.csv_file_names:
2377+
self.csv_file_names.insert(0, "real_time_data.csv")
2378+
2379+
# Keep only last iteration for normal full run
2380+
if not self.webui_stop_clicked and not self.do_bandsteering:
2381+
if len(self.csv_file_names) > 1:
2382+
self.csv_file_names = [self.csv_file_names[-1]]
23762383

23772384
for i in range(0, len(self.csv_file_names)):
23782385

@@ -2460,12 +2467,52 @@ def create_report(self, iot_summary=None):
24602467
total_err_data = data['total_err'].tolist()
24612468
else:
24622469
raise ValueError("The 'total_err' column was not found in the CSV file.")
2470+
# bandsteering bssid section
2471+
if self.do_bandsteering:
2472+
self.add_bandsteering_bssid_section(report)
2473+
# robot charging timestamps section
2474+
if self.do_bandsteering:
2475+
charging_ts = getattr(self.robo_obj, "charging_timestamps", [])
2476+
2477+
if charging_ts:
2478+
report.set_obj_html(
2479+
_obj_title="Robot Charging Timestamps",
2480+
_obj=""
2481+
)
2482+
report.build_objective()
2483+
2484+
df = pd.DataFrame(
2485+
charging_ts,
2486+
columns=[
2487+
"charge_dock_arrival_timestamp",
2488+
"charging_completion_timestamp"
2489+
]
2490+
)
2491+
2492+
# Add serial number column
2493+
df.insert(0, "S.No", range(1, len(df) + 1))
2494+
2495+
report.set_table_dataframe(df)
2496+
report.build_table()
2497+
else:
2498+
report.set_obj_html(
2499+
_obj_title="Robot Charging Timestamps",
2500+
_obj="Robot did not go to charge during this test"
2501+
)
2502+
report.build_objective()
24632503

24642504
report.set_table_title("Final Test Results")
24652505
report.build_table_title()
24662506
if self.selected_groups and self.selected_profiles:
24672507
if self.expected_passfail_value or self.device_csv_name:
2468-
pass_fail_list, test_input_list = self.generate_pass_fail_list(device_type_data, device_names, total_urls)
2508+
2509+
if self.webui_stop_clicked:
2510+
logging.info("[REPORT] WebUI stop detected. Skipping PASS/FAIL evaluation.")
2511+
pass_fail_list = ["NA"] * len(device_names)
2512+
test_input_list = ["NA"] * len(device_names)
2513+
else:
2514+
pass_fail_list, test_input_list = self.generate_pass_fail_list(
2515+
device_type_data, device_names, total_urls)
24692516

24702517
final_test_results = {
24712518

@@ -2509,13 +2556,28 @@ def create_report(self, iot_summary=None):
25092556
continue
25102557
report.set_table_title(f"{group} Test Results")
25112558
report.build_table_title()
2559+
# Ensure all columns have equal length
2560+
max_len = max(len(v) for v in final_test_results.values())
2561+
2562+
for key in final_test_results:
2563+
if len(final_test_results[key]) < max_len:
2564+
diff = max_len - len(final_test_results[key])
2565+
final_test_results[key].extend(["NA"] * diff)
2566+
25122567
test_results_df = pd.DataFrame(group_specific_test_results)
25132568
report.set_table_dataframe(test_results_df)
25142569
report.build_table()
25152570

25162571
else:
25172572
if self.expected_passfail_value or self.device_csv_name:
2518-
pass_fail_list, test_input_list = self.generate_pass_fail_list(device_type_data, device_names, total_urls)
2573+
if self.webui_stop_clicked:
2574+
logging.info("[REPORT] WebUI stop detected. Skipping PASS/FAIL evaluation.")
2575+
pass_fail_list = ["NA"] * len(device_names)
2576+
test_input_list = ["NA"] * len(device_names)
2577+
else:
2578+
pass_fail_list, test_input_list = self.generate_pass_fail_list(
2579+
device_type_data, device_names, total_urls)
2580+
25192581
final_test_results = {
25202582

25212583
"Device Type": device_type_data,
@@ -2552,6 +2614,14 @@ def create_report(self, iot_summary=None):
25522614
"Link Speed": tx_rate_data,
25532615

25542616
}
2617+
# Ensure all columns have equal length
2618+
max_len = max(len(v) for v in final_test_results.values())
2619+
2620+
for key in final_test_results:
2621+
if len(final_test_results[key]) < max_len:
2622+
diff = max_len - len(final_test_results[key])
2623+
final_test_results[key].extend(["NA"] * diff)
2624+
25552625
test_results_df = pd.DataFrame(final_test_results)
25562626
report.set_table_dataframe(test_results_df)
25572627
report.build_table()
@@ -2568,18 +2638,32 @@ def create_report(self, iot_summary=None):
25682638
except Exception as e:
25692639
logging.error(f"Error in create_report function {e}", exc_info=True)
25702640
finally:
2571-
if not self.dowebgui:
2572-
source_dir = "."
2573-
destination_dir = self.report_path_date_time
2574-
self.csv_file_names.append('real_time_data.csv')
2641+
source_dir = os.getcwd()
2642+
destination_dir = self.report_path_date_time
2643+
2644+
# Only move if report folder exists
2645+
if os.path.isdir(destination_dir):
2646+
2647+
if 'real_time_data.csv' not in self.csv_file_names:
2648+
self.csv_file_names.append('real_time_data.csv')
2649+
25752650
for filename in self.csv_file_names:
25762651
source_path = os.path.join(source_dir, filename)
25772652
destination_path = os.path.join(destination_dir, filename)
2653+
25782654
if os.path.isfile(source_path):
2579-
shutil.move(source_path, destination_path)
2580-
logging.info(f"Moved {filename} to {destination_dir}")
2581-
else:
2582-
logging.info(f"{filename} not found in the current directory")
2655+
try:
2656+
shutil.move(source_path, destination_path)
2657+
logging.info(f"Moved {filename} to {destination_dir}")
2658+
except Exception as e:
2659+
logging.warning(f"Could not move {filename}: {e}")
2660+
2661+
if self.do_bandsteering and hasattr(self, "bandsteering_dir"):
2662+
for fname in os.listdir(self.bandsteering_dir):
2663+
src = os.path.join(self.bandsteering_dir, fname)
2664+
dst = os.path.join(destination_dir, fname)
2665+
if os.path.isfile(src):
2666+
shutil.move(src, dst)
25832667

25842668
def extract_device_data(self, file_path):
25852669
# Load the CSV file
@@ -2920,16 +3004,30 @@ def create_robo_report(self):
29203004
test_setup_info = self.generate_test_setup_info()
29213005
self.report.test_setup_table(
29223006
test_setup_data=test_setup_info, value='Test Parameters')
3007+
# Ensure real_time_data.csv is included before graph generation
3008+
if os.path.isfile("real_time_data.csv") and \
3009+
"real_time_data.csv" not in self.csv_file_names:
3010+
self.csv_file_names.insert(0, "real_time_data.csv")
29233011

29243012
for coordinate in self.coordinates_list:
29253013
if self.rotations_enabled:
29263014
for angle in self.angles_list:
29273015
csv_file = f"{coordinate}_{angle}_webBrowser.csv"
3016+
if not os.path.isfile(csv_file):
3017+
logging.warning(f"CSV file {csv_file} does not exist.")
3018+
continue
29283019
self.create_robo_graphs_test_results(csv_file, coordinate, angle)
29293020

29303021
else:
29313022
csv_file = f"{coordinate}_webBrowser.csv"
3023+
if not os.path.isfile(csv_file):
3024+
logging.warning(f"CSV file {csv_file} does not exist.")
3025+
continue
29323026
self.create_robo_graphs_test_results(csv_file, coordinate)
3027+
# bandsteering bssid section
3028+
if self.do_bandsteering:
3029+
self.add_bandsteering_bssid_section(self.report)
3030+
29333031
self.add_live_view_images_to_report()
29343032

29353033
if self.dowebgui:
@@ -3058,7 +3156,13 @@ def create_robo_graphs_test_results(self, csv_file, coordinate, angle=None):
30583156
self.report.set_table_title(f"Final Test Results at coordinate {coordinate}:")
30593157
self.report.build_table_title()
30603158
if self.expected_passfail_value or self.device_csv_name:
3061-
pass_fail_list, test_input_list = self.generate_pass_fail_list(device_type_data, device_names, total_urls)
3159+
if self.webui_stop_clicked:
3160+
logging.info("[REPORT] WebUI stop detected. Skipping PASS/FAIL evaluation.")
3161+
pass_fail_list = ["NA"] * len(device_names)
3162+
test_input_list = ["NA"] * len(device_names)
3163+
else:
3164+
pass_fail_list, test_input_list = self.generate_pass_fail_list(
3165+
device_type_data, device_names, total_urls)
30623166

30633167
final_test_results = {
30643168

@@ -3102,6 +3206,104 @@ def create_robo_graphs_test_results(self, csv_file, coordinate, angle=None):
31023206
except Exception as e:
31033207
logging.error(f"Error in create_robo_graphs_test_results {e}", exc_info=True)
31043208

3209+
def add_bandsteering_bssid_section(self, report):
3210+
# for bssids things in pdf
3211+
report.set_table_title("Band Steering – BSSID Transition Analysis")
3212+
report.build_table_title()
3213+
3214+
if not self.bssids:
3215+
return
3216+
3217+
# normalize CLI BSSIDs
3218+
cli_bssids = [b.strip().upper() for b in self.bssids]
3219+
3220+
for csv_file in self.band_csv_files.values():
3221+
3222+
if not os.path.exists(csv_file):
3223+
continue
3224+
3225+
df = pd.read_csv(csv_file)
3226+
3227+
if df.empty or "bssid" not in df.columns:
3228+
continue
3229+
3230+
device_name = df["device_name"].iloc[0]
3231+
3232+
# Normalize CSV
3233+
df["bssid"] = df["bssid"].astype(str).str.upper().str.strip()
3234+
3235+
# Detect transitions from FULL CSV
3236+
df["prev_bssid"] = df["bssid"].shift()
3237+
3238+
transition_mask = (
3239+
(df["bssid"] != df["prev_bssid"]) &
3240+
(df["bssid"] != "NA")
3241+
)
3242+
3243+
transition_rows = df[transition_mask]
3244+
3245+
# Count only CLI BSSIDs
3246+
bssid_counts = {b: 0 for b in cli_bssids}
3247+
transitions = []
3248+
3249+
for _, row in transition_rows.iterrows():
3250+
curr_bssid = row["bssid"]
3251+
3252+
if curr_bssid in cli_bssids:
3253+
bssid_counts[curr_bssid] += 1
3254+
3255+
transitions.append({
3256+
"BSSID": curr_bssid,
3257+
"Timestamp": row.get("timestamp", "NA"),
3258+
"From Coordinate": row.get("from_coordinate", "NA"),
3259+
"To Coordinate": row.get("to_coordinate", "NA"),
3260+
"Channel": row.get("channel", "NA")
3261+
})
3262+
3263+
bssid_list = list(bssid_counts.keys())
3264+
count_list = list(bssid_counts.values())
3265+
3266+
report.set_graph_title(f"BSSID Change Count – {device_name}")
3267+
report.build_graph_title()
3268+
3269+
graph = lf_bar_graph_horizontal(
3270+
_data_set=[count_list],
3271+
_xaxis_name="Transition Count",
3272+
_yaxis_name="BSSID",
3273+
_yaxis_label=bssid_list,
3274+
_yaxis_categories=bssid_list,
3275+
_bar_height=0.25,
3276+
_show_bar_value=True,
3277+
_figsize=(18, max(4, len(bssid_list))),
3278+
_graph_title="BSSID Transitions",
3279+
_graph_image_name=f"{device_name}_bssid_transitions",
3280+
_label=["Transitions"]
3281+
)
3282+
3283+
graph_image = graph.build_bar_graph_horizontal()
3284+
report.set_graph_image(graph_image)
3285+
report.move_graph_image()
3286+
report.build_graph()
3287+
3288+
report.set_table_title(f"Band Steering Results for {device_name}")
3289+
report.build_table_title()
3290+
3291+
if not transitions:
3292+
first_row = df.iloc[0]
3293+
last_row = df.iloc[-1]
3294+
3295+
transitions.append({
3296+
"BSSID": first_row.get("bssid", "NA"),
3297+
"Timestamp": last_row.get("timestamp", "NA"),
3298+
"From Coordinate": first_row.get("from_coordinate", "NA"),
3299+
"To Coordinate": last_row.get("to_coordinate", "NA"),
3300+
"Channel": first_row.get("channel", "NA")
3301+
})
3302+
3303+
transition_df = pd.DataFrame(transitions)
3304+
report.set_table_dataframe(transition_df)
3305+
report.build_table()
3306+
31053307
def clear_http_cx_data(self):
31063308
"""Clears endpoint counters for all created HTTP connections."""
31073309
for cx in self.http_profile.created_cx:
@@ -3266,6 +3468,7 @@ def main():
32663468
optional.add_argument('--iot_increment', type=str, default='', help='Comma-separated list of device counts to incrementally test (e.g., "1,3,5")')
32673469

32683470
# ROBO ARGS
3471+
robo.add_argument('--duration_to_skip', help='Robot wait duration in seconds at obstacle', default="1")
32693472
robo.add_argument('--robo_ip', type=str, help='Specify the robo ip')
32703473
robo.add_argument(
32713474
'--coordinates',
@@ -3280,6 +3483,24 @@ def main():
32803483
'--do_robo',
32813484
help="Specify this flag to perform the test with robo", action='store_true'
32823485
)
3486+
robo.add_argument(
3487+
"--do_bandsteering",
3488+
action="store_true",
3489+
help="Enable continuous robo band-steering test"
3490+
)
3491+
robo.add_argument(
3492+
"--cycles",
3493+
type=int,
3494+
default=1,
3495+
help="Number of cycles to repeat coordinates for band-steering"
3496+
)
3497+
robo.add_argument(
3498+
'--bssids',
3499+
type=str,
3500+
default='',
3501+
help='bssid values'
3502+
)
3503+
32833504
args = parser.parse_args()
32843505
if args.help_summary:
32853506
print(help_summary)
@@ -3348,7 +3569,11 @@ def main():
33483569
coordinates_list=args.coordinates,
33493570
angles_list=args.rotations,
33503571
do_robo=args.do_robo,
3572+
do_bandsteering=args.do_bandsteering,
3573+
cycles=args.cycles,
3574+
bssids=args.bssids,
33513575
rotations_enabled=rotations_enabled,
3576+
duration_to_skip=args.duration_to_skip
33523577
)
33533578
obj.change_port_to_ip()
33543579
obj.validate_and_process_args()
@@ -3414,7 +3639,11 @@ def main():
34143639
logger.error("An exception occurred:\n%s", tb_str)
34153640
finally:
34163641
if '--help' not in sys.argv and '-h' not in sys.argv:
3417-
if args.do_robo:
3642+
if args.do_robo and args.do_bandsteering:
3643+
if args.dowebgui:
3644+
obj.stop_webui_test()
3645+
obj.create_report(iot_summary=iot_summary)
3646+
elif args.do_robo:
34183647
if args.dowebgui:
34193648
obj.stop_webui_test()
34203649
obj.create_robo_report()

0 commit comments

Comments
 (0)