Skip to content

Commit 75e08dd

Browse files
committed
Implement several improvements in Monitoring Tkinter Python Desktop application
1 parent 1e4c639 commit 75e08dd

File tree

2 files changed

+80
-37
lines changed

2 files changed

+80
-37
lines changed

management-monitoring-layer/monitor/app.py

+80-37
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
import threading
88
import time
99
import tkinter as tk
10-
from tkinter import ttk
10+
from tkinter import Toplevel, ttk
1111
from tkinter.ttk import Treeview
1212
from PIL import Image, ImageTk
1313
import requests
1414
import socketio
1515
import datetime
16+
from tkinter import messagebox
1617

1718
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
1819
logger = logging.getLogger(__name__)
@@ -129,6 +130,7 @@ def on_table_click(event):
129130
if item:
130131
camera_id = tree.item(item, "values")[0]
131132
camera_name = tree.item(item, "values")[2]
133+
root.destroy()
132134
display_monitoring_screen(session_token, camera_id, camera_name)
133135

134136

@@ -187,7 +189,13 @@ def on_table_click(event):
187189

188190

189191
def display_monitoring_screen(session_token, camera_id, camera_name):
192+
190193
event_queue = queue.Queue()
194+
current_socketio = None
195+
update_gui_job_id = None
196+
closing_monitoring_in_progress = False
197+
existing_vehicles = {}
198+
191199
root = tk.Tk()
192200
root.title(f"Traffic Sentinel - Monitoring #{camera_name}")
193201
root.geometry("1200x800")
@@ -217,7 +225,8 @@ def _create_info_label(frame, label_text, value, row, padx, pady):
217225
logo_label.image = logo
218226
logo_label.grid(row=0, column=0, padx=10, pady=5)
219227
except FileNotFoundError:
220-
print(f"El archivo {logo_path} no se encontró.")
228+
print(f"File {logo_path} not found")
229+
221230

222231
app_title_label = tk.Label(title_frame, bg="white", text="Traffic Sentinel - Driving Smarter Roads with IoT Traffic Monitoring", font=("Arial", 16, "bold"))
223232
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):
240249
empty_label = tk.Label(right_frame, text="", font=("Arial", 12))
241250
empty_label.grid(row=0, column=0, columnspan=2)
242251

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)
244256
annotated_frame_label.pack(expand=True, fill='both', padx=20, pady=20)
245257

246258
root.grid_rowconfigure(0, weight=0)
@@ -253,15 +265,16 @@ def _create_info_label(frame, label_text, value, row, padx, pady):
253265
vehicle_table_frame = tk.Frame(root, bg="white")
254266
vehicle_table_frame.grid(row=2, column=0, columnspan=2, sticky="nsew")
255267

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"))
257269
scrollbar = tk.Scrollbar(vehicle_table_frame, orient='vertical', command=tree.yview)
258270
tree.configure(yscroll=scrollbar.set)
259271

260-
tree.heading("#0", text="Index")
261272
tree.heading("Vehicle ID", text="Vehicle ID")
262273
tree.heading("Type", text="Type")
263274
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")
265278

266279
scrollbar.pack(side='right', fill='y')
267280
tree.pack(expand=True, fill='both')
@@ -274,10 +287,9 @@ def _create_info_label(frame, label_text, value, row, padx, pady):
274287

275288
def update_annotated_frame(image_base64):
276289
if image_base64:
277-
logger.info("update_annotated_frame.")
278290
annotated_frame_bytes = base64.b64decode(image_base64.split(',')[-1])
279291
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))
281293
annotated_frame_tk = ImageTk.PhotoImage(annotated_frame_image)
282294
annotated_frame_label.configure(image=annotated_frame_tk)
283295
annotated_frame_label.image = annotated_frame_tk
@@ -289,38 +301,44 @@ def connect_to_server():
289301

290302
while retries < MAX_RETRIES:
291303
logger.info(f"Attempting connection... (Attempt {retries + 1}/{MAX_RETRIES})")
292-
sio = socketio.Client()
304+
current_socketio = socketio.Client()
293305

294-
@sio.on('new_frame')
306+
@current_socketio.on('new_frame')
295307
def handle_new_frame(payload):
296308
logger.info(f"on new frame received")
297309
try:
298310
event_queue.put_nowait(payload)
299311
except queue.Full:
300312
pass
301313

302-
@sio.on('connect')
314+
@current_socketio.on('connect')
303315
def on_connect():
304316
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})
306318
logger.info(f"Subscribed to camera {camera_id}")
307319
root.event_generate("<<OnConnected>>", when="tail")
308320

309-
@sio.on('disconnect')
321+
@current_socketio.on('disconnect')
310322
def on_disconnect():
311323
logger.info("Disconnected from server")
312324
root.event_generate("<<OnDisconnected>>", when="tail")
313325

314-
@sio.on('subscription_success')
326+
@current_socketio.on('subscription_success')
315327
def on_subscribe_success(data):
316328
logger.info(f"Subscription to camera successfully - {data}")
317329

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')
319335
def on_subscribe_error(data):
320336
logger.info(f"Subscription to camera error" - {data})
337+
if current_socketio is not None:
338+
current_socketio.disconnect()
321339

322340
try:
323-
sio.connect(STREAM_SERVICE_ENDPOINT)
341+
current_socketio.connect(STREAM_SERVICE_ENDPOINT)
324342
break
325343
except Exception as e:
326344
logger.error(f"Error connecting: {e}")
@@ -356,44 +374,53 @@ def handle_frame_payload(payload):
356374
logger.error(f"Error evaluating processed_frame JSON: {e}")
357375
processed_frame_payload = {}
358376

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)
361377
if 'annotated_frame_base64' in processed_frame_payload:
362378
annotated_frame_base64 = processed_frame_payload['annotated_frame_base64']
363379
update_annotated_frame(annotated_frame_base64)
364380

365-
tree.delete(*tree.get_children())
366-
367381
if 'detected_vehicles' in processed_frame_payload:
382+
logger.info("detected_vehicles in processed_frame_payload CALLED!")
368383
detected_vehicles = processed_frame_payload['detected_vehicles']
369384
for idx, vehicle in enumerate(detected_vehicles):
370385
vehicle_id = vehicle.get('vehicle_id', 'N/A')
371386
vehicle_type = vehicle.get('vehicle_type', 'N/A')
372387
color_info = json.loads(vehicle.get('color_info', '[]'))
373388
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)
385405

386406
def update_gui():
387407
try:
388408
payload = event_queue.get(block=False)
389409
handle_frame_payload(json.loads(payload))
390410
except queue.Empty:
391411
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
393420

394421
def handle_streaming_server_unreachable(event):
395422
root.destroy()
396-
display_login_screen()
423+
display_home_screen(session_token)
397424

398425
def handle_on_connected(event):
399426
connected_label['text'] = "Connected"
@@ -405,17 +432,33 @@ def handle_on_disconnected(event):
405432
if connect_thread and connect_thread.is_alive():
406433
connect_thread.cancel()
407434
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+
411442
root.bind("<<StreamingServerUnreachable>>", handle_streaming_server_unreachable)
412443
root.bind("<<OnDisconnected>>", handle_on_disconnected)
413444
root.bind("<<OnConnected>>", handle_on_connected)
414445

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+
415459
update_gui()
416460

417461
connect_thread = threading.Thread(target=connect_to_server)
418-
connect_thread.daemon = True
419462
connect_thread.start()
420463

421464
root.mainloop()
Loading

0 commit comments

Comments
 (0)