Actualización de application_events.json con nuevos eventos para la gestión de sesiones de plot y el sistema de streaming. Se ajustaron las fechas de última actualización en plc_config.json, plc_datasets.json y system_state.json. Se implementó un control dinámico de la tasa de refresco en la interfaz de usuario, permitiendo a los usuarios establecer valores personalizados para la actualización de gráficos en tiempo real. Se realizaron mejoras en el código de plotting.js y tabs.js para soportar esta nueva funcionalidad.
This commit is contained in:
parent
bcef19e3f0
commit
d1ca6f6ed6
|
@ -716,4 +716,107 @@ This represents a fundamental architectural improvement that transforms the appl
|
|||
- **Cache Integration**: `DataStreamer.get_cached_dataset_values()` provides cached data access
|
||||
- **Source Tracking**: Response includes source information (`cache` vs `plc_direct`)
|
||||
- **Error Preservation**: Cached errors from streaming process are preserved and displayed
|
||||
- **Automatic Cleanup**: Cache is cleared when streaming stops or datasets are deactivated
|
||||
- **Automatic Cleanup**: Cache is cleared when streaming stops or datasets are deactivated
|
||||
|
||||
---
|
||||
|
||||
## 📊 Dynamic Refresh Rate Control for Real-Time Charts
|
||||
|
||||
### User Request Summary:
|
||||
Usuario quería poder seleccionar el tiempo de refresco de las gráficas en tiempo real en vez de que sea un valor fijo, sin impacto en el PLC ya que siempre se leen valores del cache.
|
||||
|
||||
### Implementation Details:
|
||||
|
||||
**UI Changes**:
|
||||
- **Refresh Rate Input**: Changed from dropdown to editable number input field for custom millisecond values
|
||||
- **Flexible Input**: Users can enter any value between 100ms and 60,000ms with automatic validation
|
||||
- **Dual Location Support**: Input field appears both in main tab and sub-tabs for consistency
|
||||
- **Visual Integration**: Uses clock emoji (⏱️) as label, "ms" unit indicator, and compact styling matching existing controls
|
||||
- **User Experience**: Enter key support for immediate application, auto-clamping to valid ranges
|
||||
|
||||
**Technical Implementation**:
|
||||
- **Dynamic Configuration**: Modified `createStreamingChartConfig()` to use dynamic refresh rate instead of hardcoded 1000ms
|
||||
- **Session Tracking**: Added `refreshRates` Map to track individual session refresh rates
|
||||
- **Real-time Updates**: `updateRefreshRate()` function dynamically changes chart refresh without recreation
|
||||
- **Interval Management**: Properly handles both ChartJS streaming intervals and manual fallback intervals
|
||||
- **Synchronization**: Both selectors (main/tab) stay synchronized when changed
|
||||
- **Memory Management**: Refresh rates are properly cleaned up when sessions are removed
|
||||
|
||||
**Key Features**:
|
||||
- **No PLC Impact**: Only affects visualization refresh rate, not data collection from PLC cache
|
||||
- **Instant Changes**: Refresh rate changes take effect immediately without chart recreation
|
||||
- **Persistence**: Each plot session maintains its own independent refresh rate
|
||||
- **Fallback Support**: Works with both streaming mode and manual refresh fallback
|
||||
- **User Experience**: Intuitive controls integrated seamlessly with existing UI
|
||||
|
||||
**Code Changes**:
|
||||
- **plotting.js**: Added refreshRates Map, updateRefreshRate() function, dynamic config
|
||||
- **tabs.js**: Added refresh rate selector to sub-tab controls
|
||||
- **styles.css**: Added styling for refresh-rate-control and refresh-rate-selector
|
||||
- **Memory cleanup**: Ensured refresh rates are deleted when sessions are removed
|
||||
|
||||
**Benefits**:
|
||||
- **Flexible Visualization**: Users can optimize refresh rate based on their monitoring needs
|
||||
- **Performance Control**: Slower refresh rates reduce CPU usage for long-term monitoring
|
||||
- **Independent Operation**: Each chart can have different refresh rates as needed
|
||||
- **Cache-Based**: No additional load on PLC communication system
|
||||
|
||||
---
|
||||
|
||||
### 📝 Update: Changed to Editable Input Field
|
||||
|
||||
**User Request**: Cambiar el combo box por un campo editable con el tiempo en ms
|
||||
|
||||
**Changes Made**:
|
||||
- **Replaced dropdown**: `<select>` changed to `<input type="number">` with millisecond values
|
||||
- **Direct value entry**: Users can now enter exact millisecond values instead of preset options
|
||||
- **Enhanced validation**: Auto-clamping to valid range (100-60,000ms) with console warnings
|
||||
- **Improved UX**: Enter key support for immediate application, "ms" unit label for clarity
|
||||
- **Consistent styling**: Updated CSS classes from `.refresh-rate-selector` to `.refresh-rate-input`
|
||||
|
||||
**Technical Benefits**:
|
||||
- **Precision Control**: Users can set exact refresh rates like 1500ms, 750ms, etc.
|
||||
- **Wide Range**: Supports from 100ms (high-frequency) to 60s (long-term monitoring)
|
||||
- **Input Validation**: Automatic range enforcement prevents invalid values
|
||||
- **Real-time Feedback**: Immediate visual and console feedback for out-of-range values
|
||||
|
||||
---
|
||||
|
||||
### 🐛 Bug Fix: Refresh Rate Not Working
|
||||
|
||||
**Issue**: El refresh rate no se aplicaba correctamente debido a throttles y intervalos hardcodeados.
|
||||
|
||||
**Problems Found**:
|
||||
1. **Fixed Throttle**: `onStreamingRefresh` tenía un límite fijo de 800ms que impedía refresh rates más rápidos
|
||||
2. **Fixed Manual Interval**: Modo fallback usaba 400ms fijo en lugar del refresh rate configurado
|
||||
3. **Plugin Integration**: El modo streaming no reiniciaba correctamente los intervalos internos
|
||||
|
||||
**Corrections Made**:
|
||||
- **Dynamic Throttle**: Cambiado a usar 80% del refresh rate configurado (mínimo 100ms)
|
||||
- **Dynamic Manual Refresh**: `startManualRefresh` ahora usa el refresh rate configurado por sesión
|
||||
- **Improved Streaming**: Mejor reinicio de intervalos para el plugin chartjs-streaming
|
||||
- **Enhanced Debugging**: Logs detallados para diagnosticar problemas de refresh rate
|
||||
|
||||
**Technical Implementation**:
|
||||
```javascript
|
||||
// Throttle dinámico basado en refresh rate
|
||||
const minInterval = Math.max(refreshRate * 0.8, 100);
|
||||
|
||||
// Intervalo manual con refresh rate dinámico
|
||||
sessionData.manualInterval = setInterval(() => {
|
||||
this.onStreamingRefresh(sessionId, sessionData.chart);
|
||||
}, refreshRate);
|
||||
|
||||
// Reinicio mejorado del plugin streaming
|
||||
streaming.intervalId = setInterval(() => {
|
||||
if (!chart.scales.x.realtime.pause && typeof chart.scales.x.realtime.onRefresh === 'function') {
|
||||
chart.scales.x.realtime.onRefresh(chart);
|
||||
}
|
||||
if (chart.scales.x.updateRealTimeData) {
|
||||
chart.scales.x.updateRealTimeData();
|
||||
}
|
||||
chart.update('quiet');
|
||||
}, finalRefreshRateMs);
|
||||
```
|
||||
|
||||
**Result**: El refresh rate ahora funciona correctamente tanto en modo streaming como fallback, con logs detallados para monitoreo.
|
|
@ -5069,8 +5069,230 @@
|
|||
"udp_port": 9870,
|
||||
"datasets_available": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T15:29:49.877657",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T15:29:49.924676",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 6,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T15:29:49.930199",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T15:29:49.937198",
|
||||
"level": "info",
|
||||
"event_type": "udp_streaming_started",
|
||||
"message": "UDP streaming to PlotJuggler started",
|
||||
"details": {
|
||||
"udp_host": "127.0.0.1",
|
||||
"udp_port": 9870,
|
||||
"datasets_available": 1
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T15:40:22.692868",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T15:51:39.771093",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 6,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T15:51:39.784757",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T15:51:39.799407",
|
||||
"level": "info",
|
||||
"event_type": "plc_connection",
|
||||
"message": "Successfully connected to PLC 10.1.33.11 and auto-started CSV recording for 1 datasets",
|
||||
"details": {
|
||||
"ip": "10.1.33.11",
|
||||
"rack": 0,
|
||||
"slot": 2,
|
||||
"auto_started_recording": true,
|
||||
"recording_datasets": 1,
|
||||
"dataset_names": [
|
||||
"DAR"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:09:13.079757",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:09:13.108754",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 6,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:09:13.114169",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:11:46.936399",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:11:46.966671",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 6,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:11:46.972612",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:15:26.951106",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:15:26.981971",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 6,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:15:26.989697",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 2
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:16:46.742716",
|
||||
"level": "info",
|
||||
"event_type": "dataset_deactivated",
|
||||
"message": "Dataset deactivated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:16:49.813456",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 6,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:21:53.978435",
|
||||
"level": "info",
|
||||
"event_type": "application_started",
|
||||
"message": "Application initialization completed successfully",
|
||||
"details": {}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:21:54.010004",
|
||||
"level": "info",
|
||||
"event_type": "dataset_activated",
|
||||
"message": "Dataset activated: DAR",
|
||||
"details": {
|
||||
"dataset_id": "dar",
|
||||
"variables_count": 6,
|
||||
"streaming_count": 4,
|
||||
"prefix": "dar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"timestamp": "2025-08-04T17:21:54.015891",
|
||||
"level": "info",
|
||||
"event_type": "csv_recording_started",
|
||||
"message": "CSV recording started: 1 datasets activated",
|
||||
"details": {
|
||||
"activated_datasets": 1,
|
||||
"total_datasets": 2
|
||||
}
|
||||
}
|
||||
],
|
||||
"last_updated": "2025-08-04T01:01:57.946011",
|
||||
"total_entries": 484
|
||||
"last_updated": "2025-08-04T17:21:54.015891",
|
||||
"total_entries": 506
|
||||
}
|
|
@ -16,6 +16,6 @@
|
|||
"max_days": 30,
|
||||
"max_hours": null,
|
||||
"cleanup_interval_hours": 24,
|
||||
"last_cleanup": "2025-08-03T10:18:30.271685"
|
||||
"last_cleanup": "2025-08-04T15:29:50.501222"
|
||||
}
|
||||
}
|
|
@ -70,5 +70,5 @@
|
|||
],
|
||||
"current_dataset_id": "dar",
|
||||
"version": "1.0",
|
||||
"last_update": "2025-08-04T01:01:57.925555"
|
||||
"last_update": "2025-08-04T17:21:54.008091"
|
||||
}
|
|
@ -555,6 +555,37 @@ textarea {
|
|||
min-width: auto;
|
||||
}
|
||||
|
||||
.refresh-rate-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.refresh-rate-control label {
|
||||
font-size: 0.9rem;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.refresh-rate-input {
|
||||
padding: 0.15rem 0.3rem;
|
||||
font-size: 0.75rem;
|
||||
width: 80px;
|
||||
height: auto;
|
||||
border-radius: var(--pico-border-radius);
|
||||
border: 1px solid var(--pico-border-color);
|
||||
background: var(--pico-background-color);
|
||||
color: var(--pico-color);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.refresh-rate-unit {
|
||||
font-size: 0.7rem;
|
||||
color: var(--pico-muted-color);
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
|
||||
.plot-info {
|
||||
padding: 0.5rem 1rem;
|
||||
background: var(--pico-card-background-color);
|
||||
|
|
|
@ -19,6 +19,7 @@ class PlotManager {
|
|||
this.updateInterval = null;
|
||||
this.statusUpdateInterval = null; // 🔑 NUEVO: Para updates de status
|
||||
this.isInitialized = false;
|
||||
this.refreshRates = new Map(); // Para mantener el refresh rate de cada sesión
|
||||
|
||||
// Colores para las variables
|
||||
this.colors = [
|
||||
|
@ -140,6 +141,15 @@ class PlotManager {
|
|||
<button class="btn btn-sm" onclick="plotManager.removePlot('${sessionId}')" title="Remove">
|
||||
❌ Remove
|
||||
</button>
|
||||
<div class="refresh-rate-control">
|
||||
<label for="refresh-rate-${sessionId}" title="Chart Refresh Rate (ms)">⏱️</label>
|
||||
<input type="number" id="refresh-rate-${sessionId}" class="refresh-rate-input"
|
||||
value="1000" min="100" max="60000" step="100"
|
||||
onchange="plotManager.updateRefreshRate('${sessionId}', this.value)"
|
||||
onkeypress="if(event.key==='Enter') plotManager.updateRefreshRate('${sessionId}', this.value)"
|
||||
title="Refresh rate in milliseconds (100-60000)">
|
||||
<span class="refresh-rate-unit">ms</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plot-info">
|
||||
|
@ -171,11 +181,16 @@ class PlotManager {
|
|||
|
||||
let chartConfig;
|
||||
|
||||
console.log(`🔍 Plot ${sessionId}: Checking streaming support...`);
|
||||
console.log(`📊 Chart.registry.scales.realtime available:`, !!hasRealTimeScale);
|
||||
|
||||
if (hasRealTimeScale) {
|
||||
// Configuración con chartjs-plugin-streaming
|
||||
console.log(`✅ Plot ${sessionId}: Using Real-time Streaming mode`);
|
||||
chartConfig = this.createStreamingChartConfig(sessionId, config);
|
||||
} else {
|
||||
// Configuración fallback con time scale normal
|
||||
console.log(`⚠️ Plot ${sessionId}: Using Manual Fallback mode`);
|
||||
chartConfig = this.createFallbackChartConfig(sessionId, config);
|
||||
}
|
||||
|
||||
|
@ -191,11 +206,19 @@ class PlotManager {
|
|||
isRealTimeMode: hasRealTimeScale
|
||||
});
|
||||
|
||||
// Inicializar refresh rate por defecto
|
||||
if (!this.refreshRates.has(sessionId)) {
|
||||
this.refreshRates.set(sessionId, 1000); // 1 segundo por defecto
|
||||
console.log(`⏱️ Plot ${sessionId}: Default refresh rate set to 1000ms`);
|
||||
}
|
||||
|
||||
// Inicializar datasets para las variables
|
||||
this.initializeChartDatasets(sessionId, config);
|
||||
|
||||
// Si no es modo realtime, iniciar intervalo manual
|
||||
if (!hasRealTimeScale) {
|
||||
const refreshRate = this.refreshRates.get(sessionId) || 1000;
|
||||
console.log(`🔄 Plot ${sessionId}: Starting manual refresh with ${refreshRate}ms interval`);
|
||||
this.startManualRefresh(sessionId);
|
||||
}
|
||||
}
|
||||
|
@ -219,7 +242,7 @@ class PlotManager {
|
|||
type: 'realtime',
|
||||
realtime: {
|
||||
duration: (config.time_window || 60) * 1000,
|
||||
refresh: 1000, // Actualizar cada segundo
|
||||
refresh: this.refreshRates.get(sessionId) || 1000, // Actualizar según configuración dinámica
|
||||
delay: 0,
|
||||
frameRate: 30,
|
||||
pause: !config.is_active, // Pausar si no está activo
|
||||
|
@ -349,10 +372,17 @@ class PlotManager {
|
|||
const sessionData = this.sessions.get(sessionId);
|
||||
if (!sessionData) return;
|
||||
|
||||
// Crear intervalo de actualización manual
|
||||
// Limpiar intervalo existente si hay uno
|
||||
if (sessionData.manualInterval) {
|
||||
clearInterval(sessionData.manualInterval);
|
||||
}
|
||||
|
||||
// Crear intervalo de actualización manual con refresh rate dinámico
|
||||
const refreshRate = this.refreshRates.get(sessionId) || 1000;
|
||||
console.log(`⏲️ Plot ${sessionId}: Manual interval created with ${refreshRate}ms`);
|
||||
sessionData.manualInterval = setInterval(() => {
|
||||
this.onStreamingRefresh(sessionId, sessionData.chart);
|
||||
}, 400); // Cada 400ms para streaming más suave
|
||||
}, refreshRate);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -400,12 +430,20 @@ class PlotManager {
|
|||
return;
|
||||
}
|
||||
|
||||
// Evitar llamadas muy frecuentes
|
||||
// Evitar llamadas muy frecuentes basado en el refresh rate dinámico
|
||||
const now = Date.now();
|
||||
if (now - sessionData.lastDataFetch < 800) {
|
||||
const refreshRate = this.refreshRates.get(sessionId) || 1000;
|
||||
const minInterval = Math.max(refreshRate * 0.8, 100); // 80% del refresh rate, mínimo 100ms
|
||||
|
||||
if (now - sessionData.lastDataFetch < minInterval) {
|
||||
const timeSinceLastUpdate = now - sessionData.lastDataFetch;
|
||||
console.log(`⏭️ Plot ${sessionId}: Skipping update (${timeSinceLastUpdate}ms < ${minInterval}ms threshold)`);
|
||||
return;
|
||||
}
|
||||
|
||||
const timeSinceLastUpdate = now - sessionData.lastDataFetch;
|
||||
sessionData.lastDataFetch = now;
|
||||
console.log(`🔄 Plot ${sessionId}: Updating data (${timeSinceLastUpdate}ms since last update, threshold: ${minInterval}ms)`);
|
||||
|
||||
// Obtener datos del backend (que usa solo cache)
|
||||
const response = await fetch(`/api/plots/${sessionId}/data`);
|
||||
|
@ -703,6 +741,9 @@ class PlotManager {
|
|||
}
|
||||
|
||||
this.sessions.delete(sessionId);
|
||||
|
||||
// Limpiar refresh rate
|
||||
this.refreshRates.delete(sessionId);
|
||||
}
|
||||
|
||||
// Remover contenedor
|
||||
|
@ -1375,6 +1416,9 @@ class PlotManager {
|
|||
chart.destroy();
|
||||
}
|
||||
this.sessions.delete(this.currentEditingSession);
|
||||
|
||||
// Limpiar refresh rate
|
||||
this.refreshRates.delete(this.currentEditingSession);
|
||||
}
|
||||
|
||||
// 4. Crear nuevo plot desde cero
|
||||
|
@ -1454,6 +1498,99 @@ class PlotManager {
|
|||
}
|
||||
this.sessions.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Actualiza el refresh rate de una sesión de plot
|
||||
*/
|
||||
updateRefreshRate(sessionId, refreshRate) {
|
||||
try {
|
||||
const refreshRateMs = parseInt(refreshRate);
|
||||
|
||||
// Validación de rango
|
||||
if (isNaN(refreshRateMs)) {
|
||||
console.error('❌ Invalid refresh rate - not a number:', refreshRate);
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshRateMs < 100) {
|
||||
console.warn('⚠️ Refresh rate too low, setting to minimum (100ms)');
|
||||
refreshRate = '100';
|
||||
} else if (refreshRateMs > 60000) {
|
||||
console.warn('⚠️ Refresh rate too high, setting to maximum (60000ms)');
|
||||
refreshRate = '60000';
|
||||
}
|
||||
|
||||
const finalRefreshRateMs = parseInt(refreshRate);
|
||||
|
||||
// Actualizar el map de refresh rates
|
||||
this.refreshRates.set(sessionId, finalRefreshRateMs);
|
||||
|
||||
const sessionData = this.sessions.get(sessionId);
|
||||
if (!sessionData || !sessionData.chart) {
|
||||
console.error('❌ Session not found:', sessionId);
|
||||
return;
|
||||
}
|
||||
|
||||
// Actualizar la configuración del chart
|
||||
if (sessionData.isRealTimeMode) {
|
||||
// Modo streaming con chartjs-plugin-streaming
|
||||
const chart = sessionData.chart;
|
||||
if (chart.scales && chart.scales.x && chart.scales.x.realtime) {
|
||||
// Actualizar la configuración de refresh rate
|
||||
chart.scales.x.realtime.refresh = finalRefreshRateMs;
|
||||
|
||||
// Forzar reinicio del intervalo interno del plugin
|
||||
const streaming = chart.$streaming;
|
||||
if (streaming && streaming.intervalId) {
|
||||
clearInterval(streaming.intervalId);
|
||||
|
||||
// Recrear el intervalo con el nuevo refresh rate
|
||||
streaming.intervalId = setInterval(() => {
|
||||
if (!chart.scales.x.realtime.pause && typeof chart.scales.x.realtime.onRefresh === 'function') {
|
||||
chart.scales.x.realtime.onRefresh(chart);
|
||||
}
|
||||
if (chart.scales.x.updateRealTimeData) {
|
||||
chart.scales.x.updateRealTimeData();
|
||||
}
|
||||
chart.update('quiet');
|
||||
}, finalRefreshRateMs);
|
||||
|
||||
console.log(`🔄 Streaming interval restarted with ${finalRefreshRateMs}ms`);
|
||||
}
|
||||
|
||||
// También actualizar la configuración de opciones para futuros reinicios
|
||||
chart.options.scales.x.realtime.refresh = finalRefreshRateMs;
|
||||
}
|
||||
} else {
|
||||
// Para modo manual, reiniciar el intervalo
|
||||
console.log(`🔄 Manual refresh restarted with ${finalRefreshRateMs}ms`);
|
||||
this.startManualRefresh(sessionId);
|
||||
}
|
||||
|
||||
// Sincronizar ambos inputs (main y tab)
|
||||
const mainInput = document.getElementById(`refresh-rate-${sessionId}`);
|
||||
const tabInput = document.getElementById(`refresh-rate-tab-${sessionId}`);
|
||||
|
||||
if (mainInput && mainInput.value !== refreshRate) {
|
||||
mainInput.value = refreshRate;
|
||||
}
|
||||
if (tabInput && tabInput.value !== refreshRate) {
|
||||
tabInput.value = refreshRate;
|
||||
}
|
||||
|
||||
// Debug information
|
||||
console.log(`⏱️ Plot ${sessionId}: Refresh rate updated to ${finalRefreshRateMs}ms`);
|
||||
console.log(`📊 Plot ${sessionId}: Mode: ${sessionData.isRealTimeMode ? 'Real-time Streaming' : 'Manual Fallback'}`);
|
||||
console.log(`🔧 Plot ${sessionId}: Chart scales available:`, !!sessionData.chart.scales);
|
||||
if (sessionData.chart.scales && sessionData.chart.scales.x) {
|
||||
console.log(`⚙️ Plot ${sessionId}: X-scale type:`, sessionData.chart.scales.x.type);
|
||||
console.log(`⚡ Plot ${sessionId}: Realtime config:`, sessionData.chart.scales.x.realtime);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error updating refresh rate:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Funciones de debug removidas para limpiar console.log
|
||||
|
@ -1504,6 +1641,44 @@ window.removePlotSession = async function (sessionId) {
|
|||
// Remover de PlotManager
|
||||
if (plotManager && plotManager.sessions.has(sessionId)) {
|
||||
plotManager.sessions.delete(sessionId);
|
||||
|
||||
// Limpiar refresh rate
|
||||
plotManager.refreshRates.delete(sessionId);
|
||||
}
|
||||
|
||||
showNotification(result.message, 'success');
|
||||
} else {
|
||||
showNotification(result.error, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error removing plot:', error);
|
||||
showNotification('Error removing plot session', 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Función global para remover sesiones de plot (movida fuera de la clase)
|
||||
window.removePlotSession = async function (sessionId) {
|
||||
if (confirm('¿Estás seguro de que quieres eliminar este plot?')) {
|
||||
try {
|
||||
const response = await fetch(`/api/plots/${sessionId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
// Remover tab dinámico
|
||||
if (typeof tabManager !== 'undefined') {
|
||||
tabManager.removePlotTab(sessionId);
|
||||
}
|
||||
|
||||
// Remover de PlotManager
|
||||
if (plotManager && plotManager.sessions.has(sessionId)) {
|
||||
plotManager.sessions.delete(sessionId);
|
||||
|
||||
// Limpiar refresh rate
|
||||
plotManager.refreshRates.delete(sessionId);
|
||||
}
|
||||
|
||||
showNotification(result.message, 'success');
|
||||
|
|
|
@ -99,6 +99,15 @@ class TabManager {
|
|||
<button class="btn btn-sm" onclick="plotManager.controlPlot('${sessionId}', 'stop')" title="Stop">
|
||||
⏹️ Stop
|
||||
</button>
|
||||
<div class="refresh-rate-control">
|
||||
<label for="refresh-rate-tab-${sessionId}" title="Chart Refresh Rate (ms)">⏱️</label>
|
||||
<input type="number" id="refresh-rate-tab-${sessionId}" class="refresh-rate-input"
|
||||
value="1000" min="100" max="60000" step="100"
|
||||
onchange="plotManager.updateRefreshRate('${sessionId}', this.value)"
|
||||
onkeypress="if(event.key==='Enter') plotManager.updateRefreshRate('${sessionId}', this.value)"
|
||||
title="Refresh rate in milliseconds (100-60000)">
|
||||
<span class="refresh-rate-unit">ms</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plot-info">
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"last_state": {
|
||||
"should_connect": true,
|
||||
"should_stream": true,
|
||||
"should_stream": false,
|
||||
"active_datasets": [
|
||||
"dar"
|
||||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-04T01:01:57.951970"
|
||||
"last_update": "2025-08-04T17:21:54.020081"
|
||||
}
|
Loading…
Reference in New Issue