2121from device import Device
2222from OffsetLogger import OffsetLogger
2323from config import config
24-
2524import logging
26- logging .getLogger ('pupil_labs.realtime_api.time_echo' ).setLevel (logging .ERROR )
27- logger = logging .getLogger ('main_device_monitor' )
25+
2826
2927#Define column fields
3028COLUMNS = ("Check" , "Device" , "IP" , "PING" , "WIFI" , "ADB" , "Battery" , "Storage" , "USB" , "RED_INDICATOR" ,
@@ -40,9 +38,12 @@ class TableApp(App):
4038 row_keys = []
4139 column_keys = []
4240 devices = []
43- offset_logger : OffsetLogger = None
44- file_ts = datetime .now ().strftime ('%y%m%dT%H%M%S' )
45- events_file = os .path .join (config ["paths" ]["logs" ], f"{ file_ts } _events.json" )
41+ offset_logger = None
42+ logger = None
43+ session_id = datetime .now ().strftime ('%y%m%dT%H%M%S' ) #Session ID created using timestamp; could also be created using UUID, user input, etc.
44+ session_dir = os .path .join (config ["logs" ]["path" ], session_id )
45+ events_file = os .path .join (session_dir , "events.json" )
46+ single_session_mode = config ["single_session_mode" ]
4647
4748 restart_app_in_progress = False
4849
@@ -53,15 +54,29 @@ def on_mount(self) -> None:
5354 Sets up the device table, generates IP addresses for devices, and schedules
5455 periodic updates for various metrics related to the devices.
5556 """
56- os .makedirs (config ["paths" ]["logs" ], exist_ok = True )
57+
58+ ## Setup session directory and logging
59+ try :
60+ os .makedirs (self .session_dir )
61+ except FileExistsError :
62+ print ("Session folder already exists, please try again." )
63+ self .app .exit ()
64+ logging .basicConfig (
65+ filename = os .path .join (self .session_dir ,'logs.txt' ),
66+ encoding = 'utf-8' ,
67+ level = config ["logs" ]["level" ], # change to DEBUG if required
68+ format = '[%(asctime)s] %(levelname)s [%(name)s] %(message)s' )
69+ logging .getLogger ('pupil_labs.realtime_api.time_echo' ).setLevel (logging .ERROR )
70+ self .logger = logging .getLogger ('glassesRecord_TUI' )
71+
72+ # Setup app theme and table
5773 self .theme = "textual-dark"
5874 table = self .query_one (SelectableRowsDataTable )
5975 table .cursor_type = "row"
6076 self .column_keys = table .add_columns (* COLUMNS ) #is_valid_column_index(self, column_index) can be used to verify
6177
62- self .devices = []
63-
6478 #Generate ip addrs of devices using config parameters (looks for a host_id range by default)
79+ self .devices = []
6580 if "network_id" in config and "host_id" in config and config ["network_id" ] and (config ["host_id" ]["start" ] <= config ["host_id" ]["end" ]):
6681 network_id = config ["network_id" ]
6782 host_id_range = range (config ["host_id" ]["start" ], config ["host_id" ]["end" ]+ 1 )
@@ -76,13 +91,16 @@ def on_mount(self) -> None:
7691 d = Device (str (ip_addr ), '5555' )
7792 self .devices .append (d )
7893
94+ #Init offset logger for all devices if not in single session mode
95+ if not self .single_session_mode :
96+ self .offset_logger = {dev : None for dev in ip_list }
97+
7998 data = [(None , d .ip_addr , None , None , None , None , None , None , None , None , None , None , None , None , None ) for d in self .devices ]
8099 self .row_keys = table .add_rows (data )
81100 table .styles .scroll_x = "scroll_x"
82-
83101 #Splitting columns into two batches with different update timers. This setting could be configured further with more batches or a single batch.
84- self .set_interval (3 , self .batch_col_update1 )
85- self .set_interval (5 , self .batch_col_update2 )
102+ self .set_interval (config [ "update_times" ][ "batch1" ] , self .batch_col_update1 )
103+ self .set_interval (config [ "update_times" ][ "batch2" ] , self .batch_col_update2 )
86104
87105 self .table_app_start_time = datetime .now ()
88106
@@ -451,12 +469,12 @@ def start_recording(self, ip_addr):
451469 time .sleep (3 )
452470
453471 try :
454- logger .info (f'Start recording on { ip_addr } ' )
472+ self . logger .info (f'Start recording on { ip_addr } ' )
455473 res = requests .post (f"http://{ ip_addr } :8080/api/recording:start" , timeout = 2 ).json ()
456474 time .sleep (0.1 )
457475 self .lock_phone (ip_addr )
458476 except Exception as e :
459- logger .error (f'{ ip_addr } , { e } ' )
477+ self . logger .error (f'{ ip_addr } , { e } ' )
460478 pass
461479
462480 return res
@@ -472,10 +490,9 @@ def stop_and_save_recording(self, ip_addr):
472490 """
473491 res = None
474492 try :
475- logger .info (f'Stop and save recording on { ip_addr } ' )
476493 res = requests .post (f"http://{ ip_addr } :8080/api/recording:stop_and_save" , timeout = 2 ).json ()
477494 except Exception as e :
478- logger .error (f'{ ip_addr } , { e } ' )
495+ self . logger .error (f'{ ip_addr } , { e } ' )
479496 pass
480497 return res
481498
@@ -490,10 +507,9 @@ def stop_and_discard_recording(self, ip_addr):
490507 """
491508 res = None
492509 try :
493- logger .info (f'Stop and discard recording on { ip_addr } ' )
494510 res = requests .post (f"http://{ ip_addr } :8080/api/recording:cancel" , timeout = 2 ).json ()
495511 except Exception as e :
496- logger .error (f'{ ip_addr } , { e } ' )
512+ self . logger .error (f'{ ip_addr } , { e } ' )
497513 print (e )
498514 pass
499515 return res
@@ -521,6 +537,29 @@ def on_input_submitted(self, event: Input.Submitted) -> None:
521537 # Clear box
522538 input_box .value = ""
523539
540+ def stop_recording_offsets (self , device_list ):
541+ """
542+ Stop logging offsets for the specified devices.
543+ Args:
544+ devices (list): List of device IP addresses to stop logging offsets for.
545+ """
546+ try :
547+ if self .single_session_mode :
548+ if self .offset_logger :
549+ self .offset_logger .stop_logging ()
550+ self .offset_logger = None
551+ self .logger .info ("Stopped offset logging for all devices." )
552+ else :
553+ for dev in device_list :
554+ if self .offset_logger [dev ]:
555+ self .offset_logger [dev ].stop_logging ()
556+ self .offset_logger [dev ] = None
557+ self .logger .info ("Stopped offset logging for devices: {}" .format (device_list ))
558+ except Exception as e :
559+ self .logger .error (f'Error stopping offset logging: { e } ' )
560+ print (e )
561+ pass
562+
524563 #Defining actions
525564 @work (exclusive = True , thread = True )
526565 async def action_recording_start (self ) -> None :
@@ -531,13 +570,22 @@ async def action_recording_start(self) -> None:
531570 """
532571 table = self .query_one (SelectableRowsDataTable )
533572 selected_devices = [row .data [1 ] for row in table .selected_rows ]
534- logger .info ("Selected devices ({}): {}" .format (len (selected_devices ), selected_devices ))
573+ self . logger .info ("Selected devices ({}): {}" .format (len (selected_devices ), selected_devices ))
535574 print ("STARTING REC on selected devices" )
536575
537- if not self .offset_logger :
538- self .offset_logger = OffsetLogger (selected_devices , log_dir = config ["paths" ]["logs" ])
539- logger .info (f"Starting Offset logger at { self .offset_logger .log_file } " )
540- self .offset_logger .log_offsets ()
576+ if self .single_session_mode :
577+ if not self .offset_logger :
578+ self .offset_logger = OffsetLogger (selected_devices , log_dir = self .session_dir , log_interval = config ["logs" ]["interval" ])
579+ self .logger .info (f"Starting Offset logger at { self .offset_logger .log_file } " )
580+ self .offset_logger .log_offsets ()
581+ else :
582+ for dev in selected_devices :
583+ if not self .offset_logger [dev ]:
584+ self .offset_logger [dev ] = OffsetLogger ([dev ], log_dir = os .path .join (self .session_dir , str (dev )), log_interval = config ["logs" ]["interval" ])
585+ self .logger .info (f"Starting Offset logger at { self .offset_logger [dev ].log_file } for device: { dev } " )
586+ self .offset_logger [dev ].log_offsets ()
587+
588+ #Start recording on all devices independently
541589 for d in selected_devices :
542590 t = threading .Thread (target = self .start_recording , args = (d ,), daemon = True )
543591 t .start ()
@@ -554,10 +602,10 @@ async def action_recording_stop_and_save(self) -> None:
554602 """
555603 table = self .query_one (SelectableRowsDataTable )
556604 selected_devices = [row .data [1 ] for row in table .selected_rows ]
557- logger . info ( "Selected devices ({} ): {}" . format ( len ( selected_devices ) , selected_devices ) )
558- print ( "STOPPING REC on selected devices " )
559-
560- logger . info ( "Stopping recording on devices" )
605+ print ( "STOPPING REC on selected device(s ): " , selected_devices )
606+ self . logger . info ( f"Stopping recording on { len ( selected_devices ) } device(s): { selected_devices } " )
607+ # Stop offset logging if it was started
608+ self . stop_recording_offsets ( selected_devices )
561609 for d in selected_devices :
562610 t = threading .Thread (target = self .stop_and_save_recording , args = (d ,), daemon = True )
563611 t .start ()
@@ -571,10 +619,10 @@ async def action_recording_stop_and_discard(self) -> None:
571619 """
572620 table = self .query_one (SelectableRowsDataTable )
573621 selected_devices = [row .data [1 ] for row in table .selected_rows ]
574- logger .info ("Selected devices ({}): {}" .format (len (selected_devices ), selected_devices ))
575622 print ("DISCARDING REC on selected devices" )
576-
577- logger .info ("Discarding recording on devices" )
623+ # Stop offset logging if it was started
624+ self .stop_recording_offsets (selected_devices )
625+ self .logger .info (f"Discarding recording on { len (selected_devices )} device(s): { selected_devices } " )
578626 for d in selected_devices :
579627 t = threading .Thread (target = self .stop_and_discard_recording , args = (d ,), daemon = True )
580628 t .start ()
@@ -586,9 +634,9 @@ async def action_restart_app_on_devices(self) -> None:
586634 This method retrieves the selected devices from the UI and restarts
587635 the Neon Companion application on each one.
588636 """
589- logger .info ('action_restart_app_on_devices triggered!' )
637+ self . logger .info ('action_restart_app_on_devices triggered!' )
590638 if self .restart_app_in_progress :
591- logger .info ('Another restart progress is already in progress, nothing to do...' )
639+ self . logger .info ('Another restart progress is already in progress, nothing to do...' )
592640 return
593641 print ("RESTARTING APP on selected devices" )
594642
@@ -597,14 +645,14 @@ async def action_restart_app_on_devices(self) -> None:
597645
598646 table = self .query_one (SelectableRowsDataTable )
599647 selected_device_ip_addrs = [row .data [1 ] for row in table .selected_rows ]
600- logger .info ("Selected devices ({}): {}" .format (len (selected_device_ip_addrs ), selected_device_ip_addrs ))
648+ self . logger .info ("Selected devices ({}): {}" .format (len (selected_device_ip_addrs ), selected_device_ip_addrs ))
601649
602650 def f (ip_addr ):
603- logger .info (f'Restarting app on { ip_addr } ...' )
651+ self . logger .info (f'Restarting app on { ip_addr } ...' )
604652 adb_wrapper = AdbWrapper (ip_addr )
605653 adb_wrapper .stop_neon_companion_app ()
606654 adb_wrapper .start_neon_companion_app ()
607- logger .info (f'Restarting app on { ip_addr } has finished!' )
655+ self . logger .info (f'Restarting app on { ip_addr } has finished!' )
608656
609657 tasks = []
610658 for ip_addr in selected_device_ip_addrs :
@@ -614,10 +662,10 @@ def f(ip_addr):
614662
615663 for t in tasks :
616664 t .join ()
617- logger .info (f'Restarting apps has finished!' )
665+ self . logger .info (f'Restarting apps has finished!' )
618666 finally :
619667 self .restart_app_in_progress = False
620- logger .info (f'self.restart_app_in_progress = False' )
668+ self . logger .info (f'self.restart_app_in_progress = False' )
621669
622670 @work (exclusive = True , thread = True )
623671 async def action_reconnect_adb (self ) -> None :
@@ -628,22 +676,26 @@ async def action_reconnect_adb(self) -> None:
628676 print ("RESTARTING ADB on selected devices!" )
629677 table = self .query_one (SelectableRowsDataTable )
630678 selected_device_ip_addrs = [row .data [1 ] for row in table .selected_rows ]
631- logger .info ("Selected devices ({}): {}" .format (len (selected_device_ip_addrs ), selected_device_ip_addrs ))
679+ self .logger .info ("Selected devices ({}): {}" .format (len (selected_device_ip_addrs ), selected_device_ip_addrs ))
680+
681+ def run_adb_cmd (ip_addr ):
682+ """Runs the adb connect command for a single device."""
683+ self .logger .info (f"Restarting adb on { ip_addr } ..." )
684+ res = subprocess .run (f"adb connect { ip_addr } :5555" , shell = True , capture_output = True , text = True )
685+ self .logger .info (res .stdout .strip ())
632686
633687 for ip_addr in selected_device_ip_addrs :
634- logger .info (f'Restarting adb on { ip_addr } ...' )
635- res = subprocess .run ("adb connect {}:5555" .format (ip_addr ), shell = True , capture_output = True , text = True ).stdout
636- logger .info (res .strip ())
688+ t = threading .Thread (target = run_adb_cmd , args = (ip_addr ,), daemon = True )
689+ t .start ()
637690
638- print ('FINISHED restarting adb on all devices !' )
691+ print ('FINISHED dispatching adb restart threads !' )
639692
640693 @work (exclusive = True , thread = True )
641- def action_stop_offsets (self ) -> None :
642- """"""
643- if self .offset_logger :
644- logger .info ("Stopping Offset logger" )
645- self .offset_logger .stop_logging ()
646- self .offset_logger = None
694+ def action_stop_all_offsets (self ) -> None :
695+ """
696+ Stops all ongoing offset logging activities.
697+ """
698+ self .stop_recording_offsets (self .devices )
647699
648700 def action_toggle_dark (self ) -> None :
649701 """An action to toggle dark mode."""
@@ -659,7 +711,7 @@ def action_toggle_dark(self) -> None:
659711 description = "Save Recording" ),
660712 Binding (key = "u" , action = "recording_stop_and_discard" ,
661713 description = "Cancel Recording" ),
662- Binding (key = "o" , action = "stop_offsets " , description = "Stop offsets logging" ),
714+ Binding (key = "o" , action = "stop_all_offsets " , description = "Stop offsets logging on all devices " ),
663715 Binding (key = "t" , action = "restart_app_on_devices" , description = "Restart App" ),
664716 Binding (key = "a" , action = "reconnect_adb" , description = "Reconnect adb" ),
665717 Binding (key = "d" , action = "toggle_dark" , description = "Toggle dark mode" ),
0 commit comments