From 7c069efa14620fa154836d78e88599cfc10fac8b Mon Sep 17 00:00:00 2001 From: adrigongv23 Date: Mon, 27 Jul 2026 13:34:00 +0200 Subject: [PATCH] =?UTF-8?q?Actualizaci=C3=B3n=20visual=20de=20la=20telemet?= =?UTF-8?q?ria=20con=20velocidad=20y=20grafica=20acelerador=20freno?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Escritorio_Boxes/diseño.py | 13 +-- Escritorio_Boxes/monitor.py | 154 +++++++++++++++++++++++------------- 2 files changed, 108 insertions(+), 59 deletions(-) diff --git a/Escritorio_Boxes/diseño.py b/Escritorio_Boxes/diseño.py index 26335a0..3b47a9d 100644 --- a/Escritorio_Boxes/diseño.py +++ b/Escritorio_Boxes/diseño.py @@ -28,11 +28,11 @@ while True: # Régimen siguiendo al acelerador, con algo de inercia rpm = 1200 + 12800 * (carga ** 0.7) + random.uniform(-120, 120) - # Marcha: sube con el régimen, 0 = punto muerto - if rpm < 1500: - marcha = 0 - else: - marcha = min(6, 1 + int((rpm - 1500) / 2300)) + # Velocidad: correlada con el régimen (modelo simple para el simulador) + velocidad = 15 + 105 * (carga ** 0.8) + random.uniform(-1.5, 1.5) + + # Freno delantero: presión (bar). Frena al levantar el pie del acelerador + freno_del = max(0.0, 45 * max(0.0, min(1.0, (6 - tps) / 6)) + random.uniform(-0.4, 0.4)) # Temperaturas: suben poco a poco y responden a la carga ect = 88 + 6 * math.sin(t / 20.0) + 4 * carga + random.uniform(-0.3, 0.3) @@ -66,7 +66,8 @@ while True: 'lambda': f'{lambda_val:.3f}', 'lambda_obj': f'{lambda_obj:.3f}', 'tps': f'{tps:.0f}', - 'marcha': f'{marcha}', + 'velocidad': f'{velocidad:.0f}', + 'freno_del': f'{freno_del:.1f}', } mensaje = ';'.join(f'{clave}={valor}' for clave, valor in campos.items()) else: diff --git a/Escritorio_Boxes/monitor.py b/Escritorio_Boxes/monitor.py index 73a7d3a..9db8b99 100644 --- a/Escritorio_Boxes/monitor.py +++ b/Escritorio_Boxes/monitor.py @@ -4,7 +4,7 @@ import csv import matplotlib.pyplot as plt import matplotlib.animation as animation from matplotlib.gridspec import GridSpec -from matplotlib.patches import Rectangle +from matplotlib.patches import Rectangle, Polygon from matplotlib.lines import Line2D from matplotlib.colors import to_rgb from collections import deque @@ -28,8 +28,8 @@ MAX_PUNTOS = FRECUENCIA_HZ * VENTANA_SEG # 1. "87.3|9200|14.2" -> ECT | RPM | BATERÍA # # 2. Nuevo (clave-valor, para cuando el firmware envíe el resto de canales): -# "ect=87.3;rpm=9200;vbatt=14.2;pcomb=3.6;taceite=104;paceite=4.2; -# map=98;lambda=0.88;lambda_obj=0.88;tps=45;marcha=3" +# "ect=87.3;rpm=9200;vbatt=14.2;velocidad=64;tps=45;freno_del=8.5; +# pcomb=3.6;taceite=104;paceite=4.2;map=98;lambda=0.88;lambda_obj=0.88" CLAVES_LEGADO = ('ect', 'rpm', 'vbatt') # --- PALETA (tomada del logo oficial del equipo) --- @@ -49,8 +49,12 @@ APAGADO = '#18213A' # Segmento / relleno inactivo MAX_RPM = 13000 # Fondo de escala del indicador RPM_CORTE = 12000 # Zona roja / corte de inyección -# --- MARCHAS --- -VALOR_NEUTRAL = 0 # Valor con el que la ECU codifica el punto muerto +# --- PEDALES --- +VENTANA_PEDALES_SEG = 7 # Segundos visibles en la traza de pedales +PUNTOS_PEDALES = FRECUENCIA_HZ * VENTANA_PEDALES_SEG +# El sensor de freno es de PRESIÓN (bar). Este es el valor que se dibuja como el +# 100 % de la traza. AJUSTAR cuando se mida en pista la frenada más fuerte. +FRENO_PRESION_MAX = 50.0 # --- DEFINICIÓN DE CANALES --- # Cada canal declara su rango visible, sus zonas de color y una referencia @@ -91,6 +95,21 @@ CANALES = { etiqueta='TPS', descripcion='ACELERADOR', unidad='%', vmin=0, vmax=100, decimales=0, referencia=None, zonas=[(float('inf'), AZUL)]), + # Frenos: sensores de PRESIÓN. Se guardan en bar (real) y en la traza se + # normalizan a 0-100 % contra FRENO_PRESION_MAX. Solo el delantero está + # instalado; el trasero queda declarado a la espera de montarse. + 'freno_del': dict( + etiqueta='FRENO DEL.', descripcion='PRESIÓN FRENO DELANTERO', unidad='bar', + vmin=0, vmax=FRENO_PRESION_MAX, decimales=1, referencia=None, + zonas=[(float('inf'), ROJO)]), + 'freno_tra': dict( + etiqueta='FRENO TRA.', descripcion='PRESIÓN FRENO TRASERO', unidad='bar', + vmin=0, vmax=FRENO_PRESION_MAX, decimales=1, referencia=None, + zonas=[(float('inf'), ROJO)]), + 'velocidad': dict( + etiqueta='VELOCIDAD', descripcion='VELOCIDAD', unidad='km/h', + vmin=0, vmax=160, decimales=0, referencia=None, + zonas=[(float('inf'), AZUL)]), 'rpm': dict( etiqueta='RPM', descripcion='RÉGIMEN DE MOTOR', unidad='', vmin=0, vmax=MAX_RPM, decimales=0, referencia=None, @@ -141,7 +160,8 @@ aviso_pie = '' aviso_hasta = 0.0 # Orden de las columnas del CSV de sesión -COLUMNAS_CSV = ['ect', 'rpm', 'vbatt', 'pcomb', 'taceite', 'paceite', 'map', 'lambda', 'tps', 'marcha'] +COLUMNAS_CSV = ['ect', 'rpm', 'velocidad', 'vbatt', 'tps', 'freno_del', 'freno_tra', + 'pcomb', 'taceite', 'paceite', 'map', 'lambda'] sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((UDP_IP, UDP_PORT)) @@ -218,7 +238,7 @@ def formatear(clave, valor): def valor_csv(clave): """Valor para el fichero de sesión: sin separador de millares, que rompería - el CSV, y con los decimales propios del canal (RPM y marcha son enteros).""" + el CSV, y con los decimales propios del canal (RPM y velocidad son enteros).""" valor = ultima_lectura.get(clave) if valor is None: return '' @@ -226,13 +246,6 @@ def valor_csv(clave): return f'{valor:.{decimales}f}' -def formatear_marcha(valor): - if valor is None: - return '-' - valor = int(round(valor)) - return 'N' if valor == VALOR_NEUTRAL else str(valor) - - def formato_tiempo(segundos): if segundos is None: return '--:--' @@ -349,8 +362,8 @@ fig.text(0.50, 0.017, 'R grabar S captura 1-7 gráfica F fontsize=8.5, color=TXT_DIM, ha='center', va='center') txt_fichero = fig.text(0.975, 0.017, '', fontsize=8.5, color=TXT_DIM, ha='right', va='center') -# --- BANDA A: LO QUE HACE EL PILOTO (RPM + MARCHA + ACELERADOR) --- -gs_a = GridSpec(1, 3, width_ratios=[3.0, 0.72, 0.85], +# --- BANDA A: LO QUE HACE EL PILOTO (RPM + VELOCIDAD + PEDALES) --- +gs_a = GridSpec(1, 3, width_ratios=[2.6, 0.8, 1.7], left=0.035, right=0.975, top=0.893, bottom=0.712, wspace=0.030) # A1. Luces de cambio @@ -385,26 +398,58 @@ ax_rpm.text(2, 0.88, 'RPM', fontsize=9, color=TXT_DIM, va='center') txt_rpm = ax_rpm.text(98, 0.55, '--', fontsize=30, fontweight='bold', color=TXT_DIM, ha='right', va='center') -# A2. Marcha: el dato más glanceable de un panel, por eso va a tamaño máximo -ax_marcha = fig.add_subplot(gs_a[0, 1]) -preparar_panel(ax_marcha, AZUL) -ax_marcha.text(0.5, 0.88, 'MARCHA', fontsize=9, color=TXT_DIM, ha='center', va='center') -txt_marcha = ax_marcha.text(0.5, 0.42, '-', fontsize=62, fontweight='bold', color=TXT_DIM, - ha='center', va='center') +# A2. Velocidad: número grande, hereda el hueco glanceable que dejó la marcha +ax_vel = fig.add_subplot(gs_a[0, 1]) +preparar_panel(ax_vel, AZUL) +ax_vel.text(0.5, 0.86, 'VELOCIDAD', fontsize=9, color=TXT_DIM, ha='center', va='center') +txt_vel = ax_vel.text(0.5, 0.45, '--', fontsize=54, fontweight='bold', color=TXT_DIM, + ha='center', va='center') +ax_vel.text(0.5, 0.13, 'km/h', fontsize=12, color=TXT_DIM, ha='center', va='center') -# A3. Acelerador -ax_tps = fig.add_subplot(gs_a[0, 2]) -preparar_panel(ax_tps, AZUL) -ax_tps.text(0.09, 0.86, 'TPS', fontsize=9, color=TXT_DIM, va='center') -txt_tps = ax_tps.text(0.93, 0.84, '--', fontsize=20, fontweight='bold', color=TXT_DIM, - ha='right', va='center') -X_TPS, ANCHO_TPS, Y_TPS, ALTO_TPS = 0.09, 0.84, 0.30, 0.26 -ax_tps.add_patch(Rectangle((X_TPS, Y_TPS), ANCHO_TPS, ALTO_TPS, facecolor=APAGADO)) -bar_tps = Rectangle((X_TPS, Y_TPS), 0, ALTO_TPS, facecolor=AZUL) -ax_tps.add_patch(bar_tps) -for pct in (0, 50, 100): - x = X_TPS + ANCHO_TPS * pct / 100 - ax_tps.text(x, 0.22, f'{pct}', fontsize=7, color=TXT_DIM, ha='center', va='top') +# A3. Traza de pedales: acelerador (verde) y freno (rojo) oscilando de 0 a 100 %, +# como en la telemetría real. El freno es presión (bar) normalizada a % en pantalla. +ax_ped = fig.add_subplot(gs_a[0, 2]) +ax_ped.set_facecolor(PANEL) +for lado in ('top', 'right'): + ax_ped.spines[lado].set_visible(False) +for lado in ('bottom', 'left'): + ax_ped.spines[lado].set_color(GRID) +ax_ped.set_xlim(0, PUNTOS_PEDALES) +ax_ped.set_ylim(0, 105) +ax_ped.set_yticks([0, 50, 100]) +ax_ped.set_yticklabels(['0', '50', '100'], fontsize=7) +ax_ped.set_xticks([0, PUNTOS_PEDALES / 2, PUNTOS_PEDALES]) +ax_ped.set_xticklabels([f'-{VENTANA_PEDALES_SEG:g} s'.replace('.', ','), + f'-{VENTANA_PEDALES_SEG / 2:g} s'.replace('.', ','), 'ahora'], fontsize=7) +ax_ped.tick_params(length=0, labelsize=7.5) +ax_ped.grid(True, color=GRID, alpha=0.5, linestyle='--', lw=0.6, zorder=1) +ax_ped.text(PUNTOS_PEDALES * 0.015, 99, 'PEDALES', fontsize=9, color=TXT_DIM, va='top', zorder=5) + +fill_tps = Polygon([[0, 0], [0, 0]], closed=True, facecolor=VERDE, alpha=0.22, + edgecolor='none', zorder=2) +fill_freno = Polygon([[0, 0], [0, 0]], closed=True, facecolor=ROJO, alpha=0.20, + edgecolor='none', zorder=2) +ax_ped.add_patch(fill_tps) +ax_ped.add_patch(fill_freno) +line_tps, = ax_ped.plot([], [], color=VERDE, lw=2, zorder=4, solid_capstyle='round') +line_freno, = ax_ped.plot([], [], color=ROJO, lw=2, zorder=3, solid_capstyle='round') +_caja_ped = dict(facecolor=PANEL, alpha=0.65, edgecolor='none', boxstyle='square,pad=0.2') +txt_ped_tps = ax_ped.text(PUNTOS_PEDALES * 0.985, 80, 'ACEL --', fontsize=10.5, + fontweight='bold', color=VERDE, ha='right', va='center', + zorder=6, bbox=_caja_ped) +txt_ped_freno = ax_ped.text(PUNTOS_PEDALES * 0.985, 62, 'FRENO --', fontsize=10.5, + fontweight='bold', color=ROJO, ha='right', va='center', + zorder=6, bbox=_caja_ped) + + +def area_pedales(xs, ys): + """Vértices del polígono relleno bajo una curva de pedal, saltándose los NaN + (que aparecen al desconectar), para que el relleno no se rompa.""" + m = np.isfinite(ys) + if not m.any(): + return [[0, 0], [0, 0]] + xf, yf = xs[m], ys[m] + return [(xf[0], 0)] + list(zip(xf, yf)) + [(xf[-1], 0)] # --- BANDA B: CANAL CON FOCO (número grande + gráfica) --- gs_b = GridSpec(1, 2, width_ratios=[1, 3.3], @@ -704,7 +749,7 @@ def update(frame): txt_hz.set_text('-- Hz') txt_hz.set_color(TXT_DIM) - # --- BANDA A: RPM, MARCHA, TPS --- + # --- BANDA A: RPM, VELOCIDAD, PEDALES --- valor_rpm = ultima_lectura.get('rpm') if conectado else None if valor_rpm is not None: valor_rpm = max(0, min(MAX_RPM, valor_rpm)) @@ -726,25 +771,28 @@ def update(frame): txt_rpm.set_text('--') txt_rpm.set_color(TXT_DIM) - valor_marcha = ultima_lectura.get('marcha') if conectado else None - txt_marcha.set_text(formatear_marcha(valor_marcha)) - if valor_marcha is None: - txt_marcha.set_color(TXT_DIM) - elif int(round(valor_marcha)) == VALOR_NEUTRAL: - txt_marcha.set_color(VERDE) - else: - txt_marcha.set_color(TXT) + # Velocidad: número grande + valor_vel = ultima_lectura.get('velocidad') if conectado else None + txt_vel.set_text(formatear('velocidad', valor_vel)) + txt_vel.set_color(TXT if valor_vel is not None else TXT_DIM) - valor_tps = ultima_lectura.get('tps') if conectado else None - if valor_tps is not None: - fraccion = min(max(valor_tps / 100.0, 0.0), 1.0) - bar_tps.set_width(ANCHO_TPS * fraccion) - txt_tps.set_text(f'{formatear("tps", valor_tps)} %') - txt_tps.set_color(TXT) + # Traza de pedales: acelerador (0-100 %) y freno (presión -> % de FRENO_PRESION_MAX) + serie_tps = np.array(historial['tps'], dtype=float)[-PUNTOS_PEDALES:] + serie_freno = np.array(historial['freno_del'], dtype=float)[-PUNTOS_PEDALES:] + freno_pct = np.clip(serie_freno / FRENO_PRESION_MAX * 100.0, 0, 100) + xs_ped = np.arange(len(serie_tps), dtype=float) + line_tps.set_data(xs_ped, serie_tps) + line_freno.set_data(xs_ped, freno_pct) + fill_tps.set_xy(area_pedales(xs_ped, serie_tps)) + fill_freno.set_xy(area_pedales(xs_ped, freno_pct)) + + val_tps = ultima_lectura.get('tps') if conectado else None + val_freno = ultima_lectura.get('freno_del') if conectado else None + txt_ped_tps.set_text(f'ACEL {int(round(val_tps))}%' if val_tps is not None else 'ACEL --') + if val_freno is not None: + txt_ped_freno.set_text(f'FRENO {int(round(min(100.0, val_freno / FRENO_PRESION_MAX * 100)))}%') else: - bar_tps.set_width(0) - txt_tps.set_text('--') - txt_tps.set_color(TXT_DIM) + txt_ped_freno.set_text('FRENO --') # --- BANDA C: TARJETAS --- alarmas = []