dot na zanikach

This commit is contained in:
Mateusz Gruszczyński
2026-02-03 22:36:59 +01:00
parent 57811f0db3
commit 0285b2c203
3 changed files with 134 additions and 48 deletions

41
app.py
View File

@@ -1,22 +1,20 @@
import os
import warnings
os.environ['EVENTLET_NO_GREENDNS'] = 'yes'
import eventlet
eventlet.monkey_patch()
eventlet.monkey_patch(all=True)
from flask import Flask, render_template, jsonify, request
from flask_socketio import SocketIO
from influxdb import InfluxDBClient
import threading
import time
from datetime import datetime
import config
import os
warnings.filterwarnings("ignore", category=DeprecationWarning, module="eventlet")
app = Flask(__name__)
app.config['SECRET_KEY'] = config.FLASK_CONFIG['secret_key']
socketio = SocketIO(app,
cors_allowed_origins="*",
async_mode='eventlet',
ping_timeout=60,
ping_interval=25)
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='eventlet')
def get_influx_client():
client = InfluxDBClient(
@@ -35,10 +33,10 @@ def get_current_voltage(phase_id):
try:
result = client.query(query)
points = list(result.get_points())
if points:
if points and points[0].get('value') is not None:
return {'voltage': round(float(points[0]['value']), 2), 'timestamp': points[0]['time']}
except Exception as e:
print(f"Influx Error Phase {phase_id}: {e}")
print(f"Current Error: {e}")
finally:
client.close()
return {'voltage': 0, 'timestamp': None}
@@ -57,7 +55,13 @@ def api_timeseries(phase_id):
query = config.PHASES[phase_id]['query'].replace('$timeFilter', cfg['filter']).replace('$__interval', cfg['interval'])
try:
result = client.query(query)
data = [{'time': p['time'], 'voltage': round(p['mean'], 2)} for p in result.get_points() if p.get('mean') is not None]
data = []
for p in result.get_points():
val = p.get('voltage') or p.get('min') or p.get('mean') or p.get('value')
if val is not None:
data.append({'time': p['time'], 'voltage': round(float(val), 2)})
else:
data.append({'time': p['time'], 'voltage': 0})
return jsonify(data)
except Exception as e:
print(f"History Error: {e}")
@@ -66,19 +70,16 @@ def api_timeseries(phase_id):
client.close()
def background_voltage_update():
print("Background worker started...")
while True:
try:
voltages = {'timestamp': None}
for pid in config.PHASES.keys():
data = get_current_voltage(pid)
voltages[f'phase{pid}'] = data['voltage']
if data['timestamp']: voltages['timestamp'] = data['timestamp']
res = get_current_voltage(pid)
voltages[f'phase{pid}'] = res['voltage']
if res['timestamp']: voltages['timestamp'] = res['timestamp']
socketio.emit('voltage_update', voltages)
except Exception as e:
print(f"Worker Loop Error: {e}")
print(f"Worker Error: {e}")
eventlet.sleep(config.CHART_CONFIG['update_interval'] / 1000)
if __name__ == '__main__':

View File

@@ -15,20 +15,20 @@ PHASES = {
1: {
'entity_id': '0_electricity_meter_voltage_phase_1',
'label': 'L1',
'color': '#0d6efd',
'query': 'SELECT mean("value") FROM "V" WHERE ("entity_id" = \'0_electricity_meter_voltage_phase_1\') AND time > now() - $timeFilter GROUP BY time($__interval) fill(null)'
'color': '#3498db',
'query': 'SELECT min("value") as voltage FROM "V" WHERE ("entity_id" = \'0_electricity_meter_voltage_phase_1\') AND time > now() - $timeFilter GROUP BY time($__interval) fill(null)'
},
2: {
'entity_id': '0_electricity_meter_voltage_phase_2',
'label': 'L2',
'color': '#198754',
'query': 'SELECT mean("value") FROM "V" WHERE ("entity_id" = \'0_electricity_meter_voltage_phase_2\') AND time > now() - $timeFilter GROUP BY time($__interval) fill(null)'
'color': '#9112f3',
'query': 'SELECT min("value") as voltage FROM "V" WHERE ("entity_id" = \'0_electricity_meter_voltage_phase_2\') AND time > now() - $timeFilter GROUP BY time($__interval) fill(null)'
},
3: {
'entity_id': '0_electricity_meter_voltage_phase_3',
'label': 'L3',
'color': '#dc3545',
'query': 'SELECT mean("value") FROM "V" WHERE ("entity_id" = \'0_electricity_meter_voltage_phase_3\') AND time > now() - $timeFilter GROUP BY time($__interval) fill(null)'
'color': '#2ecc71',
'query': 'SELECT min("value") as voltage FROM "V" WHERE ("entity_id" = \'0_electricity_meter_voltage_phase_3\') AND time > now() - $timeFilter GROUP BY time($__interval) fill(null)'
}
}

View File

@@ -41,30 +41,50 @@ function initMonitor(phases, defaultRange) {
if (ctxChart) {
voltageChart = new Chart(ctxChart, {
type: 'line',
data: {
datasets: Object.keys(phasesConfig).map(id => ({
label: phasesConfig[id].label,
data: [],
borderColor: phasesConfig[id].color,
backgroundColor: phasesConfig[id].color + '20',
tension: 0.3,
pointRadius: 0,
borderWidth: 2
}))
},
data: { datasets: [] },
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
type: 'time',
time: {
displayFormats: { hour: 'HH:mm', minute: 'HH:mm' },
tooltipFormat: 'HH:mm'
},
time: { displayFormats: { hour: 'HH:mm', minute: 'HH:mm' }, tooltipFormat: 'HH:mm' },
grid: { color: '#2d3139' }
},
y: { min: 190, max: 270, grid: { color: '#2d3139' }, ticks: { stepSize: 10 } }
y: {
beginAtZero: false,
suggestedMin: 210,
suggestedMax: 255,
grid: { color: '#2d3139' },
ticks: { stepSize: 5, color: '#c9d1d9' }
}
},
plugins: {
legend: {
labels: {
color: '#c9d1d9',
filter: function(item) {
return !item.text.includes('Zanik') && !item.text.includes('Powrot');
}
}
},
tooltip: {
callbacks: {
label: function(context) {
if (context.dataset.label.includes('Zanik')) {
return 'ZANIK: ' + context.raw.realV.toFixed(1) + 'V';
}
if (context.dataset.label.includes('Powrot')) {
return 'POWROT: ' + context.raw.realV.toFixed(1) + 'V';
}
let label = context.dataset.label || '';
if (context.parsed.y !== null) {
label += ': ' + context.parsed.y.toFixed(1) + 'V';
}
return label;
}
}
}
}
}
});
@@ -74,15 +94,18 @@ function initMonitor(phases, defaultRange) {
Object.keys(phasesConfig).forEach(id => {
const val = data['phase' + id];
const textElement = document.getElementById('value' + id);
if (val !== undefined && val !== null && val !== 0) {
if (val !== undefined && val !== null) {
const numVal = parseFloat(val);
if (textElement) textElement.textContent = numVal.toFixed(1) + 'V';
if (textElement) {
textElement.textContent = numVal.toFixed(1) + 'V';
textElement.style.color = numVal < 200 ? '#dc3545' : '#fff';
}
updateGaugeUI(id, numVal);
}
});
if (data.timestamp) {
const date = new Date(data.timestamp);
document.getElementById('lastUpdate').textContent = 'Odczyt: ' + date.toLocaleTimeString('pl-PL', {hour: '2-digit', minute:'2-digit', second:'2-digit', hour12: false});
document.getElementById('lastUpdate').textContent = 'Odczyt: ' + date.toLocaleTimeString('pl-PL', {hour12: false});
}
});
@@ -105,12 +128,74 @@ window.changeTimeRange = async function(range) {
document.querySelectorAll('.time-btn').forEach(btn => btn.classList.remove('active'));
const activeBtn = document.querySelector(`[data-range="${range}"]`);
if (activeBtn) activeBtn.classList.add('active');
if (!voltageChart) return;
for (let i = 1; i <= Object.keys(phasesConfig).length; i++) {
const phaseIds = Object.keys(phasesConfig);
voltageChart.data.datasets = [];
for (let id of phaseIds) {
try {
const response = await fetch(`/api/timeseries/${i}?range=${range}`);
const response = await fetch(`/api/timeseries/${id}?range=${range}`);
const data = await response.json();
voltageChart.data.datasets[i-1].data = data.map(d => ({ x: new Date(d.time), y: d.voltage }));
const lineData = [];
const outagePoints = [];
const recoveryPoints = [];
let wasOutage = false;
data.forEach(p => {
const v = p.voltage;
const t = new Date(p.time);
const minY = voltageChart.scales.y.min || 190;
if (v < 200 && v !== null) {
outagePoints.push({ x: t, y: minY + 0.5, realV: v });
lineData.push({ x: t, y: null });
wasOutage = true;
} else {
if (wasOutage) {
recoveryPoints.push({ x: t, y: v, realV: v });
wasOutage = false;
}
lineData.push({ x: t, y: v });
}
});
voltageChart.data.datasets.push({
label: phasesConfig[id].label,
data: lineData,
borderColor: phasesConfig[id].color,
backgroundColor: phasesConfig[id].color + '15',
tension: 0,
borderWidth: 2,
spanGaps: false,
pointRadius: 0
});
if (outagePoints.length > 0) {
voltageChart.data.datasets.push({
label: 'Zanik ' + phasesConfig[id].label,
data: outagePoints,
type: 'scatter',
pointRadius: 5,
pointBackgroundColor: '#ff0000ff',
pointBorderColor: phasesConfig[id].color,
pointBorderWidth: 2,
z: 999
});
}
if (recoveryPoints.length > 0) {
voltageChart.data.datasets.push({
label: 'Powrot ' + phasesConfig[id].label,
data: recoveryPoints,
type: 'scatter',
pointRadius: 5,
pointBackgroundColor: '#3fb99aff',
pointBorderColor: '#ffffff',
pointBorderWidth: 2,
z: 999
});
}
} catch (e) { console.error(e); }
}
voltageChart.update();