This commit is contained in:
Yurii
2025-08-22 10:20:26 +02:00
committed by GitHub
15 changed files with 485 additions and 170 deletions

View File

@@ -340,7 +340,7 @@
</details>
<hr />
<details>
<summary><b data-i18n>settings.section.equitherm</b></summary>
<div>
@@ -353,21 +353,44 @@
</label>
</fieldset>
<div>
<div>
<canvas id="etChart"></canvas>
</div>
<label>
<div>
<span data-i18n>settings.equitherm.chart.targetTemp</span>: <b class="etChartTargetTempValue"></b>°
</div>
<input class="etChartTargetTemp" type="range" value="0" min="0" max="0" step="0.5">
</label>
</div>
<div class="grid">
<label>
<span data-i18n>settings.equitherm.n</span>
<input type="number" inputmode="decimal" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
<span data-i18n>settings.equitherm.slope.title</span>
<input type="number" inputmode="decimal" name="equitherm[slope]" min="0.001" max="10" step="0.001" required>
<small data-i18n>settings.equitherm.slope.note</small>
</label>
<label>
<span data-i18n>settings.equitherm.k</span>
<input type="number" inputmode="decimal" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
<span data-i18n>settings.equitherm.exponent.title</span>
<input type="number" inputmode="decimal" name="equitherm[exponent]" min="0.1" max="2" step="0.001" required>
<small data-i18n>settings.equitherm.exponent.note</small>
</label>
</div>
<div class="grid">
<label>
<span data-i18n>settings.equitherm.shift.title</span>
<input type="number" inputmode="decimal" name="equitherm[shift]" min="-15" max="15" step="0.01" required>
<small data-i18n>settings.equitherm.shift.note</small>
</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>
<small data-i18n>settings.equitherm.t.note</small>
<span data-i18n>settings.equitherm.targetDiffFactor.title</span>
<input type="number" inputmode="decimal" name="equitherm[targetDiffFactor]" min="0" max="10" step="0.01" required>
<small data-i18n>settings.equitherm.targetDiffFactor.note</small>
</label>
</div>
@@ -377,7 +400,7 @@
</details>
<hr />
<details>
<summary><b data-i18n>settings.section.pid</b></summary>
<div>
@@ -860,11 +883,169 @@
</footer>
<script src="/static/app.js?{BUILD_TIME}"></script>
<script src="/static/chart.js?{BUILD_TIME}"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));
lang.build();
let etChart = null;
let etChartConfig = {
slope: null,
exponent: null,
shift: null,
unitSystem: null,
targetTemp: null,
minTemp: null,
maxTemp: null
};
const hasNeedDecimationChart = () => {
return window.innerWidth <= 800;
}
const makeEquithermChart = () => {
if (etChart == null) {
const ctx = document.getElementById('etChart').getContext('2d');
try {
etChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
borderColor: (context) => {
const chart = context.chart;
const {ctx, chartArea} = chart;
if (!chartArea) {
return;
}
const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
gradient.addColorStop(0, 'rgba(1, 114, 173, 1)');
gradient.addColorStop(0.5, 'rgba(255, 99, 132, 1)');
return gradient;
},
borderWidth: 3,
fill: false,
tension: 0.1,
pointRadius: 2,
pointHoverRadius: 4,
indexAxis: "x",
data: []
}]
},
options: {
responsive: true,
resizeDelay: 500,
parsing: false,
interaction: {
mode: 'nearest',
intersect: false
},
plugins: {
tooltip: {
enabled: true,
position: 'nearest',
displayColors: false,
callbacks: {
title: (items) => {
return `${i18n("settings.equitherm.chart.outdoorTemp")}: ${items[0].label}`;
}
}
},
legend: {
display: false
},
decimation: {
enabled: hasNeedDecimationChart(),
algorithm: 'lttb',
samples: 30,
threshold: 30
}
},
scales: {
x: {
display: true,
type: "linear",
reverse: true,
title: {
display: true
},
ticks: {
stepSize: 1,
format: {
style: "unit",
unit: "degree",
unitDisplay: "narrow"
}
}
},
y: {
display: true,
title: {
display: true
},
ticks: {
format: {
style: "unit",
unit: "degree",
unitDisplay: "narrow"
}
}
}
}
}
});
} catch (error) {
console.log(error);
}
}
if (!etChart) {
return;
}
while (etChart.data.datasets[0].data.length) {
etChart.data.datasets[0].data.pop();
}
for (let value = 30; value >= -30; value--) {
const outdoorTemp = etChartConfig.unitSystem == 0 ? value : c2f(value);
etChart.data.datasets[0].data.push({
x: parseFloat(outdoorTemp.toFixed(1)),
y: parseFloat(calculateEquithermTemp(outdoorTemp).toFixed(1))
});
}
etChart.data.datasets[0].label = i18n("settings.equitherm.chart.setpointTemp");
etChart.options.scales.x.title.text = i18n("settings.equitherm.chart.outdoorTemp");
etChart.options.scales.y.title.text = i18n("settings.equitherm.chart.setpointTemp");
etChart.update();
}
const calculateEquithermTemp = (outdoorTemp) => {
const tempDelta = etChartConfig.targetTemp - outdoorTemp;
const maxPoint = etChartConfig.targetTemp - (
etChartConfig.maxTemp - etChartConfig.targetTemp
) / etChartConfig.slope;
const sf = (etChartConfig.maxTemp - etChartConfig.targetTemp) / Math.pow(
etChartConfig.targetTemp - maxPoint,
1 / etChartConfig.exponent
);
const result = etChartConfig.targetTemp + etChartConfig.shift + sf * (
tempDelta >= 0
? Math.pow(tempDelta, 1 / etChartConfig.exponent)
: -(Math.pow(-(tempDelta), 1 / etChartConfig.exponent))
);
return Math.max(Math.min(result, etChartConfig.maxTemp), etChartConfig.minTemp);
}
const fillData = (data) => {
// System
setSelectValue("[name='system[logLevel]']", data.system.logLevel);
@@ -1012,9 +1193,10 @@
// Equitherm
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[t_factor]']", data.equitherm.t_factor);
setInputValue("[name='equitherm[slope]']", data.equitherm.slope);
setInputValue("[name='equitherm[exponent]']", data.equitherm.exponent);
setInputValue("[name='equitherm[shift]']", data.equitherm.shift);
setInputValue("[name='equitherm[targetDiffFactor]']", data.equitherm.targetDiffFactor);
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
// PID
@@ -1038,6 +1220,24 @@
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);
const etMinTemp = parseInt(data.system.unitSystem == 0 ? 5 : 41);
const etMaxTemp = parseInt(data.system.unitSystem == 0 ? 30 : 86);
const etTargetTemp = constrain(parseFloat(data.heating.target), etMinTemp, etMaxTemp);
setInputValue(".etChartTargetTemp", etTargetTemp.toFixed(1), {
"min": etMinTemp,
"max": etMaxTemp
});
etChartConfig.slope = data.equitherm.slope;
etChartConfig.exponent = data.equitherm.exponent;
etChartConfig.shift = data.equitherm.shift;
etChartConfig.unitSystem = data.system.unitSystem;
etChartConfig.minTemp = data.heating.minTemp;
etChartConfig.maxTemp = data.heating.maxTemp;
makeEquithermChart();
};
try {
@@ -1090,6 +1290,58 @@
} catch (error) {
console.log(error);
}
document.querySelector(".etChartTargetTemp").addEventListener("input", async (event) => {
setValue('.etChartTargetTempValue', parseFloat(event.target.value).toFixed(1));
});
document.querySelector(".etChartTargetTemp").addEventListener("change", async (event) => {
if (!event.target.checkValidity()) {
return;
}
etChartConfig.targetTemp = parseFloat(event.target.value);
setValue('.etChartTargetTempValue', etChartConfig.targetTemp.toFixed(1));
makeEquithermChart();
});
document.querySelector("[name='equitherm[slope]']").addEventListener("change", async (event) => {
if (!event.target.checkValidity()) {
return;
}
etChartConfig.slope = parseFloat(event.target.value);
makeEquithermChart();
});
document.querySelector("[name='equitherm[exponent]']").addEventListener("change", async (event) => {
if (!event.target.checkValidity()) {
return;
}
etChartConfig.exponent = parseFloat(event.target.value);
makeEquithermChart();
});
document.querySelector("[name='equitherm[shift]']").addEventListener("change", async (event) => {
if (!event.target.checkValidity()) {
return;
}
etChartConfig.shift = parseFloat(event.target.value);
makeEquithermChart();
});
window.addEventListener('resize', async (event) => {
if (etChart) {
etChart.resize();
if (etChart.options.plugins.decimation.enabled != hasNeedDecimationChart()) {
etChart.options.plugins.decimation.enabled = hasNeedDecimationChart();
etChart.update();
}
}
});
});
</script>
</body>