1 Commits

Author SHA1 Message Date
Yurii
f4af237472 chore: bump version to 1.4.6 2024-11-16 14:19:40 +03:00
34 changed files with 4021 additions and 5683 deletions

View File

@@ -6,7 +6,6 @@ const cssnano = require('cssnano');
const terser = require('gulp-terser');
const jsonminify = require('gulp-jsonminify');
const htmlmin = require('gulp-html-minifier-terser');
const replace = require('gulp-replace');
// Paths for tasks
let paths = {
@@ -120,10 +119,6 @@ const staticFiles = (cb) => {
const pages = () => {
return src(paths.pages.src)
.pipe(replace(
"{BUILD_TIME}",
Math.floor(Date.now() / 1000)
))
.pipe(htmlmin({
html5: true,
caseSensitive: true,

View File

@@ -10,8 +10,7 @@ public:
free(this->buffer);
}
template <class T>
void send(int code, T contentType, const JsonVariantConst content, bool pretty = false) {
void send(int code, const char* contentType, JsonDocument& content, bool pretty = false) {
#ifdef ARDUINO_ARCH_ESP8266
if (!this->webServer->chunkedResponseModeStart(code, contentType)) {
this->webServer->send(505, F("text/html"), F("HTTP1.1 required"));
@@ -77,19 +76,10 @@ public:
return;
}
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
#endif
auto& client = this->webServer->client();
if (client.connected()) {
this->webServer->sendContent((const char*)this->buffer, this->bufferPos);
}
this->webServer->sendContent((const char*)this->buffer, this->bufferPos);
this->bufferPos = 0;
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
::delay(0);
#endif
}

View File

@@ -98,6 +98,66 @@ public:
));
}
bool setHeatingCh1Temp(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TSet,
temperatureToData(temperature)
));
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TSet);
}
bool setHeatingCh2Temp(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TsetCH2,
temperatureToData(temperature)
));
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TsetCH2);
}
bool setDhwTemp(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TdhwSet,
temperatureToData(temperature)
));
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TdhwSet);
}
bool setRoomSetpoint(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TrSet,
temperatureToData(temperature)
));
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TrSet);
}
bool setRoomSetpointCh2(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TrSetCH2,
temperatureToData(temperature)
));
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::TrSetCH2);
}
bool setRoomTemp(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::Tr,
temperatureToData(temperature)
));
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::Tr);
}
bool sendBoilerReset() {
unsigned int data = 1;
data <<= 8;

View File

@@ -100,8 +100,8 @@ public:
return result;
}
template <class CT, class NT>
String makeConfigTopic(CT category, NT name, char nameSeparator = '/') {
template <class T>
String getTopic(T category, T name, char nameSeparator = '/') {
String topic = "";
topic.concat(this->prefix);
topic.concat('/');
@@ -110,45 +110,21 @@ public:
topic.concat(this->devicePrefix);
topic.concat(nameSeparator);
topic.concat(name);
topic.concat(F("/config"));
topic.concat("/config");
return topic;
}
template <class T>
String getDeviceTopic(T value, char dpvSeparator = '/') {
String getDeviceTopic(T value, char separator = '/') {
String topic = "";
topic.concat(this->devicePrefix);
topic.concat(dpvSeparator);
topic.concat(separator);
topic.concat(value);
return topic;
}
template <class CT, class NT>
String getDeviceTopic(CT category, NT name, char dpcSeparator = '/', char cnSeparator = '/') {
String topic = "";
topic.concat(this->devicePrefix);
topic.concat(dpcSeparator);
topic.concat(category);
topic.concat(cnSeparator);
topic.concat(name);
return topic;
}
template <class CT, class NT, class ST>
String getDeviceTopic(CT category, NT name, ST suffix, char dpcSeparator = '/', char cnSeparator = '/', char nsSeparator = '/') {
String topic = "";
topic.concat(this->devicePrefix);
topic.concat(dpcSeparator);
topic.concat(category);
topic.concat(cnSeparator);
topic.concat(name);
topic.concat(nsSeparator);
topic.concat(suffix);
return topic;
}
template <class T>
String getObjectIdWithPrefix(T value, char separator = '_') {
String getObjectId(T value, char separator = '_') {
String topic = "";
topic.concat(this->devicePrefix);
topic.concat(separator);

View File

@@ -25,8 +25,6 @@ const char HA_ENABLED_BY_DEFAULT[] PROGMEM = "enabled_by_default";
const char HA_UNIQUE_ID[] PROGMEM = "unique_id";
const char HA_OBJECT_ID[] PROGMEM = "object_id";
const char HA_ENTITY_CATEGORY[] PROGMEM = "entity_category";
const char HA_ENTITY_CATEGORY_DIAGNOSTIC[] PROGMEM = "diagnostic";
const char HA_ENTITY_CATEGORY_CONFIG[] PROGMEM = "config";
const char HA_STATE_TOPIC[] PROGMEM = "state_topic";
const char HA_VALUE_TEMPLATE[] PROGMEM = "value_template";
const char HA_OPTIONS[] PROGMEM = "options";
@@ -37,21 +35,16 @@ const char HA_DEVICE_CLASS[] PROGMEM = "device_class";
const char HA_UNIT_OF_MEASUREMENT[] PROGMEM = "unit_of_measurement";
const char HA_UNIT_OF_MEASUREMENT_C[] PROGMEM = "°C";
const char HA_UNIT_OF_MEASUREMENT_F[] PROGMEM = "°F";
const char HA_UNIT_OF_MEASUREMENT_PERCENT[] PROGMEM = "%";
const char HA_UNIT_OF_MEASUREMENT_L_MIN[] PROGMEM = "L/min";
const char HA_UNIT_OF_MEASUREMENT_GAL_MIN[] PROGMEM = "gal/min";
const char HA_ICON[] PROGMEM = "icon";
const char HA_MIN[] PROGMEM = "min";
const char HA_MAX[] PROGMEM = "max";
const char HA_STEP[] PROGMEM = "step";
const char HA_MODE[] PROGMEM = "mode";
const char HA_MODE_BOX[] PROGMEM = "box";
const char HA_STATE_ON[] PROGMEM = "state_on";
const char HA_STATE_OFF[] PROGMEM = "state_off";
const char HA_PAYLOAD_ON[] PROGMEM = "payload_on";
const char HA_PAYLOAD_OFF[] PROGMEM = "payload_off";
const char HA_STATE_CLASS[] PROGMEM = "state_class";
const char HA_STATE_CLASS_MEASUREMENT[] PROGMEM = "measurement";
const char HA_EXPIRE_AFTER[] PROGMEM = "expire_after";
const char HA_CURRENT_TEMPERATURE_TOPIC[] PROGMEM = "current_temperature_topic";
const char HA_CURRENT_TEMPERATURE_TEMPLATE[] PROGMEM = "current_temperature_template";

View File

@@ -77,7 +77,7 @@ public:
#endif
}
bool publish(const char* topic, const JsonVariantConst doc, bool retained = false) {
bool publish(const char* topic, JsonDocument& doc, bool retained = false) {
if (!this->client->connected()) {
this->bufferPos = 0;
return false;

View File

@@ -61,7 +61,7 @@ public:
}
if (this->cacheHeader != nullptr) {
server.sendHeader(F("Cache-Control"), this->cacheHeader);
server.sendHeader("Cache-Control", this->cacheHeader);
}
#ifdef ARDUINO_ARCH_ESP8266

View File

@@ -8,8 +8,7 @@ public:
typedef std::function<bool(HTTPMethod, const String&)> CanHandleCallback;
typedef std::function<bool()> BeforeSendCallback;
template <class T>
StaticPage(const char* uri, FS* fs, T path, const char* cacheHeader = nullptr) {
StaticPage(const char* uri, FS* fs, const char* path, const char* cacheHeader = nullptr) {
this->uri = uri;
this->fs = fs;
this->path = path;
@@ -56,7 +55,7 @@ public:
this->eTag = esp8266webserver::calcETag(*this->fs, this->path);
}
if (server.header(F("If-None-Match")).equals(this->eTag.c_str())) {
if (server.header("If-None-Match").equals(this->eTag.c_str())) {
server.send(304);
return true;
}
@@ -81,12 +80,12 @@ public:
}
if (this->cacheHeader != nullptr) {
server.sendHeader(F("Cache-Control"), this->cacheHeader);
server.sendHeader("Cache-Control", this->cacheHeader);
}
#if defined(ARDUINO_ARCH_ESP8266)
if (server._eTagEnabled && this->eTag.length() > 0) {
server.sendHeader(F("ETag"), this->eTag);
server.sendHeader("ETag", this->eTag);
}
server.streamFile(file, F("text/html"), method);

View File

@@ -93,12 +93,10 @@ public:
void upload(WebServer& server, const String& uri, HTTPUpload& upload) override {
UpgradeResult* result;
if (upload.name.equals(F("firmware"))) {
if (upload.name.equals("firmware")) {
result = &this->firmwareResult;
} else if (upload.name.equals(F("filesystem"))) {
} else if (upload.name.equals("filesystem")) {
result = &this->filesystemResult;
} else {
return;
}

View File

@@ -12,7 +12,6 @@
"gulp-html-minifier-terser": "^7.1.0",
"gulp-jsonminify": "^1.1.0",
"gulp-postcss": "^10.0.0",
"gulp-terser": "^2.1.0",
"gulp-replace": "^1.1.4"
"gulp-terser": "^2.1.0"
}
}

View File

@@ -14,7 +14,7 @@ extra_configs = secrets.default.ini
core_dir = .pio
[env]
version = 1.5.0-alpha
version = 1.4.6
framework = arduino
lib_deps =
bblanchon/ArduinoJson@^7.1.0
@@ -30,14 +30,17 @@ lib_deps =
laxilef/TinyLogger@^1.1.1
build_type = ${secrets.build_type}
build_flags =
-D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
-D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
-mtext-section-literals
-D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1
;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial
-D BUILD_VERSION='"${this.version}"'
-D BUILD_ENV='"$PIOENV"'
-D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled}
-D DEFAULT_SERIAL_ENABLE=${secrets.serial_enable}
-D DEFAULT_SERIAL_BAUD=${secrets.serial_baud}
-D DEFAULT_TELNET_ENABLED=${secrets.telnet_enabled}
-D DEFAULT_TELNET_ENABLE=${secrets.telnet_enable}
-D DEFAULT_TELNET_PORT=${secrets.telnet_port}
-D DEFAULT_LOG_LEVEL=${secrets.log_level}
-D DEFAULT_HOSTNAME='"${secrets.hostname}"'
@@ -47,7 +50,6 @@ build_flags =
-D DEFAULT_STA_PASSWORD='"${secrets.sta_password}"'
-D DEFAULT_PORTAL_LOGIN='"${secrets.portal_login}"'
-D DEFAULT_PORTAL_PASSWORD='"${secrets.portal_password}"'
-D DEFAULT_MQTT_ENABLED=${secrets.mqtt_enabled}
-D DEFAULT_MQTT_SERVER='"${secrets.mqtt_server}"'
-D DEFAULT_MQTT_PORT=${secrets.mqtt_port}
-D DEFAULT_MQTT_USER='"${secrets.mqtt_user}"'
@@ -55,10 +57,7 @@ build_flags =
-D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"'
upload_speed = 921600
monitor_speed = 115200
;monitor_filters = direct
monitor_filters =
esp32_exception_decoder
esp8266_exception_decoder
monitor_filters = direct
board_build.flash_mode = dio
board_build.filesystem = littlefs
@@ -72,11 +71,7 @@ lib_ignore =
extra_scripts =
post:tools/build.py
build_type = ${env.build_type}
build_flags =
${env.build_flags}
-D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
-D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
build_flags = ${env.build_flags}
board_build.ldscript = eagle.flash.4m1m.ld
[esp32_defaults]

View File

@@ -1,9 +1,9 @@
[secrets]
build_type = release
serial_enabled = true
serial_enable = true
serial_baud = 115200
telnet_enabled = true
telnet_enable = true
telnet_port = 23
log_level = 5
hostname = opentherm
@@ -17,7 +17,6 @@ sta_password =
portal_login = admin
portal_password = admin
mqtt_enabled = false
mqtt_server =
mqtt_port = 1883
mqtt_user =

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@ using namespace NetworkUtils;
extern NetworkMgr* network;
extern MqttTask* tMqtt;
extern OpenThermTask* tOt;
extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
extern FileData fsSettings, fsNetworkSettings;
extern ESPTelnetStream* telnetStream;
@@ -60,28 +60,31 @@ protected:
void loop() {
network->loop();
if (fsNetworkSettings.tick() == FD_WRITE) {
Log.sinfoln(FPSTR(L_NETWORK_SETTINGS), F("Updated"));
}
if (fsSettings.tick() == FD_WRITE) {
Log.sinfoln(FPSTR(L_SETTINGS), F("Updated"));
}
if (fsSensorsSettings.tick() == FD_WRITE) {
Log.sinfoln(FPSTR(L_SENSORS_SETTINGS), F("Updated"));
if (fsNetworkSettings.tick() == FD_WRITE) {
Log.sinfoln(FPSTR(L_NETWORK_SETTINGS), F("Updated"));
}
if (vars.actions.restart) {
vars.actions.restart = false;
this->restartSignalTime = millis();
// save settings
fsSettings.updateNow();
// force save network settings
if (fsNetworkSettings.updateNow() == FD_FILE_ERR && LittleFS.begin()) {
fsNetworkSettings.write();
}
Log.sinfoln(FPSTR(L_MAIN), F("Restart signal received. Restart after 10 sec."));
}
vars.mqtt.connected = tMqtt->isConnected();
vars.network.connected = network->isConnected();
vars.network.rssi = network->isConnected() ? WiFi.RSSI() : 0;
vars.states.mqtt = tMqtt->isConnected();
vars.sensors.rssi = network->isConnected() ? WiFi.RSSI() : 0;
if (settings.system.logLevel >= TinyLogger::Level::SILENT && settings.system.logLevel <= TinyLogger::Level::VERBOSE) {
if (Log.getLevel() != settings.system.logLevel) {
@@ -95,14 +98,20 @@ protected:
this->telnetStarted = true;
}
if (settings.mqtt.enabled && !tMqtt->isEnabled()) {
if (settings.mqtt.enable && !tMqtt->isEnabled()) {
tMqtt->enable();
} else if (!settings.mqtt.enabled && tMqtt->isEnabled()) {
} else if (!settings.mqtt.enable && tMqtt->isEnabled()) {
tMqtt->disable();
}
Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, !settings.mqtt.enabled || vars.mqtt.connected, false);
if (settings.sensors.indoor.type == SensorType::MANUAL) {
vars.sensors.indoor.connected = !settings.mqtt.enable || vars.states.mqtt;
}
if (settings.sensors.outdoor.type == SensorType::MANUAL) {
vars.sensors.outdoor.connected = !settings.mqtt.enable || vars.states.mqtt;
}
} else {
if (this->telnetStarted) {
@@ -114,7 +123,13 @@ protected:
tMqtt->disable();
}
Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, false, false);
if (settings.sensors.indoor.type == SensorType::MANUAL) {
vars.sensors.indoor.connected = false;
}
if (settings.sensors.outdoor.type == SensorType::MANUAL) {
vars.sensors.outdoor.connected = false;
}
}
this->yield();
@@ -136,9 +151,8 @@ protected:
for (Stream* stream : Log.getStreams()) {
while (stream->available() > 0) {
stream->read();
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
::delay(0);
#endif
}
}
@@ -149,19 +163,7 @@ protected:
// restart
if (this->restartSignalTime > 0 && millis() - this->restartSignalTime > 10000) {
// save settings
fsSettings.updateNow();
// save sensors settings
fsSensorsSettings.updateNow();
// force save network settings
if (fsNetworkSettings.updateNow() == FD_FILE_ERR && LittleFS.begin()) {
fsNetworkSettings.write();
}
this->restartSignalTime = 0;
this->delay(500);
ESP.restart();
}
}
@@ -207,21 +209,18 @@ protected:
uint8_t emergencyFlags = 0b00000000;
// set outdoor sensor flag
if (settings.equitherm.enabled) {
if (!Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) {
emergencyFlags |= 0b00000001;
}
if (settings.equitherm.enable && !vars.sensors.outdoor.connected) {
emergencyFlags |= 0b00000001;
}
// set indoor sensor flags
if (!Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP)) {
if (!settings.equitherm.enabled && settings.pid.enabled) {
emergencyFlags |= 0b00000010;
}
if (settings.opentherm.nativeHeatingControl) {
emergencyFlags |= 0b00000100;
}
// set indoor sensor flag
if (!settings.equitherm.enable && settings.pid.enable && !vars.sensors.indoor.connected) {
emergencyFlags |= 0b00000010;
}
// set indoor sensor flag for OT native heating control
if (settings.opentherm.nativeHeatingControl && !vars.sensors.indoor.connected) {
emergencyFlags |= 0b00000100;
}
// if any flags is true
@@ -231,10 +230,10 @@ protected:
this->emergencyDetected = true;
this->emergencyFlipTime = millis();
} else if (this->emergencyDetected && !vars.emergency.state) {
} else if (this->emergencyDetected && !vars.states.emergency) {
// enable emergency
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
vars.emergency.state = true;
vars.states.emergency = true;
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled (%hhu)"), emergencyFlags);
}
}
@@ -245,10 +244,10 @@ protected:
this->emergencyDetected = false;
this->emergencyFlipTime = millis();
} else if (!this->emergencyDetected && vars.emergency.state) {
} else if (!this->emergencyDetected && vars.states.emergency) {
// disable emergency
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
vars.emergency.state = false;
vars.states.emergency = false;
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled"));
}
}
@@ -287,15 +286,15 @@ protected:
errors[errCount++] = 2;
}
if (!vars.slave.connected) {
if (!vars.states.otStatus) {
errors[errCount++] = 3;
}
if (vars.slave.fault.active) {
if (vars.states.fault) {
errors[errCount++] = 4;
}
if (vars.emergency.state) {
if (vars.states.emergency) {
errors[errCount++] = 5;
}
@@ -343,7 +342,7 @@ protected:
static unsigned long outputChangedTs = 0;
// input
if (settings.cascadeControl.input.enabled) {
if (settings.cascadeControl.input.enable) {
if (settings.cascadeControl.input.gpio != configuredInputGpio) {
if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) {
pinMode(configuredInputGpio, OUTPUT);
@@ -394,7 +393,7 @@ protected:
}
}
if (!settings.cascadeControl.input.enabled || configuredInputGpio == GPIO_IS_NOT_CONFIGURED) {
if (!settings.cascadeControl.input.enable || configuredInputGpio == GPIO_IS_NOT_CONFIGURED) {
if (!vars.cascadeControl.input) {
vars.cascadeControl.input = true;
@@ -408,7 +407,7 @@ protected:
// output
if (settings.cascadeControl.output.enabled) {
if (settings.cascadeControl.output.enable) {
if (settings.cascadeControl.output.gpio != configuredOutputGpio) {
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
pinMode(configuredOutputGpio, OUTPUT);
@@ -438,13 +437,13 @@ protected:
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
bool value = false;
if (settings.cascadeControl.output.onFault && vars.slave.fault.active) {
if (settings.cascadeControl.output.onFault && vars.states.fault) {
value = true;
} else if (settings.cascadeControl.output.onLossConnection && !vars.slave.connected) {
} else if (settings.cascadeControl.output.onLossConnection && !vars.states.otStatus) {
value = true;
} else if (settings.cascadeControl.output.onEnabledHeating && settings.heating.enabled && vars.cascadeControl.input) {
} else if (settings.cascadeControl.output.onEnabledHeating && settings.heating.enable && vars.cascadeControl.input) {
value = true;
}
@@ -476,7 +475,7 @@ protected:
}
}
if (!settings.cascadeControl.output.enabled || configuredOutputGpio == GPIO_IS_NOT_CONFIGURED) {
if (!settings.cascadeControl.output.enable || configuredOutputGpio == GPIO_IS_NOT_CONFIGURED) {
if (vars.cascadeControl.output) {
vars.cascadeControl.output = false;
@@ -517,75 +516,75 @@ protected:
}
if (configuredGpio == GPIO_IS_NOT_CONFIGURED) {
if (vars.externalPump.state) {
vars.externalPump.state = false;
vars.externalPump.lastEnabledTime = millis();
if (vars.states.externalPump) {
vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis();
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off"));
Log.sinfoln("EXTPUMP", F("Disabled: use = off"));
}
return;
}
if (!vars.master.heating.enabled && this->heatingEnabled) {
if (!vars.states.heating && this->heatingEnabled) {
this->heatingEnabled = false;
this->heatingDisabledTime = millis();
} else if (vars.master.heating.enabled && !this->heatingEnabled) {
} else if (vars.states.heating && !this->heatingEnabled) {
this->heatingEnabled = true;
}
if (!settings.externalPump.use) {
if (vars.externalPump.state) {
if (vars.states.externalPump) {
digitalWrite(configuredGpio, LOW);
vars.externalPump.state = false;
vars.externalPump.lastEnabledTime = millis();
vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis();
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off"));
Log.sinfoln("EXTPUMP", F("Disabled: use = off"));
}
return;
}
if (vars.externalPump.state && !this->heatingEnabled) {
if (vars.states.externalPump && !this->heatingEnabled) {
if (this->extPumpStartReason == MainTask::PumpStartReason::HEATING && millis() - this->heatingDisabledTime > (settings.externalPump.postCirculationTime * 1000u)) {
digitalWrite(configuredGpio, LOW);
vars.externalPump.state = false;
vars.externalPump.lastEnabledTime = millis();
vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis();
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired post circulation time"));
Log.sinfoln("EXTPUMP", F("Disabled: expired post circulation time"));
} else if (this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK && millis() - this->externalPumpStartTime >= (settings.externalPump.antiStuckTime * 1000u)) {
digitalWrite(configuredGpio, LOW);
vars.externalPump.state = false;
vars.externalPump.lastEnabledTime = millis();
vars.states.externalPump = false;
vars.parameters.extPumpLastEnableTime = millis();
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired anti stuck time"));
Log.sinfoln("EXTPUMP", F("Disabled: expired anti stuck time"));
}
} else if (vars.externalPump.state && this->heatingEnabled && this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK) {
} else if (vars.states.externalPump && this->heatingEnabled && this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK) {
this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
} else if (!vars.externalPump.state && this->heatingEnabled) {
vars.externalPump.state = true;
} else if (!vars.states.externalPump && this->heatingEnabled) {
vars.states.externalPump = true;
this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
digitalWrite(configuredGpio, HIGH);
Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: heating on"));
Log.sinfoln("EXTPUMP", F("Enabled: heating on"));
} else if (!vars.externalPump.state && (vars.externalPump.lastEnabledTime == 0 || millis() - vars.externalPump.lastEnabledTime >= (settings.externalPump.antiStuckInterval * 1000lu))) {
vars.externalPump.state = true;
} else if (!vars.states.externalPump && (vars.parameters.extPumpLastEnableTime == 0 || millis() - vars.parameters.extPumpLastEnableTime >= (settings.externalPump.antiStuckInterval * 1000ul))) {
vars.states.externalPump = true;
this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK;
digitalWrite(configuredGpio, HIGH);
Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: anti stuck"));
Log.sinfoln("EXTPUMP", F("Enabled: anti stuck"));
}
}
};

View File

@@ -1,4 +1,3 @@
#include <unordered_map>
#include <MqttClient.h>
#include <MqttWiFiClient.h>
#include <MqttWriter.h>
@@ -62,18 +61,10 @@ public:
this->prevPubSettingsTime = 0;
}
inline void resetPublishedSensorTime(uint8_t sensorId) {
this->prevPubSensorTime[sensorId] = 0;
}
inline void resetPublishedVarsTime() {
this->prevPubVarsTime = 0;
}
inline void rebuildHaEntity(uint8_t sensorId, Sensors::Settings& prevSettings) {
this->queueRebuildingHaEntities[sensorId] = prevSettings;
}
protected:
MqttWiFiClient* wifiClient = nullptr;
MqttClient* client = nullptr;
@@ -81,14 +72,12 @@ protected:
MqttWriter* writer = nullptr;
UnitSystem currentUnitSystem = UnitSystem::METRIC;
bool currentHomeAssistantDiscovery = false;
std::unordered_map<uint8_t, Sensors::Settings> queueRebuildingHaEntities;
unsigned short readyForSendTime = 30000;
unsigned long lastReconnectTime = 0;
unsigned long connectedTime = 0;
unsigned long disconnectedTime = 0;
unsigned long prevPubVarsTime = 0;
unsigned long prevPubSettingsTime = 0;
std::unordered_map<uint8_t, unsigned long> prevPubSensorTime;
bool connected = false;
bool newConnection = false;
@@ -129,7 +118,7 @@ protected:
#endif
this->client->onMessage([this] (void*, size_t length) {
const String& topic = this->client->messageTopic();
String topic = this->client->messageTopic();
if (!length || length > 2048 || !topic.length()) {
return;
}
@@ -139,7 +128,7 @@ protected:
payload[i] = this->client->read();
}
this->onMessage(topic, payload, length);
this->onMessage(topic.c_str(), payload, length);
});
// writer settings
@@ -153,7 +142,7 @@ protected:
Log.straceln(FPSTR(L_MQTT), F("%s publish %u of %u bytes to topic: %s"), result ? F("Successfully") : F("Failed"), written, length, topic);
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
::delay(0);
#endif
//this->client->poll();
@@ -162,13 +151,13 @@ protected:
#ifdef ARDUINO_ARCH_ESP8266
this->writer->setFlushEventCallback([this] (size_t, size_t) {
::optimistic_yield(1000);
::delay(0);
if (this->wifiClient->connected()) {
this->wifiClient->flush();
}
::optimistic_yield(1000);
::delay(0);
});
#endif
@@ -184,6 +173,11 @@ protected:
}
void loop() {
if (settings.mqtt.interval > 120) {
settings.mqtt.interval = 5;
fsSettings.update();
}
if (this->connected && !this->client->connected()) {
this->connected = false;
this->onDisconnect();
@@ -192,15 +186,9 @@ protected:
Log.sinfoln(FPSTR(L_MQTT), F("Connecting to %s:%u..."), settings.mqtt.server, settings.mqtt.port);
this->haHelper->setDevicePrefix(settings.mqtt.prefix);
this->haHelper->updateCachedTopics();
this->client->stop();
this->client->setId(networkSettings.hostname);
this->client->setUsernamePassword(settings.mqtt.user, settings.mqtt.password);
this->client->beginWill(this->haHelper->getDeviceTopic(F("status")).c_str(), 7, true, 1);
this->client->print(F("offline"));
this->client->endWill();
this->client->connect(settings.mqtt.server, settings.mqtt.port);
this->lastReconnectTime = millis();
this->yield();
@@ -222,44 +210,22 @@ protected:
}
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
::delay(0);
#endif
// publish variables and status
if (this->newConnection || millis() - this->prevPubVarsTime > (settings.mqtt.interval * 1000u)) {
this->writer->publish(this->haHelper->getDeviceTopic(F("status")).c_str(), "online", false);
this->publishVariables(this->haHelper->getDeviceTopic(F("state")).c_str());
this->writer->publish(this->haHelper->getDeviceTopic("status").c_str(), "online", false);
this->publishVariables(this->haHelper->getDeviceTopic("state").c_str());
this->prevPubVarsTime = millis();
}
// publish settings
if (this->newConnection || millis() - this->prevPubSettingsTime > (settings.mqtt.interval * 10000u)) {
this->publishSettings(this->haHelper->getDeviceTopic(F("settings")).c_str());
this->publishSettings(this->haHelper->getDeviceTopic("settings").c_str());
this->prevPubSettingsTime = millis();
}
// publish sensors
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
if (!Sensors::hasEnabledAndValid(sensorId)) {
continue;
}
auto& rSensor = Sensors::results[sensorId];
bool needUpdate = false;
if (millis() - this->prevPubSensorTime[sensorId] > ((this->haHelper->getExpireAfter() - 10) * 1000u)) {
needUpdate = true;
} else if (rSensor.activityTime >= this->prevPubSensorTime[sensorId]) {
auto estimated = rSensor.activityTime - this->prevPubSensorTime[sensorId];
needUpdate = estimated > 1000u;
}
if (this->newConnection || needUpdate) {
this->publishSensor(sensorId);
this->prevPubSensorTime[sensorId] = millis();
}
}
// publish ha entities if not published
if (settings.mqtt.homeAssistantDiscovery) {
if (this->newConnection || !this->currentHomeAssistantDiscovery || this->currentUnitSystem != settings.system.unitSystem) {
@@ -273,79 +239,6 @@ protected:
this->publishNonStaticHaEntities();
}
for (auto& [sensorId, prevSettings] : this->queueRebuildingHaEntities) {
Log.sinfoln(FPSTR(L_MQTT_HA), F("Rebuilding config for sensor #%hhu '%s'"), sensorId, prevSettings.name);
// delete old config
if (strlen(prevSettings.name) && prevSettings.enabled) {
switch (prevSettings.type) {
case Sensors::Type::BLUETOOTH:
this->haHelper->deleteConnectionDynamicSensor(prevSettings);
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::HUMIDITY);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::BATTERY);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::RSSI);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->deleteConnectionDynamicSensor(prevSettings);
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE);
break;
case Sensors::Type::MANUAL:
this->client->unsubscribe(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(prevSettings.name).c_str(),
F("set")
).c_str()
);
default:
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::PRIMARY);
}
}
if (!Sensors::hasEnabledAndValid(sensorId)) {
continue;
}
// make new config
auto& sSettings = Sensors::settings[sensorId];
switch (sSettings.type) {
case Sensors::Type::BLUETOOTH:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::HUMIDITY, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::BATTERY, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::RSSI, settings.system.unitSystem, false);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
break;
case Sensors::Type::MANUAL:
this->client->subscribe(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(prevSettings.name).c_str(),
F("set")
).c_str()
);
default:
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem);
}
}
this->queueRebuildingHaEntities.clear();
} else if (this->currentHomeAssistantDiscovery) {
this->currentHomeAssistantDiscovery = false;
}
@@ -361,24 +254,24 @@ protected:
unsigned long downtime = (millis() - this->disconnectedTime) / 1000;
Log.sinfoln(FPSTR(L_MQTT), F("Connected (downtime: %u s.)"), downtime);
this->client->subscribe(this->haHelper->getDeviceTopic(F("settings/set")).c_str());
this->client->subscribe(this->haHelper->getDeviceTopic(F("state/set")).c_str());
this->client->subscribe(this->haHelper->getDeviceTopic("settings/set").c_str());
this->client->subscribe(this->haHelper->getDeviceTopic("state/set").c_str());
}
void onDisconnect() {
this->disconnectedTime = millis();
unsigned long uptime = (millis() - this->connectedTime) / 1000;
Log.swarningln(FPSTR(L_MQTT), F("Disconnected (reason: %d uptime: %lu s.)"), this->client->connectError(), uptime);
Log.swarningln(FPSTR(L_MQTT), F("Disconnected (reason: %d uptime: %u s.)"), this->client->connectError(), uptime);
}
void onMessage(const String& topic, uint8_t* payload, size_t length) {
void onMessage(const char* topic, uint8_t* payload, size_t length) {
if (!length) {
return;
}
if (settings.system.logLevel >= TinyLogger::Level::TRACE) {
Log.strace(FPSTR(L_MQTT_MSG), F("Topic: %s\r\n> "), topic.c_str());
Log.strace(FPSTR(L_MQTT_MSG), F("Topic: %s\r\n> "), topic);
if (Log.lock()) {
for (size_t i = 0; i < length; i++) {
if (payload[i] == 0) {
@@ -386,12 +279,12 @@ protected:
} else if (payload[i] == 13) {
continue;
} else if (payload[i] == 10) {
Log.print(F("\r\n> "));
Log.print("\r\n> ");
} else {
Log.print((char) payload[i]);
}
}
Log.print(F("\r\n\n"));
Log.print("\r\n\n");
Log.flush();
Log.unlock();
}
@@ -409,48 +302,34 @@ protected:
}
doc.shrinkToFit();
// delete topic
this->writer->publish(topic.c_str(), nullptr, 0, true);
if (this->haHelper->getDeviceTopic(F("state/set")).equals(topic)) {
if (this->haHelper->getDeviceTopic("state/set").equals(topic)) {
this->writer->publish(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true);
if (jsonToVars(doc, vars)) {
this->resetPublishedVarsTime();
}
} else if (this->haHelper->getDeviceTopic(F("settings/set")).equals(topic)) {
} else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) {
this->writer->publish(this->haHelper->getDeviceTopic("settings/set").c_str(), nullptr, 0, true);
if (safeJsonToSettings(doc, settings)) {
this->resetPublishedSettingsTime();
fsSettings.update();
}
} else {
const String& sensorsTopic = this->haHelper->getDeviceTopic(F("sensors/"));
auto stLength = sensorsTopic.length();
if (topic.startsWith(sensorsTopic) && topic.endsWith(F("/set"))) {
if (topic.length() > stLength + 4) {
const String& name = topic.substring(stLength, topic.indexOf('/', stLength));
int16_t id = Sensors::getIdByObjectId(name.c_str());
if (id == -1) {
return;
}
if (jsonToSensorResult(id, doc)) {
this->resetPublishedSensorTime(id);
}
}
}
}
}
void publishHaEntities() {
// heating
this->haHelper->publishSwitchHeating(false);
this->haHelper->publishSwitchHeatingTurbo(false);
this->haHelper->publishInputHeatingHysteresis(settings.system.unitSystem);
this->haHelper->publishInputHeatingTurboFactor(false);
this->haHelper->publishInputHeatingMinTemp(settings.system.unitSystem);
this->haHelper->publishInputHeatingMaxTemp(settings.system.unitSystem);
this->haHelper->publishSensorHeatingSetpoint(settings.system.unitSystem, false);
this->haHelper->publishSensorBoilerHeatingMinTemp(settings.system.unitSystem, false);
this->haHelper->publishSensorBoilerHeatingMaxTemp(settings.system.unitSystem, false);
// pid
this->haHelper->publishSwitchPid();
@@ -468,98 +347,103 @@ protected:
this->haHelper->publishInputEquithermFactorT(false);
// states
this->haHelper->publishStatusState();
this->haHelper->publishEmergencyState();
this->haHelper->publishOpenthermConnectedState();
this->haHelper->publishHeatingState();
this->haHelper->publishFlameState();
this->haHelper->publishFaultState();
this->haHelper->publishDiagState();
this->haHelper->publishExternalPumpState(false);
this->haHelper->publishStateStatus();
this->haHelper->publishStateEmergency();
this->haHelper->publishStateOtStatus();
this->haHelper->publishStateHeating();
this->haHelper->publishStateFlame();
this->haHelper->publishStateFault();
this->haHelper->publishStateDiagnostic();
this->haHelper->publishStateExtPump(false);
// sensors
this->haHelper->publishFaultCode();
this->haHelper->publishDiagCode();
this->haHelper->publishNetworkRssi(false);
this->haHelper->publishUptime(false);
this->haHelper->publishSensorModulation();
this->haHelper->publishSensorPressure(settings.system.unitSystem, false);
this->haHelper->publishSensorPower();
this->haHelper->publishSensorFaultCode();
this->haHelper->publishSensorDiagnosticCode();
this->haHelper->publishSensorRssi(false);
this->haHelper->publishSensorUptime(false);
this->haHelper->publishOutdoorSensorConnected();
this->haHelper->publishOutdoorSensorRssi(false);
this->haHelper->publishOutdoorSensorBattery(false);
this->haHelper->publishOutdoorSensorHumidity(false);
this->haHelper->publishIndoorSensorConnected();
this->haHelper->publishIndoorSensorRssi(false);
this->haHelper->publishIndoorSensorBattery(false);
this->haHelper->publishIndoorSensorHumidity(false);
// temperatures
this->haHelper->publishSensorHeatingTemp(settings.system.unitSystem);
this->haHelper->publishSensorHeatingReturnTemp(settings.system.unitSystem, false);
this->haHelper->publishSensorExhaustTemp(settings.system.unitSystem, false);
// buttons
this->haHelper->publishRestartButton(false);
this->haHelper->publishResetFaultButton();
this->haHelper->publishResetDiagButton();
// dynamic sensors
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
if (!Sensors::hasEnabledAndValid(sensorId)) {
continue;
}
auto& sSettings = Sensors::settings[sensorId];
switch (sSettings.type) {
case Sensors::Type::BLUETOOTH:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::HUMIDITY, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::BATTERY, settings.system.unitSystem);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::RSSI, settings.system.unitSystem, false);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::TEMPERATURE, settings.system.unitSystem);
break;
case Sensors::Type::MANUAL:
this->client->subscribe(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(sSettings.name).c_str(),
F("set")
).c_str()
);
default:
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::PRIMARY, settings.system.unitSystem);
}
}
this->haHelper->publishButtonRestart(false);
this->haHelper->publishButtonResetFault();
this->haHelper->publishButtonResetDiagnostic();
}
bool publishNonStaticHaEntities(bool force = false) {
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
static bool _indoorTempControl, _dhwPresent = false;
static bool _noRegulators, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false;
bool published = false;
bool noRegulators = !settings.opentherm.nativeHeatingControl && !settings.pid.enable && !settings.equitherm.enable;
byte heatingMinTemp = 0;
byte heatingMaxTemp = 0;
bool editableOutdoorTemp = settings.sensors.outdoor.type == SensorType::MANUAL;
bool editableIndoorTemp = settings.sensors.indoor.type == SensorType::MANUAL;
if (noRegulators) {
heatingMinTemp = settings.heating.minTemp;
heatingMaxTemp = settings.heating.maxTemp;
} else {
heatingMinTemp = convertTemp(THERMOSTAT_INDOOR_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
heatingMaxTemp = convertTemp(THERMOSTAT_INDOOR_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
}
if (force || _dhwPresent != settings.opentherm.dhwPresent) {
_dhwPresent = settings.opentherm.dhwPresent;
if (_dhwPresent) {
this->haHelper->publishSwitchDhw(false);
this->haHelper->publishSensorBoilerDhwMinTemp(settings.system.unitSystem, false);
this->haHelper->publishSensorBoilerDhwMaxTemp(settings.system.unitSystem, false);
this->haHelper->publishInputDhwMinTemp(settings.system.unitSystem);
this->haHelper->publishInputDhwMaxTemp(settings.system.unitSystem);
this->haHelper->publishDhwState();
this->haHelper->publishStateDhw();
this->haHelper->publishSensorDhwTemp(settings.system.unitSystem);
this->haHelper->publishSensorDhwFlowRate(settings.system.unitSystem);
} else {
this->haHelper->deleteSwitchDhw();
this->haHelper->deleteSensorBoilerDhwMinTemp();
this->haHelper->deleteSensorBoilerDhwMaxTemp();
this->haHelper->deleteInputDhwMinTemp();
this->haHelper->deleteInputDhwMaxTemp();
this->haHelper->deleteDhwState();
this->haHelper->deleteStateDhw();
this->haHelper->deleteSensorDhwTemp();
this->haHelper->deleteInputDhwTarget();
this->haHelper->deleteClimateDhw();
this->haHelper->deleteSensorDhwFlowRate();
}
published = true;
}
if (force || _indoorTempControl != vars.master.heating.indoorTempControl || _heatingMinTemp != vars.master.heating.minTemp || _heatingMaxTemp != vars.master.heating.maxTemp) {
_heatingMinTemp = vars.master.heating.minTemp;
_heatingMaxTemp = vars.master.heating.maxTemp;
_indoorTempControl = vars.master.heating.indoorTempControl;
if (force || _noRegulators != noRegulators || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) {
_heatingMinTemp = heatingMinTemp;
_heatingMaxTemp = heatingMaxTemp;
_noRegulators = noRegulators;
this->haHelper->publishInputHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false);
this->haHelper->publishClimateHeating(
settings.system.unitSystem,
vars.master.heating.minTemp,
vars.master.heating.maxTemp
heatingMinTemp,
heatingMaxTemp,
noRegulators ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
);
published = true;
@@ -569,11 +453,40 @@ protected:
_dhwMinTemp = settings.dhw.minTemp;
_dhwMaxTemp = settings.dhw.maxTemp;
this->haHelper->publishInputDhwTarget(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp, false);
this->haHelper->publishClimateDhw(settings.system.unitSystem, settings.dhw.minTemp, settings.dhw.maxTemp);
published = true;
}
if (force || _editableOutdoorTemp != editableOutdoorTemp) {
_editableOutdoorTemp = editableOutdoorTemp;
if (editableOutdoorTemp) {
this->haHelper->deleteSensorOutdoorTemp();
this->haHelper->publishInputOutdoorTemp(settings.system.unitSystem);
} else {
this->haHelper->deleteInputOutdoorTemp();
this->haHelper->publishSensorOutdoorTemp(settings.system.unitSystem);
}
published = true;
}
if (force || _editableIndoorTemp != editableIndoorTemp) {
_editableIndoorTemp = editableIndoorTemp;
if (editableIndoorTemp) {
this->haHelper->deleteSensorIndoorTemp();
this->haHelper->publishInputIndoorTemp(settings.system.unitSystem);
} else {
this->haHelper->deleteInputIndoorTemp();
this->haHelper->publishSensorIndoorTemp(settings.system.unitSystem);
}
published = true;
}
return published;
}
@@ -585,30 +498,6 @@ protected:
return this->writer->publish(topic, doc, true);
}
bool publishSensor(uint8_t sensorId) {
auto& sSettings = Sensors::settings[sensorId];
if (!Sensors::isValidSensorId(sensorId)) {
return false;
} else if (!strlen(sSettings.name)) {
return false;
}
JsonDocument doc;
sensorResultToJson(sensorId, doc);
doc.shrinkToFit();
return this->writer->publish(
this->haHelper->getDeviceTopic(
F("sensors"),
Sensors::makeObjectId(sSettings.name).c_str()
).c_str(),
doc,
true
);
}
bool publishVariables(const char* topic) {
JsonDocument doc;
varsToJson(vars, doc);

File diff suppressed because it is too large Load Diff

View File

@@ -17,7 +17,7 @@ using WebServer = ESP8266WebServer;
using namespace NetworkUtils;
extern NetworkMgr* network;
extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
extern FileData fsSettings, fsNetworkSettings;
extern MqttTask* tMqtt;
@@ -88,10 +88,10 @@ protected:
return result;
});
this->webServer->addHandler(indexPage);*/
this->webServer->addHandler(new StaticPage("/", &LittleFS, F("/pages/index.html"), PORTAL_CACHE));
this->webServer->addHandler(new StaticPage("/", &LittleFS, "/pages/index.html", PORTAL_CACHE));
// dashboard page
auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, F("/pages/dashboard.html"), PORTAL_CACHE))
auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, "/pages/dashboard.html", PORTAL_CACHE))
->setBeforeSendCallback([this]() {
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->requestAuthentication(DIGEST_AUTH);
@@ -103,7 +103,7 @@ protected:
this->webServer->addHandler(dashboardPage);
// restart
this->webServer->on(F("/restart.html"), HTTP_GET, [this]() {
this->webServer->on("/restart.html", HTTP_GET, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->send(401);
@@ -112,12 +112,12 @@ protected:
}
vars.actions.restart = true;
this->webServer->sendHeader(F("Location"), "/");
this->webServer->sendHeader("Location", "/");
this->webServer->send(302);
});
// network settings page
auto networkPage = (new StaticPage("/network.html", &LittleFS, F("/pages/network.html"), PORTAL_CACHE))
auto networkPage = (new StaticPage("/network.html", &LittleFS, "/pages/network.html", PORTAL_CACHE))
->setBeforeSendCallback([this]() {
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->requestAuthentication(DIGEST_AUTH);
@@ -129,7 +129,7 @@ protected:
this->webServer->addHandler(networkPage);
// settings page
auto settingsPage = (new StaticPage("/settings.html", &LittleFS, F("/pages/settings.html"), PORTAL_CACHE))
auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/pages/settings.html", PORTAL_CACHE))
->setBeforeSendCallback([this]() {
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->requestAuthentication(DIGEST_AUTH);
@@ -140,20 +140,8 @@ protected:
});
this->webServer->addHandler(settingsPage);
// sensors page
auto sensorsPage = (new StaticPage("/sensors.html", &LittleFS, F("/pages/sensors.html"), PORTAL_CACHE))
->setBeforeSendCallback([this]() {
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->requestAuthentication(DIGEST_AUTH);
return false;
}
return true;
});
this->webServer->addHandler(sensorsPage);
// upgrade page
auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, F("/pages/upgrade.html"), PORTAL_CACHE))
auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/pages/upgrade.html", PORTAL_CACHE))
->setBeforeSendCallback([this]() {
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->requestAuthentication(DIGEST_AUTH);
@@ -167,7 +155,7 @@ protected:
// OTA
auto upgradeHandler = (new UpgradeHandler("/api/upgrade"))->setCanUploadCallback([this](const String& uri) {
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->sendHeader(F("Connection"), F("close"));
this->webServer->sendHeader("Connection", "close");
this->webServer->send(401);
return false;
}
@@ -184,55 +172,53 @@ protected:
status = 400;
}
String response = F("{\"firmware\": {\"status\": ");
String response = "{\"firmware\": {\"status\": ";
response.concat((short int) fwResult.status);
response.concat(F(", \"error\": \""));
response.concat(", \"error\": \"");
response.concat(fwResult.error);
response.concat(F("\"}, \"filesystem\": {\"status\": "));
response.concat("\"}, \"filesystem\": {\"status\": ");
response.concat((short int) fsResult.status);
response.concat(F(", \"error\": \""));
response.concat(", \"error\": \"");
response.concat(fsResult.error);
response.concat(F("\"}}"));
this->webServer->send(status, F("application/json"), response);
response.concat("\"}}");
this->webServer->send(status, "application/json", response);
});
this->webServer->addHandler(upgradeHandler);
// backup
this->webServer->on(F("/api/backup/save"), HTTP_GET, [this]() {
this->webServer->on("/api/backup/save", HTTP_GET, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
JsonDocument doc;
JsonDocument networkSettingsDoc;
networkSettingsToJson(networkSettings, networkSettingsDoc);
networkSettingsDoc.shrinkToFit();
auto networkDoc = doc[FPSTR(S_NETWORK)].to<JsonObject>();
networkSettingsToJson(networkSettings, networkDoc);
auto settingsDoc = doc[FPSTR(S_SETTINGS)].to<JsonObject>();
JsonDocument settingsDoc;
settingsToJson(settings, settingsDoc);
settingsDoc.shrinkToFit();
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
auto sensorsettingsDoc = doc[FPSTR(S_SENSORS)][sensorId].to<JsonObject>();
sensorSettingsToJson(sensorId, Sensors::settings[sensorId], sensorsettingsDoc);
}
JsonDocument doc;
doc["network"] = networkSettingsDoc;
doc["settings"] = settingsDoc;
doc.shrinkToFit();
this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"backup.json\""));
this->bufferedWebServer->send(200, F("application/json"), doc);
this->bufferedWebServer->send(200, "application/json", doc);
});
this->webServer->on(F("/api/backup/restore"), HTTP_POST, [this]() {
this->webServer->on("/api/backup/restore", HTTP_POST, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
const String& plain = this->webServer->arg(0);
String plain = this->webServer->arg(0);
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/backup/restore %d bytes: %s"), plain.length(), plain.c_str());
if (plain.length() < 5) {
@@ -246,6 +232,7 @@ protected:
JsonDocument doc;
DeserializationError dErr = deserializeJson(doc, plain);
plain.clear();
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
this->webServer->send(400);
@@ -253,7 +240,13 @@ protected:
}
bool changed = false;
if (!doc[FPSTR(S_NETWORK)].isNull() && jsonToNetworkSettings(doc[FPSTR(S_NETWORK)], networkSettings)) {
if (doc["settings"] && jsonToSettings(doc["settings"], settings)) {
vars.actions.restart = true;
fsSettings.update();
changed = true;
}
if (doc["network"] && jsonToNetworkSettings(doc["network"], networkSettings)) {
fsNetworkSettings.update();
network->setHostname(networkSettings.hostname)
->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel)
@@ -264,45 +257,19 @@ protected:
networkSettings.staticConfig.gateway,
networkSettings.staticConfig.subnet,
networkSettings.staticConfig.dns
);
)
->reconnect();
changed = true;
}
if (!doc[FPSTR(S_SETTINGS)].isNull() && jsonToSettings(doc[FPSTR(S_SETTINGS)], settings)) {
fsSettings.update();
changed = true;
}
if (!doc[FPSTR(S_SENSORS)].isNull()) {
for (auto sensor : doc[FPSTR(S_SENSORS)].as<JsonObject>()) {
if (!isDigit(sensor.key().c_str())) {
continue;
}
int sensorId = atoi(sensor.key().c_str());
if (sensorId < 0 || sensorId > 255 || !Sensors::isValidSensorId(sensorId)) {
continue;
}
if (jsonToSensorSettings(sensorId, sensor.value(), Sensors::settings[sensorId])) {
fsSensorsSettings.update();
changed = true;
}
}
}
doc.clear();
doc.shrinkToFit();
if (changed) {
vars.actions.restart = true;
}
this->webServer->send(changed ? 201 : 200);
});
// network
this->webServer->on(F("/api/network/settings"), HTTP_GET, [this]() {
this->webServer->on("/api/network/settings", HTTP_GET, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
@@ -313,17 +280,17 @@ protected:
networkSettingsToJson(networkSettings, doc);
doc.shrinkToFit();
this->bufferedWebServer->send(200, F("application/json"), doc);
this->bufferedWebServer->send(200, "application/json", doc);
});
this->webServer->on(F("/api/network/settings"), HTTP_POST, [this]() {
this->webServer->on("/api/network/settings", HTTP_POST, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
const String& plain = this->webServer->arg(0);
String plain = this->webServer->arg(0);
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/network/settings %d bytes: %s"), plain.length(), plain.c_str());
if (plain.length() < 5) {
@@ -337,6 +304,7 @@ protected:
JsonDocument doc;
DeserializationError dErr = deserializeJson(doc, plain);
plain.clear();
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
this->webServer->send(400);
@@ -350,7 +318,7 @@ protected:
networkSettingsToJson(networkSettings, doc);
doc.shrinkToFit();
this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc);
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
if (changed) {
doc.clear();
@@ -371,7 +339,7 @@ protected:
}
});
this->webServer->on(F("/api/network/scan"), HTTP_GET, [this]() {
this->webServer->on("/api/network/scan", HTTP_GET, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
this->webServer->send(401);
@@ -395,29 +363,29 @@ protected:
JsonDocument doc;
for (short int i = 0; i < apCount; i++) {
const String& ssid = WiFi.SSID(i);
doc[i][FPSTR(S_SSID)] = ssid;
doc[i][FPSTR(S_BSSID)] = WiFi.BSSIDstr(i);
doc[i][FPSTR(S_SIGNAL_QUALITY)] = NetworkMgr::rssiToSignalQuality(WiFi.RSSI(i));
doc[i][FPSTR(S_CHANNEL)] = WiFi.channel(i);
doc[i][FPSTR(S_HIDDEN)] = !ssid.length();
String ssid = WiFi.SSID(i);
doc[i]["ssid"] = ssid;
doc[i]["bssid"] = WiFi.BSSIDstr(i);
doc[i]["signalQuality"] = NetworkMgr::rssiToSignalQuality(WiFi.RSSI(i));
doc[i]["channel"] = WiFi.channel(i);
doc[i]["hidden"] = !ssid.length();
#ifdef ARDUINO_ARCH_ESP8266
const bss_info* info = WiFi.getScanInfoByIndex(i);
doc[i][FPSTR(S_AUTH)] = info->authmode;
doc[i]["auth"] = info->authmode;
#else
doc[i][FPSTR(S_AUTH)] = WiFi.encryptionType(i);
doc[i]["auth"] = WiFi.encryptionType(i);
#endif
}
doc.shrinkToFit();
this->bufferedWebServer->send(200, F("application/json"), doc);
this->bufferedWebServer->send(200, "application/json", doc);
WiFi.scanDelete();
});
// settings
this->webServer->on(F("/api/settings"), HTTP_GET, [this]() {
this->webServer->on("/api/settings", HTTP_GET, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
@@ -428,17 +396,17 @@ protected:
settingsToJson(settings, doc);
doc.shrinkToFit();
this->bufferedWebServer->send(200, F("application/json"), doc);
this->bufferedWebServer->send(200, "application/json", doc);
});
this->webServer->on(F("/api/settings"), HTTP_POST, [this]() {
this->webServer->on("/api/settings", HTTP_POST, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
const String& plain = this->webServer->arg(0);
String plain = this->webServer->arg(0);
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/settings %d bytes: %s"), plain.length(), plain.c_str());
if (plain.length() < 5) {
@@ -452,6 +420,7 @@ protected:
JsonDocument doc;
DeserializationError dErr = deserializeJson(doc, plain);
plain.clear();
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
this->webServer->send(400);
@@ -465,7 +434,7 @@ protected:
settingsToJson(settings, doc);
doc.shrinkToFit();
this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc);
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
if (changed) {
doc.clear();
@@ -477,152 +446,23 @@ protected:
});
// sensors list
this->webServer->on(F("/api/sensors"), HTTP_GET, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
bool detailed = false;
if (this->webServer->hasArg(F("detailed"))) {
detailed = this->webServer->arg(F("detailed")).toInt() > 0;
}
JsonDocument doc;
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
if (detailed) {
auto& sSensor = Sensors::settings[sensorId];
doc[sensorId][FPSTR(S_NAME)] = sSensor.name;
doc[sensorId][FPSTR(S_PURPOSE)] = static_cast<uint8_t>(sSensor.purpose);
sensorResultToJson(sensorId, doc[sensorId]);
} else {
doc[sensorId] = Sensors::settings[sensorId].name;
}
}
doc.shrinkToFit();
this->bufferedWebServer->send(200, F("application/json"), doc);
});
// sensor settings
this->webServer->on(F("/api/sensor"), HTTP_GET, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
if (!this->webServer->hasArg(F("id"))) {
return this->webServer->send(400);
}
auto id = this->webServer->arg(F("id"));
if (!isDigit(id.c_str())) {
return this->webServer->send(400);
}
uint8_t sensorId = id.toInt();
id.clear();
if (!Sensors::isValidSensorId(sensorId)) {
return this->webServer->send(404);
}
JsonDocument doc;
sensorSettingsToJson(sensorId, Sensors::settings[sensorId], doc);
doc.shrinkToFit();
this->bufferedWebServer->send(200, F("application/json"), doc);
});
this->webServer->on(F("/api/sensor"), HTTP_POST, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
#ifdef ARDUINO_ARCH_ESP8266
if (!this->webServer->hasArg(F("id")) || this->webServer->args() != 1) {
return this->webServer->send(400);
}
#else
if (!this->webServer->hasArg(F("id")) || this->webServer->args() != 2) {
return this->webServer->send(400);
}
#endif
auto id = this->webServer->arg(F("id"));
if (!isDigit(id.c_str())) {
return this->webServer->send(400);
}
uint8_t sensorId = id.toInt();
id.clear();
if (!Sensors::isValidSensorId(sensorId)) {
return this->webServer->send(404);
}
auto plain = this->webServer->arg(1);
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/sensor/?id=%hhu %d bytes: %s"), sensorId, plain.length(), plain.c_str());
if (plain.length() < 5) {
return this->webServer->send(406);
} else if (plain.length() > 1024) {
return this->webServer->send(413);
}
bool changed = false;
auto prevSettings = Sensors::settings[sensorId];
{
JsonDocument doc;
DeserializationError dErr = deserializeJson(doc, plain);
plain.clear();
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
return this->webServer->send(400);
}
if (jsonToSensorSettings(sensorId, doc, Sensors::settings[sensorId])) {
changed = true;
}
}
{
JsonDocument doc;
auto& sSettings = Sensors::settings[sensorId];
sensorSettingsToJson(sensorId, sSettings, doc);
doc.shrinkToFit();
this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc);
}
if (changed) {
tMqtt->rebuildHaEntity(sensorId, prevSettings);
fsSensorsSettings.update();
}
});
// vars
this->webServer->on(F("/api/vars"), HTTP_GET, [this]() {
this->webServer->on("/api/vars", HTTP_GET, [this]() {
JsonDocument doc;
varsToJson(vars, doc);
doc.shrinkToFit();
this->bufferedWebServer->send(200, F("application/json"), doc);
this->bufferedWebServer->send(200, "application/json", doc);
});
this->webServer->on(F("/api/vars"), HTTP_POST, [this]() {
this->webServer->on("/api/vars", HTTP_POST, [this]() {
if (this->isAuthRequired()) {
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
return this->webServer->send(401);
}
}
const String& plain = this->webServer->arg(0);
String plain = this->webServer->arg(0);
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Request /api/vars %d bytes: %s"), plain.length(), plain.c_str());
if (plain.length() < 5) {
@@ -636,6 +476,7 @@ protected:
JsonDocument doc;
DeserializationError dErr = deserializeJson(doc, plain);
plain.clear();
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
this->webServer->send(400);
@@ -649,7 +490,7 @@ protected:
varsToJson(vars, doc);
doc.shrinkToFit();
this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc);
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
if (changed) {
doc.clear();
@@ -659,138 +500,78 @@ protected:
}
});
this->webServer->on(F("/api/info"), HTTP_GET, [this]() {
this->webServer->on("/api/info", HTTP_GET, [this]() {
bool isConnected = network->isConnected();
JsonDocument doc;
doc["system"]["resetReason"] = getResetReason();
doc["system"]["uptime"] = millis() / 1000ul;
auto docSystem = doc[FPSTR(S_SYSTEM)].to<JsonObject>();
docSystem[FPSTR(S_RESET_REASON)] = getResetReason();
docSystem[FPSTR(S_UPTIME)] = millis() / 1000;
doc["network"]["hostname"] = networkSettings.hostname;
doc["network"]["mac"] = network->getStaMac();
doc["network"]["connected"] = isConnected;
doc["network"]["ssid"] = network->getStaSsid();
doc["network"]["signalQuality"] = isConnected ? NetworkMgr::rssiToSignalQuality(network->getRssi()) : 0;
doc["network"]["channel"] = isConnected ? network->getStaChannel() : 0;
doc["network"]["ip"] = isConnected ? network->getStaIp().toString() : "";
doc["network"]["subnet"] = isConnected ? network->getStaSubnet().toString() : "";
doc["network"]["gateway"] = isConnected ? network->getStaGateway().toString() : "";
doc["network"]["dns"] = isConnected ? network->getStaDns().toString() : "";
auto docNetwork = doc[FPSTR(S_NETWORK)].to<JsonObject>();
docNetwork[FPSTR(S_HOSTNAME)] = networkSettings.hostname;
docNetwork[FPSTR(S_MAC)] = network->getStaMac();
docNetwork[FPSTR(S_CONNECTED)] = isConnected;
docNetwork[FPSTR(S_SSID)] = network->getStaSsid();
docNetwork[FPSTR(S_SIGNAL_QUALITY)] = isConnected ? NetworkMgr::rssiToSignalQuality(network->getRssi()) : 0;
docNetwork[FPSTR(S_CHANNEL)] = isConnected ? network->getStaChannel() : 0;
docNetwork[FPSTR(S_IP)] = isConnected ? network->getStaIp().toString() : "";
docNetwork[FPSTR(S_SUBNET)] = isConnected ? network->getStaSubnet().toString() : "";
docNetwork[FPSTR(S_GATEWAY)] = isConnected ? network->getStaGateway().toString() : "";
docNetwork[FPSTR(S_DNS)] = isConnected ? network->getStaDns().toString() : "";
doc["build"]["version"] = BUILD_VERSION;
doc["build"]["date"] = __DATE__ " " __TIME__;
doc["build"]["env"] = BUILD_ENV;
auto docBuild = doc[FPSTR(S_BUILD)].to<JsonObject>();
docBuild[FPSTR(S_VERSION)] = BUILD_VERSION;
docBuild[FPSTR(S_DATE)] = __DATE__ " " __TIME__;
docBuild[FPSTR(S_ENV)] = BUILD_ENV;
#ifdef ARDUINO_ARCH_ESP8266
docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion();
docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion();
#elif ARDUINO_ARCH_ESP32
docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion();
docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion();
#else
docBuild[FPSTR(S_CORE)] = 0;
docBuild[FPSTR(S_SDK)] = 0;
#endif
auto docHeap = doc[FPSTR(S_HEAP)].to<JsonObject>();
docHeap[FPSTR(S_TOTAL)] = getTotalHeap();
docHeap[FPSTR(S_FREE)] = getFreeHeap();
docHeap[FPSTR(S_MIN_FREE)] = getFreeHeap(true);
docHeap[FPSTR(S_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap();
docHeap[FPSTR(S_MIN_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap(true);
doc["heap"]["total"] = getTotalHeap();
doc["heap"]["free"] = getFreeHeap();
doc["heap"]["minFree"] = getFreeHeap(true);
doc["heap"]["maxFreeBlock"] = getMaxFreeBlockHeap();
doc["heap"]["minMaxFreeBlock"] = getMaxFreeBlockHeap(true);
auto docChip = doc[FPSTR(S_CHIP)].to<JsonObject>();
#ifdef ARDUINO_ARCH_ESP8266
docChip[FPSTR(S_MODEL)] = esp_is_8285() ? F("ESP8285") : F("ESP8266");
docChip[FPSTR(S_REV)] = 0;
docChip[FPSTR(S_CORES)] = 1;
docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz();
doc["build"]["core"] = ESP.getCoreVersion();
doc["build"]["sdk"] = ESP.getSdkVersion();
doc["chip"]["model"] = esp_is_8285() ? "ESP8285" : "ESP8266";
doc["chip"]["rev"] = 0;
doc["chip"]["cores"] = 1;
doc["chip"]["freq"] = ESP.getCpuFreqMHz();
doc["flash"]["size"] = ESP.getFlashChipSize();
doc["flash"]["realSize"] = ESP.getFlashChipRealSize();
#elif ARDUINO_ARCH_ESP32
docChip[FPSTR(S_MODEL)] = ESP.getChipModel();
docChip[FPSTR(S_REV)] = ESP.getChipRevision();
docChip[FPSTR(S_CORES)] = ESP.getChipCores();
docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz();
doc["build"]["core"] = ESP.getCoreVersion();
doc["build"]["sdk"] = ESP.getSdkVersion();
doc["chip"]["model"] = ESP.getChipModel();
doc["chip"]["rev"] = ESP.getChipRevision();
doc["chip"]["cores"] = ESP.getChipCores();
doc["chip"]["freq"] = ESP.getCpuFreqMHz();
doc["flash"]["size"] = ESP.getFlashChipSize();
doc["flash"]["realSize"] = doc["flash"]["size"];
#else
docChip[FPSTR(S_MODEL)] = 0;
docChip[FPSTR(S_REV)] = 0;
docChip[FPSTR(S_CORES)] = 0;
docChip[FPSTR(S_FREQ)] = 0;
#endif
auto docFlash = doc[FPSTR(S_FLASH)].to<JsonObject>();
#ifdef ARDUINO_ARCH_ESP8266
docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize();
docFlash[FPSTR(S_REAL_SIZE)] = ESP.getFlashChipRealSize();
#elif ARDUINO_ARCH_ESP32
docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize();
docFlash[FPSTR(S_REAL_SIZE)] = docFlash[FPSTR(S_SIZE)];
#else
docFlash[FPSTR(S_SIZE)] = 0;
docFlash[FPSTR(S_REAL_SIZE)] = 0;
doc["build"]["core"] = 0;
doc["build"]["sdk"] = 0;
doc["chip"]["model"] = 0;
doc["chip"]["rev"] = 0;
doc["chip"]["cores"] = 0;
doc["chip"]["freq"] = 0;
doc["flash"]["size"] = 0;
doc["flash"]["realSize"] = 0;
#endif
doc.shrinkToFit();
this->bufferedWebServer->send(200, F("application/json"), doc);
this->bufferedWebServer->send(200, "application/json", doc);
});
this->webServer->on(F("/api/debug"), HTTP_GET, [this]() {
this->webServer->on("/api/debug", HTTP_GET, [this]() {
JsonDocument doc;
auto docBuild = doc[FPSTR(S_BUILD)].to<JsonObject>();
docBuild[FPSTR(S_VERSION)] = BUILD_VERSION;
docBuild[FPSTR(S_DATE)] = __DATE__ " " __TIME__;
docBuild[FPSTR(S_ENV)] = BUILD_ENV;
#ifdef ARDUINO_ARCH_ESP8266
docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion();
docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion();
#elif ARDUINO_ARCH_ESP32
docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion();
docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion();
#else
docBuild[FPSTR(S_CORE)] = 0;
docBuild[FPSTR(S_SDK)] = 0;
#endif
auto docHeap = doc[FPSTR(S_HEAP)].to<JsonObject>();
docHeap[FPSTR(S_TOTAL)] = getTotalHeap();
docHeap[FPSTR(S_FREE)] = getFreeHeap();
docHeap[FPSTR(S_MIN_FREE)] = getFreeHeap(true);
docHeap[FPSTR(S_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap();
docHeap[FPSTR(S_MIN_MAX_FREE_BLOCK)] = getMaxFreeBlockHeap(true);
auto docChip = doc[FPSTR(S_CHIP)].to<JsonObject>();
#ifdef ARDUINO_ARCH_ESP8266
docChip[FPSTR(S_MODEL)] = esp_is_8285() ? F("ESP8285") : F("ESP8266");
docChip[FPSTR(S_REV)] = 0;
docChip[FPSTR(S_CORES)] = 1;
docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz();
#elif ARDUINO_ARCH_ESP32
docChip[FPSTR(S_MODEL)] = ESP.getChipModel();
docChip[FPSTR(S_REV)] = ESP.getChipRevision();
docChip[FPSTR(S_CORES)] = ESP.getChipCores();
docChip[FPSTR(S_FREQ)] = ESP.getCpuFreqMHz();
#else
docChip[FPSTR(S_MODEL)] = 0;
docChip[FPSTR(S_REV)] = 0;
docChip[FPSTR(S_CORES)] = 0;
docChip[FPSTR(S_FREQ)] = 0;
#endif
auto docFlash = doc[FPSTR(S_FLASH)].to<JsonObject>();
#ifdef ARDUINO_ARCH_ESP8266
docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize();
docFlash[FPSTR(S_REAL_SIZE)] = ESP.getFlashChipRealSize();
#elif ARDUINO_ARCH_ESP32
docFlash[FPSTR(S_SIZE)] = ESP.getFlashChipSize();
docFlash[FPSTR(S_REAL_SIZE)] = docFlash[FPSTR(S_SIZE)];
#else
docFlash[FPSTR(S_SIZE)] = 0;
docFlash[FPSTR(S_REAL_SIZE)] = 0;
#endif
doc["build"]["version"] = BUILD_VERSION;
doc["build"]["date"] = __DATE__ " " __TIME__;
doc["build"]["env"] = BUILD_ENV;
doc["heap"]["total"] = getTotalHeap();
doc["heap"]["free"] = getFreeHeap();
doc["heap"]["minFree"] = getFreeHeap(true);
doc["heap"]["maxFreeBlock"] = getMaxFreeBlockHeap();
doc["heap"]["minMaxFreeBlock"] = getMaxFreeBlockHeap(true);
#if defined(ARDUINO_ARCH_ESP32)
auto reason = esp_reset_reason();
@@ -801,30 +582,58 @@ protected:
#else
if (false) {
#endif
auto docCrash = doc[FPSTR(S_CRASH)].to<JsonObject>();
docCrash[FPSTR(S_REASON)] = getResetReason();
docCrash[FPSTR(S_CORE)] = CrashRecorder::ext.core;
docCrash[FPSTR(S_HEAP)] = CrashRecorder::ext.heap;
docCrash[FPSTR(S_UPTIME)] = CrashRecorder::ext.uptime;
doc["crash"]["reason"] = getResetReason();
doc["crash"]["core"] = CrashRecorder::ext.core;
doc["crash"]["heap"] = CrashRecorder::ext.heap;
doc["crash"]["uptime"] = CrashRecorder::ext.uptime;
if (CrashRecorder::backtrace.length > 0 && CrashRecorder::backtrace.length <= CrashRecorder::backtraceMaxLength) {
String backtraceStr;
arr2str(backtraceStr, CrashRecorder::backtrace.data, CrashRecorder::backtrace.length);
docCrash[FPSTR(S_BACKTRACE)][FPSTR(S_DATA)] = backtraceStr;
docCrash[FPSTR(S_BACKTRACE)][FPSTR(S_CONTINUES)] = CrashRecorder::backtrace.continues;
doc["crash"]["backtrace"]["data"] = backtraceStr;
doc["crash"]["backtrace"]["continues"] = CrashRecorder::backtrace.continues;
}
if (CrashRecorder::epc.length > 0 && CrashRecorder::epc.length <= CrashRecorder::epcMaxLength) {
String epcStr;
arr2str(epcStr, CrashRecorder::epc.data, CrashRecorder::epc.length);
docCrash[FPSTR(S_EPC)] = epcStr;
doc["crash"]["epc"] = epcStr;
}
}
#ifdef ARDUINO_ARCH_ESP8266
doc["build"]["core"] = ESP.getCoreVersion();
doc["build"]["sdk"] = ESP.getSdkVersion();
doc["chip"]["model"] = esp_is_8285() ? "ESP8285" : "ESP8266";
doc["chip"]["rev"] = 0;
doc["chip"]["cores"] = 1;
doc["chip"]["freq"] = ESP.getCpuFreqMHz();
doc["flash"]["size"] = ESP.getFlashChipSize();
doc["flash"]["realSize"] = ESP.getFlashChipRealSize();
#elif ARDUINO_ARCH_ESP32
doc["build"]["core"] = ESP.getCoreVersion();
doc["build"]["sdk"] = ESP.getSdkVersion();
doc["chip"]["model"] = ESP.getChipModel();
doc["chip"]["rev"] = ESP.getChipRevision();
doc["chip"]["cores"] = ESP.getChipCores();
doc["chip"]["freq"] = ESP.getCpuFreqMHz();
doc["flash"]["size"] = ESP.getFlashChipSize();
doc["flash"]["realSize"] = doc["flash"]["size"];
#else
doc["build"]["core"] = 0;
doc["build"]["sdk"] = 0;
doc["chip"]["model"] = 0;
doc["chip"]["rev"] = 0;
doc["chip"]["cores"] = 0;
doc["chip"]["freq"] = 0;
doc["flash"]["size"] = 0;
doc["flash"]["realSize"] = 0;
#endif
doc.shrinkToFit();
this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"debug.json\""));
this->bufferedWebServer->send(200, F("application/json"), doc, true);
this->bufferedWebServer->send(200, "application/json", doc, true);
});
@@ -832,15 +641,15 @@ protected:
this->webServer->onNotFound([this]() {
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Page not found, uri: %s"), this->webServer->uri().c_str());
const String& uri = this->webServer->uri();
if (uri.equals(F("/"))) {
this->webServer->send(200, F("text/plain"), F("The file system is not flashed!"));
const String uri = this->webServer->uri();
if (uri.equals("/")) {
this->webServer->send(200, "text/plain", F("The file system is not flashed!"));
} else if (network->isApEnabled()) {
this->onCaptivePortal();
} else {
this->webServer->send(404, F("text/plain"), F("Page not found"));
this->webServer->send(404, "text/plain", F("Page not found"));
}
});
@@ -859,7 +668,7 @@ protected:
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Started: AP up or STA connected"));
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
::delay(0);
#endif
} else if (this->stateWebServer() && !network->isApEnabled() && !network->isStaEnabled()) {
@@ -867,7 +676,7 @@ protected:
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Stopped: AP and STA down"));
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
::delay(0);
#endif
}
@@ -877,7 +686,7 @@ protected:
Log.straceln(FPSTR(L_PORTAL_DNSSERVER), F("Started: AP up"));
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
::delay(0);
#endif
} else if (this->stateDnsServer() && (!network->isApEnabled() || !this->stateWebServer())) {
@@ -885,15 +694,14 @@ protected:
Log.straceln(FPSTR(L_PORTAL_DNSSERVER), F("Stopped: AP down"));
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
::delay(0);
#endif
}
if (this->stateDnsServer()) {
this->dnsServer->processNextRequest();
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
::delay(0);
#endif
}
@@ -911,30 +719,28 @@ protected:
}
void onCaptivePortal() {
const String& uri = this->webServer->uri();
const String uri = this->webServer->uri();
if (uri.equals(F("/connecttest.txt"))) {
if (uri.equals("/connecttest.txt")) {
this->webServer->sendHeader(F("Location"), F("http://logout.net"));
this->webServer->send(302);
Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Redirect to http://logout.net with 302 code"));
} else if (uri.equals(F("/wpad.dat"))) {
} else if (uri.equals("/wpad.dat")) {
this->webServer->send(404);
Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Send empty page with 404 code"));
} else if (uri.equals(F("/success.txt"))) {
} else if (uri.equals("/success.txt")) {
this->webServer->send(200);
Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Send empty page with 200 code"));
} else {
String portalUrl = F("http://");
portalUrl += network->getApIp().toString().c_str();
portalUrl += '/';
String portalUrl = "http://" + network->getApIp().toString() + '/';
this->webServer->sendHeader(F("Location"), portalUrl);
this->webServer->sendHeader("Location", portalUrl.c_str());
this->webServer->send(302);
Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Redirect to portal page with 302 code"));

View File

@@ -10,12 +10,9 @@ public:
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
protected:
float prevHeatingTarget = 0.0f;
float prevEtResult = 0.0f;
float prevPidResult = 0.0f;
bool indoorSensorsConnected = false;
//bool outdoorSensorsConnected = false;
float prevHeatingTarget = 0;
float prevEtResult = 0;
float prevPidResult = 0;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
@@ -32,51 +29,20 @@ protected:
#endif
void loop() {
this->indoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP);
//this->outdoorSensorsConnected = Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP);
if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl) {
vars.master.heating.indoorTempControl = true;
vars.master.heating.minTemp = THERMOSTAT_INDOOR_MIN_TEMP;
vars.master.heating.maxTemp = THERMOSTAT_INDOOR_MAX_TEMP;
} else {
vars.master.heating.indoorTempControl = false;
vars.master.heating.minTemp = settings.heating.minTemp;
vars.master.heating.maxTemp = settings.heating.maxTemp;
}
if (!settings.pid.enabled && fabsf(pidRegulator.integral) > 0.01f) {
if (!settings.pid.enable && fabs(pidRegulator.integral) > 0.01f) {
pidRegulator.integral = 0.0f;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
this->turbo();
this->hysteresis();
vars.master.heating.targetTemp = settings.heating.target;
vars.master.heating.setpointTemp = constrain(
this->getHeatingSetpointTemp(),
this->getHeatingMinSetpointTemp(),
this->getHeatingMaxSetpointTemp()
);
Sensors::setValueByType(
Sensors::Type::HEATING_SETPOINT_TEMP, vars.master.heating.setpointTemp,
Sensors::ValueType::PRIMARY, true, true
);
}
void turbo() {
if (settings.heating.turbo) {
if (!settings.heating.enabled || vars.emergency.state || !this->indoorSensorsConnected) {
if (!settings.heating.enable || vars.states.emergency || !vars.sensors.indoor.connected) {
settings.heating.turbo = false;
} else if (!settings.pid.enabled && !settings.equitherm.enabled) {
} else if (!settings.pid.enable && !settings.equitherm.enable) {
settings.heating.turbo = false;
} else if (fabsf(settings.heating.target - vars.master.heating.indoorTemp) <= 1.0f) {
} else if (fabs(settings.heating.target - vars.temperatures.indoor) <= 1.0f) {
settings.heating.turbo = false;
}
@@ -84,69 +50,45 @@ protected:
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
}
}
void hysteresis() {
bool useHyst = false;
if (settings.heating.hysteresis > 0.01f && this->indoorSensorsConnected) {
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.nativeHeatingControl;
}
if (useHyst) {
if (!vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target + 0.0001f >= settings.heating.hysteresis) {
vars.master.heating.blocking = true;
float newTemp = vars.states.emergency
? settings.emergency.target
: this->getNormalModeTemp();
} else if (vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis)) {
vars.master.heating.blocking = false;
}
// Limits
newTemp = constrain(
newTemp,
!settings.opentherm.nativeHeatingControl ? settings.heating.minTemp : THERMOSTAT_INDOOR_MIN_TEMP,
!settings.opentherm.nativeHeatingControl ? settings.heating.maxTemp : THERMOSTAT_INDOOR_MAX_TEMP
);
} else if (vars.master.heating.blocking) {
vars.master.heating.blocking = false;
if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.09f) {
vars.parameters.heatingSetpoint = newTemp;
}
}
inline float getHeatingMinSetpointTemp() {
return settings.opentherm.nativeHeatingControl
? vars.master.heating.minTemp
: settings.heating.minTemp;
}
inline float getHeatingMaxSetpointTemp() {
return settings.opentherm.nativeHeatingControl
? vars.master.heating.maxTemp
: settings.heating.maxTemp;
}
float getHeatingSetpointTemp() {
float getNormalModeTemp() {
float newTemp = 0;
if (fabsf(prevHeatingTarget - settings.heating.target) > 0.0001f) {
if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001f) {
prevHeatingTarget = settings.heating.target;
Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target);
/*if (settings.pid.enabled) {
/*if (settings.pid.enable) {
pidRegulator.integral = 0.0f;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}*/
}
if (vars.emergency.state) {
return settings.emergency.target;
} else if (settings.opentherm.nativeHeatingControl) {
return settings.heating.target;
} else if (!settings.equitherm.enabled && !settings.pid.enabled) {
return settings.heating.target;
}
// if use equitherm
if (settings.equitherm.enabled) {
if (settings.equitherm.enable) {
unsigned short minTemp = settings.heating.minTemp;
unsigned short maxTemp = settings.heating.maxTemp;
float targetTemp = settings.heating.target;
float indoorTemp = vars.master.heating.indoorTemp;
float outdoorTemp = vars.master.heating.outdoorTemp;
float indoorTemp = vars.temperatures.indoor;
float outdoorTemp = vars.temperatures.outdoor;
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
minTemp = f2c(minTemp);
@@ -156,7 +98,7 @@ protected:
outdoorTemp = f2c(outdoorTemp);
}
if (!this->indoorSensorsConnected || settings.pid.enabled) {
if (!vars.sensors.indoor.connected || settings.pid.enable) {
etRegulator.Kt = 0.0f;
etRegulator.indoorTemp = 0.0f;
@@ -176,7 +118,7 @@ protected:
etResult = c2f(etResult);
}
if (fabsf(prevEtResult - etResult) > 0.09f) {
if (fabs(prevEtResult - etResult) > 0.09f) {
prevEtResult = etResult;
newTemp += etResult;
@@ -188,18 +130,18 @@ protected:
}
// if use pid
if (settings.pid.enabled) {
if (settings.pid.enable) {
//if (vars.parameters.heatingEnabled) {
if (settings.heating.enabled && this->indoorSensorsConnected) {
if (settings.heating.enable && vars.sensors.indoor.connected) {
pidRegulator.Kp = settings.heating.turbo ? 0.0f : settings.pid.p_factor;
pidRegulator.Kd = settings.pid.d_factor;
pidRegulator.setLimits(settings.pid.minTemp, settings.pid.maxTemp);
pidRegulator.setDt(settings.pid.dt * 1000u);
pidRegulator.input = vars.master.heating.indoorTemp;
pidRegulator.input = vars.temperatures.indoor;
pidRegulator.setpoint = settings.heating.target;
if (fabsf(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
if (fabs(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
pidRegulator.Ki = settings.pid.i_factor;
pidRegulator.integral = 0.0f;
pidRegulator.getResultNow();
@@ -208,7 +150,7 @@ protected:
}
float pidResult = pidRegulator.getResultTimer();
if (fabsf(prevPidResult - pidResult) > 0.09f) {
if (fabs(prevPidResult - pidResult) > 0.09f) {
prevPidResult = pidResult;
newTemp += pidResult;
@@ -225,14 +167,19 @@ protected:
}
// Turbo mode
if (settings.heating.turbo && (settings.equitherm.enabled || settings.pid.enabled)) {
if (settings.heating.turbo && (settings.equitherm.enable || settings.pid.enable)) {
newTemp += constrain(
settings.heating.target - vars.master.heating.indoorTemp,
settings.heating.target - vars.temperatures.indoor,
-3.0f,
3.0f
) * settings.heating.turboFactor;
}
// default temp, manual mode
if (!settings.equitherm.enable && !settings.pid.enable) {
newTemp = settings.heating.target;
}
return newTemp;
}
};

View File

@@ -1,454 +0,0 @@
#pragma once
class Sensors {
protected:
static uint8_t maxSensors;
public:
enum class Type : uint8_t {
OT_OUTDOOR_TEMP = 0,
OT_HEATING_TEMP = 1,
OT_HEATING_RETURN_TEMP = 2,
OT_DHW_TEMP = 3,
OT_DHW_TEMP2 = 4,
OT_DHW_FLOW_RATE = 5,
OT_CH2_TEMP = 6,
OT_EXHAUST_TEMP = 7,
OT_HEAT_EXCHANGER_TEMP = 8,
OT_PRESSURE = 9,
OT_MODULATION_LEVEL = 10,
OT_CURRENT_POWER = 11,
NTC_10K_TEMP = 50,
DALLAS_TEMP = 51,
BLUETOOTH = 52,
HEATING_SETPOINT_TEMP = 253,
MANUAL = 254,
NOT_CONFIGURED = 255
};
enum class Purpose : uint8_t {
OUTDOOR_TEMP = 0,
INDOOR_TEMP = 1,
HEATING_TEMP = 2,
HEATING_RETURN_TEMP = 3,
DHW_TEMP = 4,
DHW_RETURN_TEMP = 5,
DHW_FLOW_RATE = 6,
EXHAUST_TEMP = 7,
MODULATION_LEVEL = 8,
CURRENT_POWER = 9,
PRESSURE = 252,
HUMIDITY = 253,
TEMPERATURE = 254,
NOT_CONFIGURED = 255
};
enum class ValueType : uint8_t {
PRIMARY = 0,
TEMPERATURE = 0,
HUMIDITY = 1,
BATTERY = 2,
RSSI = 3
};
typedef struct {
bool enabled = false;
char name[33];
Purpose purpose = Purpose::NOT_CONFIGURED;
Type type = Type::NOT_CONFIGURED;
uint8_t gpio = GPIO_IS_NOT_CONFIGURED;
uint8_t address[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
float offset = 0.0f;
float factor = 1.0f;
bool filtering = false;
float filteringFactor = 0.15f;
} Settings;
typedef struct {
bool connected = false;
unsigned long activityTime = 0;
uint8_t signalQuality = 0;
//float raw[4] = {0.0f, 0.0f, 0.0f, 0.0f};
float values[4] = {0.0f, 0.0f, 0.0f, 0.0f};
} Result;
static Settings* settings;
static Result* results;
static inline void setMaxSensors(uint8_t value) {
maxSensors = value;
}
static inline uint8_t getMaxSensors() {
return maxSensors;
}
static uint8_t getMaxSensorId() {
uint8_t maxSensors = getMaxSensors();
return maxSensors > 1 ? (maxSensors - 1) : 0;
}
static inline bool isValidSensorId(const uint8_t id) {
return id >= 0 && id <= getMaxSensorId();
}
static inline bool isValidValueId(const uint8_t id) {
return id >= (uint8_t) ValueType::TEMPERATURE && id <= (uint8_t) ValueType::RSSI;
}
static bool hasEnabledAndValid(const uint8_t id) {
if (!isValidSensorId(id) || !settings[id].enabled) {
return false;
}
if (settings[id].type == Type::NOT_CONFIGURED || settings[id].purpose == Purpose::NOT_CONFIGURED) {
return false;
}
return true;
}
static uint8_t getAmountByType(Type type) {
if (settings == nullptr) {
return 0;
}
uint8_t amount = 0;
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
if (settings[id].type == type) {
amount++;
}
}
return amount;
}
static int16_t getIdByName(const char* name) {
if (settings == nullptr) {
return 0;
}
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
if (strcmp(settings[id].name, name) == 0) {
return id;
}
}
return -1;
}
static int16_t getIdByObjectId(const char* objectId) {
if (settings == nullptr) {
return 0;
}
String refObjectId;
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
Sensors::makeObjectId(refObjectId, settings[id].name);
if (refObjectId.equals(objectId)) {
return id;
}
}
return -1;
}
static bool setValueById(const uint8_t sensorId, float value, const ValueType valueType, const bool updateActivityTime = false, const bool markConnected = false) {
if (settings == nullptr || results == nullptr) {
return false;
}
uint8_t valueId = (uint8_t) valueType;
if (!isValidSensorId(sensorId) || !isValidValueId(valueId)) {
return false;
}
auto& sSensor = settings[sensorId];
auto& rSensor = results[sensorId];
float compensatedValue = value;
if (sSensor.type == Type::HEATING_SETPOINT_TEMP || sSensor.type == Type::MANUAL) {
rSensor.values[valueId] = compensatedValue;
} else {
if (valueType == ValueType::PRIMARY) {
if (fabsf(sSensor.factor) > 0.001f) {
compensatedValue *= sSensor.factor;
}
if (fabsf(sSensor.offset) > 0.001f) {
compensatedValue += sSensor.offset;
}
} else if (valueType == ValueType::RSSI) {
if (sSensor.type == Type::BLUETOOTH) {
rSensor.signalQuality = Sensors::bluetoothRssiToQuality(value);
}
}
if (sSensor.filtering && fabsf(rSensor.values[valueId]) >= 0.1f) {
rSensor.values[valueId] += (compensatedValue - rSensor.values[valueId]) * sSensor.filteringFactor;
} else {
rSensor.values[valueId] = compensatedValue;
}
}
if (updateActivityTime) {
rSensor.activityTime = millis();
}
if (markConnected && !rSensor.connected) {
rSensor.connected = true;
Log.snoticeln(
FPSTR(L_SENSORS), F("#%hhu '%s' new status: CONNECTED"),
sensorId, sSensor.name
);
}
Log.snoticeln(
FPSTR(L_SENSORS), F("#%hhu '%s' new value %hhu: %.2f, compensated: %.2f, raw: %.2f"),
sensorId, sSensor.name, valueId, rSensor.values[valueId], compensatedValue, value
);
return true;
}
static uint8_t setValueByType(Type type, float value, const ValueType valueType, const bool updateActivityTime = false, const bool markConnected = false) {
if (settings == nullptr) {
return 0;
}
uint8_t updated = 0;
// read sensors data for current instance
for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) {
auto& sSensor = settings[sensorId];
// only target & valid sensors
if (!sSensor.enabled || sSensor.type != type) {
continue;
}
if (setValueById(sensorId, value, valueType, updateActivityTime, markConnected)) {
updated++;
}
}
return updated;
}
static bool getConnectionStatusById(const uint8_t sensorId) {
if (settings == nullptr || results == nullptr) {
return false;
}
if (!isValidSensorId(sensorId)) {
return false;
}
return results[sensorId].connected;
}
static bool setConnectionStatusById(const uint8_t sensorId, const bool status, const bool updateActivityTime = true) {
if (settings == nullptr || results == nullptr) {
return false;
}
if (!isValidSensorId(sensorId)) {
return false;
}
auto& sSensor = settings[sensorId];
auto& rSensor = results[sensorId];
if (rSensor.connected != status) {
Log.snoticeln(
FPSTR(L_SENSORS), F("#%hhu '%s' new status: %s"),
sensorId, sSensor.name, status ? F("CONNECTED") : F("DISCONNECTED")
);
rSensor.connected = status;
}
if (updateActivityTime) {
rSensor.activityTime = millis();
}
return true;
}
static uint8_t setConnectionStatusByType(Type type, const bool status, const bool updateActivityTime = true) {
if (settings == nullptr) {
return 0;
}
uint8_t updated = 0;
// read sensors data for current instance
for (uint8_t sensorId = 0; sensorId < getMaxSensorId(); sensorId++) {
auto& sSensor = settings[sensorId];
// only target & valid sensors
if (!sSensor.enabled || sSensor.type != type) {
continue;
}
if (setConnectionStatusById(sensorId, status, updateActivityTime)) {
updated++;
}
}
return updated;
}
static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, bool onlyConnected = true) {
if (settings == nullptr || results == nullptr) {
return 0;
}
uint8_t valueId = (uint8_t) valueType;
if (!isValidValueId(valueId)) {
return false;
}
float value = 0.0f;
uint8_t amount = 0;
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
auto& sSensor = settings[id];
auto& rSensor = results[id];
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
value += rSensor.values[valueId];
amount++;
}
}
if (!amount) {
return 0.0f;
} else if (amount == 1) {
return value;
} else {
return value / amount;
}
}
static bool existsConnectedSensorsByPurpose(Purpose purpose) {
if (settings == nullptr || results == nullptr) {
return 0;
}
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
if (settings[id].purpose == purpose && results[id].connected) {
return true;
}
}
return false;
}
static String& cleanName(String& value, char space = ' ') {
// only valid symbols
for (uint8_t pos = 0; pos < value.length(); pos++) {
char symbol = value.charAt(pos);
// 0..9
if (symbol >= 48 && symbol <= 57) {
continue;
}
// A..Z
if (symbol >= 65 && symbol <= 90) {
continue;
}
// a..z
if (symbol >= 97 && symbol <= 122) {
continue;
}
// _-
if (symbol == 95 || symbol == 45 || symbol == space) {
continue;
}
value.setCharAt(pos, space);
}
value.trim();
return value;
}
template <class T>
static String cleanName(T value, char space = ' ') {
String res = value;
return cleanName(res, space);
}
template <class T>
static String& makeObjectId(String& res, T value, char separator = '_') {
res = value;
cleanName(res);
res.toLowerCase();
res.replace(' ', separator);
return res;
}
template <class T>
static String makeObjectId(T value, char separator = '_') {
String res;
makeObjectId(res, value, separator);
return res;
}
template <class TV, class TS>
static String& makeObjectIdWithSuffix(String& res, TV value, TS suffix, char separator = '_') {
res.clear();
makeObjectId(res, value, separator);
res += separator;
res += suffix;
return res;
}
template <class TV, class TS>
static String makeObjectIdWithSuffix(TV value, TS suffix, char separator = '_') {
String res;
makeObjectIdWithSuffix(res, value, suffix, separator);
return res;
}
template <class TV, class TP>
static String& makeObjectIdWithPrefix(String& res, TV value, TP prefix, char separator = '_') {
res = prefix;
res += separator;
res += makeObjectId(value, separator).c_str();
return res;
}
template <class TV, class TP>
static String makeObjectIdWithPrefix(TV value, TP prefix, char separator = '_') {
String res;
return makeObjectIdWithPrefix(res, value, prefix, separator);
}
static uint8_t bluetoothRssiToQuality(int rssi) {
return constrain(map(rssi, -110, -50, 0, 100), 0, 100);;
}
};
uint8_t Sensors::maxSensors = 0;
Sensors::Settings* Sensors::settings = nullptr;
Sensors::Result* Sensors::results = nullptr;

File diff suppressed because it is too large Load Diff

View File

@@ -27,12 +27,12 @@ struct Settings {
uint8_t logLevel = DEFAULT_LOG_LEVEL;
struct {
bool enabled = DEFAULT_SERIAL_ENABLED;
bool enable = DEFAULT_SERIAL_ENABLE;
unsigned int baudrate = DEFAULT_SERIAL_BAUD;
} serial;
struct {
bool enabled = DEFAULT_TELNET_ENABLED;
bool enable = DEFAULT_TELNET_ENABLE;
unsigned short port = DEFAULT_TELNET_PORT;
} telnet;
@@ -51,12 +51,12 @@ struct Settings {
byte inGpio = DEFAULT_OT_IN_GPIO;
byte outGpio = DEFAULT_OT_OUT_GPIO;
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
uint8_t memberId = 0;
uint8_t flags = 0;
unsigned int memberIdCode = 0;
uint8_t maxModulation = 100;
float pressureFactor = 1.0f;
float dhwFlowRateFactor = 1.0f;
float minPower = 0.0f;
float maxPower = 0.0f;
bool dhwPresent = true;
bool summerWinterMode = false;
bool heatingCh2Enabled = true;
@@ -67,10 +67,15 @@ struct Settings {
bool getMinMaxTemp = true;
bool nativeHeatingControl = false;
bool immergasFix = false;
struct {
bool enable = false;
float factor = 0.1f;
} filterNumValues;
} opentherm;
struct {
bool enabled = DEFAULT_MQTT_ENABLED;
bool enable = false;
char server[81] = DEFAULT_MQTT_SERVER;
unsigned short port = DEFAULT_MQTT_PORT;
char user[33] = DEFAULT_MQTT_USER;
@@ -86,7 +91,7 @@ struct Settings {
} emergency;
struct {
bool enabled = true;
bool enable = true;
bool turbo = false;
float target = DEFAULT_HEATING_TARGET_TEMP;
float hysteresis = 0.5f;
@@ -96,14 +101,14 @@ struct Settings {
} heating;
struct {
bool enabled = true;
bool enable = true;
float target = DEFAULT_DHW_TARGET_TEMP;
byte minTemp = DEFAULT_DHW_MIN_TEMP;
byte maxTemp = DEFAULT_DHW_MAX_TEMP;
} dhw;
struct {
bool enabled = false;
bool enable = false;
float p_factor = 2.0f;
float i_factor = 0.0055f;
float d_factor = 0.0f;
@@ -113,12 +118,28 @@ struct Settings {
} pid;
struct {
bool enabled = false;
bool enable = false;
float n_factor = 0.7f;
float k_factor = 3.0f;
float t_factor = 2.0f;
} equitherm;
struct {
struct {
SensorType type = SensorType::BOILER_OUTDOOR;
byte gpio = DEFAULT_SENSOR_OUTDOOR_GPIO;
uint8_t bleAddress[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
float offset = 0.0f;
} outdoor;
struct {
SensorType type = SensorType::MANUAL;
byte gpio = DEFAULT_SENSOR_INDOOR_GPIO;
uint8_t bleAddress[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
float offset = 0.0f;
} indoor;
} sensors;
struct {
bool use = false;
byte gpio = DEFAULT_EXT_PUMP_GPIO;
@@ -129,14 +150,14 @@ struct Settings {
struct {
struct {
bool enabled = false;
bool enable = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
unsigned short thresholdTime = 60;
} input;
struct {
bool enabled = false;
bool enable = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
unsigned short thresholdTime = 60;
@@ -149,95 +170,51 @@ struct Settings {
char validationValue[8] = SETTINGS_VALID_VALUE;
} settings;
Sensors::Settings sensorsSettings[SENSORS_AMOUNT] = {
{
false,
"Outdoor temp",
Sensors::Purpose::OUTDOOR_TEMP,
Sensors::Type::OT_OUTDOOR_TEMP,
DEFAULT_SENSOR_OUTDOOR_GPIO
},
{
false,
"Indoor temp",
Sensors::Purpose::INDOOR_TEMP,
Sensors::Type::DALLAS_TEMP,
DEFAULT_SENSOR_INDOOR_GPIO
},
{
true,
"Heating temp",
Sensors::Purpose::HEATING_TEMP,
Sensors::Type::OT_HEATING_TEMP,
},
{
true,
"Heating return temp",
Sensors::Purpose::HEATING_RETURN_TEMP,
Sensors::Type::OT_HEATING_RETURN_TEMP,
},
{
true,
"Heating setpoint temp",
Sensors::Purpose::TEMPERATURE,
Sensors::Type::HEATING_SETPOINT_TEMP,
},
{
true,
"DHW temp",
Sensors::Purpose::DHW_TEMP,
Sensors::Type::OT_DHW_TEMP,
},
{
true,
"DHW flow rate",
Sensors::Purpose::DHW_FLOW_RATE,
Sensors::Type::OT_DHW_FLOW_RATE,
},
{
true,
"Exhaust temp",
Sensors::Purpose::EXHAUST_TEMP,
Sensors::Type::OT_EXHAUST_TEMP,
},
{
true,
"Pressure",
Sensors::Purpose::PRESSURE,
Sensors::Type::OT_PRESSURE,
},
{
true,
"Modulation level",
Sensors::Purpose::MODULATION_LEVEL,
Sensors::Type::OT_MODULATION_LEVEL,
},
{
true,
"Power",
Sensors::Purpose::CURRENT_POWER,
Sensors::Type::OT_CURRENT_POWER,
}
};
struct Variables {
struct {
bool connected = false;
bool otStatus = false;
bool emergency = false;
bool heating = false;
bool dhw = false;
bool flame = false;
bool fault = false;
bool diagnostic = false;
bool externalPump = false;
bool mqtt = false;
} states;
struct {
float modulation = 0.0f;
float pressure = 0.0f;
float dhwFlowRate = 0.0f;
float power = 0.0f;
byte faultCode = 0;
unsigned short diagnosticCode = 0;
int8_t rssi = 0;
} network;
struct {
bool connected = false;
int8_t rssi = 0;
float battery = 0.0f;
float humidity = 0.0f;
} outdoor;
struct {
bool connected = false;
int8_t rssi = 0;
float battery = 0.0f;
float humidity = 0.0f;
} indoor;
} sensors;
struct {
bool connected = false;
} mqtt;
struct {
bool state = false;
} emergency;
struct {
bool state = false;
unsigned long lastEnabledTime = 0;
} externalPump;
float indoor = 0.0f;
float outdoor = 0.0f;
float heating = 0.0f;
float heatingReturn = 0.0f;
float dhw = 0.0f;
float exhaust = 0.0f;
} temperatures;
struct {
bool input = false;
@@ -245,105 +222,27 @@ struct Variables {
} cascadeControl;
struct {
uint8_t memberId = 0;
uint8_t flags = 0;
uint8_t type = 0;
uint8_t appVersion = 0;
float protocolVersion = 0.0f;
struct {
bool blocking = false;
bool enabled = false;
bool indoorTempControl = false;
float setpointTemp = 0.0f;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float returnTemp = 0.0f;
float indoorTemp = 0.0f;
float outdoorTemp = 0.0f;
float minTemp = 0.0f;
float maxTemp = 0.0f;
} heating;
struct {
bool enabled = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float returnTemp = 0.0f;
} dhw;
struct {
bool enabled = false;
float targetTemp = 0.0f;
} ch2;
} master;
struct {
uint8_t memberId = 0;
uint8_t flags = 0;
uint8_t type = 0;
uint8_t appVersion = 0;
float protocolVersion = 0.0f;
bool connected = false;
bool flame = false;
float pressure = 0.0f;
float exhaustTemp = 0.0f;
float heatExchangerTemp = 0.0f;
struct {
bool active = false;
uint8_t code = 0;
} fault;
struct {
bool active = false;
uint16_t code = 0;
} diag;
struct {
uint8_t current = 0;
uint8_t min = 0;
uint8_t max = 100;
} modulation;
struct {
float current = 0.0f;
float min = 0.0f;
float max = 0.0f;
} power;
struct {
bool active = false;
bool enabled = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float returnTemp = 0.0f;
float indoorTemp = 0.0f;
float outdoorTemp = 0.0f;
uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP;
uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP;
} heating;
struct {
bool active = false;
bool enabled = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float currentTemp2 = 0.0f;
float returnTemp = 0.0f;
float flowRate = 0.0f;
uint8_t minTemp = DEFAULT_DHW_MIN_TEMP;
uint8_t maxTemp = DEFAULT_DHW_MAX_TEMP;
} dhw;
struct {
bool enabled = false;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
float indoorTemp = 0.0f;
} ch2;
} slave;
bool heatingEnabled = false;
byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP;
byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP;
float heatingSetpoint = 0;
unsigned long extPumpLastEnableTime = 0;
byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP;
byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP;
byte minModulation = 0;
byte maxModulation = 0;
uint8_t maxPower = 0;
uint8_t slaveMemberId = 0;
uint8_t slaveFlags = 0;
uint8_t slaveType = 0;
uint8_t slaveVersion = 0;
float slaveOtVersion = 0.0f;
uint8_t masterMemberId = 0;
uint8_t masterFlags = 0;
uint8_t masterType = 0;
uint8_t masterVersion = 0;
float masterOtVersion = 0;
} parameters;
struct {
bool restart = false;

View File

@@ -2,6 +2,10 @@
#define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
#define MQTT_RECONNECT_INTERVAL 15000
#define EXT_SENSORS_INTERVAL 5000
#define EXT_SENSORS_FILTER_K 0.15
#define CONFIG_URL "http://%s/"
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
#define GPIO_IS_NOT_CONFIGURED 0xff
@@ -18,14 +22,6 @@
#define THERMOSTAT_INDOOR_MIN_TEMP 5
#define THERMOSTAT_INDOOR_MAX_TEMP 30
#define DEFAULT_NTC_NOMINAL_RESISTANCE 10000.0f
#define DEFAULT_NTC_NOMINAL_TEMP 25.0f
#define DEFAULT_NTC_REF_RESISTANCE 10000.0f
#define DEFAULT_NTC_BETA_FACTOR 3950.0f
#define DEFAULT_NTC_VREF 3300.0f
#define DEFAULT_NTC_VLOW_TRESHOLD 25.0f
#define DEFAULT_NTC_VHIGH_TRESHOLD 3298.0f
#ifndef BUILD_VERSION
#define BUILD_VERSION "0.0.0"
#endif
@@ -34,16 +30,16 @@
#define BUILD_ENV "undefined"
#endif
#ifndef DEFAULT_SERIAL_ENABLED
#define DEFAULT_SERIAL_ENABLED true
#ifndef DEFAULT_SERIAL_ENABLE
#define DEFAULT_SERIAL_ENABLE true
#endif
#ifndef DEFAULT_SERIAL_BAUD
#define DEFAULT_SERIAL_BAUD 115200
#endif
#ifndef DEFAULT_TELNET_ENABLED
#define DEFAULT_TELNET_ENABLED true
#ifndef DEFAULT_TELNET_ENABLE
#define DEFAULT_TELNET_ENABLE true
#endif
#ifndef DEFAULT_TELNET_PORT
@@ -90,10 +86,6 @@
#define DEFAULT_PORTAL_PASSWORD ""
#endif
#ifndef DEFAULT_MQTT_ENABLED
#define DEFAULT_MQTT_ENABLED false
#endif
#ifndef DEFAULT_MQTT_SERVER
#define DEFAULT_MQTT_SERVER ""
#endif
@@ -138,10 +130,6 @@
#define DEFAULT_SENSOR_INDOOR_GPIO GPIO_IS_NOT_CONFIGURED
#endif
#ifndef SENSORS_AMOUNT
#define SENSORS_AMOUNT 20
#endif
#ifndef DEFAULT_EXT_PUMP_GPIO
#define DEFAULT_EXT_PUMP_GPIO GPIO_IS_NOT_CONFIGURED
#endif
@@ -153,14 +141,22 @@
#ifdef ARDUINO_ARCH_ESP32
#include <driver/gpio.h>
#elif !defined(GPIO_IS_VALID_GPIO)
#define GPIO_IS_VALID_GPIO(gpioNum) (gpioNum >= 0 && gpioNum <= 17)
#define GPIO_IS_VALID_GPIO(gpioNum) (gpioNum >= 0 && gpioNum <= 16)
#endif
#define GPIO_IS_VALID(gpioNum) (gpioNum != GPIO_IS_NOT_CONFIGURED && GPIO_IS_VALID_GPIO(gpioNum))
enum class UnitSystem : uint8_t {
METRIC = 0,
IMPERIAL = 1
enum class SensorType : byte {
BOILER_OUTDOOR = 0,
BOILER_RETURN = 4,
MANUAL = 1,
DS18B20 = 2,
BLUETOOTH = 3
};
enum class UnitSystem : byte {
METRIC,
IMPERIAL
};
char buffer[255];

View File

@@ -1,18 +1,13 @@
#define ARDUINOJSON_USE_DOUBLE 0
#define ARDUINOJSON_USE_LONG_LONG 0
#include <Arduino.h>
#include "defines.h"
#include "strings.h"
#include "CrashRecorder.h"
#include <ArduinoJson.h>
#include <FileData.h>
#include <LittleFS.h>
#include <ESPTelnetStream.h>
#include <TinyLogger.h>
#include <NetworkMgr.h>
#include "defines.h"
#include "strings.h"
#include "CrashRecorder.h"
#include "Sensors.h"
#include "Settings.h"
#include "utils.h"
@@ -36,13 +31,10 @@
using namespace NetworkUtils;
// Vars
ESPTelnetStream* telnetStream = nullptr;
NetworkMgr* network = nullptr;
Sensors::Result sensorsResults[SENSORS_AMOUNT];
FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000);
FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000);
FileData fsSensorsSettings(&LittleFS, "/sensors.conf", 'e', &sensorsSettings, sizeof(sensorsSettings), 60000);
ESPTelnetStream* telnetStream = nullptr;
NetworkMgr* network = nullptr;
// Tasks
MqttTask* tMqtt;
@@ -55,9 +47,6 @@ MainTask* tMain;
void setup() {
CrashRecorder::init();
Sensors::setMaxSensors(SENSORS_AMOUNT);
Sensors::settings = sensorsSettings;
Sensors::results = sensorsResults;
LittleFS.begin();
Log.setLevel(TinyLogger::Level::VERBOSE);
@@ -75,14 +64,10 @@ void setup() {
});
Serial.begin(115200);
#if ARDUINO_USB_MODE
Serial.setTxBufferSize(512);
#endif
Log.addStream(&Serial);
Log.print("\n\n\r");
//
// Network settings
// network settings
switch (fsNetworkSettings.read()) {
case FD_FS_ERR:
Log.swarningln(FPSTR(L_NETWORK_SETTINGS), F("Filesystem error, load default"));
@@ -101,27 +86,7 @@ void setup() {
break;
}
network = (new NetworkMgr)
->setHostname(networkSettings.hostname)
->setStaCredentials(
strlen(networkSettings.sta.ssid) ? networkSettings.sta.ssid : nullptr,
strlen(networkSettings.sta.password) ? networkSettings.sta.password : nullptr,
networkSettings.sta.channel
)->setApCredentials(
strlen(networkSettings.ap.ssid) ? networkSettings.ap.ssid : nullptr,
strlen(networkSettings.ap.password) ? networkSettings.ap.password : nullptr,
networkSettings.ap.channel
)
->setUseDhcp(networkSettings.useDhcp)
->setStaticConfig(
networkSettings.staticConfig.ip,
networkSettings.staticConfig.gateway,
networkSettings.staticConfig.subnet,
networkSettings.staticConfig.dns
);
//
// Settings
// settings
switch (fsSettings.read()) {
case FD_FS_ERR:
Log.swarningln(FPSTR(L_SETTINGS), F("Filesystem error, load default"));
@@ -147,8 +112,8 @@ void setup() {
break;
}
// Logs settings
if (!settings.system.serial.enabled) {
// logs
if (!settings.system.serial.enable) {
Serial.end();
Log.clearStreams();
@@ -160,7 +125,7 @@ void setup() {
Log.addStream(&Serial);
}
if (settings.system.telnet.enabled) {
if (settings.system.telnet.enable) {
telnetStream = new ESPTelnetStream;
telnetStream->setKeepAliveInterval(500);
Log.addStream(telnetStream);
@@ -170,34 +135,34 @@ void setup() {
Log.setLevel(static_cast<TinyLogger::Level>(settings.system.logLevel));
}
//
// Sensors settings
switch (fsSensorsSettings.read()) {
case FD_FS_ERR:
Log.swarningln(FPSTR(L_SENSORS), F("Filesystem error, load default"));
break;
case FD_FILE_ERR:
Log.swarningln(FPSTR(L_SENSORS), F("Bad data, load default"));
break;
case FD_WRITE:
Log.sinfoln(FPSTR(L_SENSORS), F("Not found, load default"));
break;
case FD_ADD:
case FD_READ:
Log.sinfoln(FPSTR(L_SENSORS), F("Loaded"));
default:
break;
}
// network
network = (new NetworkMgr)
->setHostname(networkSettings.hostname)
->setStaCredentials(
strlen(networkSettings.sta.ssid) ? networkSettings.sta.ssid : nullptr,
strlen(networkSettings.sta.password) ? networkSettings.sta.password : nullptr,
networkSettings.sta.channel
)->setApCredentials(
strlen(networkSettings.ap.ssid) ? networkSettings.ap.ssid : nullptr,
strlen(networkSettings.ap.password) ? networkSettings.ap.password : nullptr,
networkSettings.ap.channel
)
->setUseDhcp(networkSettings.useDhcp)
->setStaticConfig(
networkSettings.staticConfig.ip,
networkSettings.staticConfig.gateway,
networkSettings.staticConfig.subnet,
networkSettings.staticConfig.dns
);
//
// Make tasks
// tasks
tMqtt = new MqttTask(false, 500);
Scheduler.start(tMqtt);
tOt = new OpenThermTask(true, 750);
Scheduler.start(tOt);
tSensors = new SensorsTask(true, 1000);
tSensors = new SensorsTask(true, EXT_SENSORS_INTERVAL);
Scheduler.start(tSensors);
tRegulator = new RegulatorTask(true, 10000);

View File

@@ -3,192 +3,27 @@
#define PROGMEM
#endif
const char L_SETTINGS[] PROGMEM = "SETTINGS";
const char L_SETTINGS_OT[] PROGMEM = "SETTINGS.OT";
const char L_SETTINGS_DHW[] PROGMEM = "SETTINGS.DHW";
const char L_SETTINGS_HEATING[] PROGMEM = "SETTINGS.HEATING";
const char L_NETWORK[] PROGMEM = "NETWORK";
const char L_NETWORK_SETTINGS[] PROGMEM = "NETWORK.SETTINGS";
const char L_PORTAL_WEBSERVER[] PROGMEM = "PORTAL.WEBSERVER";
const char L_PORTAL_DNSSERVER[] PROGMEM = "PORTAL.DNSSERVER";
const char L_PORTAL_CAPTIVE[] PROGMEM = "PORTAL.CAPTIVE";
const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA";
const char L_MAIN[] PROGMEM = "MAIN";
const char L_MQTT[] PROGMEM = "MQTT";
const char L_MQTT_HA[] PROGMEM = "MQTT.HA";
const char L_MQTT_MSG[] PROGMEM = "MQTT.MSG";
const char L_OT[] PROGMEM = "OT";
const char L_OT_DHW[] PROGMEM = "OT.DHW";
const char L_OT_HEATING[] PROGMEM = "OT.HEATING";
const char L_OT_CH2[] PROGMEM = "OT.CH2";
const char L_SENSORS[] PROGMEM = "SENSORS";
const char L_SENSORS_SETTINGS[] PROGMEM = "SENSORS.SETTINGS";
const char L_SENSORS_DALLAS[] PROGMEM = "SENSORS.DALLAS";
const char L_SENSORS_NTC[] PROGMEM = "SENSORS.NTC";
const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE";
const char L_REGULATOR[] PROGMEM = "REGULATOR";
const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID";
const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM";
const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT";
const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT";
const char L_EXTPUMP[] PROGMEM = "EXTPUMP";
const char S_ACTIONS[] PROGMEM = "actions";
const char S_ACTIVE[] PROGMEM = "active";
const char S_ADDRESS[] PROGMEM = "address";
const char S_ANTI_STUCK_INTERVAL[] PROGMEM = "antiStuckInterval";
const char S_ANTI_STUCK_TIME[] PROGMEM = "antiStuckTime";
const char S_AP[] PROGMEM = "ap";
const char S_APP_VERSION[] PROGMEM = "appVersion";
const char S_AUTH[] PROGMEM = "auth";
const char S_BACKTRACE[] PROGMEM = "backtrace";
const char S_BATTERY[] PROGMEM = "battery";
const char S_BAUDRATE[] PROGMEM = "baudrate";
const char S_BLOCKING[] PROGMEM = "blocking";
const char S_BSSID[] PROGMEM = "bssid";
const char S_BUILD[] PROGMEM = "build";
const char S_CASCADE_CONTROL[] PROGMEM = "cascadeControl";
const char S_CHANNEL[] PROGMEM = "channel";
const char S_CHIP[] PROGMEM = "chip";
const char S_CODE[] PROGMEM = "code";
const char S_CONNECTED[] PROGMEM = "connected";
const char S_CONTINUES[] PROGMEM = "continues";
const char S_CORE[] PROGMEM = "core";
const char S_CORES[] PROGMEM = "cores";
const char S_CRASH[] PROGMEM = "crash";
const char S_CURRENT_TEMP[] PROGMEM = "currentTemp";
const char S_DATA[] PROGMEM = "data";
const char S_DATE[] PROGMEM = "date";
const char S_DHW[] PROGMEM = "dhw";
const char S_DHW_BLOCKING[] PROGMEM = "dhwBlocking";
const char S_DHW_PRESENT[] PROGMEM = "dhwPresent";
const char S_DHW_TO_CH2[] PROGMEM = "dhwToCh2";
const char S_DIAG[] PROGMEM = "diag";
const char S_DNS[] PROGMEM = "dns";
const char S_DT[] PROGMEM = "dt";
const char S_D_FACTOR[] PROGMEM = "d_factor";
const char S_EMERGENCY[] PROGMEM = "emergency";
const char S_ENABLED[] PROGMEM = "enabled";
const char S_ENV[] PROGMEM = "env";
const char S_EPC[] PROGMEM = "epc";
const char S_EQUITHERM[] PROGMEM = "equitherm";
const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump";
const char S_FACTOR[] PROGMEM = "factor";
const char S_FAULT[] PROGMEM = "fault";
const char S_FILTERING[] PROGMEM = "filtering";
const char S_FILTERING_FACTOR[] PROGMEM = "filteringFactor";
const char S_FLAGS[] PROGMEM = "flags";
const char S_FLAME[] PROGMEM = "flame";
const char S_FLASH[] PROGMEM = "flash";
const char S_FREE[] PROGMEM = "free";
const char S_FREQ[] PROGMEM = "freq";
const char S_GATEWAY[] PROGMEM = "gateway";
const char S_GET_MIN_MAX_TEMP[] PROGMEM = "getMinMaxTemp";
const char S_GPIO[] PROGMEM = "gpio";
const char S_HEAP[] PROGMEM = "heap";
const char S_HEATING[] PROGMEM = "heating";
const char S_HEATING_CH1_TO_CH2[] PROGMEM = "heatingCh1ToCh2";
const char S_HEATING_CH2_ENABLED[] PROGMEM = "heatingCh2Enabled";
const char S_HIDDEN[] PROGMEM = "hidden";
const char S_HOME_ASSISTANT_DISCOVERY[] PROGMEM = "homeAssistantDiscovery";
const char S_HOSTNAME[] PROGMEM = "hostname";
const char S_HUMIDITY[] PROGMEM = "humidity";
const char S_HYSTERESIS[] PROGMEM = "hysteresis";
const char S_ID[] PROGMEM = "id";
const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix";
const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp";
const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl";
const char S_IN_GPIO[] PROGMEM = "inGpio";
const char S_INPUT[] PROGMEM = "input";
const char S_INTERVAL[] PROGMEM = "interval";
const char S_INVERT_STATE[] PROGMEM = "invertState";
const char S_IP[] PROGMEM = "ip";
const char S_I_FACTOR[] PROGMEM = "i_factor";
const char S_K_FACTOR[] PROGMEM = "k_factor";
const char S_LOGIN[] PROGMEM = "login";
const char S_LOG_LEVEL[] PROGMEM = "logLevel";
const char S_MAC[] PROGMEM = "mac";
const char S_MASTER[] PROGMEM = "master";
const char S_MAX[] PROGMEM = "max";
const char S_MAX_FREE_BLOCK[] PROGMEM = "maxFreeBlock";
const char S_MAX_MODULATION[] PROGMEM = "maxModulation";
const char S_MAX_POWER[] PROGMEM = "maxPower";
const char S_MAX_TEMP[] PROGMEM = "maxTemp";
const char S_MEMBER_ID[] PROGMEM = "memberId";
const char S_MIN[] PROGMEM = "min";
const char S_MIN_FREE[] PROGMEM = "minFree";
const char S_MIN_MAX_FREE_BLOCK[] PROGMEM = "minMaxFreeBlock";
const char S_MIN_POWER[] PROGMEM = "minPower";
const char S_MIN_TEMP[] PROGMEM = "minTemp";
const char S_MODEL[] PROGMEM = "model";
const char S_MODULATION[] PROGMEM = "modulation";
const char S_MODULATION_SYNC_WITH_HEATING[] PROGMEM = "modulationSyncWithHeating";
const char S_MQTT[] PROGMEM = "mqtt";
const char S_NAME[] PROGMEM = "name";
const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl";
const char S_NETWORK[] PROGMEM = "network";
const char S_N_FACTOR[] PROGMEM = "n_factor";
const char S_OFFSET[] PROGMEM = "offset";
const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating";
const char S_ON_FAULT[] PROGMEM = "onFault";
const char S_ON_LOSS_CONNECTION[] PROGMEM = "onLossConnection";
const char S_OPENTHERM[] PROGMEM = "opentherm";
const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
const char S_OUT_GPIO[] PROGMEM = "outGpio";
const char S_OUTPUT[] PROGMEM = "output";
const char S_PASSWORD[] PROGMEM = "password";
const char S_PID[] PROGMEM = "pid";
const char S_PORT[] PROGMEM = "port";
const char S_PORTAL[] PROGMEM = "portal";
const char S_POST_CIRCULATION_TIME[] PROGMEM = "postCirculationTime";
const char S_POWER[] PROGMEM = "power";
const char S_PREFIX[] PROGMEM = "prefix";
const char S_PROTOCOL_VERSION[] PROGMEM = "protocolVersion";
const char S_PURPOSE[] PROGMEM = "purpose";
const char S_P_FACTOR[] PROGMEM = "p_factor";
const char S_REAL_SIZE[] PROGMEM = "realSize";
const char S_REASON[] PROGMEM = "reason";
const char S_RESET_DIAGNOSTIC[] PROGMEM = "resetDiagnostic";
const char S_RESET_FAULT[] PROGMEM = "resetFault";
const char S_RESET_REASON[] PROGMEM = "resetReason";
const char S_RESTART[] PROGMEM = "restart";
const char S_RETURN_TEMP[] PROGMEM = "returnTemp";
const char S_REV[] PROGMEM = "rev";
const char S_RSSI[] PROGMEM = "rssi";
const char S_RX_LED_GPIO[] PROGMEM = "rxLedGpio";
const char S_SDK[] PROGMEM = "sdk";
const char S_SENSORS[] PROGMEM = "sensors";
const char S_SERIAL[] PROGMEM = "serial";
const char S_SERVER[] PROGMEM = "server";
const char S_SETTINGS[] PROGMEM = "settings";
const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality";
const char S_SIZE[] PROGMEM = "size";
const char S_SLAVE[] PROGMEM = "slave";
const char S_SSID[] PROGMEM = "ssid";
const char S_STA[] PROGMEM = "sta";
const char S_STATE[] PROGMEM = "state";
const char S_STATIC_CONFIG[] PROGMEM = "staticConfig";
const char S_STATUS_LED_GPIO[] PROGMEM = "statusLedGpio";
const char S_SETPOINT_TEMP[] PROGMEM = "setpointTemp";
const char S_SUBNET[] PROGMEM = "subnet";
const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode";
const char S_SYSTEM[] PROGMEM = "system";
const char S_TARGET[] PROGMEM = "target";
const char S_TARGET_TEMP[] PROGMEM = "targetTemp";
const char S_TELNET[] PROGMEM = "telnet";
const char S_TEMPERATURE[] PROGMEM = "temperature";
const char S_THRESHOLD_TIME[] PROGMEM = "thresholdTime";
const char S_TOTAL[] PROGMEM = "total";
const char S_TRESHOLD_TIME[] PROGMEM = "tresholdTime";
const char S_TURBO[] PROGMEM = "turbo";
const char S_TURBO_FACTOR[] PROGMEM = "turboFactor";
const char S_TYPE[] PROGMEM = "type";
const char S_T_FACTOR[] PROGMEM = "t_factor";
const char S_UNIT_SYSTEM[] PROGMEM = "unitSystem";
const char S_UPTIME[] PROGMEM = "uptime";
const char S_USE[] PROGMEM = "use";
const char S_USE_DHCP[] PROGMEM = "useDhcp";
const char S_USER[] PROGMEM = "user";
const char S_VALUE[] PROGMEM = "value";
const char S_VERSION[] PROGMEM = "version";
const char L_SETTINGS[] PROGMEM = "SETTINGS";
const char L_SETTINGS_OT[] PROGMEM = "SETTINGS.OT";
const char L_SETTINGS_DHW[] PROGMEM = "SETTINGS.DHW";
const char L_SETTINGS_HEATING[] PROGMEM = "SETTINGS.HEATING";
const char L_NETWORK[] PROGMEM = "NETWORK";
const char L_NETWORK_SETTINGS[] PROGMEM = "NETWORK.SETTINGS";
const char L_PORTAL_WEBSERVER[] PROGMEM = "PORTAL.WEBSERVER";
const char L_PORTAL_DNSSERVER[] PROGMEM = "PORTAL.DNSSERVER";
const char L_PORTAL_CAPTIVE[] PROGMEM = "PORTAL.CAPTIVE";
const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA";
const char L_MAIN[] PROGMEM = "MAIN";
const char L_MQTT[] PROGMEM = "MQTT";
const char L_MQTT_MSG[] PROGMEM = "MQTT.MSG";
const char L_OT[] PROGMEM = "OT";
const char L_OT_DHW[] PROGMEM = "OT.DHW";
const char L_OT_HEATING[] PROGMEM = "OT.HEATING";
const char L_SENSORS_OUTDOOR[] PROGMEM = "SENSORS.OUTDOOR";
const char L_SENSORS_INDOOR[] PROGMEM = "SENSORS.INDOOR";
const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE";
const char L_REGULATOR[] PROGMEM = "REGULATOR";
const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID";
const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM";
const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT";
const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT";

File diff suppressed because it is too large Load Diff

View File

@@ -74,9 +74,8 @@
"section": {
"control": "Control",
"states": "States",
"sensors": "Sensors",
"diag": "OpenTherm diagnostic"
"states": "States and sensors",
"otDiag": "OpenTherm diagnostic"
},
"thermostat": {
@@ -87,36 +86,39 @@
"turbo": "Turbo mode"
},
"states": {
"mNetworkConnected": "Network connection",
"mMqttConnected": "MQTT connection",
"mEmergencyState": "Emergency mode",
"mExtPumpState": "External pump",
"mCascadeControlInput": "Cascade control (input)",
"mCascadeControlOutput": "Cascade control (output)",
"sConnected": "OpenTherm connection",
"sFlame": "Flame",
"sFaultActive": "Fault",
"sFaultCode": "Faul code",
"sDiagActive": "Diagnostic",
"sDiagCode": "Diagnostic code",
"mHeatEnabled": "Heating enabled",
"mHeatBlocking": "Heating blocked",
"sHeatActive": "Heating active",
"mHeatSetpointTemp": "Heating setpoint temp",
"mHeatTargetTemp": "Heating target temp",
"mHeatCurrTemp": "Heating current temp",
"mHeatRetTemp": "Heating return temp",
"mHeatIndoorTemp": "Heating, indoor temp",
"mHeatOutdoorTemp": "Heating, outdoor temp",
"mDhwEnabled": "DHW enabled",
"sDhwActive": "DHW active",
"mDhwTargetTemp": "DHW target temp",
"mDhwCurrTemp": "DHW current temp",
"mDhwRetTemp": "DHW return temp"
"state": {
"ot": "OpenTherm connected",
"mqtt": "MQTT connected",
"emergency": "Emergency",
"heating": "Heating",
"dhw": "DHW",
"flame": "Flame",
"fault": "Fault",
"diag": "Diagnostic",
"extpump": "External pump",
"outdoorSensorConnected": "Outdoor sensor connected",
"outdoorSensorRssi": "Outdoor sensor RSSI",
"outdoorSensorHumidity": "Outdoor sensor humidity",
"outdoorSensorBattery": "Outdoor sensor battery",
"indoorSensorConnected": "Indoor sensor connected",
"cascadeControlInput": "Cascade control (input)",
"cascadeControlOutput": "Cascade control (output)",
"indoorSensorRssi": "Indoor sensor RSSI",
"indoorSensorHumidity": "Indoor sensor humidity",
"indoorSensorBattery": "Indoor sensor battery",
"modulation": "Modulation",
"pressure": "Pressure",
"dhwFlowRate": "DHW flow rate",
"power": "Current power",
"faultCode": "Fault code",
"diagCode": "Diagnostic code",
"indoorTemp": "Indoor temp",
"outdoorTemp": "Outdoor temp",
"heatingTemp": "Heating temp",
"heatingSetpointTemp": "Heating setpoint temp",
"heatingReturnTemp": "Heating return temp",
"dhwTemp": "DHW temp",
"exhaustTemp": "Exhaust temp"
}
},
@@ -159,76 +161,6 @@
}
},
"sensors": {
"title": "Sensors settings - OpenTherm Gateway",
"name": "Sensors settings",
"enabled": "Enabled",
"sensorName": {
"title": "Sensor name",
"note": "May only contain: a-z, A-Z, 0-9, _ and space"
},
"purpose": "Purpose",
"purposes": {
"outdoorTemp": "Outdoor temperature",
"indoorTemp": "Indoor temperature",
"heatTemp": "Heating, temperature",
"heatRetTemp": "Heating, return temperature",
"dhwTemp": "DHW, temperature",
"dhwRetTemp": "DHW, return temperature",
"dhwFlowRate": "DHW, flow rate",
"exhaustTemp": "Exhaust temperature",
"modLevel": "Modulation level (in percents)",
"currentPower": "Current power (in kWt)",
"pressure": "Pressure",
"humidity": "Humidity",
"temperature": "Temperature",
"notConfigured": "Not configured"
},
"type": "Type/source",
"types": {
"otOutdoorTemp": "OpenTherm, outdoor temp",
"otHeatTemp": "OpenTherm, heating, temp",
"otHeatRetTemp": "OpenTherm, heating, return temp",
"otDhwTemp": "OpenTherm, DHW, temperature",
"otDhwTemp2": "OpenTherm, DHW, temperature 2",
"otDhwFlowRate": "OpenTherm, DHW, flow rate",
"otCh2Temp": "OpenTherm, channel 2, temp",
"otExhaustTemp": "OpenTherm, exhaust temp",
"otHeatExchangerTemp": "OpenTherm, heat exchanger temp",
"otPressure": "OpenTherm, pressure",
"otModLevel": "OpenTherm, modulation level",
"otCurrentPower": "OpenTherm, current power",
"ntcTemp": "NTC sensor",
"dallasTemp": "DALLAS sensor",
"bluetooth": "BLE sensor",
"heatSetpointTemp": "Heating, setpoint temp",
"manual": "Manual via MQTT/API",
"notConfigured": "Not configured"
},
"gpio": "GPIO",
"address": {
"title": "Sensor address",
"note": "For auto detection of DALLAS sensors leave it at default, for BLE devices need a MAC address"
},
"correction": {
"desc": "Correction of values",
"offset": "Compensation (offset)",
"factor": "Multiplier"
},
"filtering": {
"desc": "Filtering values",
"enabled": {
"title": "Enabled filtering",
"note": "It can be useful if there is a lot of sharp noise on the charts. The filter used is \"Running Average\"."
},
"factor": {
"title": "Filtration factor",
"note": "The lower the value, the smoother and <u>longer</u> the change in numeric values."
}
}
},
"settings": {
"title": "Settings - OpenTherm Gateway",
"name": "Settings",
@@ -244,6 +176,8 @@
"pid": "PID settings",
"ot": "OpenTherm settings",
"mqtt": "MQTT settings",
"outdorSensor": "Outdoor sensor settings",
"indoorSensor": "Indoor sensor settings",
"extPump": "External pump settings",
"cascadeControl": "Cascade control settings"
},
@@ -273,11 +207,11 @@
"statusLedGpio": "Status LED GPIO",
"logLevel": "Log level",
"serial": {
"enable": "Enabled Serial port",
"enable": "Enable Serial port",
"baud": "Serial port baud rate"
},
"telnet": {
"enable": "Enabled Telnet",
"enable": "Enable Telnet",
"port": {
"title": "Telnet port",
"note": "Default: 23"
@@ -322,9 +256,16 @@
"inGpio": "In GPIO",
"outGpio": "Out GPIO",
"ledGpio": "RX LED GPIO",
"memberId": "Master member ID",
"flags": "Master flags",
"memberIdCode": "Master MemberID code",
"maxMod": "Max modulation level",
"pressureFactor": {
"title": "Coeff. pressure correction",
"note": "If the pressure displayed is <b>X10</b> from the real one, set the <b>0.1</b>."
},
"dhwFlowRateFactor": {
"title": "Coeff. DHW flow rate correction",
"note": "If the DHW flow rate displayed is <b>X10</b> from the real one, set the <b>0.1</b>."
},
"minPower": {
"title": "Min boiler power <small>(kW)</small>",
"note": "This value is at 0-1% boiler modulation level. Typically found in the boiler specification as \"minimum useful heat output\"."
@@ -333,6 +274,17 @@
"title": "Max boiler power <small>(kW)</small>",
"note": "<b>0</b> - try detect automatically. Typically found in the boiler specification as \"maximum useful heat output\"."
},
"fnv": {
"desc": "Filtering numeric values",
"enable": {
"title": "Enable filtering",
"note": "It can be useful if there is a lot of sharp noise on the charts. The filter used is \"Running Average\"."
},
"factor": {
"title": "Filtration coeff.",
"note": "The lower the value, the smoother and <u>longer</u> the change in numeric values."
}
},
"options": {
"desc": "Options",
@@ -363,6 +315,20 @@
"interval": "Publish interval <small>(sec)</small>"
},
"tempSensor": {
"source": {
"type": "Source type",
"boilerOutdoor": "From boiler via OpenTherm",
"boilerReturn": "Return heat carrier temp via OpenTherm",
"manual": "Manual via MQTT/API",
"ext": "External (DS18B20)",
"ble": "BLE device"
},
"gpio": "GPIO",
"offset": "Temp offset <small>(calibration)</small>",
"bleAddress": "BLE device MAC address"
},
"extPump": {
"use": "Use external pump",
"gpio": "Relay GPIO",
@@ -374,14 +340,14 @@
"cascadeControl": {
"input": {
"desc": "Can be used to turn on the heating only if another boiler is faulty. The other boiler controller must change the state of the GPIO input in the event of a fault.",
"enable": "Enabled input",
"enable": "Enable input",
"gpio": "GPIO",
"invertState": "Invert GPIO state",
"thresholdTime": "State change threshold time <small>(sec)</small>"
},
"output": {
"desc": "Can be used to switch on another boiler <u>via relay</u>.",
"enable": "Enabled output",
"enable": "Enable output",
"gpio": "GPIO",
"invertState": "Invert GPIO state",
"thresholdTime": "State change threshold time <small>(sec)</small>",

View File

@@ -74,9 +74,8 @@
"section": {
"control": "Управление",
"states": "Состояние",
"sensors": "Сенсоры",
"diag": "Диагностика OpenTherm"
"states": "Состояние и сенсоры",
"otDiag": "Диагностика OpenTherm"
},
"thermostat": {
@@ -87,36 +86,39 @@
"turbo": "Турбо"
},
"states": {
"mNetworkConnected": одключение к сети",
"mMqttConnected": "Подключение к MQTT",
"mEmergencyState": "Аварийный режим",
"mExtPumpState": "Внешний насос",
"mCascadeControlInput": "Каскадное управление (вход)",
"mCascadeControlOutput": "Каскадное управление (выход)",
"sConnected": "Подключение к OpenTherm",
"sFlame": "Пламя",
"sFaultActive": "Ошибка",
"sFaultCode": "Код ошибки",
"sDiagActive": "Диагностика",
"sDiagCode": "Диагностический код",
"mHeatEnabled": "Отопление",
"mHeatBlocking": "Блокировка отопления",
"sHeatActive": "Активность отопления",
"mHeatSetpointTemp": "Отопление, уставка",
"mHeatTargetTemp": "Отопление, целевая температура",
"mHeatCurrTemp": "Отопление, текущая температура",
"mHeatRetTemp": "Отопление, температура обратки",
"mHeatIndoorTemp": "Отопление, внутренняя темп.",
"mHeatOutdoorTemp": "Отопление, наружная темп.",
"mDhwEnabled": "ГВС",
"sDhwActive": "Активность ГВС",
"mDhwTargetTemp": "ГВС, целевая температура",
"mDhwCurrTemp": "ГВС, текущая температура",
"mDhwRetTemp": "ГВС, температура обратки"
"state": {
"ot": "OpenTherm подключение",
"mqtt": "MQTT подключение",
"emergency": "Аварийный режим",
"heating": "Отопление",
"dhw": "ГВС",
"flame": "Пламя",
"fault": "Ошибка",
"diag": "Диагностика",
"extpump": "Внешний насос",
"outdoorSensorConnected": "Датчик наруж. темп.",
"outdoorSensorRssi": "RSSI датчика наруж. темп.",
"outdoorSensorHumidity": "Влажность с наруж. датчика темп.",
"outdoorSensorBattery": "Заряд наруж. датчика темп.",
"indoorSensorConnected": "Датчик внутр. темп.",
"cascadeControlInput": "Каскадное управление (вход)",
"cascadeControlOutput": "Каскадное управление (выход)",
"indoorSensorRssi": "RSSI датчика внутр. темп.",
"indoorSensorHumidity": "Влажность с внутр. датчика темп.",
"indoorSensorBattery": "Заряд внутр. датчика темп.",
"modulation": "Уровень модуляции",
"pressure": "Давление",
"dhwFlowRate": "Расход ГВС",
"power": "Текущая мощность",
"faultCode": "Код ошибки",
"diagCode": "Диагностический код",
"indoorTemp": "Внутренняя темп.",
"outdoorTemp": "Наружная темп.",
"heatingTemp": "Темп. отопления",
"heatingSetpointTemp": "Уставка темп. отопления",
"heatingReturnTemp": "Темп. обратки отопления",
"dhwTemp": "Темп. ГВС",
"exhaustTemp": "Темп. выхлопных газов"
}
},
@@ -159,76 +161,6 @@
}
},
"sensors": {
"title": "Настройки сенсоров - OpenTherm Gateway",
"name": "Настройки сенсоров",
"enabled": "Включить и использовать",
"sensorName": {
"title": "Имя сенсора",
"note": "Может содержать только: a-z, A-Z, 0-9, _ и пробел"
},
"purpose": "Назначение",
"purposes": {
"outdoorTemp": "Внешняя температура",
"indoorTemp": "Внутреняя температура",
"heatTemp": "Отопление, температура",
"heatRetTemp": "Отопление, температура обратки",
"dhwTemp": "ГВС, температура",
"dhwRetTemp": "ГВС, температура обратки",
"dhwFlowRate": "ГВС, расход/скорость потока",
"exhaustTemp": "Температура выхлопных газов",
"modLevel": "Уровень модуляции (в процентах)",
"currentPower": "Текущая мощность (в кВт)",
"pressure": "Давление",
"humidity": "Влажность",
"temperature": "Температура",
"notConfigured": "Не сконфигурировано"
},
"type": "Тип/источник",
"types": {
"otOutdoorTemp": "OpenTherm, внешняя температура",
"otHeatTemp": "OpenTherm, отопление, температура",
"otHeatRetTemp": "OpenTherm, отопление, температура обратки",
"otDhwTemp": "OpenTherm, ГВС, температура",
"otDhwTemp2": "OpenTherm, ГВС, температура 2",
"otDhwFlowRate": "OpenTherm, ГВС, расход/скорость потока",
"otCh2Temp": "OpenTherm, канал 2, температура",
"otExhaustTemp": "OpenTherm, температура выхлопных газов",
"otHeatExchangerTemp": "OpenTherm, температура теплообменника",
"otPressure": "OpenTherm, давление",
"otModLevel": "OpenTherm, уровень модуляции",
"otCurrentPower": "OpenTherm, текущая мощность",
"ntcTemp": "NTC датчик",
"dallasTemp": "DALLAS датчик",
"bluetooth": "BLE датчик",
"heatSetpointTemp": "Отопление, температура уставки",
"manual": "Вручную через MQTT/API",
"notConfigured": "Не сконфигурировано"
},
"gpio": "GPIO датчика",
"address": {
"title": "Адрес датчика",
"note": "Для DALLAS датчиков оставьте по умолчанию для автоопределения, для BLE устройств необходимо указать MAC адрес"
},
"correction": {
"desc": "Коррекция показаний",
"offset": "Компенсация (смещение)",
"factor": "Множитель"
},
"filtering": {
"desc": "Фильтрация показаний",
"enabled": {
"title": "Включить фильтрацию",
"note": "Может быть полезно, если на графиках много резкого шума. В качестве фильтра используется \"бегущее среднее\"."
},
"factor": {
"title": "Коэфф. фильтрации",
"note": "Чем меньше коэф., тем плавнее и <u>дольше</u> изменение числовых значений."
}
}
},
"settings": {
"title": "Настройки - OpenTherm Gateway",
"name": "Настройки",
@@ -244,6 +176,8 @@
"pid": "Настройки ПИД",
"ot": "Настройки OpenTherm",
"mqtt": "Настройки MQTT",
"outdorSensor": "Настройки наружного датчика температуры",
"indoorSensor": "Настройки внутреннего датчика температуры",
"extPump": "Настройки дополнительного насоса",
"cascadeControl": "Настройки каскадного управления"
},
@@ -297,7 +231,21 @@
"title": "Целевая температура",
"note": "<b>Важно:</b> <u>Целевая температура в помещении</u>, если включена ОТ опция <i>«Передать управление отоплением котлу»</i>.<br />Во всех остальных случаях <u>целевая температура теплоносителя</u>."
},
"treshold": "Пороговое время включения <small>(сек)</small>"
"treshold": "Пороговое время включения <small>(сек)</small>",
"events": {
"desc": "События",
"network": "При отключении сети",
"mqtt": "При отключении MQTT",
"indoorSensorDisconnect": "При потере связи с датчиком внутренней темп.",
"outdoorSensorDisconnect": "При потере связи с датчиком наружной темп."
},
"regulators": {
"desc": "Используемые регуляторы",
"equitherm": "ПЗА <small>(требуется внешний (DS18B20) или подключенный к котлу датчик <u>наружной</u> температуры)</small>",
"pid": "ПИД <small>(требуется внешний (DS18B20) датчик <u>внутренней</u> температуры)</small>"
}
},
"equitherm": {
@@ -322,9 +270,16 @@
"inGpio": "Вход GPIO",
"outGpio": "Выход GPIO",
"ledGpio": "RX LED GPIO",
"memberId": "Master member ID",
"flags": "Master flags",
"memberIdCode": "Master MemberID код",
"maxMod": "Макс. уровень модуляции",
"pressureFactor": {
"title": "Коэфф. коррекции давления",
"note": "Если давление отображается <b>Х10</b> от реального, установите значение <b>0.1</b>."
},
"dhwFlowRateFactor": {
"title": "Коэфф. коррекции потока ГВС",
"note": "Если поток ГВС отображается <b>Х10</b> от реального, установите значение <b>0.1</b>."
},
"minPower": {
"title": "Мин. мощность котла <small>(кВт)</small>",
"note": "Это значение соответствует уровню модуляции котла 01%. Обычно можно найти в спецификации котла как \"минимальная полезная тепловая мощность\"."
@@ -333,6 +288,17 @@
"title": "Макс. мощность котла <small>(кВт)</small>",
"note": "<b>0</b> - попробовать определить автоматически. Обычно можно найти в спецификации котла как \"максимальная полезная тепловая мощность\"."
},
"fnv": {
"desc": "Фильтрация числовых значений",
"enable": {
"title": "Включить фильтрацию",
"note": "Может быть полезно, если на графиках много резкого шума. В качестве фильтра используется \"бегущее среднее\"."
},
"factor": {
"title": "Коэфф. фильтрации",
"note": "Чем меньше коэф., тем плавнее и <u>дольше</u> изменение числовых значений."
}
},
"options": {
"desc": "Опции",
@@ -363,6 +329,20 @@
"interval": "Интервал публикации <small>(сек)</small>"
},
"tempSensor": {
"source": {
"type": "Источник данных",
"boilerOutdoor": "От котла через OpenTherm",
"boilerReturn": "Температура обратки через OpenTherm",
"manual": "Вручную через MQTT/API",
"ext": "Внешний датчик (DS18B20)",
"ble": "BLE устройство"
},
"gpio": "GPIO",
"offset": "Смещение температуры <small>(калибровка)</small>",
"bleAddress": "MAC адрес BLE устройства"
},
"extPump": {
"use": "Использовать доп. насос",
"gpio": "GPIO реле",
@@ -386,7 +366,7 @@
"invertState": "Инвертировать состояние GPIO",
"thresholdTime": "Пороговое время изменения состояния <small>(сек)</small>",
"events": {
"desc": "События",
"title": "События",
"onFault": "Если состояние fault (ошибки) активно",
"onLossConnection": "Если соединение по OpenTherm потеряно",
"onEnabledHeating": "Если отопление включено"

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n>dashboard.title</title>
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
<link rel="stylesheet" href="/static/app.css" />
</head>
<body>
@@ -42,31 +42,31 @@
<div class="thermostat" id="thermostat-heating">
<div class="thermostat-header" data-i18n>dashboard.thermostat.heating</div>
<div class="thermostat-temp">
<div class="thermostat-temp-target"><span id="tHeatTargetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="tHeatCurrentTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-target"><span id="thermostat-heating-target"></span> <span class="temp-unit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="thermostat-heating-current"></span> <span class="temp-unit"></span></div>
</div>
<div class="thermostat-minus"><button id="tHeatActionMinus" class="outline"><i class="icons-down"></i></button></div>
<div class="thermostat-plus"><button id="tHeatActionPlus" class="outline"><i class="icons-up"></i></button></div>
<div class="thermostat-minus"><button id="thermostat-heating-minus" class="outline"><i class="icons-down"></i></button></div>
<div class="thermostat-plus"><button id="thermostat-heating-plus" class="outline"><i class="icons-up"></i></button></div>
<div class="thermostat-control">
<input type="checkbox" role="switch" id="tHeatEnabled" value="true">
<label htmlFor="tHeatEnabled" data-i18n>dashboard.thermostat.enable</label>
<input type="checkbox" role="switch" id="thermostat-heating-enabled" value="true">
<label htmlFor="thermostat-heating-enabled" data-i18n>dashboard.thermostat.enable</label>
<input type="checkbox" role="switch" id="tHeatTurbo" value="true">
<label htmlFor="tHeatTurbo" data-i18n>dashboard.thermostat.turbo</label>
<input type="checkbox" role="switch" id="thermostat-heating-turbo" value="true">
<label htmlFor="thermostat-heating-turbo" data-i18n>dashboard.thermostat.turbo</label>
</div>
</div>
<div class="thermostat" id="thermostat-dhw">
<div class="thermostat-header" data-i18n>dashboard.thermostat.dhw</div>
<div class="thermostat-temp">
<div class="thermostat-temp-target"><span id="tDhwTargetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="tDhwCurrentTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-target"><span id="thermostat-dhw-target"></span> <span class="temp-unit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.temp.current</span>: <span id="thermostat-dhw-current"></span> <span class="temp-unit"></span></div>
</div>
<div class="thermostat-minus"><button class="outline" id="tDhwActionMinus"><i class="icons-down"></i></button></div>
<div class="thermostat-plus"><button class="outline" id="tDhwActionPlus"><i class="icons-up"></i></button></div>
<div class="thermostat-minus"><button class="outline" id="thermostat-dhw-minus"><i class="icons-down"></i></button></div>
<div class="thermostat-plus"><button class="outline" id="thermostat-dhw-plus"><i class="icons-up"></i></button></div>
<div class="thermostat-control">
<input type="checkbox" role="switch" id="tDhwEnabled" value="true">
<label htmlFor="tDhwEnabled" data-i18n>dashboard.thermostat.enable</label>
<input type="checkbox" role="switch" id="thermostat-dhw-enabled" value="true">
<label htmlFor="thermostat-dhw-enabled" data-i18n>dashboard.thermostat.enable</label>
</div>
</div>
</div>
@@ -79,116 +79,132 @@
<table>
<tbody>
<tr>
<th scope="row" data-i18n>dashboard.states.mNetworkConnected</th>
<td><input type="radio" class="mNetworkConnected" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.ot</th>
<td><input type="radio" id="ot-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mMqttConnected</th>
<td><input type="radio" class="mMqttConnected" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.mqtt</th>
<td><input type="radio" id="mqtt-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mEmergencyState</th>
<td><input type="radio" class="mEmergencyState" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.emergency</th>
<td><input type="radio" id="ot-emergency" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mExtPumpState</th>
<td><input type="radio" class="mExtPumpState" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.heating</th>
<td><input type="radio" id="ot-heating" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mCascadeControlInput</th>
<td><input type="radio" id="mCascadeControlInput" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.dhw</th>
<td><input type="radio" id="ot-dhw" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mCascadeControlOutput</th>
<td><input type="radio" id="mCascadeControlOutput" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sConnected</th>
<td><input type="radio" class="sConnected" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.flame</th>
<td><input type="radio" id="ot-flame" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sFlame</th>
<td><input type="radio" class="sFlame" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sFaultActive</th>
<td><input type="radio" class="sFaultActive" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.fault</th>
<td><input type="radio" id="ot-fault" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sFaultCode</th>
<td><b class="sFaultCode"></b></td>
<th scope="row" data-i18n>dashboard.state.diag</th>
<td><input type="radio" id="ot-diagnostic" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sDiagActive</th>
<td><input type="radio" class="sDiagActive" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.extpump</th>
<td><input type="radio" id="ot-external-pump" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sDiagCode</th>
<td><b class="sDiagCode"></b></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatEnabled</th>
<td><input type="radio" class="mHeatEnabled" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.outdoorSensorConnected</th>
<td><input type="radio" id="outdoor-sensor-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatBlocking</th>
<td><input type="radio" class="mHeatBlocking" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.outdoorSensorRssi</th>
<td><b id="outdoor-sensor-rssi"></b> <span data-i18n>dbm</span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sHeatActive</th>
<td><input type="radio" class="sHeatActive" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.outdoorSensorHumidity</th>
<td><b id="outdoor-sensor-humidity"></b> %</td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatTargetTemp</th>
<td><b class="mHeatTargetTemp"></b> <span class="tempUnit"></span></td>
<th scope="row" data-i18n>dashboard.state.outdoorSensorBattery</th>
<td><b id="outdoor-sensor-battery"></b> %</td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatSetpointTemp</th>
<td><b class="mHeatSetpointTemp"></b> <span class="tempUnit"></span></td>
<th scope="row" data-i18n>dashboard.state.indoorSensorConnected</th>
<td><input type="radio" id="indoor-sensor-connected" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatCurrTemp</th>
<td><b class="mHeatCurrTemp"></b> <span class="tempUnit"></span></td>
<th scope="row" data-i18n>dashboard.state.cascadeControlInput</th>
<td><input type="radio" id="cc-input" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatRetTemp</th>
<td><b class="mHeatRetTemp"></b> <span class="tempUnit"></span></td>
<th scope="row" data-i18n>dashboard.state.cascadeControlOutput</th>
<td><input type="radio" id="cc-output" aria-invalid="false" checked disabled /></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatIndoorTemp</th>
<td><b class="mHeatIndoorTemp"></b> <span class="tempUnit"></span></td>
<th scope="row" data-i18n>dashboard.state.indoorSensorRssi</th>
<td><b id="indoor-sensor-rssi"></b> <span data-i18n>dbm</span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatOutdoorTemp</th>
<td><b class="mHeatOutdoorTemp"></b> <span class="tempUnit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mDhwEnabled</th>
<td><input type="radio" class="mDhwEnabled" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.indoorSensorHumidity</th>
<td><b id="indoor-sensor-humidity"></b> %</td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sDhwActive</th>
<td><input type="radio" class="sDhwActive" aria-invalid="false" checked disabled /></td>
<th scope="row" data-i18n>dashboard.state.indoorSensorBattery</th>
<td><b id="indoor-sensor-battery"></b> %</td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mDhwTargetTemp</th>
<td><b class="mDhwTargetTemp"></b> <span class="tempUnit"></span></td>
<th scope="row" data-i18n>dashboard.state.modulation</th>
<td><b id="ot-modulation"></b> %</td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mDhwCurrTemp</th>
<td><b class="mDhwCurrTemp"></b> <span class="tempUnit"></span></td>
<th scope="row" data-i18n>dashboard.state.pressure</th>
<td><b id="ot-pressure"></b> <span class="pressure-unit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mDhwRetTemp</th>
<td><b class="mDhwRetTemp"></b> <span class="tempUnit"></span></td>
<th scope="row" data-i18n>dashboard.state.dhwFlowRate</th>
<td><b id="ot-dhw-flow-rate"></b> <span class="volume-unit"></span>/min</td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.power</th>
<td><b id="ot-power"></b> <span data-i18n>kw</span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.faultCode</th>
<td><b id="ot-fault-code"></b></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.diagCode</th>
<td><b id="ot-diag-code"></b></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.indoorTemp</th>
<td><b id="indoor-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.outdoorTemp</th>
<td><b id="outdoor-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.heatingTemp</th>
<td><b id="heating-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.heatingSetpointTemp</th>
<td><b id="heating-setpoint-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.heatingReturnTemp</th>
<td><b id="heating-return-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.dhwTemp</th>
<td><b id="dhw-temp"></b> <span class="temp-unit"></span></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.state.exhaustTemp</th>
<td><b id="exhaust-temp"></b> <span class="temp-unit"></span></td>
</tr>
</tbody>
</table>
@@ -197,17 +213,15 @@
<hr />
<details>
<summary><b data-i18n>dashboard.section.diag</b></summary>
<pre><b>Vendor:</b> <span class="sVendor"></span>
<b>Member ID:</b> <span class="sMemberId"></span>
<b>Flags:</b> <span class="sFlags"></span>
<b>Type:</b> <span class="sType"></span>
<b>AppVersion:</b> <span class="sAppVersion"></span>
<b>OT version:</b> <span class="sProtocolVersion"></span>
<b>Modulation limits:</b> <span class="sModMin"></span>...<span class="sModMax"></span> %
<b>Power limits:</b> <span class="sPowerMin"></span>...<span class="sPowerMax"></span> kW
<b>Heating limits:</b> <span class="sHeatMinTemp"></span>...<span class="sHeatMaxTemp"></span> <span class="tempUnit"></span>
<b>DHW limits:</b> <span class="sDhwMinTemp"></span>...<span class="sDhwMaxTemp"></span> <span class="tempUnit"></span></pre>
<summary><b data-i18n>dashboard.section.otDiag</b></summary>
<pre><b>Vendor:</b> <span id="slave-vendor"></span>
<b>Member ID:</b> <span id="slave-member-id"></span>
<b>Flags:</b> <span id="slave-flags"></span>
<b>Type:</b> <span id="slave-type"></span>
<b>Version:</b> <span id="slave-version"></span>
<b>OT version:</b> <span id="slave-ot-version"></span>
<b>Heating limits:</b> <span id="heating-min-temp"></span>...<span id="heating-max-temp"></span> <span class="temp-unit"></span>
<b>DHW limits:</b> <span id="dhw-min-temp"></span>...<span id="dhw-max-temp"></span> <span class="temp-unit"></span></pre>
</details>
</div>
</article>
@@ -224,7 +238,7 @@
</small>
</footer>
<script src="/static/app.js?{BUILD_TIME}"></script>
<script src="/static/app.js"></script>
<script>
let modifiedTime = null;
let noRegulators;
@@ -245,7 +259,7 @@
const lang = new Lang(document.getElementById('lang'));
lang.build();
document.querySelector('#tHeatActionMinus').addEventListener('click', (event) => {
document.querySelector('#thermostat-heating-minus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
@@ -264,10 +278,10 @@
newSettings.heating.target = minTemp;
}
setValue('#tHeatTargetTemp', newSettings.heating.target);
setValue('#thermostat-heating-target', newSettings.heating.target);
});
document.querySelector('#tHeatActionPlus').addEventListener('click', (event) => {
document.querySelector('#thermostat-heating-plus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
@@ -286,10 +300,10 @@
newSettings.heating.target = maxTemp;
}
setValue('#tHeatTargetTemp', newSettings.heating.target);
setValue('#thermostat-heating-target', newSettings.heating.target);
});
document.querySelector('#tDhwActionMinus').addEventListener('click', (event) => {
document.querySelector('#thermostat-dhw-minus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
@@ -301,10 +315,10 @@
newSettings.dhw.target = prevSettings.dhw.minTemp;
}
setValue('#tDhwTargetTemp', newSettings.dhw.target);
setValue('#thermostat-dhw-target', newSettings.dhw.target);
});
document.querySelector('#tDhwActionPlus').addEventListener('click', (event) => {
document.querySelector('#thermostat-dhw-plus').addEventListener('click', (event) => {
if (!prevSettings) {
return;
}
@@ -316,22 +330,22 @@
newSettings.dhw.target = prevSettings.dhw.maxTemp;
}
setValue('#tDhwTargetTemp', newSettings.dhw.target);
setValue('#thermostat-dhw-target', newSettings.dhw.target);
});
document.querySelector('#tHeatEnabled').addEventListener('change', (event) => {
document.querySelector('#thermostat-heating-enabled').addEventListener('change', (event) => {
modifiedTime = Date.now();
newSettings.heating.enabled = event.currentTarget.checked;
newSettings.heating.enable = event.currentTarget.checked;
});
document.querySelector('#tHeatTurbo').addEventListener('change', (event) => {
document.querySelector('#thermostat-heating-turbo').addEventListener('change', (event) => {
modifiedTime = Date.now();
newSettings.heating.turbo = event.currentTarget.checked;
});
document.querySelector('#tDhwEnabled').addEventListener('change', (event) => {
document.querySelector('#thermostat-dhw-enabled').addEventListener('change', (event) => {
modifiedTime = Date.now();
newSettings.dhw.enabled = event.currentTarget.checked;
newSettings.dhw.enable = event.currentTarget.checked;
});
setTimeout(async function onLoadPage() {
@@ -347,10 +361,10 @@
// settings
try {
let modified = prevSettings && (
(prevSettings.heating.enabled != newSettings.heating.enabled)
(prevSettings.heating.enable != newSettings.heating.enable)
|| (prevSettings.heating.turbo != newSettings.heating.turbo)
|| (prevSettings.heating.target != newSettings.heating.target)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enabled != newSettings.dhw.enabled)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enable != newSettings.dhw.enable)
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target)
);
@@ -370,12 +384,12 @@
}
const result = await response.json();
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enabled && !result.pid.enabled;
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enable && !result.pid.enable;
prevSettings = result;
newSettings.heating.enabled = result.heating.enabled;
newSettings.heating.enable = result.heating.enable;
newSettings.heating.turbo = result.heating.turbo;
newSettings.heating.target = result.heating.target;
newSettings.dhw.enabled = result.dhw.enabled;
newSettings.dhw.enable = result.dhw.enable;
newSettings.dhw.target = result.dhw.target;
if (result.opentherm.dhwPresent) {
@@ -384,16 +398,16 @@
hide('#thermostat-dhw');
}
setCheckboxValue('#tHeatEnabled', result.heating.enabled);
setCheckboxValue('#tHeatTurbo', result.heating.turbo);
setValue('#tHeatTargetTemp', result.heating.target);
setCheckboxValue('#thermostat-heating-enabled', result.heating.enable);
setCheckboxValue('#thermostat-heating-turbo', result.heating.turbo);
setValue('#thermostat-heating-target', result.heating.target);
setCheckboxValue('#tDhwEnabled', result.dhw.enabled);
setValue('#tDhwTargetTemp', result.dhw.target);
setCheckboxValue('#thermostat-dhw-enabled', result.dhw.enable);
setValue('#thermostat-dhw-target', result.dhw.target);
setValue('.tempUnit', temperatureUnit(result.system.unitSystem));
setValue('.pressureUnit', pressureUnit(result.system.unitSystem));
setValue('.volumeUnit', volumeUnit(result.system.unitSystem));
setValue('.temp-unit', temperatureUnit(result.system.unitSystem));
setValue('.pressure-unit', pressureUnit(result.system.unitSystem));
setValue('.volume-unit', volumeUnit(result.system.unitSystem));
} catch (error) {
console.log(error);
@@ -407,84 +421,67 @@
}
const result = await response.json();
// Graph
setValue('#tHeatCurrentTemp', result.master.heating.indoorTempControl
? result.master.heating.indoorTemp
: result.master.heating.currentTemp
);
setValue('#tDhwCurrentTemp', result.master.dhw.currentTemp);
setValue('#thermostat-heating-current', noRegulators ? result.temperatures.heating : result.temperatures.indoor);
setValue('#thermostat-dhw-current', result.temperatures.dhw);
// SLAVE
setValue('.sMemberId', result.slave.memberId);
setValue('.sVendor', memberIdToVendor(result.slave.memberId));
setValue('.sFlags', result.slave.flags);
setValue('.sType', result.slave.type);
setValue('.sAppVersion', result.slave.appVersion);
setValue('.sProtocolVersion', result.slave.protocolVersion);
setState('#ot-connected', result.states.otStatus);
setState('#mqtt-connected', result.states.mqtt);
setState('#ot-emergency', result.states.emergency);
setState('#ot-heating', result.states.heating);
setState('#ot-dhw', result.states.dhw);
setState('#ot-flame', result.states.flame);
setState('#ot-fault', result.states.fault);
setState('#ot-diagnostic', result.states.diagnostic);
setState('#ot-external-pump', result.states.externalPump);
setState('#outdoor-sensor-connected', result.sensors.outdoor.connected);
setState('#indoor-sensor-connected', result.sensors.indoor.connected);
setState('#cc-input', result.cascadeControl.input);
setState('#cc-output', result.cascadeControl.output);
setState('.sConnected', result.slave.connected);
setState('.sFlame', result.slave.flame);
setValue('#outdoor-sensor-rssi', result.sensors.outdoor.rssi);
setValue('#outdoor-sensor-humidity', result.sensors.outdoor.humidity);
setValue('#outdoor-sensor-battery', result.sensors.outdoor.battery);
setValue('#indoor-sensor-rssi', result.sensors.indoor.rssi);
setValue('#indoor-sensor-humidity', result.sensors.indoor.humidity);
setValue('#indoor-sensor-battery', result.sensors.indoor.battery);
setValue('.sModMin', result.slave.modulation.min);
setValue('.sModMax', result.slave.modulation.max);
setValue('.sPowerMin', result.slave.power.min);
setValue('.sPowerMax', result.slave.power.max);
setState('.sHeatActive', result.slave.heating.active);
setValue('.sHeatMinTemp', result.slave.heating.minTemp);
setValue('.sHeatMaxTemp', result.slave.heating.maxTemp);
setState('.sDhwActive', result.slave.dhw.active);
setValue('.sDhwMinTemp', result.slave.dhw.minTemp);
setValue('.sDhwMaxTemp', result.slave.dhw.maxTemp);
setState('.sFaultActive', result.slave.fault.active);
setValue('#ot-modulation', result.sensors.modulation);
setValue('#ot-pressure', result.sensors.pressure);
setValue('#ot-dhw-flow-rate', result.sensors.dhwFlowRate);
setValue('#ot-power', result.sensors.power);
setValue(
'.sFaultCode',
result.slave.fault.active
? (result.slave.fault.code + " (0x" + dec2hex(result.slave.fault.code) + ")")
'#ot-fault-code',
result.sensors.faultCode
? (result.sensors.faultCode + " (0x" + dec2hex(result.sensors.faultCode) + ")")
: "-"
);
setValue(
'#ot-diag-code',
result.sensors.diagnosticCode
? (result.sensors.diagnosticCode + " (0x" + dec2hex(result.sensors.diagnosticCode) + ")")
: "-"
);
setState('.sDiagActive', result.slave.diag.active);
setValue(
'.sDiagCode',
result.slave.diag.active
? (result.slave.diag.code + " (0x" + dec2hex(result.slave.diag.code) + ")")
: "-"
);
setValue('#indoor-temp', result.temperatures.indoor);
setValue('#outdoor-temp', result.temperatures.outdoor);
setValue('#heating-temp', result.temperatures.heating);
setValue('#heating-return-temp', result.temperatures.heatingReturn);
setValue('#dhw-temp', result.temperatures.dhw);
setValue('#exhaust-temp', result.temperatures.exhaust);
setValue('#heating-min-temp', result.parameters.heatingMinTemp);
setValue('#heating-max-temp', result.parameters.heatingMaxTemp);
setValue('#heating-setpoint-temp', result.parameters.heatingSetpoint);
setValue('#dhw-min-temp', result.parameters.dhwMinTemp);
setValue('#dhw-max-temp', result.parameters.dhwMaxTemp);
// MASTER
setState('.mHeatEnabled', result.master.heating.enabled);
setState('.mHeatBlocking', result.master.heating.blocking);
setState('.mHeatIndoorTempControl', result.master.heating.indoorTempControl);
setValue('.mHeatSetpointTemp', result.master.heating.setpointTemp);
setValue('.mHeatTargetTemp', result.master.heating.targetTemp);
setValue('.mHeatCurrTemp', result.master.heating.currentTemp);
setValue('.mHeatRetTemp', result.master.heating.returnTemp);
setValue('.mHeatIndoorTemp', result.master.heating.indoorTemp);
setValue('.mHeatOutdoorTemp', result.master.heating.outdoorTemp);
setValue('.mHeatMinTemp', result.master.heating.minTemp);
setValue('.mHeatMaxTemp', result.master.heating.maxTemp);
setState('.mDhwEnabled', result.master.dhw.enabled);
setValue('.mDhwTargetTemp', result.master.dhw.targetTemp);
setValue('.mDhwCurrTemp', result.master.dhw.currentTemp);
setValue('.mDhwRetTemp', result.master.dhw.returnTemp);
setValue('.mDhwMinTemp', result.master.dhw.minTemp);
setValue('.mDhwMaxTemp', result.master.dhw.maxTemp);
setState('.mNetworkConnected', result.master.network.connected);
setState('.mMqttConnected', result.master.mqtt.connected);
setState('.mEmergencyState', result.master.emergency.state);
setState('.mExtPumpState', result.master.externalPump.state);
setState('.mCascadeControlInput', result.master.cascadeControl.input);
setState('.mCascadeControlOutput', result.master.cascadeControl.output);
setValue('#slave-member-id', result.parameters.slaveMemberId);
setValue('#slave-vendor', memberIdToVendor(result.parameters.slaveMemberId));
setValue('#slave-flags', result.parameters.slaveFlags);
setValue('#slave-type', result.parameters.slaveType);
setValue('#slave-version', result.parameters.slaveVersion);
setValue('#slave-ot-version', result.parameters.slaveOtVersion);
setBusy('#dashboard-busy', '#dashboard-container', false);
} catch (error) {

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n>index.title</title>
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
<link rel="stylesheet" href="/static/app.css" />
</head>
<body>
@@ -139,10 +139,9 @@
</tbody>
</table>
<div class="grid" style="grid-template-columns: repeat(auto-fit,minmax(12rem,1fr)) !important;">
<div class="grid">
<a href="/dashboard.html" role="button" data-i18n>dashboard.name</a>
<a href="/settings.html" role="button" data-i18n>settings.name</a>
<a href="/sensors.html" role="button" data-i18n>sensors.name</a>
<a href="/upgrade.html" role="button" data-i18n>upgrade.name</a>
<a href="/restart.html" role="button" class="secondary restart" data-i18n>button.restart</a>
</div>
@@ -161,7 +160,7 @@
</small>
</footer>
<script src="/static/app.js?{BUILD_TIME}"></script>
<script src="/static/app.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n>network.title</title>
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
<link rel="stylesheet" href="/static/app.css" />
</head>
<body>
@@ -171,7 +171,7 @@
</small>
</footer>
<script src="/static/app.js?{BUILD_TIME}"></script>
<script src="/static/app.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));

View File

@@ -1,301 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n>sensors.title</title>
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
</head>
<body>
<header class="container">
<nav>
<ul>
<li><a href="/">
<div class="logo" data-i18n>logo</div>
</a></li>
</ul>
<ul>
<!--<li><a href="https://github.com/Laxilef/OTGateway/wiki" role="button" class="secondary" target="_blank">Help</a></li>-->
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="ru">RU</option>
</select>
</li>
</ul>
</nav>
</header>
<main class="container">
<article>
<hgroup>
<h2 data-i18n>sensors.name</h2>
<p></p>
</hgroup>
<details id="template" class="sensor hidden" data-id="" data-preloaded="0">
<summary><b>#<span class="id"></span>: <span class="name"></span></b></summary>
<div>
<div class="form-busy" aria-busy="true"></div>
<form action="/api/sensor?id={id}" class="hidden">
<fieldset>
<label>
<input type="checkbox" role="switch" name="enabled" value="true">
<span data-i18n>sensors.enabled</span>
</label>
<br />
<label>
<span data-i18n>sensors.sensorName.title</span>
<input type="text" name="name" maxlength="32" required>
<small data-i18n>sensors.sensorName.note</small>
</label>
<div class="grid">
<label>
<span data-i18n>sensors.purpose</span>
<select name="purpose" required>
<option value="0" data-i18n>sensors.purposes.outdoorTemp</option>
<option value="1" data-i18n>sensors.purposes.indoorTemp</option>
<option value="2" data-i18n>sensors.purposes.heatTemp</option>
<option value="3" data-i18n>sensors.purposes.heatRetTemp</option>
<option value="4" data-i18n>sensors.purposes.dhwTemp</option>
<option value="5" data-i18n>sensors.purposes.dhwRetTemp</option>
<option value="6" data-i18n>sensors.purposes.dhwFlowRate</option>
<option value="7" data-i18n>sensors.purposes.exhaustTemp</option>
<option value="8" data-i18n>sensors.purposes.modLevel</option>
<option value="9" data-i18n>sensors.purposes.currentPower</option>
<option value="252" data-i18n>sensors.purposes.pressure</option>
<option value="253" data-i18n>sensors.purposes.humidity</option>
<option value="254" data-i18n>sensors.purposes.temperature</option>
<option value="255" data-i18n>sensors.purposes.notConfigured</option>
</select>
</label>
<label>
<span data-i18n>sensors.type</span>
<select name="type" required>
<option value="0" data-i18n>sensors.types.otOutdoorTemp</option>
<option value="1" data-i18n>sensors.types.otHeatTemp</option>
<option value="2" data-i18n>sensors.types.otHeatRetTemp</option>
<option value="3" data-i18n>sensors.types.otDhwTemp</option>
<option value="4" data-i18n>sensors.types.otDhwTemp2</option>
<option value="5" data-i18n>sensors.types.otDhwFlowRate</option>
<option value="6" data-i18n>sensors.types.otCh2Temp</option>
<option value="7" data-i18n>sensors.types.otExhaustTemp</option>
<option value="8" data-i18n>sensors.types.otHeatExchangerTemp</option>
<option value="9" data-i18n>sensors.types.otPressure</option>
<option value="10" data-i18n>sensors.types.otModLevel</option>
<option value="11" data-i18n>sensors.types.otCurrentPower</option>
<option value="50" data-i18n>sensors.types.ntcTemp</option>
<option value="51" data-i18n>sensors.types.dallasTemp</option>
<option value="52" data-i18n>sensors.types.bluetooth</option>
<option value="253" data-i18n>sensors.types.heatSetpointTemp</option>
<option value="254" data-i18n>sensors.types.manual</option>
<option value="255" data-i18n>sensors.types.notConfigured</option>
</select>
</label>
</div>
</fieldset>
<div class="grid">
<label>
<span data-i18n>sensors.gpio</span>
<input type="number" outputmode="numeric" name="gpio" min="0" max="254" step="1">
</label>
<label>
<span data-i18n>sensors.address.title</span>
<input type="text" name="address">
<small data-i18n>sensors.address.note</small>
</label>
</div>
<hr class="correction" />
<details class="correction">
<summary><b data-i18n>sensors.correction.desc</b></summary>
<div class="grid">
<label>
<span data-i18n>sensors.correction.offset</span>
<input type="number" inputmode="numeric" name="offset" min="-20" max="20" step="0.01" required>
</label>
<label>
<span data-i18n>sensors.correction.factor</span>
<input type="number" inputmode="numeric" name="factor" min="0.01" max="10" step="0.01" required>
</label>
</div>
</details>
<hr class="filtering" />
<details class="filtering">
<summary><b data-i18n>sensors.filtering.desc</b></summary>
<div>
<label>
<input type="checkbox" name="filtering" value="true">
<span data-i18n>sensors.filtering.enabled.title</span>
<br />
<small data-i18n>sensors.filtering.enabled.note</small>
</label>
<label>
<span data-i18n>sensors.filtering.factor.title</span>
<input type="number" inputmode="numeric" name="filteringFactor" min="0.01" max="1" step="0.01">
<small data-i18n>sensors.filtering.factor.note</small>
</label>
</div>
</details>
<br/>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</details>
</article>
</main>
<footer class="container">
<small>
<b>Made by Laxilef</b>
<a href="https://github.com/Laxilef/OTGateway/blob/master/LICENSE" target="_blank" class="secondary" data-i18n>nav.license</a>
<a href="https://github.com/Laxilef/OTGateway/blob/master/" target="_blank" class="secondary" data-i18n>nav.source</a>
<a href="https://github.com/Laxilef/OTGateway/wiki" target="_blank" class="secondary" data-i18n>nav.help</a>
<a href="https://github.com/Laxilef/OTGateway/issues" target="_blank" class="secondary" data-i18n>nav.issues</a>
<a href="https://github.com/Laxilef/OTGateway/releases" target="_blank" class="secondary" data-i18n>nav.releases</a>
</small>
</footer>
<script src="/static/app.js?{BUILD_TIME}"></script>
<script>
document.addEventListener("DOMContentLoaded", async () => {
const lang = new Lang(document.getElementById("lang"));
lang.build();
const container = document.querySelector("article");
const templateNode = container.querySelector("#template");
try {
const response = await fetch("/api/sensors", { cache: "no-cache" });
if (!response.ok) {
throw new Error("Response not valid");
}
const result = await response.json();
for (const sensorId in result) {
const sensorNode = templateNode.cloneNode(true);
sensorNode.removeAttribute("id");
sensorNode.classList.remove("hidden");
sensorNode.dataset.id = sensorId;
setValue(".id", sensorId, sensorNode);
setValue(".name", result[sensorId], sensorNode);
container.appendChild(sensorNode);
container.appendChild(document.createElement("hr"));
const sensorForm = sensorNode.querySelector("form");
const fillData = (data) => {
setCheckboxValue("[name='enabled']", data.enabled, sensorForm);
setInputValue("[name='name']", data.name, {}, sensorForm);
setSelectValue("[name='purpose']", data.purpose, sensorForm);
setSelectValue("[name='type']", data.type, sensorForm);
setInputValue("[name='gpio']", data.gpio < 255 ? data.gpio : "", {}, sensorForm);
setInputValue("[name='address']", data.address, {}, sensorForm);
setInputValue("[name='offset']", data.offset, {}, sensorForm);
setInputValue("[name='factor']", data.factor, {}, sensorForm);
setCheckboxValue("[name='filtering']", data.filtering, sensorForm);
setInputValue("[name='filteringFactor']", data.filteringFactor, {}, sensorForm);
sensorForm.querySelector("[name='type']").dispatchEvent(new Event("change"));
setBusy(".form-busy", "form", false, sensorNode);
};
sensorForm.action = sensorForm.action.replace("{id}", sensorId);
sensorForm.querySelector("[name='type']").addEventListener("change", async (event) => {
const gpio = sensorForm.querySelector("[name='gpio']");
const address = sensorForm.querySelector("[name='address']");
const parentGpio = gpio.parentElement;
const parentAddress = address.parentElement;
switch(parseInt(event.target.value)) {
// heating setpoint, manual
case 253:
case 254:
hide(".correction", sensorForm);
hide(".filtering", sensorForm);
break;
// other
default:
show(".correction", sensorForm);
show(".filtering", sensorForm);
}
switch(parseInt(event.target.value)) {
// ntc
case 50:
parentGpio.classList.remove("hidden");
parentAddress.classList.add("hidden");
address.removeAttribute("pattern");
break;
// dallas
case 51:
parentGpio.classList.remove("hidden");
parentAddress.classList.remove("hidden");
address.setAttribute("pattern", "([A-Fa-f0-9]{2}:){7}[A-Fa-f0-9]{2}");
break;
// ble
case 52:
parentGpio.classList.add("hidden");
parentAddress.classList.remove("hidden");
address.setAttribute("pattern", "([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}");
break;
// other
default:
parentGpio.classList.add("hidden");
parentAddress.classList.add("hidden");
address.removeAttribute("pattern");
break;
}
});
sensorNode.addEventListener("click", async (event) => {
if (parseInt(sensorNode.dataset.preloaded)) {
return;
}
try {
const response = await fetch(sensorForm.action, { cache: "no-cache" });
if (response.status != 200) {
return;
}
const result = await response.json();
fillData(result);
sensorNode.dataset.preloaded = 1;
} catch (error) {
console.log(error);
}
});
setupForm(".sensor[data-id='" + sensorId + "'] form", fillData, ['address']);
}
} catch (error) {
console.log(error);
}
});
</script>
</body>
</html>

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n>settings.title</title>
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
<link rel="stylesheet" href="/static/app.css" />
</head>
<body>
@@ -95,12 +95,12 @@
<legend data-i18n>settings.section.diag</legend>
<label for="system-serial-enable">
<input type="checkbox" id="system-serial-enable" name="system[serial][enabled]" value="true">
<input type="checkbox" id="system-serial-enable" name="system[serial][enable]" value="true">
<span data-i18n>settings.system.serial.enable</span>
</label>
<label for="system-telnet-enable">
<input type="checkbox" id="system-telnet-enable" name="system[telnet][enabled]" value="true">
<input type="checkbox" id="system-telnet-enable" name="system[telnet][enable]" value="true">
<span data-i18n>settings.system.telnet.enable</span>
</label>
@@ -245,7 +245,7 @@
<form action="/api/settings" id="equitherm-settings" class="hidden">
<fieldset>
<label for="equitherm-enable">
<input type="checkbox" id="equitherm-enable" name="equitherm[enabled]" value="true">
<input type="checkbox" id="equitherm-enable" name="equitherm[enable]" value="true">
<span data-i18n>settings.enable</span>
</label>
</fieldset>
@@ -282,7 +282,7 @@
<form action="/api/settings" id="pid-settings" class="hidden">
<fieldset>
<label for="pid-enable">
<input type="checkbox" id="pid-enable" name="pid[enabled]" value="true">
<input type="checkbox" id="pid-enable" name="pid[enable]" value="true">
<span data-i18n>settings.enable</span>
</label>
</fieldset>
@@ -371,13 +371,8 @@
<div class="grid">
<label for="opentherm-member-id-code">
<span data-i18n>settings.ot.memberId</span>
<input type="number" inputmode="numeric" id="opentherm-member-id" name="opentherm[memberId]" min="0" max="255" step="1" required>
</label>
<label for="opentherm-flags">
<span data-i18n>settings.ot.flags</span>
<input type="number" inputmode="numeric" id="opentherm-flags" name="opentherm[flags]" min="0" max="255" step="1" required>
<span data-i18n>settings.ot.memberIdCode</span>
<input type="number" inputmode="numeric" id="opentherm-member-id-code" name="opentherm[memberIdCode]" min="0" max="65535" step="1" required>
</label>
<label for="opentherm-max-modulation">
@@ -455,6 +450,44 @@
<small data-i18n>settings.ot.nativeHeating.note</small>
</label>
</fieldset>
<hr />
<details>
<summary role="button" class="secondary" data-i18n>settings.ot.advanced</summary>
<div>
<div class="grid">
<label for="opentherm-pressure-factor">
<span data-i18n>settings.ot.pressureFactor.title</span>
<input type="number" inputmode="numeric" id="opentherm-pressure-factor" name="opentherm[pressureFactor]" min="0.1" max="100" step="0.01">
<small data-i18n>settings.ot.pressureFactor.note</small>
</label>
<label for="opentherm-dhw-fr-factor">
<span data-i18n>settings.ot.dhwFlowRateFactor.title</span>
<input type="number" inputmode="numeric" id="opentherm-dhw-fr-factor" name="opentherm[dhwFlowRateFactor]" min="0.1" max="100" step="0.01">
<small data-i18n>settings.ot.dhwFlowRateFactor.note</small>
</label>
</div>
<hr />
<fieldset>
<legend data-i18n>settings.ot.fnv.desc</legend>
<label for="opentherm-fnv-enable">
<input type="checkbox" id="opentherm-fnv-enable" name="opentherm[filterNumValues][enable]" value="true">
<span data-i18n>settings.ot.fnv.enable.title</span>
<br>
<small data-i18n>settings.ot.fnv.enable.note</small>
</label>
<label for="opentherm-fnv-factor">
<span data-i18n>settings.ot.fnv.factor.title</span>
<input type="number" inputmode="numeric" id="opentherm-fnv-factor" name="opentherm[filterNumValues][factor]" min="0.01" max="1" step="0.01">
<small data-i18n>settings.ot.fnv.factor.note</small>
</label>
</fieldset>
</div>
</details>
<button type="submit" data-i18n>button.save</button>
</form>
@@ -470,7 +503,7 @@
<form action="/api/settings" id="mqtt-settings" class="hidden">
<fieldset>
<label for="mqtt-enable">
<input type="checkbox" id="mqtt-enable" name="mqtt[enabled]" value="true">
<input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true">
<span data-i18n>settings.enable</span>
</label>
@@ -523,6 +556,120 @@
<hr />
<details>
<summary><b data-i18n>settings.section.outdorSensor</b></summary>
<div>
<div id="outdoor-sensor-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="outdoor-sensor-settings" class="hidden">
<fieldset>
<legend data-i18n>settings.tempSensor.source.type</legend>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
<span data-i18n>settings.tempSensor.source.boilerOutdoor</span>
</label>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" />
<span data-i18n>settings.tempSensor.source.manual</span>
</label>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
<span data-i18n>settings.tempSensor.source.ext</span>
</label>
<label>
<input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="3" />
<span data-i18n>settings.tempSensor.source.ble</span>
</label>
</fieldset>
<div class="grid">
<label for="outdoor-sensor-gpio">
<span data-i18n>settings.tempSensor.gpio</span>
<input type="number" inputmode="numeric" id="outdoor-sensor-gpio" name="sensors[outdoor][gpio]" min="0" max="254" step="1">
</label>
<label for="outdoor-sensor-ble-addresss">
<span data-i18n>settings.tempSensor.bleAddress</span>
<input type="text" id="outdoor-sensor-ble-addresss" name="sensors[outdoor][bleAddress]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
</label>
</div>
<label for="outdoor-sensor-offset">
<span data-i18n>settings.tempSensor.offset</span>
<input type="number" inputmode="numeric" id="outdoor-sensor-offset" name="sensors[outdoor][offset]" min="-20" max="20" step="0.01" required>
</label>
<fieldset>
<mark data-i18n>settings.note.bleDevice</mark>
</fieldset>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b data-i18n>settings.section.indoorSensor</b></summary>
<div>
<div id="indoor-sensor-settings-busy" aria-busy="true"></div>
<form action="/api/settings" id="indoor-sensor-settings" class="hidden">
<fieldset>
<legend data-i18n>settings.tempSensor.source.type</legend>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="4" />
<span data-i18n>settings.tempSensor.source.boilerReturn</span>
</label>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
<span data-i18n>settings.tempSensor.source.manual</span>
</label>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="2" />
<span data-i18n>settings.tempSensor.source.ext</span>
</label>
<label>
<input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="3" />
<span data-i18n>settings.tempSensor.source.ble</span>
</label>
</fieldset>
<div class="grid">
<label for="indoor-sensor-gpio">
<span data-i18n>settings.tempSensor.gpio</span>
<input type="number" inputmode="numeric" id="indoor-sensor-gpio" name="sensors[indoor][gpio]" min="0" max="254" step="1">
</label>
<label for="indoor-sensor-ble-addresss">
<span data-i18n>settings.tempSensor.bleAddress</span>
<input type="text" id="indoor-sensor-ble-addresss" name="sensors[indoor][bleAddress]" pattern="([A-Fa-f0-9]{2}:){5}[A-Fa-f0-9]{2}">
</label>
</div>
<label for="indoor-sensor-offset">
<span data-i18n>settings.tempSensor.offset</span>
<input type="number" inputmode="numeric" id="indoor-sensor-offset" name="sensors[indoor][offset]" min="-20" max="20" step="0.01" required>
</label>
<fieldset>
<mark data-i18n>settings.note.bleDevice</mark>
</fieldset>
<button type="submit" data-i18n>button.save</button>
</form>
</div>
</details>
<hr />
<details>
<summary><b data-i18n>settings.section.extPump</b></summary>
<div>
@@ -573,9 +720,9 @@
<form action="/api/settings" id="cc-settings" class="hidden">
<fieldset>
<label for="cc-input-enable">
<input type="checkbox" id="cc-input-enable" name="cascadeControl[input][enabled]" value="true">
<input type="checkbox" id="cc-input-enable" name="cascadeControl[input][enable]" value="true">
<span data-i18n>settings.cascadeControl.input.enable</span>
<br />
<br>
<small data-i18n>settings.cascadeControl.input.desc</small>
</label>
@@ -601,9 +748,9 @@
<fieldset>
<label for="cc-output-enable">
<input type="checkbox" id="cc-output-enable" name="cascadeControl[output][enabled]" value="true">
<input type="checkbox" id="cc-output-enable" name="cascadeControl[output][enable]" value="true">
<span data-i18n>settings.cascadeControl.output.enable</span>
<br />
<br>
<small data-i18n>settings.cascadeControl.output.desc</small>
</label>
@@ -662,18 +809,19 @@
</small>
</footer>
<script src="/static/app.js?{BUILD_TIME}"></script>
<script src="/static/app.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));
lang.build();
const fillData = (data) => {
// System
setSelectValue('#system-log-level', data.system.logLevel);
setCheckboxValue('#system-serial-enable', data.system.serial.enabled);
setCheckboxValue('#system-serial-enable', data.system.serial.enable);
setSelectValue('#system-serial-baudrate', data.system.serial.baudrate);
setCheckboxValue('#system-telnet-enable', data.system.telnet.enabled);
setCheckboxValue('#system-telnet-enable', data.system.telnet.enable);
setInputValue('#system-telnet-port', data.system.telnet.port);
setRadioValue('.system-unit-system', data.system.unitSystem);
setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : '');
@@ -690,9 +838,10 @@
setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : '');
setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : '');
setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : '');
setInputValue('#opentherm-member-id', data.opentherm.memberId);
setInputValue('#opentherm-flags', data.opentherm.flags);
setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode);
setInputValue('#opentherm-max-modulation', data.opentherm.maxModulation);
setInputValue('#opentherm-pressure-factor', data.opentherm.pressureFactor);
setInputValue('#opentherm-dhw-fr-factor', data.opentherm.dhwFlowRateFactor);
setInputValue('#opentherm-min-power', data.opentherm.minPower);
setInputValue('#opentherm-max-power', data.opentherm.maxPower);
setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent);
@@ -705,10 +854,12 @@
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl);
setCheckboxValue('#opentherm-immergas-fix', data.opentherm.immergasFix);
setCheckboxValue('#opentherm-fnv-enable', data.opentherm.filterNumValues.enable);
setInputValue('#opentherm-fnv-factor', data.opentherm.filterNumValues.factor);
setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
// MQTT
setCheckboxValue('#mqtt-enable', data.mqtt.enabled);
setCheckboxValue('#mqtt-enable', data.mqtt.enable);
setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery);
setInputValue('#mqtt-server', data.mqtt.server);
setInputValue('#mqtt-port', data.mqtt.port);
@@ -718,6 +869,20 @@
setInputValue('#mqtt-interval', data.mqtt.interval);
setBusy('#mqtt-settings-busy', '#mqtt-settings', false);
// Outdoor sensor
setRadioValue('.outdoor-sensor-type', data.sensors.outdoor.type);
setInputValue('#outdoor-sensor-gpio', data.sensors.outdoor.gpio < 255 ? data.sensors.outdoor.gpio : '');
setInputValue('#outdoor-sensor-offset', data.sensors.outdoor.offset);
setInputValue('#outdoor-sensor-ble-addresss', data.sensors.outdoor.bleAddress);
setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false);
// Indoor sensor
setRadioValue('.indoor-sensor-type', data.sensors.indoor.type);
setInputValue('#indoor-sensor-gpio', data.sensors.indoor.gpio < 255 ? data.sensors.indoor.gpio : '');
setInputValue('#indoor-sensor-offset', data.sensors.indoor.offset);
setInputValue('#indoor-sensor-ble-addresss', data.sensors.indoor.bleAddress);
setBusy('#indoor-sensor-settings-busy', '#indoor-sensor-settings', false);
// Extpump
setCheckboxValue('#extpump-use', data.externalPump.use);
setInputValue('#extpump-gpio', data.externalPump.gpio < 255 ? data.externalPump.gpio : '');
@@ -727,12 +892,12 @@
setBusy('#extpump-settings-busy', '#extpump-settings', false);
// Cascade control
setCheckboxValue('#cc-input-enable', data.cascadeControl.input.enabled);
setCheckboxValue('#cc-input-enable', data.cascadeControl.input.enable);
setInputValue('#cc-input-gpio', data.cascadeControl.input.gpio < 255 ? data.cascadeControl.input.gpio : '');
setCheckboxValue('#cc-input-invert-state', data.cascadeControl.input.invertState);
setInputValue('#cc-input-tt', data.cascadeControl.input.thresholdTime);
setCheckboxValue('#cc-output-enable', data.cascadeControl.output.enabled);
setCheckboxValue('#cc-output-enable', data.cascadeControl.output.enable);
setInputValue('#cc-output-gpio', data.cascadeControl.output.gpio < 255 ? data.cascadeControl.output.gpio : '');
setCheckboxValue('#cc-output-invert-state', data.cascadeControl.output.invertState);
setInputValue('#cc-output-tt', data.cascadeControl.output.thresholdTime);
@@ -783,20 +948,20 @@
setBusy('#emergency-settings-busy', '#emergency-settings', false);
// Equitherm
setCheckboxValue('#equitherm-enable', data.equitherm.enabled);
setCheckboxValue('#equitherm-enable', data.equitherm.enable);
setInputValue('#equitherm-n-factor', data.equitherm.n_factor);
setInputValue('#equitherm-k-factor', data.equitherm.k_factor);
setInputValue('#equitherm-t-factor', data.equitherm.t_factor);
setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
// PID
setCheckboxValue('#pid-enable', data.pid.enabled);
setCheckboxValue('#pid-enable', data.pid.enable);
setInputValue('#pid-p-factor', data.pid.p_factor);
setInputValue('#pid-i-factor', data.pid.i_factor);
setInputValue('#pid-d-factor', data.pid.d_factor);
setInputValue('#pid-dt', data.pid.dt);
setInputValue('#pid-min-temp', data.pid.minTemp, {
"min": data.equitherm.enabled ? (data.system.unitSystem == 0 ? -100 : -146) : (data.system.unitSystem == 0 ? 0 : 32),
"min": data.equitherm.enable ? (data.system.unitSystem == 0 ? -100 : -146) : (data.system.unitSystem == 0 ? 0 : 32),
"max": (data.system.unitSystem == 0 ? 99 : 211)
});
setInputValue('#pid-max-temp', data.pid.maxTemp, {
@@ -824,6 +989,8 @@
setupForm('#pid-settings', fillData);
setupForm('#opentherm-settings', fillData);
setupForm('#mqtt-settings', fillData, ['mqtt.user', 'mqtt.password', 'mqtt.prefix']);
setupForm('#outdoor-sensor-settings', fillData);
setupForm('#indoor-sensor-settings', fillData, ['sensors.indoor.bleAddress']);
setupForm('#extpump-settings', fillData);
setupForm('#cc-settings', fillData);

View File

@@ -4,7 +4,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n>upgrade.title</title>
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}">
<link rel="stylesheet" href="/static/app.css">
</head>
<body>
@@ -97,7 +97,7 @@
</small>
</footer>
<script src="/static/app.js?{BUILD_TIME}"></script>
<script src="/static/app.js"></script>
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));

View File

@@ -1,4 +1,4 @@
const setupForm = (formSelector, onResultCallback = null, noCastItems = []) => {
function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
const form = document.querySelector(formSelector);
if (!form) {
return;
@@ -10,13 +10,13 @@ const setupForm = (formSelector, onResultCallback = null, noCastItems = []) => {
})
});
const url = form.action;
let button = form.querySelector('button[type="submit"]');
let defaultText;
form.addEventListener('submit', async (event) => {
event.preventDefault();
const url = form.action;
let button = form.querySelector('button[type="submit"]');
let defaultText;
if (button) {
defaultText = button.textContent;
button.textContent = i18n("button.wait");
@@ -86,7 +86,7 @@ const setupForm = (formSelector, onResultCallback = null, noCastItems = []) => {
});
}
const setupNetworkScanForm = (formSelector, tableSelector) => {
function setupNetworkScanForm(formSelector, tableSelector) {
const form = document.querySelector(formSelector);
if (!form) {
console.error("form not found");
@@ -132,7 +132,7 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
let row = tbody.insertRow(-1);
row.classList.add("network");
row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid);
row.onclick = () => {
row.onclick = function () {
const input = document.querySelector('input#sta-ssid');
const ssid = this.getAttribute('data-ssid');
if (!input || !ssid) {
@@ -246,7 +246,7 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
onSubmitFn();
}
const setupRestoreBackupForm = (formSelector) => {
function setupRestoreBackupForm(formSelector) {
const form = document.querySelector(formSelector);
if (!form) {
return;
@@ -266,7 +266,7 @@ const setupRestoreBackupForm = (formSelector) => {
button.setAttribute('aria-busy', true);
}
const onSuccess = () => {
const onSuccess = (response) => {
if (button) {
button.textContent = i18n('button.restored');
button.classList.add('success');
@@ -280,7 +280,7 @@ const setupRestoreBackupForm = (formSelector) => {
}
};
const onFailed = () => {
const onFailed = (response) => {
if (button) {
button.textContent = i18n('button.error');
button.classList.add('failed');
@@ -302,79 +302,35 @@ const setupRestoreBackupForm = (formSelector) => {
let reader = new FileReader();
reader.readAsText(files[0]);
reader.onload = async (event) => {
reader.onload = async function () {
try {
const data = JSON.parse(event.target.result);
console.log("Backup: ", data);
let response = await fetch(url, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: reader.result
});
if (data.settings != undefined) {
let response = await fetch(url, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({"settings": data.settings})
});
if (response.ok) {
onSuccess(response);
if (!response.ok) {
onFailed();
return;
}
} else {
onFailed(response);
}
if (data.sensors != undefined) {
for (const sensorId in data.sensors) {
const payload = {
"sensors": {}
};
payload["sensors"][sensorId] = data.sensors[sensorId];
const response = await fetch(url, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
if (!response.ok) {
onFailed();
return;
}
}
}
if (data.network != undefined) {
let response = await fetch(url, {
method: 'POST',
cache: 'no-cache',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({"network": data.network})
});
if (!response.ok) {
onFailed();
return;
}
}
onSuccess();
} catch (err) {
onFailed();
onFailed(false);
}
};
reader.onerror = () => {
reader.onerror = function () {
console.log(reader.error);
};
});
}
const setupUpgradeForm = (formSelector) => {
function setupUpgradeForm(formSelector) {
const form = document.querySelector(formSelector);
if (!form) {
return;
@@ -515,23 +471,19 @@ const setupUpgradeForm = (formSelector) => {
}
const setBusy = (busySelector, contentSelector, value, parent = undefined) => {
function setBusy(busySelector, contentSelector, value) {
if (!value) {
hide(busySelector, parent);
show(contentSelector, parent);
hide(busySelector);
show(contentSelector);
} else {
show(busySelector, parent);
hide(contentSelector, parent);
show(busySelector);
hide(contentSelector);
}
}
const setState = (selector, value, parent = undefined) => {
if (parent == undefined) {
parent = document;
}
let item = parent.querySelector(selector);
function setState(selector, value) {
let item = document.querySelector(selector);
if (!item) {
return;
}
@@ -539,12 +491,8 @@ const setState = (selector, value, parent = undefined) => {
item.setAttribute('aria-invalid', !value);
}
const setValue = (selector, value, parent = undefined) => {
if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
function setValue(selector, value) {
let items = document.querySelectorAll(selector);
if (!items.length) {
return;
}
@@ -554,12 +502,8 @@ const setValue = (selector, value, parent = undefined) => {
}
}
const setCheckboxValue = (selector, value, parent = undefined) => {
if (parent == undefined) {
parent = document;
}
let item = parent.querySelector(selector);
function setCheckboxValue(selector, value) {
let item = document.querySelector(selector);
if (!item) {
return;
}
@@ -567,12 +511,8 @@ const setCheckboxValue = (selector, value, parent = undefined) => {
item.checked = value;
}
const setRadioValue = (selector, value, parent = undefined) => {
if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
function setRadioValue(selector, value) {
let items = document.querySelectorAll(selector);
if (!items.length) {
return;
}
@@ -582,12 +522,8 @@ const setRadioValue = (selector, value, parent = undefined) => {
}
}
const setInputValue = (selector, value, attrs = {}, parent = undefined) => {
if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
function setInputValue(selector, value, attrs = {}) {
let items = document.querySelectorAll(selector);
if (!items.length) {
return;
}
@@ -603,12 +539,8 @@ const setInputValue = (selector, value, attrs = {}, parent = undefined) => {
}
}
const setSelectValue = (selector, value, parent = undefined) => {
if (parent == undefined) {
parent = document;
}
let item = parent.querySelector(selector);
function setSelectValue(selector, value) {
let item = document.querySelector(selector);
if (!item) {
return;
}
@@ -618,12 +550,8 @@ const setSelectValue = (selector, value, parent = undefined) => {
}
}
const show = (selector, parent = undefined) => {
if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
function show(selector) {
let items = document.querySelectorAll(selector);
if (!items.length) {
return;
}
@@ -635,12 +563,8 @@ const show = (selector, parent = undefined) => {
}
}
const hide = (selector, parent = undefined) => {
if (parent == undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
function hide(selector) {
let items = document.querySelectorAll(selector);
if (!items.length) {
return;
}
@@ -658,28 +582,28 @@ function unit2str(unitSystem, units = {}, defaultValue = '?') {
: defaultValue;
}
const temperatureUnit = (unitSystem) => {
function temperatureUnit(unitSystem) {
return unit2str(unitSystem, {
0: "°C",
1: "°F"
});
}
const pressureUnit = (unitSystem) => {
function pressureUnit(unitSystem) {
return unit2str(unitSystem, {
0: "bar",
1: "psi"
});
}
const volumeUnit = (unitSystem) => {
function volumeUnit(unitSystem) {
return unit2str(unitSystem, {
0: "L",
1: "gal"
});
}
const memberIdToVendor = (memberId) => {
function memberIdToVendor(memberId) {
// https://github.com/Jeroen88/EasyOpenTherm/blob/main/src/EasyOpenTherm.h
// https://github.com/Evgen2/SmartTherm/blob/v0.7/src/Web.cpp
const vendorList = {