diff --git a/src_data/locales/en.json b/src_data/locales/en.json index 3eb3469..753b90b 100644 --- a/src_data/locales/en.json +++ b/src_data/locales/en.json @@ -347,6 +347,13 @@ "title": "T factor", "note": "Not used if PID is enabled" }, + "calibration": "2 points Calibration", + "obs1_outdoor": "Warm Point Outdoor", + "obs1_radiator": "Warm Point Radiator", + "obs2_outdoor": "Cold Point Outdoor", + "obs2_radiator": "Cold Point Radiator", + "calibrate": "Calibrate", + "chart": { "radiatorTemp": "Radiator Temperature (°C)", "outdoorTemp": "Outdoor Temperature (°C)" diff --git a/src_data/locales/it.json b/src_data/locales/it.json index 643a6df..580fda4 100644 --- a/src_data/locales/it.json +++ b/src_data/locales/it.json @@ -347,6 +347,12 @@ "title": "Fattore T", "note": "Non usato se PID è attivato" }, + "calibration": "Calibrazione a 2 punti", + "obs1_outdoor": "Punto caldo esterno", + "obs1_radiator": "Punto caldo (radiatore)", + "obs2_outdoor": "Punto freddo esterno", + "obs2_radiator": "Punto freddo (radiatore)", + "calibrate": "Calibra", "chart": { "radiatorTemp": "Temperatura Del Radiatore (°C)", "outdoorTemp": "Outdoor Temperature (°C)" diff --git a/src_data/locales/ru.json b/src_data/locales/ru.json index 6f98b81..f882726 100644 --- a/src_data/locales/ru.json +++ b/src_data/locales/ru.json @@ -347,6 +347,12 @@ "title": "Коэффициент T", "note": "Не используется, если ПИД включен" }, + "calibration": "Калибровка по двум точкам", + "obs1_outdoor": "Теплая точка (снаружи)", + "obs1_radiator": "Теплая точка (радиатор)", + "obs2_outdoor": "Холодная точка (снаружи)", + "obs2_radiator": "Холодная точка (радиатор)", + "calibrate": "Калибровать", "chart": { "radiatorTemp": "Температура радиатора (°C)", "outdoorTemp": "Наружная температура (°C)" diff --git a/src_data/pages/settings.html b/src_data/pages/settings.html index 297126a..5287e47 100644 --- a/src_data/pages/settings.html +++ b/src_data/pages/settings.html @@ -274,30 +274,59 @@ settings.enable - +
- + - + - +
- + settings.equitherm.calibration +
+ + + + + + + + + +
+ @@ -978,87 +1007,89 @@ //График let equithermChart; - async function fetchSettings() { try { const response = await fetch("/api/settings", { cache: "no-cache", credentials: "include" }); - if (!response.ok) { - throw new Error('Response not valid'); + throw new Error("Response not valid"); } - return await response.json(); } catch (error) { console.log(error); } } - // Считаем температуру function calculateTRad(targetTemp, outdoorTemp, maxOut, Kn, Ke, Kk) { let tempDelta = targetTemp - outdoorTemp; const maxPoint = targetTemp - (maxOut - targetTemp) / Kn; let base = targetTemp - maxPoint; - if (base <= 0) { base = 0.0001; } - const sf = (maxOut - targetTemp) / Math.pow(base, 1.0 / Ke); - - let T_rad = targetTemp + sf * (tempDelta >= 0 ? Math.pow(tempDelta, 1.0 / Ke) : -Math.pow(-tempDelta, 1.0 / Ke)) + Kk; + let T_rad = + targetTemp + + sf * (tempDelta >= 0 ? Math.pow(tempDelta, 1.0 / Ke) : -Math.pow(-tempDelta, 1.0 / Ke)) + + Kk; return Math.min(T_rad, maxOut); } - // Генерируем данные для графика function generateChartData(targetTemp, maxOut, Kn, Ke, Kk) { const outdoorTemps = []; const predictedTRad = []; - for (let temp = 25; temp >= -30; temp -= 1) { outdoorTemps.push(temp); predictedTRad.push(calculateTRad(targetTemp, temp, maxOut, Kn, Ke, Kk).toFixed(1)); } - return { outdoorTemps, predictedTRad }; } - // Создаем график - function createChart(outdoorTemps, predictedTRad) { - const ctx = document.getElementById('equithermChart').getContext('2d'); - + function createChart(outdoorTemps, predictedTRad, obsPoints = []) { + const ctx = document.getElementById("equithermChart").getContext("2d"); const canvasHeight = ctx.canvas.height; const gradient = ctx.createLinearGradient(0, canvasHeight, 0, 0); - gradient.addColorStop(0, 'rgba(75, 192, 192, 1)'); - gradient.addColorStop(0.5, 'rgba(255, 99, 132, 1)'); - + gradient.addColorStop(0, "rgba(75, 192, 192, 1)"); + gradient.addColorStop(0.5, "rgba(255, 99, 132, 1)"); equithermChart = new Chart(ctx, { - type: 'line', + type: "line", data: { labels: outdoorTemps, - datasets: [{ - label: 'Температура Радиатора (°C)', - borderColor: gradient, - borderWidth: 1, - fill: false, - tension: 0.1, - pointRadius: 2, - pointHoverRadius: 4, - data: predictedTRad - }] + datasets: [ + { + label: "Температура Радиатора (°C)", + borderColor: gradient, + borderWidth: 1, + fill: false, + tension: 0.1, + pointRadius: 2, + pointHoverRadius: 4, + data: predictedTRad + }, + // Точки калибровки + { + label: "Наблюдаемые точки", + type: "scatter", + data: obsPoints, + backgroundColor: "gold", + borderColor: "gold", + pointRadius: 6, + pointHoverRadius: 8 + } + ] }, options: { responsive: true, interaction: { - mode: 'nearest', + mode: "nearest", intersect: false }, plugins: { tooltip: { enabled: true, - position: 'nearest', + position: "nearest" } }, scales: { @@ -1066,14 +1097,14 @@ display: true, title: { display: true, - text: 'Наружная температура (°C)' + text: "Наружная температура (°C)" } }, y: { display: true, title: { display: true, - text: 'Температура Радиатора (°C)' + text: "Температура Радиатора (°C)" } } } @@ -1081,54 +1112,111 @@ }); } - // Инициализируем график async function initChart() { try { const result = await fetchSettings(); - const { heating, equitherm } = result; const targetTemp = heating?.target ?? 24; const maxOut = heating?.maxTemp ?? 90; const Kn = equitherm?.n_factor ?? 1; const Ke = equitherm?.e_factor ?? 1.3; const Kk = equitherm?.k_factor ?? 0; - - const { outdoorTemps, predictedTRad } = generateChartData(targetTemp, maxOut, Kn, Ke, Kk); - - createChart(outdoorTemps, predictedTRad); - - - document.getElementById('equitherm-settings-busy').classList.add('hidden'); - document.getElementById('equitherm-settings').classList.remove('hidden'); + document.getElementById("equitherm-settings-busy").classList.add("hidden"); + document.getElementById("equitherm-settings").classList.remove("hidden"); } catch (error) { console.log(error); } } - function updateChart(formData) { if (!equithermChart) return; - fetchSettings() .then(result => { const targetTemp = result?.heating?.target ?? 24; const maxOut = result?.heating?.maxTemp ?? 90; - - const Kn = parseFloat(formData.get('equitherm[n_factor]')) || 1; - const Ke = parseFloat(formData.get('equitherm[e_factor]')) || 1.3; - const Kk = parseFloat(formData.get('equitherm[k_factor]')) || 0; - + const Kn = parseFloat(formData.get("equitherm[n_factor]")) || 1; + const Ke = parseFloat(formData.get("equitherm[e_factor]")) || 1.3; + const Kk = parseFloat(formData.get("equitherm[k_factor]")) || 0; const { outdoorTemps, predictedTRad } = generateChartData(targetTemp, maxOut, Kn, Ke, Kk); - + // Точки калибровки + let obsPoints = []; + const obs1Out = parseFloat(formData.get("equitherm[obs1_outdoor]")); + const obs1Rad = parseFloat(formData.get("equitherm[obs1_radiator]")); + const obs2Out = parseFloat(formData.get("equitherm[obs2_outdoor]")); + const obs2Rad = parseFloat(formData.get("equitherm[obs2_radiator]")); + if (!isNaN(obs1Out) && !isNaN(obs1Rad) && !isNaN(obs2Out) && !isNaN(obs2Rad)) { + obsPoints.push({ x: obs1Out, y: obs1Rad }); + obsPoints.push({ x: obs2Out, y: obs2Rad }); + } equithermChart.data.labels = outdoorTemps; equithermChart.data.datasets[0].data = predictedTRad; + if (equithermChart.data.datasets.length > 1) { + equithermChart.data.datasets[1].data = obsPoints; + } equithermChart.update(); }) .catch(error => console.log(error)); } + // Калибровка + async function fetchAndCalibrate() { + try { + const result = await fetchSettings(); + const { heating, equitherm } = result; + const targetTemp = heating?.target ?? 24; + const maxOut = heating?.maxTemp ?? 90; + const Ke = equitherm?.e_factor ?? 1.3; + + const form = document.getElementById("equitherm-settings"); + const formData = new FormData(form); + + const obs1Out = parseFloat(formData.get("equitherm[obs1_outdoor]")); + const obs1Rad = parseFloat(formData.get("equitherm[obs1_radiator]")); + const obs2Out = parseFloat(formData.get("equitherm[obs2_outdoor]")); + const obs2Rad = parseFloat(formData.get("equitherm[obs2_radiator]")); + + if (isNaN(obs1Out) || isNaN(obs1Rad) || isNaN(obs2Out) || isNaN(obs2Rad)) { + alert("Please enter valid observation points."); + return; + } + + // Проверяем чтобы наружн. меньше целевой + const diff1 = targetTemp - obs1Out; + const diff2 = targetTemp - obs2Out; + if (diff1 <= 0 || diff2 <= 0) { + alert("Outdoor temperature must be below the target temperature."); + return; + } + + // A + const denominator = Math.pow(diff2, 1 / Ke) - Math.pow(diff1, 1 / Ke); + if (denominator === 0) { + alert("Calibration error: Denominator is zero."); + return; + } + const A = (obs2Rad - obs1Rad) / denominator; + + // Считаем N из A + const N = Math.pow(A / Math.pow(maxOut - targetTemp, 1 - 1 / Ke), Ke); + + // Считаем K по первой точке + const K = obs1Rad - targetTemp - A * Math.pow(diff1, 1 / Ke); + + // Обновляем поля + document.querySelector("input[name='equitherm[n_factor]']").value = N.toFixed(3); + document.querySelector("input[name='equitherm[k_factor]']").value = K.toFixed(2); + + // Обновляем график + updateChart(new FormData(form)); + } catch (error) { + console.log("Error during calibration:", error); + } + } + // Кнопка + const calibrateButton = document.getElementById("calibrateEquitherm"); + calibrateButton.addEventListener("click", fetchAndCalibrate); // Слушаем отправку const form = document.getElementById('equitherm-settings');