mirror of
https://github.com/Laxilef/OTGateway.git
synced 2026-11-02 17:48:44 +05:00
Implementation of the new Equitherm algorithm (#146)
* feat: new equitherm algorithm and chart for it (#144) * refactor: refactoring after #144 * refactor: cosmetic changes (equitherm chart) * chore: fix typo * refactor: cosmetic changes * chore: remove unused files * chore: resolve conflicts * refactor: added notes for equitherm parameters * fix: decimation for Equitherm chart fixed; chartjs updated * style: HTML code formatting * chore: added additional description of the ``T`` parameter for Equitherm * flx: typo * refactor: after merge --------- Co-authored-by: P43YM <ip43ym@gmail.com>
This commit is contained in:
1 parent
00baf10b9f
commit
cb8251dd40
15 files changed
+475
-173
No files matched your search
@@ -27,6 +27,9 @@ let paths = {
|
|||||||
'src_data/scripts/i18n.min.js',
|
'src_data/scripts/i18n.min.js',
|
||||||
'src_data/scripts/lang.js',
|
'src_data/scripts/lang.js',
|
||||||
'src_data/scripts/utils.js'
|
'src_data/scripts/utils.js'
|
||||||
|
],
|
||||||
|
'chart.js': [
|
||||||
|
'src_data/scripts/chart.js'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
#include <Arduino.h>
|
|
||||||
|
|
||||||
#if defined(EQUITHERM_INTEGER)
|
|
||||||
// расчёты с целыми числами
|
|
||||||
typedef int datatype;
|
|
||||||
#else
|
|
||||||
// расчёты с float числами
|
|
||||||
typedef float datatype;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class Equitherm {
|
|
||||||
public:
|
|
||||||
datatype targetTemp = 0;
|
|
||||||
datatype indoorTemp = 0;
|
|
||||||
datatype outdoorTemp = 0;
|
|
||||||
float Kn = 0.0;
|
|
||||||
float Kk = 0.0;
|
|
||||||
float Kt = 0.0;
|
|
||||||
|
|
||||||
Equitherm() = default;
|
|
||||||
|
|
||||||
// kn, kk, kt
|
|
||||||
Equitherm(float new_kn, float new_kk, float new_kt) {
|
|
||||||
Kn = new_kn;
|
|
||||||
Kk = new_kk;
|
|
||||||
Kt = new_kt;
|
|
||||||
}
|
|
||||||
|
|
||||||
// лимит выходной величины
|
|
||||||
void setLimits(unsigned short min_output, unsigned short max_output) {
|
|
||||||
_minOut = min_output;
|
|
||||||
_maxOut = max_output;
|
|
||||||
}
|
|
||||||
|
|
||||||
// возвращает новое значение при вызове
|
|
||||||
datatype getResult() {
|
|
||||||
datatype output = getResultN() + getResultK() + getResultT();
|
|
||||||
output = constrain(output, _minOut, _maxOut); // ограничиваем выход
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
unsigned short _minOut = 20, _maxOut = 90;
|
|
||||||
|
|
||||||
// температура контура отопления в зависимости от наружной температуры
|
|
||||||
datatype getResultN() {
|
|
||||||
float a = (-0.21 * Kn) - 0.06; // a = -0,21k — 0,06
|
|
||||||
float b = (6.04 * Kn) + 1.98; // b = 6,04k + 1,98
|
|
||||||
float c = (-5.06 * Kn) + 18.06; // с = -5,06k + 18,06
|
|
||||||
float x = (-0.2 * outdoorTemp) + 5; // x = -0.2*t1 + 5
|
|
||||||
return (a * x * x) + (b * x) + c; // Tn = ax2 + bx + c
|
|
||||||
}
|
|
||||||
|
|
||||||
// поправка на желаемую комнатную температуру
|
|
||||||
datatype getResultK() {
|
|
||||||
return (targetTemp - 20) * Kk;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Расчет поправки (ошибки) термостата
|
|
||||||
datatype getResultT() {
|
|
||||||
return constrain((targetTemp - indoorTemp), -3, 3) * Kt;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
+51
-27
@@ -844,19 +844,19 @@ public:
|
|||||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("equitherm")).c_str(), doc);
|
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("equitherm")).c_str(), doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool publishInputEquithermFactorN(bool enabledByDefault = true) {
|
bool publishInputEquithermSlope(bool enabledByDefault = true) {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_n"));
|
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_slope"));
|
||||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_n"));
|
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_slope"));
|
||||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
||||||
doc[FPSTR(HA_NAME)] = F("Equitherm factor N");
|
doc[FPSTR(HA_NAME)] = F("Equitherm slope");
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:alpha-n-circle-outline");
|
doc[FPSTR(HA_ICON)] = F("mdi:slope-uphill");
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|float(0)|round(3) }}");
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.slope|float(0)|round(3) }}");
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"slope\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = 0.001f;
|
doc[FPSTR(HA_MIN)] = 0.001f;
|
||||||
doc[FPSTR(HA_MAX)] = 10;
|
doc[FPSTR(HA_MAX)] = 10;
|
||||||
doc[FPSTR(HA_STEP)] = 0.001f;
|
doc[FPSTR(HA_STEP)] = 0.001f;
|
||||||
@@ -864,56 +864,80 @@ public:
|
|||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
|
|
||||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_n_factor")).c_str(), doc);
|
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_slope")).c_str(), doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool publishInputEquithermFactorK(bool enabledByDefault = true) {
|
bool publishInputEquithermExponent(bool enabledByDefault = true) {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_k"));
|
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_exponent"));
|
||||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_k"));
|
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_exponent"));
|
||||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
||||||
doc[FPSTR(HA_NAME)] = F("Equitherm factor K");
|
doc[FPSTR(HA_NAME)] = F("Equitherm exponent");
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:alpha-k-circle-outline");
|
doc[FPSTR(HA_ICON)] = F("mdi:exponent");
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.k_factor|float(0)|round(2) }}");
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.exponent|float(0)|round(3) }}");
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"exponent\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = 0;
|
doc[FPSTR(HA_MIN)] = 0.1;
|
||||||
doc[FPSTR(HA_MAX)] = 10;
|
doc[FPSTR(HA_MAX)] = 2;
|
||||||
|
doc[FPSTR(HA_STEP)] = 0.001f;
|
||||||
|
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
|
||||||
|
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
||||||
|
doc.shrinkToFit();
|
||||||
|
|
||||||
|
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_exponent")).c_str(), doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool publishInputEquithermShift(bool enabledByDefault = true) {
|
||||||
|
JsonDocument doc;
|
||||||
|
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||||
|
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||||
|
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_shift"));
|
||||||
|
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_shift"));
|
||||||
|
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
||||||
|
doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE);
|
||||||
|
doc[FPSTR(HA_NAME)] = F("Equitherm shift");
|
||||||
|
doc[FPSTR(HA_ICON)] = F("mdi:chart-areaspline");
|
||||||
|
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||||
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.shift|float(0)|round(2) }}");
|
||||||
|
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||||
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"shift\" : {{ value }}}}");
|
||||||
|
doc[FPSTR(HA_MIN)] = -15;
|
||||||
|
doc[FPSTR(HA_MAX)] = 15;
|
||||||
doc[FPSTR(HA_STEP)] = 0.01f;
|
doc[FPSTR(HA_STEP)] = 0.01f;
|
||||||
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
|
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
|
|
||||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_k_factor")).c_str(), doc);
|
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_shift")).c_str(), doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool publishInputEquithermFactorT(bool enabledByDefault = true) {
|
bool publishInputEquithermTargetDiffFactor(bool enabledByDefault = true) {
|
||||||
JsonDocument doc;
|
JsonDocument doc;
|
||||||
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
|
||||||
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->settingsTopic.c_str();
|
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->settingsTopic.c_str();
|
||||||
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.pid.enabled, 'offline', 'online') }}");
|
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.pid.enabled, 'offline', 'online') }}");
|
||||||
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
|
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
|
||||||
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
|
||||||
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_t"));
|
doc[FPSTR(HA_UNIQUE_ID)] = this->getUniqueIdWithPrefix(F("equitherm_target_diff_factor"));
|
||||||
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_t"));
|
doc[FPSTR(HA_DEFAULT_ENTITY_ID)] = this->getEntityIdWithPrefix(FPSTR(HA_ENTITY_NUMBER), F("equitherm_target_diff_factor"));
|
||||||
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
|
||||||
doc[FPSTR(HA_NAME)] = F("Equitherm factor T");
|
doc[FPSTR(HA_NAME)] = F("Equitherm target diff factor");
|
||||||
doc[FPSTR(HA_ICON)] = F("mdi:alpha-t-circle-outline");
|
doc[FPSTR(HA_ICON)] = F("mdi:chart-timeline-variant-shimmer");
|
||||||
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
|
||||||
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.t_factor|float(0)|round(2) }}");
|
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.targetDiffFactor|float(0)|round(3) }}");
|
||||||
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
|
||||||
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ value }}}}");
|
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"targetDiffFactor\" : {{ value }}}}");
|
||||||
doc[FPSTR(HA_MIN)] = 0;
|
doc[FPSTR(HA_MIN)] = 0;
|
||||||
doc[FPSTR(HA_MAX)] = 10;
|
doc[FPSTR(HA_MAX)] = 10;
|
||||||
doc[FPSTR(HA_STEP)] = 0.01f;
|
doc[FPSTR(HA_STEP)] = 0.001f;
|
||||||
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
|
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
|
||||||
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
|
||||||
doc.shrinkToFit();
|
doc.shrinkToFit();
|
||||||
|
|
||||||
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_t_factor")).c_str(), doc);
|
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_target_diff_factor")).c_str(), doc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+4
-3
@@ -502,9 +502,10 @@ protected:
|
|||||||
|
|
||||||
// equitherm
|
// equitherm
|
||||||
this->haHelper->publishSwitchEquitherm();
|
this->haHelper->publishSwitchEquitherm();
|
||||||
this->haHelper->publishInputEquithermFactorN(false);
|
this->haHelper->publishInputEquithermSlope(false);
|
||||||
this->haHelper->publishInputEquithermFactorK(false);
|
this->haHelper->publishInputEquithermExponent(false);
|
||||||
this->haHelper->publishInputEquithermFactorT(false);
|
this->haHelper->publishInputEquithermShift(false);
|
||||||
|
this->haHelper->publishInputEquithermTargetDiffFactor(false);
|
||||||
|
|
||||||
// states
|
// states
|
||||||
this->haHelper->publishStatusState();
|
this->haHelper->publishStatusState();
|
||||||
|
|||||||
+23
-32
@@ -1,7 +1,5 @@
|
|||||||
#include <Equitherm.h>
|
|
||||||
#include <GyverPID.h>
|
#include <GyverPID.h>
|
||||||
|
|
||||||
Equitherm etRegulator;
|
|
||||||
GyverPID pidRegulator(0, 0, 0);
|
GyverPID pidRegulator(0, 0, 0);
|
||||||
|
|
||||||
|
|
||||||
@@ -146,39 +144,32 @@ protected:
|
|||||||
|
|
||||||
// if use equitherm
|
// if use equitherm
|
||||||
if (settings.equitherm.enabled) {
|
if (settings.equitherm.enabled) {
|
||||||
unsigned short minTemp = settings.heating.minTemp;
|
float tempDelta = settings.heating.target - vars.master.heating.outdoorTemp;
|
||||||
unsigned short maxTemp = settings.heating.maxTemp;
|
float maxPoint = settings.heating.target - (
|
||||||
float targetTemp = settings.heating.target;
|
settings.heating.maxTemp - settings.heating.target
|
||||||
float indoorTemp = vars.master.heating.indoorTemp;
|
) / settings.equitherm.slope;
|
||||||
float outdoorTemp = vars.master.heating.outdoorTemp;
|
|
||||||
|
|
||||||
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
|
float sf = (settings.heating.maxTemp - settings.heating.target) / pow(
|
||||||
minTemp = f2c(minTemp);
|
settings.heating.target - maxPoint,
|
||||||
maxTemp = f2c(maxTemp);
|
1.0f / settings.equitherm.exponent
|
||||||
targetTemp = f2c(targetTemp);
|
);
|
||||||
indoorTemp = f2c(indoorTemp);
|
float etResult = settings.heating.target + settings.equitherm.shift + sf * (
|
||||||
outdoorTemp = f2c(outdoorTemp);
|
tempDelta >= 0
|
||||||
|
? pow(tempDelta, 1.0f / settings.equitherm.exponent)
|
||||||
|
: -(pow(-(tempDelta), 1.0f / settings.equitherm.exponent))
|
||||||
|
);
|
||||||
|
|
||||||
|
// add diff
|
||||||
|
if (this->indoorSensorsConnected && !settings.pid.enabled && !settings.heating.turbo) {
|
||||||
|
etResult += constrain(
|
||||||
|
settings.heating.target - vars.master.heating.indoorTemp,
|
||||||
|
-3.0f,
|
||||||
|
3.0f
|
||||||
|
) * settings.equitherm.targetDiffFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->indoorSensorsConnected || settings.pid.enabled) {
|
// limit
|
||||||
etRegulator.Kt = 0.0f;
|
etResult = constrain(etResult, settings.heating.minTemp, settings.heating.maxTemp);
|
||||||
etRegulator.indoorTemp = 0.0f;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
etRegulator.Kt = settings.heating.turbo ? 0.0f : settings.equitherm.t_factor;
|
|
||||||
etRegulator.indoorTemp = indoorTemp;
|
|
||||||
}
|
|
||||||
|
|
||||||
etRegulator.setLimits(minTemp, maxTemp);
|
|
||||||
etRegulator.Kn = settings.equitherm.n_factor;
|
|
||||||
etRegulator.Kk = settings.equitherm.k_factor;
|
|
||||||
etRegulator.targetTemp = targetTemp;
|
|
||||||
etRegulator.outdoorTemp = outdoorTemp;
|
|
||||||
float etResult = etRegulator.getResult();
|
|
||||||
|
|
||||||
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
|
|
||||||
etResult = c2f(etResult);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fabsf(prevEtResult - etResult) > 0.09f) {
|
if (fabsf(prevEtResult - etResult) > 0.09f) {
|
||||||
prevEtResult = etResult;
|
prevEtResult = etResult;
|
||||||
|
|||||||
+4
-3
@@ -154,9 +154,10 @@ struct Settings {
|
|||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool enabled = false;
|
bool enabled = false;
|
||||||
float n_factor = 0.7f;
|
float slope = 0.7f;
|
||||||
float k_factor = 3.0f;
|
float exponent = 1.3f;
|
||||||
float t_factor = 2.0f;
|
float shift = 0.0f;
|
||||||
|
float targetDiffFactor = 2.0f;
|
||||||
} equitherm;
|
} equitherm;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
|||||||
+4
-3
@@ -81,6 +81,7 @@ const char S_ENABLED[] PROGMEM = "enabled";
|
|||||||
const char S_ENV[] PROGMEM = "env";
|
const char S_ENV[] PROGMEM = "env";
|
||||||
const char S_EPC[] PROGMEM = "epc";
|
const char S_EPC[] PROGMEM = "epc";
|
||||||
const char S_EQUITHERM[] PROGMEM = "equitherm";
|
const char S_EQUITHERM[] PROGMEM = "equitherm";
|
||||||
|
const char S_EXPONENT[] PROGMEM = "exponent";
|
||||||
const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump";
|
const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump";
|
||||||
const char S_FACTOR[] PROGMEM = "factor";
|
const char S_FACTOR[] PROGMEM = "factor";
|
||||||
const char S_FAULT[] PROGMEM = "fault";
|
const char S_FAULT[] PROGMEM = "fault";
|
||||||
@@ -117,7 +118,6 @@ const char S_INVERT_STATE[] PROGMEM = "invertState";
|
|||||||
const char S_IP[] PROGMEM = "ip";
|
const char S_IP[] PROGMEM = "ip";
|
||||||
const char S_I_FACTOR[] PROGMEM = "i_factor";
|
const char S_I_FACTOR[] PROGMEM = "i_factor";
|
||||||
const char S_I_MULTIPLIER[] PROGMEM = "i_multiplier";
|
const char S_I_MULTIPLIER[] PROGMEM = "i_multiplier";
|
||||||
const char S_K_FACTOR[] PROGMEM = "k_factor";
|
|
||||||
const char S_LOGIN[] PROGMEM = "login";
|
const char S_LOGIN[] PROGMEM = "login";
|
||||||
const char S_LOG_LEVEL[] PROGMEM = "logLevel";
|
const char S_LOG_LEVEL[] PROGMEM = "logLevel";
|
||||||
const char S_LOW_TEMP[] PROGMEM = "lowTemp";
|
const char S_LOW_TEMP[] PROGMEM = "lowTemp";
|
||||||
@@ -143,7 +143,6 @@ const char S_NAME[] PROGMEM = "name";
|
|||||||
const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl";
|
const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl";
|
||||||
const char S_NETWORK[] PROGMEM = "network";
|
const char S_NETWORK[] PROGMEM = "network";
|
||||||
const char S_NTP[] PROGMEM = "ntp";
|
const char S_NTP[] PROGMEM = "ntp";
|
||||||
const char S_N_FACTOR[] PROGMEM = "n_factor";
|
|
||||||
const char S_OFFSET[] PROGMEM = "offset";
|
const char S_OFFSET[] PROGMEM = "offset";
|
||||||
const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating";
|
const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating";
|
||||||
const char S_ON_FAULT[] PROGMEM = "onFault";
|
const char S_ON_FAULT[] PROGMEM = "onFault";
|
||||||
@@ -182,9 +181,11 @@ const char S_SERIAL[] PROGMEM = "serial";
|
|||||||
const char S_SERVER[] PROGMEM = "server";
|
const char S_SERVER[] PROGMEM = "server";
|
||||||
const char S_SETTINGS[] PROGMEM = "settings";
|
const char S_SETTINGS[] PROGMEM = "settings";
|
||||||
const char S_SET_DATE_AND_TIME[] PROGMEM = "setDateAndTime";
|
const char S_SET_DATE_AND_TIME[] PROGMEM = "setDateAndTime";
|
||||||
|
const char S_SHIFT[] PROGMEM = "shift";
|
||||||
const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality";
|
const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality";
|
||||||
const char S_SIZE[] PROGMEM = "size";
|
const char S_SIZE[] PROGMEM = "size";
|
||||||
const char S_SLAVE[] PROGMEM = "slave";
|
const char S_SLAVE[] PROGMEM = "slave";
|
||||||
|
const char S_SLOPE[] PROGMEM = "slope";
|
||||||
const char S_SSID[] PROGMEM = "ssid";
|
const char S_SSID[] PROGMEM = "ssid";
|
||||||
const char S_STA[] PROGMEM = "sta";
|
const char S_STA[] PROGMEM = "sta";
|
||||||
const char S_STATE[] PROGMEM = "state";
|
const char S_STATE[] PROGMEM = "state";
|
||||||
@@ -196,6 +197,7 @@ const char S_SUBNET[] PROGMEM = "subnet";
|
|||||||
const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode";
|
const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode";
|
||||||
const char S_SYSTEM[] PROGMEM = "system";
|
const char S_SYSTEM[] PROGMEM = "system";
|
||||||
const char S_TARGET[] PROGMEM = "target";
|
const char S_TARGET[] PROGMEM = "target";
|
||||||
|
const char S_TARGET_DIFF_FACTOR[] PROGMEM = "targetDiffFactor";
|
||||||
const char S_TARGET_TEMP[] PROGMEM = "targetTemp";
|
const char S_TARGET_TEMP[] PROGMEM = "targetTemp";
|
||||||
const char S_TELNET[] PROGMEM = "telnet";
|
const char S_TELNET[] PROGMEM = "telnet";
|
||||||
const char S_TEMPERATURE[] PROGMEM = "temperature";
|
const char S_TEMPERATURE[] PROGMEM = "temperature";
|
||||||
@@ -208,7 +210,6 @@ const char S_TRESHOLD_TIME[] PROGMEM = "tresholdTime";
|
|||||||
const char S_TURBO[] PROGMEM = "turbo";
|
const char S_TURBO[] PROGMEM = "turbo";
|
||||||
const char S_TURBO_FACTOR[] PROGMEM = "turboFactor";
|
const char S_TURBO_FACTOR[] PROGMEM = "turboFactor";
|
||||||
const char S_TYPE[] PROGMEM = "type";
|
const char S_TYPE[] PROGMEM = "type";
|
||||||
const char S_T_FACTOR[] PROGMEM = "t_factor";
|
|
||||||
const char S_UNIT_SYSTEM[] PROGMEM = "unitSystem";
|
const char S_UNIT_SYSTEM[] PROGMEM = "unitSystem";
|
||||||
const char S_UPTIME[] PROGMEM = "uptime";
|
const char S_UPTIME[] PROGMEM = "uptime";
|
||||||
const char S_USE[] PROGMEM = "use";
|
const char S_USE[] PROGMEM = "use";
|
||||||
|
|||||||
+25
-15
@@ -517,9 +517,10 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
|||||||
|
|
||||||
auto equitherm = dst[FPSTR(S_EQUITHERM)].to<JsonObject>();
|
auto equitherm = dst[FPSTR(S_EQUITHERM)].to<JsonObject>();
|
||||||
equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled;
|
equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled;
|
||||||
equitherm[FPSTR(S_N_FACTOR)] = roundf(src.equitherm.n_factor, 3);
|
equitherm[FPSTR(S_SLOPE)] = roundf(src.equitherm.slope, 3);
|
||||||
equitherm[FPSTR(S_K_FACTOR)] = roundf(src.equitherm.k_factor, 3);
|
equitherm[FPSTR(S_EXPONENT)] = roundf(src.equitherm.exponent, 3);
|
||||||
equitherm[FPSTR(S_T_FACTOR)] = roundf(src.equitherm.t_factor, 3);
|
equitherm[FPSTR(S_SHIFT)] = roundf(src.equitherm.shift, 2);
|
||||||
|
equitherm[FPSTR(S_TARGET_DIFF_FACTOR)] = roundf(src.equitherm.targetDiffFactor, 3);
|
||||||
|
|
||||||
auto pid = dst[FPSTR(S_PID)].to<JsonObject>();
|
auto pid = dst[FPSTR(S_PID)].to<JsonObject>();
|
||||||
pid[FPSTR(S_ENABLED)] = src.pid.enabled;
|
pid[FPSTR(S_ENABLED)] = src.pid.enabled;
|
||||||
@@ -1127,29 +1128,38 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_N_FACTOR)].isNull()) {
|
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_SLOPE)].isNull()) {
|
||||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_N_FACTOR)].as<float>();
|
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_SLOPE)].as<float>();
|
||||||
|
|
||||||
if (value > 0 && value <= 10 && fabsf(value - dst.equitherm.n_factor) > 0.0001f) {
|
if (value > 0.0f && value <= 10.0f && fabsf(value - dst.equitherm.slope) > 0.0001f) {
|
||||||
dst.equitherm.n_factor = roundf(value, 3);
|
dst.equitherm.slope = roundf(value, 3);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_K_FACTOR)].isNull()) {
|
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_EXPONENT)].isNull()) {
|
||||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_K_FACTOR)].as<float>();
|
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_EXPONENT)].as<float>();
|
||||||
|
|
||||||
if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.k_factor) > 0.0001f) {
|
if (value > 0.0f && value <= 2.0f && fabsf(value - dst.equitherm.exponent) > 0.0001f) {
|
||||||
dst.equitherm.k_factor = roundf(value, 3);
|
dst.equitherm.exponent = roundf(value, 3);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_T_FACTOR)].isNull()) {
|
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_SHIFT)].isNull()) {
|
||||||
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_T_FACTOR)].as<float>();
|
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_SHIFT)].as<float>();
|
||||||
|
|
||||||
if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.t_factor) > 0.0001f) {
|
if (value >= -15.0f && value <= 15.0f && fabsf(value - dst.equitherm.shift) > 0.0001f) {
|
||||||
dst.equitherm.t_factor = roundf(value, 3);
|
dst.equitherm.shift = roundf(value, 2);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_TARGET_DIFF_FACTOR)].isNull()) {
|
||||||
|
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_TARGET_DIFF_FACTOR)].as<float>();
|
||||||
|
|
||||||
|
if (value >= 0.0f && value <= 10.0f && fabsf(value - dst.equitherm.targetDiffFactor) > 0.0001f) {
|
||||||
|
dst.equitherm.targetDiffFactor = roundf(value, 3);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -371,11 +371,26 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"equitherm": {
|
"equitherm": {
|
||||||
"n": "N factor",
|
"slope": {
|
||||||
"k": "K factor",
|
"title": "Slope",
|
||||||
"t": {
|
"note": "Heat loss compensation. Main tuning parameter."
|
||||||
|
},
|
||||||
|
"exponent": {
|
||||||
|
"title": "Exponent",
|
||||||
|
"note": "Radiator efficiency. Typical values: <code>1.1</code> - Floor heating, <code>1.2</code> - Cast iron, <code>1.3</code> - Panel radiators, <code>1.4</code> - Convectors."
|
||||||
|
},
|
||||||
|
"shift": {
|
||||||
|
"title": "Shift",
|
||||||
|
"note": "Compensates for additional heat losses (e.g., in pipes) or extra heat sources."
|
||||||
|
},
|
||||||
|
"targetDiffFactor": {
|
||||||
"title": "T factor",
|
"title": "T factor",
|
||||||
"note": "Not used if PID is enabled"
|
"note": "Not used if PID is enabled. Adds to the setpoint the difference between the target and current indoor temp: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
|
||||||
|
},
|
||||||
|
"chart": {
|
||||||
|
"targetTemp": "Target indoor temperature",
|
||||||
|
"setpointTemp": "Heat carrier temperature",
|
||||||
|
"outdoorTemp": "Outdoor temperature"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -371,11 +371,26 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"equitherm": {
|
"equitherm": {
|
||||||
"n": "Fattore N",
|
"slope": {
|
||||||
"k": "Fattore K",
|
"title": "Pendenza",
|
||||||
"t": {
|
"note": "Compensazione delle perdite di calore. Principale parametro di regolazione."
|
||||||
|
},
|
||||||
|
"exponent": {
|
||||||
|
"title": "Esponente",
|
||||||
|
"note": "Efficienza del radiatore. Valori tipici: <code>1.1</code> - Riscaldamento a pavimento, <code>1.2</code> - Radiatori in ghisa, <code>1.3</code> - Radiatori a pannelli, <code>1.4</code> - Convetttori."
|
||||||
|
},
|
||||||
|
"shift": {
|
||||||
|
"title": "Spostare",
|
||||||
|
"note": "Compensa le perdite di calore aggiuntive (ad esempio, nelle tubature) o fonti di calore extra."
|
||||||
|
},
|
||||||
|
"targetDiffFactor": {
|
||||||
"title": "Fattore T",
|
"title": "Fattore T",
|
||||||
"note": "Non usato se PID è attivato"
|
"note": "Non usato se PID è attivato. Aggiunge al setpoint la differenza tra la temperatura interna target e quella attuale: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
|
||||||
|
},
|
||||||
|
"chart": {
|
||||||
|
"targetTemp": "Temperatura interna target",
|
||||||
|
"setpointTemp": "Temperatura del portatore di calore",
|
||||||
|
"outdoorTemp": "Temperatura esterna"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -371,11 +371,26 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"equitherm": {
|
"equitherm": {
|
||||||
"n": "Коэффициент N",
|
"slope": {
|
||||||
"k": "Коэффициент K",
|
"title": "Наклон",
|
||||||
"t": {
|
"note": "Компенсация теплопотерь. Основной параметр настройки."
|
||||||
|
},
|
||||||
|
"exponent": {
|
||||||
|
"title": "Экспонента",
|
||||||
|
"note": "Эффективность радиатора. Типичные значения: <code>1.1</code> - Тёплый пол, <code>1.2</code> - Чугунные радиаторы, <code>1.3</code> - Панельные радиаторы, <code>1.4</code> - Конвекторы."
|
||||||
|
},
|
||||||
|
"shift": {
|
||||||
|
"title": "Смещение",
|
||||||
|
"note": "Компенсирует дополнительные теплопотери (например, в трубах) или дополнительные источники тепла."
|
||||||
|
},
|
||||||
|
"targetDiffFactor": {
|
||||||
"title": "Коэффициент T",
|
"title": "Коэффициент T",
|
||||||
"note": "Не используется, если ПИД включен"
|
"note": "Не используется, если ПИД включен. Добавляет разницу между целевой и текущей температурой в помещении: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
|
||||||
|
},
|
||||||
|
"chart": {
|
||||||
|
"targetTemp": "Целевая внутренняя температура",
|
||||||
|
"setpointTemp": "Температура теплоносителя",
|
||||||
|
"outdoorTemp": "Наружная температура"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -241,7 +241,9 @@
|
|||||||
setCheckboxValue("[name='filtering']", data.filtering, sensorForm);
|
setCheckboxValue("[name='filtering']", data.filtering, sensorForm);
|
||||||
setInputValue("[name='filteringFactor']", data.filteringFactor, {}, sensorForm);
|
setInputValue("[name='filteringFactor']", data.filteringFactor, {}, sensorForm);
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
sensorForm.querySelector("[name='type']").dispatchEvent(new Event("change"));
|
sensorForm.querySelector("[name='type']").dispatchEvent(new Event("change"));
|
||||||
|
}, 10);
|
||||||
|
|
||||||
setBusy(".form-busy", "form", false, sensorNode);
|
setBusy(".form-busy", "form", false, sensorNode);
|
||||||
};
|
};
|
||||||
|
|||||||
+255
-10
@@ -354,21 +354,44 @@
|
|||||||
</label>
|
</label>
|
||||||
</fieldset>
|
</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">
|
<div class="grid">
|
||||||
<label>
|
<label>
|
||||||
<span data-i18n>settings.equitherm.n</span>
|
<span data-i18n>settings.equitherm.slope.title</span>
|
||||||
<input type="number" inputmode="decimal" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
|
<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>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<span data-i18n>settings.equitherm.k</span>
|
<span data-i18n>settings.equitherm.exponent.title</span>
|
||||||
<input type="number" inputmode="decimal" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
|
<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>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<span data-i18n>settings.equitherm.t.title</span>
|
<span data-i18n>settings.equitherm.targetDiffFactor.title</span>
|
||||||
<input type="number" inputmode="decimal" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
|
<input type="number" inputmode="decimal" name="equitherm[targetDiffFactor]" min="0" max="10" step="0.01" required>
|
||||||
<small data-i18n>settings.equitherm.t.note</small>
|
<small data-i18n>settings.equitherm.targetDiffFactor.note</small>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -866,11 +889,163 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="/static/app.js?{BUILD_TIME}"></script>
|
<script src="/static/app.js?{BUILD_TIME}"></script>
|
||||||
|
<script src="/static/chart.js?{BUILD_TIME}"></script>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
const lang = new Lang(document.getElementById('lang'));
|
const lang = new Lang(document.getElementById('lang'));
|
||||||
lang.build();
|
lang.build();
|
||||||
|
|
||||||
|
let etChart = null;
|
||||||
|
let etChartConfig = {
|
||||||
|
slope: null,
|
||||||
|
exponent: null,
|
||||||
|
shift: null,
|
||||||
|
unitSystem: null,
|
||||||
|
targetTemp: null,
|
||||||
|
minTemp: null,
|
||||||
|
maxTemp: null,
|
||||||
|
decimated: false
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
let data = [];
|
||||||
|
etChartConfig.decimated = hasNeedDecimationChart();
|
||||||
|
for (let value = 30; value >= -30; value -= etChartConfig.decimated ? 2 : 1) {
|
||||||
|
const outdoorTemp = etChartConfig.unitSystem == 0 ? value : c2f(value);
|
||||||
|
|
||||||
|
data.push({
|
||||||
|
x: parseFloat(outdoorTemp.toFixed(1)),
|
||||||
|
y: parseFloat(calculateEquithermTemp(outdoorTemp).toFixed(1))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
etChart.data.datasets[0].data = data;
|
||||||
|
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) => {
|
const fillData = (data) => {
|
||||||
// System
|
// System
|
||||||
setSelectValue("[name='system[logLevel]']", data.system.logLevel);
|
setSelectValue("[name='system[logLevel]']", data.system.logLevel);
|
||||||
@@ -1019,9 +1194,10 @@
|
|||||||
|
|
||||||
// Equitherm
|
// Equitherm
|
||||||
setCheckboxValue("[name='equitherm[enabled]']", data.equitherm.enabled);
|
setCheckboxValue("[name='equitherm[enabled]']", data.equitherm.enabled);
|
||||||
setInputValue("[name='equitherm[n_factor]']", data.equitherm.n_factor);
|
setInputValue("[name='equitherm[slope]']", data.equitherm.slope);
|
||||||
setInputValue("[name='equitherm[k_factor]']", data.equitherm.k_factor);
|
setInputValue("[name='equitherm[exponent]']", data.equitherm.exponent);
|
||||||
setInputValue("[name='equitherm[t_factor]']", data.equitherm.t_factor);
|
setInputValue("[name='equitherm[shift]']", data.equitherm.shift);
|
||||||
|
setInputValue("[name='equitherm[targetDiffFactor]']", data.equitherm.targetDiffFactor);
|
||||||
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
|
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
|
||||||
|
|
||||||
// PID
|
// PID
|
||||||
@@ -1045,6 +1221,24 @@
|
|||||||
setInputValue("[name='pid[deadband][thresholdHigh]']", data.pid.deadband.thresholdHigh);
|
setInputValue("[name='pid[deadband][thresholdHigh]']", data.pid.deadband.thresholdHigh);
|
||||||
setInputValue("[name='pid[deadband][thresholdLow]']", data.pid.deadband.thresholdLow);
|
setInputValue("[name='pid[deadband][thresholdLow]']", data.pid.deadband.thresholdLow);
|
||||||
setBusy('#pid-settings-busy', '#pid-settings', false);
|
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 {
|
try {
|
||||||
@@ -1097,6 +1291,57 @@
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(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 (etChartConfig.decimated != hasNeedDecimationChart()) {
|
||||||
|
makeEquithermChart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
/*!
|
||||||
|
* Chart.js v4.5.0
|
||||||
|
* https://www.chartjs.org
|
||||||
|
* (c) 2025 Chart.js Contributors
|
||||||
|
* Released under the MIT License
|
||||||
|
*/
|
||||||
|
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Jo},get Decimation(){return ta},get Filler(){return ba},get Legend(){return Ma},get SubTitle(){return Pa},get Title(){return ka},get Tooltip(){return Na}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;a<r;a++)e.call(i,t[a],a);else if(o(t))for(l=Object.keys(t),r=l.length,a=0;a<r;a++)e.call(i,t[l[a]],l[a])}function f(t,e){let i,s,n,o;if(!t||!e||t.length!==e.length)return!1;for(i=0,s=t.length;i<s;++i)if(n=t[i],o=e[i],n.datasetIndex!==o.datasetIndex||n.index!==o.index)return!1;return!0}function g(t){if(n(t))return t.map(g);if(o(t)){const e=Object.create(null),i=Object.keys(t),s=i.length;let n=0;for(;n<s;++n)e[i[n]]=g(t[i[n]]);return e}return t}function p(t){return-1===["__proto__","prototype","constructor"].indexOf(t)}function m(t,e,i,s){if(!p(t))return;const n=e[t],a=i[t];o(n)&&o(a)?x(n,a,s):e[t]=g(a)}function x(t,e,i){const s=n(e)?e:[e],a=s.length;if(!o(t))return t;const r=(i=i||{}).merger||m;let l;for(let e=0;e<a;++e){if(l=s[e],!o(l))continue;const n=Object.keys(l);for(let e=0,s=n.length;e<s;++e)r(n[e],t,l,i)}return t}function b(t,e){return x(t,e,{merger:_})}function _(t,e,i){if(!p(t))return;const s=e[t],n=i[t];o(s)&&o(n)?b(s,n):Object.prototype.hasOwnProperty.call(e,t)||(e[t]=g(n))}const y={"":t=>t,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)<i}function B(t){const e=Math.round(t);t=V(t,e,t/1e3)?e:t;const i=Math.pow(10,Math.floor(z(t))),s=t/i;return(s<=1?1:s<=2?2:s<=5?5:10)*i}function W(t){const e=[],i=Math.sqrt(t);let s;for(s=1;s<i;s++)t%s==0&&(e.push(s),e.push(t/s));return i===(0|i)&&e.push(i),e.sort(((t,e)=>t-e)).pop(),e}function N(t){return!function(t){return"symbol"==typeof t||"object"==typeof t&&null!==t&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;s<n;s++)o=t[s][i],isNaN(o)||(e.min=Math.min(e.min,o),e.max=Math.max(e.max,o))}function $(t){return t*(C/180)}function Y(t){return t*(180/C)}function U(t){if(!a(t))return;let e=1,i=0;for(;Math.round(t*e)/e!==t;)e*=10,i++;return i}function X(t,e){const i=e.x-t.x,s=e.y-t.y,n=Math.sqrt(i*i+s*s);let o=Math.atan2(s,i);return o<-.5*C&&(o+=O),{angle:o,distance:n}}function q(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function K(t,e){return(t-e+A)%O-C}function G(t){return(t%O+O)%O}function J(t,e,i,s){const n=G(t),o=G(e),a=G(i),r=G(o-n),l=G(a-n),h=G(n-o),c=G(n-a);return n===o||n===a||s&&o===a||r>l&&h<c}function Z(t,e,i){return Math.max(e,Math.min(i,t))}function Q(t){return Z(t,-32768,32767)}function tt(t,e,i,s=1e-6){return t>=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]<e);let s,n=t.length-1,o=0;for(;n-o>1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return n<i||n===i&&t[s+1][e]===i}:s=>t[s][e]<i),st=(t,e,i)=>et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;s<n&&t[s]<e;)s++;for(;n>s&&t[n-1]>i;)n--;return s>0||n<t.length?t.slice(s,n):t}const ot=["push","pop","shift","splice","unshift"];function at(t,e){t._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),ot.forEach((e=>{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...eLine truncated
|
||||||
|
/*!
|
||||||
|
* @kurkle/color v0.3.2
|
||||||
|
* https://github.com/kurkle/color#readme
|
||||||
|
* (c) 2023 Jukka Kurkela
|
||||||
|
* Released under the MIT License
|
||||||
|
*/function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e<i?6:0):e===n?(i-t)/s+2:(t-e)/s+4}(e,i,s,h,n),r=60*r+.5),[0|r,l||0,a]}function zt(t,e,i,s){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,i,s)).map(Mt)}function Ft(t,e,i){return zt(Lt,t,e,i)}function Vt(t){return(t%360+360)%360}function Bt(t){const e=Tt.exec(t);let i,s=255;if(!e)return;e[5]!==i&&(s=e[6]?vt(+e[5]):Mt(+e[5]));const n=Vt(+e[2]),o=+e[3]/100,a=+e[4]/100;return i="hwb"===e[1]?function(t,e,i){return zt(Rt,t,e,i)}(n,o,a):"hsv"===e[1]?function(t,e,i){return zt(Et,t,e,i)}(n,o,a):Ft(n,o,a),{r:i[0],g:i[1],b:i[2],a:s}}const Wt={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Nt={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};let Ht;function jt(t){Ht||(Ht=function(){const t={},e=Object.keys(Nt),i=Object.keys(Wt);let s,n,o,a,r;for(s=0;s<e.length;s++){for(a=r=e[s],n=0;n<i.length;n++)o=i[n],r=r.replace(o,Wt[o]);o=parseInt(Nt[a],16),t[r]=[o>>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),Line truncated
|
||||||
|
//# sourceMappingURL=chart.umd.min.js.map
|
||||||
@@ -5,8 +5,13 @@ const setupForm = (formSelector, onResultCallback = null, noCastItems = []) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
form.querySelectorAll('input').forEach(item => {
|
form.querySelectorAll('input').forEach(item => {
|
||||||
item.addEventListener('change', (e) => {
|
item.addEventListener('change', (event) => {
|
||||||
e.target.setAttribute('aria-invalid', !e.target.checkValidity());
|
if (!event.target.checkValidity()) {
|
||||||
|
event.target.setAttribute('aria-invalid', true);
|
||||||
|
|
||||||
|
} else if (event.target.hasAttribute('aria-invalid')) {
|
||||||
|
event.target.removeAttribute('aria-invalid');
|
||||||
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -630,6 +635,10 @@ const setCheckboxValue = (selector, value, parent = undefined) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
item.checked = value;
|
item.checked = value;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
item.dispatchEvent(new Event("change"));
|
||||||
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
const setRadioValue = (selector, value, parent = undefined) => {
|
const setRadioValue = (selector, value, parent = undefined) => {
|
||||||
@@ -643,7 +652,14 @@ const setRadioValue = (selector, value, parent = undefined) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
item.checked = item.value == value;
|
const checked = item.value == value;
|
||||||
|
if (item.checked != checked) {
|
||||||
|
item.checked = checked;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
item.dispatchEvent(new Event("change"));
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,13 +674,17 @@ const setInputValue = (selector, value, attrs = {}, parent = undefined) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let item of items) {
|
for (let item of items) {
|
||||||
item.value = value;
|
|
||||||
|
|
||||||
if (attrs instanceof Object) {
|
if (attrs instanceof Object) {
|
||||||
for (let attrKey of Object.keys(attrs)) {
|
for (let attrKey of Object.keys(attrs)) {
|
||||||
item.setAttribute(attrKey, attrs[attrKey]);
|
item.setAttribute(attrKey, attrs[attrKey]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
item.value = value;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
item.dispatchEvent(new Event("change"));
|
||||||
|
}, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,3 +874,11 @@ function dec2hex(i) {
|
|||||||
function constrain(amt, low, high) {
|
function constrain(amt, low, high) {
|
||||||
return ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)));
|
return ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function c2f(value) {
|
||||||
|
return (9 / 5) * value + 32;
|
||||||
|
}
|
||||||
|
|
||||||
|
function f2c(value) {
|
||||||
|
return (value - 32) * (5 / 9);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user