7
7
import threading
8
8
import time
9
9
import tkinter as tk
10
- from tkinter import ttk
10
+ from tkinter import Toplevel , ttk
11
11
from tkinter .ttk import Treeview
12
12
from PIL import Image , ImageTk
13
13
import requests
14
14
import socketio
15
15
import datetime
16
+ from tkinter import messagebox
16
17
17
18
logging .basicConfig (level = logging .INFO , format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )
18
19
logger = logging .getLogger (__name__ )
@@ -129,6 +130,7 @@ def on_table_click(event):
129
130
if item :
130
131
camera_id = tree .item (item , "values" )[0 ]
131
132
camera_name = tree .item (item , "values" )[2 ]
133
+ root .destroy ()
132
134
display_monitoring_screen (session_token , camera_id , camera_name )
133
135
134
136
@@ -187,7 +189,13 @@ def on_table_click(event):
187
189
188
190
189
191
def display_monitoring_screen (session_token , camera_id , camera_name ):
192
+
190
193
event_queue = queue .Queue ()
194
+ current_socketio = None
195
+ update_gui_job_id = None
196
+ closing_monitoring_in_progress = False
197
+ existing_vehicles = {}
198
+
191
199
root = tk .Tk ()
192
200
root .title (f"Traffic Sentinel - Monitoring #{ camera_name } " )
193
201
root .geometry ("1200x800" )
@@ -217,7 +225,8 @@ def _create_info_label(frame, label_text, value, row, padx, pady):
217
225
logo_label .image = logo
218
226
logo_label .grid (row = 0 , column = 0 , padx = 10 , pady = 5 )
219
227
except FileNotFoundError :
220
- print (f"El archivo { logo_path } no se encontró." )
228
+ print (f"File { logo_path } not found" )
229
+
221
230
222
231
app_title_label = tk .Label (title_frame , bg = "white" , text = "Traffic Sentinel - Driving Smarter Roads with IoT Traffic Monitoring" , font = ("Arial" , 16 , "bold" ))
223
232
app_title_label .grid (row = 0 , column = 1 , padx = 10 , pady = 10 )
@@ -240,7 +249,10 @@ def _create_info_label(frame, label_text, value, row, padx, pady):
240
249
empty_label = tk .Label (right_frame , text = "" , font = ("Arial" , 12 ))
241
250
empty_label .grid (row = 0 , column = 0 , columnspan = 2 )
242
251
243
- annotated_frame_label = tk .Label (left_frame )
252
+ default_image = Image .open ("resources/default_no_available.png" )
253
+ default_image = default_image .resize ((500 , 400 ))
254
+ default_image_tk = ImageTk .PhotoImage (default_image )
255
+ annotated_frame_label = tk .Label (left_frame , image = default_image_tk )
244
256
annotated_frame_label .pack (expand = True , fill = 'both' , padx = 20 , pady = 20 )
245
257
246
258
root .grid_rowconfigure (0 , weight = 0 )
@@ -253,15 +265,16 @@ def _create_info_label(frame, label_text, value, row, padx, pady):
253
265
vehicle_table_frame = tk .Frame (root , bg = "white" )
254
266
vehicle_table_frame .grid (row = 2 , column = 0 , columnspan = 2 , sticky = "nsew" )
255
267
256
- tree = Treeview (vehicle_table_frame , columns = ("Vehicle ID" , "Type" , "Color" , "Image " ))
268
+ tree = Treeview (vehicle_table_frame , columns = ("Vehicle ID" , "Type" , "Color" , "Model" , "Speed" , "Direction " ))
257
269
scrollbar = tk .Scrollbar (vehicle_table_frame , orient = 'vertical' , command = tree .yview )
258
270
tree .configure (yscroll = scrollbar .set )
259
271
260
- tree .heading ("#0" , text = "Index" )
261
272
tree .heading ("Vehicle ID" , text = "Vehicle ID" )
262
273
tree .heading ("Type" , text = "Type" )
263
274
tree .heading ("Color" , text = "Color" )
264
- tree .heading ("Image" , text = "Image" )
275
+ tree .heading ("Model" , text = "Model" )
276
+ tree .heading ("Speed" , text = "Speed" )
277
+ tree .heading ("Direction" , text = "Direction" )
265
278
266
279
scrollbar .pack (side = 'right' , fill = 'y' )
267
280
tree .pack (expand = True , fill = 'both' )
@@ -274,10 +287,9 @@ def _create_info_label(frame, label_text, value, row, padx, pady):
274
287
275
288
def update_annotated_frame (image_base64 ):
276
289
if image_base64 :
277
- logger .info ("update_annotated_frame." )
278
290
annotated_frame_bytes = base64 .b64decode (image_base64 .split (',' )[- 1 ])
279
291
annotated_frame_image = Image .open (io .BytesIO (annotated_frame_bytes ))
280
- annotated_frame_image = annotated_frame_image .resize ((500 , 400 ))
292
+ annotated_frame_image = annotated_frame_image .resize ((600 , 400 ))
281
293
annotated_frame_tk = ImageTk .PhotoImage (annotated_frame_image )
282
294
annotated_frame_label .configure (image = annotated_frame_tk )
283
295
annotated_frame_label .image = annotated_frame_tk
@@ -289,38 +301,44 @@ def connect_to_server():
289
301
290
302
while retries < MAX_RETRIES :
291
303
logger .info (f"Attempting connection... (Attempt { retries + 1 } /{ MAX_RETRIES } )" )
292
- sio = socketio .Client ()
304
+ current_socketio = socketio .Client ()
293
305
294
- @sio .on ('new_frame' )
306
+ @current_socketio .on ('new_frame' )
295
307
def handle_new_frame (payload ):
296
308
logger .info (f"on new frame received" )
297
309
try :
298
310
event_queue .put_nowait (payload )
299
311
except queue .Full :
300
312
pass
301
313
302
- @sio .on ('connect' )
314
+ @current_socketio .on ('connect' )
303
315
def on_connect ():
304
316
logger .info ("Connected to server" )
305
- sio .emit ('subscribe_camera' , {'session_token' : session_token , 'camera_id' : camera_id })
317
+ current_socketio .emit ('subscribe_camera' , {'session_token' : session_token , 'camera_id' : camera_id })
306
318
logger .info (f"Subscribed to camera { camera_id } " )
307
319
root .event_generate ("<<OnConnected>>" , when = "tail" )
308
320
309
- @sio .on ('disconnect' )
321
+ @current_socketio .on ('disconnect' )
310
322
def on_disconnect ():
311
323
logger .info ("Disconnected from server" )
312
324
root .event_generate ("<<OnDisconnected>>" , when = "tail" )
313
325
314
- @sio .on ('subscription_success' )
326
+ @current_socketio .on ('subscription_success' )
315
327
def on_subscribe_success (data ):
316
328
logger .info (f"Subscription to camera successfully - { data } " )
317
329
318
- @sio .on ('subscription_error' )
330
+ @current_socketio .on ('unsubscription_success' )
331
+ def on_subscribe_success (data ):
332
+ logger .info (f"Unsubscription from camera successfully - { data } " )
333
+
334
+ @current_socketio .on ('subscription_error' )
319
335
def on_subscribe_error (data ):
320
336
logger .info (f"Subscription to camera error" - {data })
337
+ if current_socketio is not None :
338
+ current_socketio .disconnect ()
321
339
322
340
try :
323
- sio .connect (STREAM_SERVICE_ENDPOINT )
341
+ current_socketio .connect (STREAM_SERVICE_ENDPOINT )
324
342
break
325
343
except Exception as e :
326
344
logger .error (f"Error connecting: { e } " )
@@ -356,44 +374,53 @@ def handle_frame_payload(payload):
356
374
logger .error (f"Error evaluating processed_frame JSON: { e } " )
357
375
processed_frame_payload = {}
358
376
359
- number_of_vehicles_detected = processed_frame_payload .get ('number_of_vehicles_detected' , 0 )
360
- number_of_vehicles_label_value ["text" ] = str (number_of_vehicles_detected )
361
377
if 'annotated_frame_base64' in processed_frame_payload :
362
378
annotated_frame_base64 = processed_frame_payload ['annotated_frame_base64' ]
363
379
update_annotated_frame (annotated_frame_base64 )
364
380
365
- tree .delete (* tree .get_children ())
366
-
367
381
if 'detected_vehicles' in processed_frame_payload :
382
+ logger .info ("detected_vehicles in processed_frame_payload CALLED!" )
368
383
detected_vehicles = processed_frame_payload ['detected_vehicles' ]
369
384
for idx , vehicle in enumerate (detected_vehicles ):
370
385
vehicle_id = vehicle .get ('vehicle_id' , 'N/A' )
371
386
vehicle_type = vehicle .get ('vehicle_type' , 'N/A' )
372
387
color_info = json .loads (vehicle .get ('color_info' , '[]' ))
373
388
color = color_info [0 ]['color' ] if color_info else 'N/A'
374
-
375
- tree .insert ("" , tk .END , text = str (idx ), values = (vehicle_id , vehicle_type , color ))
376
-
377
- vehicle_frame_base64 = vehicle .get ('vehicle_frame_base64' , None )
378
- if vehicle_frame_base64 :
379
- vehicle_frame_bytes = base64 .b64decode (vehicle_frame_base64 .split (',' )[- 1 ])
380
- vehicle_frame_image = Image .open (io .BytesIO (vehicle_frame_bytes ))
381
- vehicle_frame_image = vehicle_frame_image .resize ((150 , 100 ), Image .ANTIALIAS )
382
- vehicle_frame_tk = ImageTk .PhotoImage (vehicle_frame_image )
383
-
384
- tree .insert ("" , tk .END , values = ("" , "" , "" , vehicle_frame_tk ))
389
+ speed_info = vehicle .get ('speed_info' , {})
390
+ speed = speed_info .get ('kph' , 'N/A' )
391
+ direction_label = speed_info .get ('direction_label' , 'N/A' )
392
+ model_info = json .loads (vehicle .get ('model_info' , '[]' ))
393
+ make = model_info [0 ].get ('make' , 'N/A' )
394
+ model = model_info [0 ].get ('model' , 'N/A' )
395
+ make_model_combined = f"{ make } { model } "
396
+ logger .info (f"#idx - #{ idx } vehicle_id #{ vehicle_id } " )
397
+ if vehicle_id in existing_vehicles :
398
+ item_id = existing_vehicles [vehicle_id ]
399
+ tree .item (item_id , values = (vehicle_id , vehicle_type , color , make_model_combined , speed , direction_label ))
400
+ else :
401
+ item_id = tree .insert ("" , tk .END , text = str (len (existing_vehicles )), values = (vehicle_id , vehicle_type , color , make_model_combined , speed , direction_label ))
402
+ existing_vehicles [vehicle_id ] = item_id
403
+
404
+ number_of_vehicles_label_value ["text" ] = len (existing_vehicles )
385
405
386
406
def update_gui ():
387
407
try :
388
408
payload = event_queue .get (block = False )
389
409
handle_frame_payload (json .loads (payload ))
390
410
except queue .Empty :
391
411
pass
392
- root .after (1000 , update_gui )
412
+ global update_gui_job_id
413
+ update_gui_job_id = root .after (1000 , update_gui )
414
+
415
+ def stop_update_gui ():
416
+ global update_gui_job_id
417
+ if update_gui_job_id :
418
+ root .after_cancel (update_gui_job_id )
419
+ update_gui_job_id = None
393
420
394
421
def handle_streaming_server_unreachable (event ):
395
422
root .destroy ()
396
- display_login_screen ( )
423
+ display_home_screen ( session_token )
397
424
398
425
def handle_on_connected (event ):
399
426
connected_label ['text' ] = "Connected"
@@ -405,17 +432,33 @@ def handle_on_disconnected(event):
405
432
if connect_thread and connect_thread .is_alive ():
406
433
connect_thread .cancel ()
407
434
connect_thread .join ()
408
- connect_thread = threading .Thread (target = connect_to_server )
409
- connect_thread .start ()
410
-
435
+ if closing_monitoring_in_progress :
436
+ root .destroy ()
437
+ display_home_screen (session_token )
438
+ else :
439
+ connect_thread = threading .Thread (target = connect_to_server )
440
+ connect_thread .start ()
441
+
411
442
root .bind ("<<StreamingServerUnreachable>>" , handle_streaming_server_unreachable )
412
443
root .bind ("<<OnDisconnected>>" , handle_on_disconnected )
413
444
root .bind ("<<OnConnected>>" , handle_on_connected )
414
445
446
+ def on_closing ():
447
+ if messagebox .askokcancel ("Close Monitoring" , "¿Are you sure to close monitoring?" ):
448
+ global closing_monitoring_in_progress
449
+ closing_monitoring_in_progress = True
450
+ stop_update_gui ()
451
+ if connect_thread and connect_thread .is_alive ():
452
+ connect_thread .cancel ()
453
+ connect_thread .join ()
454
+ if current_socketio is not None :
455
+ current_socketio .emit ('unsubscribe_camera' , {'session_token' : session_token , 'camera_id' : camera_id })
456
+
457
+ root .protocol ("WM_DELETE_WINDOW" , on_closing )
458
+
415
459
update_gui ()
416
460
417
461
connect_thread = threading .Thread (target = connect_to_server )
418
- connect_thread .daemon = True
419
462
connect_thread .start ()
420
463
421
464
root .mainloop ()
0 commit comments