mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-12 11:14:28 +05:00
feat: new equitherm algorithm and chart for it (#144)
This commit is contained in:
@@ -342,9 +342,14 @@
|
||||
"equitherm": {
|
||||
"n": "N factor",
|
||||
"k": "K factor",
|
||||
"e": "Exponent E",
|
||||
"t": {
|
||||
"title": "T factor",
|
||||
"note": "Not used if PID is enabled"
|
||||
},
|
||||
"chart": {
|
||||
"radiatorTemp": "Radiator Temperature (°C)",
|
||||
"outdoorTemp": "Outdoor Temperature (°C)"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -342,9 +342,14 @@
|
||||
"equitherm": {
|
||||
"n": "Fattore N",
|
||||
"k": "Fattore K",
|
||||
"e": "Esponente E",
|
||||
"t": {
|
||||
"title": "Fattore T",
|
||||
"note": "Non usato se PID è attivato"
|
||||
},
|
||||
"chart": {
|
||||
"radiatorTemp": "Temperatura Del Radiatore (°C)",
|
||||
"outdoorTemp": "Outdoor Temperature (°C)"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -342,9 +342,14 @@
|
||||
"equitherm": {
|
||||
"n": "Коэффициент N",
|
||||
"k": "Коэффициент K",
|
||||
"e": "Экспонента E",
|
||||
"t": {
|
||||
"title": "Коэффициент T",
|
||||
"note": "Не используется, если ПИД включен"
|
||||
},
|
||||
"chart": {
|
||||
"radiatorTemp": "Температура радиатора (°C)",
|
||||
"outdoorTemp": "Наружная температура (°C)"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -261,9 +261,10 @@
|
||||
</details>
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.equitherm</b></summary>
|
||||
<canvas id="equithermChart" width="400" height="200"></canvas>
|
||||
<div>
|
||||
<div id="equitherm-settings-busy" aria-busy="true"></div>
|
||||
<form action="/api/settings" id="equitherm-settings" class="hidden">
|
||||
@@ -273,7 +274,7 @@
|
||||
<span data-i18n>settings.enable</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<div class="grid">
|
||||
<label>
|
||||
<span data-i18n>settings.equitherm.n</span>
|
||||
@@ -285,6 +286,11 @@
|
||||
<input type="number" inputmode="decimal" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span data-i18n>settings.equitherm.e</span>
|
||||
<input type="number" inputmode="decimal" name="equitherm[e_factor]" min="1" max="2" step="0.01" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span data-i18n>settings.equitherm.t.title</span>
|
||||
<input type="number" inputmode="decimal" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
|
||||
@@ -299,6 +305,8 @@
|
||||
|
||||
<hr />
|
||||
|
||||
|
||||
|
||||
<details>
|
||||
<summary><b data-i18n>settings.section.pid</b></summary>
|
||||
<div>
|
||||
@@ -756,6 +764,7 @@
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js?{BUILD_TIME}"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
@@ -885,6 +894,7 @@
|
||||
setCheckboxValue("[name='equitherm[enabled]']", data.equitherm.enabled);
|
||||
setInputValue("[name='equitherm[n_factor]']", data.equitherm.n_factor);
|
||||
setInputValue("[name='equitherm[k_factor]']", data.equitherm.k_factor);
|
||||
setInputValue("[name='equitherm[e_factor]']", data.equitherm.e_factor);
|
||||
setInputValue("[name='equitherm[t_factor]']", data.equitherm.t_factor);
|
||||
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
|
||||
|
||||
@@ -909,6 +919,9 @@
|
||||
setInputValue("[name='pid[deadband][thresholdHigh]']", data.pid.deadband.thresholdHigh);
|
||||
setInputValue("[name='pid[deadband][thresholdLow]']", data.pid.deadband.thresholdLow);
|
||||
setBusy('#pid-settings-busy', '#pid-settings', false);
|
||||
|
||||
|
||||
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -961,6 +974,190 @@
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
//График
|
||||
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');
|
||||
}
|
||||
|
||||
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;
|
||||
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');
|
||||
|
||||
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)');
|
||||
|
||||
equithermChart = new Chart(ctx, {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: outdoorTemps,
|
||||
datasets: [{
|
||||
label: 'Температура Радиатора (°C)',
|
||||
borderColor: gradient,
|
||||
borderWidth: 1,
|
||||
fill: false,
|
||||
tension: 0.1,
|
||||
pointRadius: 2,
|
||||
pointHoverRadius: 4,
|
||||
data: predictedTRad
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
interaction: {
|
||||
mode: 'nearest',
|
||||
intersect: false
|
||||
},
|
||||
plugins: {
|
||||
tooltip: {
|
||||
enabled: true,
|
||||
position: 'nearest',
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Наружная температура (°C)'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Температура Радиатора (°C)'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Инициализируем график
|
||||
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');
|
||||
} 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 { outdoorTemps, predictedTRad } = generateChartData(targetTemp, maxOut, Kn, Ke, Kk);
|
||||
|
||||
equithermChart.data.labels = outdoorTemps;
|
||||
equithermChart.data.datasets[0].data = predictedTRad;
|
||||
equithermChart.update();
|
||||
})
|
||||
.catch(error => console.log(error));
|
||||
}
|
||||
|
||||
|
||||
// Слушаем отправку
|
||||
const form = document.getElementById('equitherm-settings');
|
||||
form.addEventListener('submit', (e) => {
|
||||
|
||||
const formData = new FormData(form);
|
||||
updateChart(formData);
|
||||
});
|
||||
|
||||
// Слушаем кнопку сохранить
|
||||
const equithermSection = document.querySelector('details');
|
||||
const saveButton = equithermSection.querySelector('button[data-i18n="button.save"]');
|
||||
saveButton.addEventListener('click', () => {
|
||||
const form = document.getElementById('equitherm-settings');
|
||||
const formData = new FormData(form);
|
||||
updateChart(formData);
|
||||
});
|
||||
|
||||
//Следим за изменениями в полях
|
||||
document.querySelectorAll('#equitherm-settings input').forEach(input => {
|
||||
input.addEventListener('change', () => {
|
||||
const form = document.getElementById('equitherm-settings');
|
||||
const formData = new FormData(form);
|
||||
updateChart(formData);
|
||||
});
|
||||
});
|
||||
|
||||
// инициализируем график
|
||||
initChart();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user