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
-
+
-
+
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -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');