mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-10 18:24:27 +05:00
chore: bump version to 1.5.0
This commit is contained in:
17
gulpfile.js
17
gulpfile.js
@@ -6,6 +6,7 @@ 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 = {
|
||||
@@ -59,6 +60,10 @@ const styles = (cb) => {
|
||||
const items = paths.styles.bundles[name];
|
||||
|
||||
src(items)
|
||||
.pipe(replace(
|
||||
"{BUILD_TIME}",
|
||||
Math.floor(Date.now() / 1000)
|
||||
))
|
||||
.pipe(postcss([
|
||||
cssnano({ preset: 'advanced' })
|
||||
]))
|
||||
@@ -77,6 +82,10 @@ const scripts = (cb) => {
|
||||
const items = paths.scripts.bundles[name];
|
||||
|
||||
src(items)
|
||||
.pipe(replace(
|
||||
"{BUILD_TIME}",
|
||||
Math.floor(Date.now() / 1000)
|
||||
))
|
||||
.pipe(terser().on('error', console.error))
|
||||
.pipe(concat(name))
|
||||
.pipe(gzip({
|
||||
@@ -93,6 +102,10 @@ const jsonFiles = (cb) => {
|
||||
const item = paths.json[i];
|
||||
|
||||
src(item.src)
|
||||
.pipe(replace(
|
||||
"{BUILD_TIME}",
|
||||
Math.floor(Date.now() / 1000)
|
||||
))
|
||||
.pipe(jsonminify())
|
||||
.pipe(gzip({
|
||||
append: true
|
||||
@@ -119,6 +132,10 @@ 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,
|
||||
|
||||
@@ -10,7 +10,8 @@ public:
|
||||
free(this->buffer);
|
||||
}
|
||||
|
||||
void send(int code, const char* contentType, JsonDocument& content, bool pretty = false) {
|
||||
template <class T>
|
||||
void send(int code, T contentType, const JsonVariantConst 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"));
|
||||
@@ -76,10 +77,19 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
this->webServer->sendContent((const char*)this->buffer, this->bufferPos);
|
||||
this->bufferPos = 0;
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
|
||||
auto& client = this->webServer->client();
|
||||
if (client.connected()) {
|
||||
this->webServer->sendContent((const char*)this->buffer, this->bufferPos);
|
||||
}
|
||||
|
||||
this->bufferPos = 0;
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -98,66 +98,6 @@ 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;
|
||||
|
||||
@@ -100,8 +100,8 @@ public:
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
String getTopic(T category, T name, char nameSeparator = '/') {
|
||||
template <class CT, class NT>
|
||||
String makeConfigTopic(CT category, NT name, char nameSeparator = '/') {
|
||||
String topic = "";
|
||||
topic.concat(this->prefix);
|
||||
topic.concat('/');
|
||||
@@ -110,21 +110,45 @@ public:
|
||||
topic.concat(this->devicePrefix);
|
||||
topic.concat(nameSeparator);
|
||||
topic.concat(name);
|
||||
topic.concat("/config");
|
||||
topic.concat(F("/config"));
|
||||
return topic;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
String getDeviceTopic(T value, char separator = '/') {
|
||||
String getDeviceTopic(T value, char dpvSeparator = '/') {
|
||||
String topic = "";
|
||||
topic.concat(this->devicePrefix);
|
||||
topic.concat(separator);
|
||||
topic.concat(dpvSeparator);
|
||||
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 getObjectId(T value, char separator = '_') {
|
||||
String getObjectIdWithPrefix(T value, char separator = '_') {
|
||||
String topic = "";
|
||||
topic.concat(this->devicePrefix);
|
||||
topic.concat(separator);
|
||||
|
||||
@@ -25,6 +25,8 @@ 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";
|
||||
@@ -35,16 +37,21 @@ 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";
|
||||
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
bool publish(const char* topic, JsonDocument& doc, bool retained = false) {
|
||||
bool publish(const char* topic, const JsonVariantConst doc, bool retained = false) {
|
||||
if (!this->client->connected()) {
|
||||
this->bufferPos = 0;
|
||||
return false;
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
}
|
||||
|
||||
if (this->cacheHeader != nullptr) {
|
||||
server.sendHeader("Cache-Control", this->cacheHeader);
|
||||
server.sendHeader(F("Cache-Control"), this->cacheHeader);
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
@@ -218,7 +218,6 @@ protected:
|
||||
CanHandleCallback canHandleCallback;
|
||||
BeforeSendCallback beforeSendCallback;
|
||||
TemplateCallback templateCallback;
|
||||
String eTag;
|
||||
const char* uri = nullptr;
|
||||
const char* path = nullptr;
|
||||
const char* cacheHeader = nullptr;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
#include <FS.h>
|
||||
#include <detail/mimetable.h>
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include <detail/RequestHandlersImpl.h>
|
||||
#endif
|
||||
|
||||
using namespace mime;
|
||||
|
||||
@@ -8,7 +11,8 @@ public:
|
||||
typedef std::function<bool(HTTPMethod, const String&)> CanHandleCallback;
|
||||
typedef std::function<bool()> BeforeSendCallback;
|
||||
|
||||
StaticPage(const char* uri, FS* fs, const char* path, const char* cacheHeader = nullptr) {
|
||||
template <class T>
|
||||
StaticPage(const char* uri, FS* fs, T path, const char* cacheHeader = nullptr) {
|
||||
this->uri = uri;
|
||||
this->fs = fs;
|
||||
this->path = path;
|
||||
@@ -46,21 +50,25 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
if (server._eTagEnabled) {
|
||||
if (this->eTag.isEmpty()) {
|
||||
if (server._eTagFunction) {
|
||||
this->eTag = (server._eTagFunction)(*this->fs, this->path);
|
||||
|
||||
} else if (this->eTag.isEmpty()) {
|
||||
} else {
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
this->eTag = esp8266webserver::calcETag(*this->fs, this->path);
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
this->eTag = StaticRequestHandler::calcETag(*this->fs, this->path);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
if (server.header("If-None-Match").equals(this->eTag.c_str())) {
|
||||
if (!this->eTag.isEmpty() && server.header(F("If-None-Match")).equals(this->eTag.c_str())) {
|
||||
server.send(304);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!this->path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !this->fs->exists(path)) {
|
||||
String pathWithGz = this->path + FPSTR(mimeTable[gz].endsWith);
|
||||
@@ -80,14 +88,14 @@ public:
|
||||
}
|
||||
|
||||
if (this->cacheHeader != nullptr) {
|
||||
server.sendHeader("Cache-Control", this->cacheHeader);
|
||||
server.sendHeader(F("Cache-Control"), this->cacheHeader);
|
||||
}
|
||||
|
||||
if (server._eTagEnabled && !this->eTag.isEmpty()) {
|
||||
server.sendHeader(F("ETag"), this->eTag);
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP8266)
|
||||
if (server._eTagEnabled && this->eTag.length() > 0) {
|
||||
server.sendHeader("ETag", this->eTag);
|
||||
}
|
||||
|
||||
server.streamFile(file, F("text/html"), method);
|
||||
#else
|
||||
server.streamFile(file, F("text/html"), 200);
|
||||
|
||||
@@ -93,10 +93,12 @@ public:
|
||||
|
||||
void upload(WebServer& server, const String& uri, HTTPUpload& upload) override {
|
||||
UpgradeResult* result;
|
||||
if (upload.name.equals("firmware")) {
|
||||
if (upload.name.equals(F("firmware"))) {
|
||||
result = &this->firmwareResult;
|
||||
} else if (upload.name.equals("filesystem")) {
|
||||
|
||||
} else if (upload.name.equals(F("filesystem"))) {
|
||||
result = &this->filesystemResult;
|
||||
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
/*
|
||||
This file is needed by the Arduino IDE because the ino file needs to be named as the directory name.
|
||||
Don't worry, the Arduino compiler will "merge" all files, including src/main.cpp
|
||||
*/
|
||||
@@ -12,6 +12,7 @@
|
||||
"gulp-html-minifier-terser": "^7.1.0",
|
||||
"gulp-jsonminify": "^1.1.0",
|
||||
"gulp-postcss": "^10.0.0",
|
||||
"gulp-terser": "^2.1.0"
|
||||
"gulp-terser": "^2.1.0",
|
||||
"gulp-replace": "^1.1.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ extra_configs = secrets.default.ini
|
||||
core_dir = .pio
|
||||
|
||||
[env]
|
||||
version = 1.4.6
|
||||
version = 1.5.0
|
||||
framework = arduino
|
||||
lib_deps =
|
||||
bblanchon/ArduinoJson@^7.1.0
|
||||
@@ -30,17 +30,14 @@ 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_ENABLE=${secrets.serial_enable}
|
||||
-D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled}
|
||||
-D DEFAULT_SERIAL_BAUD=${secrets.serial_baud}
|
||||
-D DEFAULT_TELNET_ENABLE=${secrets.telnet_enable}
|
||||
-D DEFAULT_TELNET_ENABLED=${secrets.telnet_enabled}
|
||||
-D DEFAULT_TELNET_PORT=${secrets.telnet_port}
|
||||
-D DEFAULT_LOG_LEVEL=${secrets.log_level}
|
||||
-D DEFAULT_HOSTNAME='"${secrets.hostname}"'
|
||||
@@ -50,6 +47,7 @@ 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}"'
|
||||
@@ -57,7 +55,10 @@ build_flags =
|
||||
-D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"'
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
monitor_filters = direct
|
||||
;monitor_filters = direct
|
||||
monitor_filters =
|
||||
esp32_exception_decoder
|
||||
esp8266_exception_decoder
|
||||
board_build.flash_mode = dio
|
||||
board_build.filesystem = littlefs
|
||||
|
||||
@@ -71,7 +72,11 @@ lib_ignore =
|
||||
extra_scripts =
|
||||
post:tools/build.py
|
||||
build_type = ${env.build_type}
|
||||
build_flags = ${env.build_flags}
|
||||
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
|
||||
board_build.ldscript = eagle.flash.4m1m.ld
|
||||
|
||||
[esp32_defaults]
|
||||
@@ -80,7 +85,7 @@ board_build.ldscript = eagle.flash.4m1m.ld
|
||||
;platform_packages =
|
||||
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.5
|
||||
; framework-arduinoespressif32-libs @ https://github.com/espressif/esp32-arduino-lib-builder/releases/download/idf-release_v5.1/esp32-arduino-libs-idf-release_v5.1-33fbade6.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc2/platform-espressif32.zip
|
||||
platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.10-rc3/platform-espressif32.zip
|
||||
platform_packages =
|
||||
board_build.partitions = esp32_partitions.csv
|
||||
lib_deps =
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
[secrets]
|
||||
build_type = release
|
||||
|
||||
serial_enable = true
|
||||
serial_enabled = true
|
||||
serial_baud = 115200
|
||||
telnet_enable = true
|
||||
telnet_enabled = true
|
||||
telnet_port = 23
|
||||
log_level = 5
|
||||
hostname = opentherm
|
||||
@@ -17,6 +17,7 @@ sta_password =
|
||||
portal_login = admin
|
||||
portal_password = admin
|
||||
|
||||
mqtt_enabled = false
|
||||
mqtt_server =
|
||||
mqtt_port = 1883
|
||||
mqtt_user =
|
||||
|
||||
1972
src/HaHelper.h
1972
src/HaHelper.h
File diff suppressed because it is too large
Load Diff
159
src/MainTask.h
159
src/MainTask.h
@@ -5,7 +5,7 @@ using namespace NetworkUtils;
|
||||
extern NetworkMgr* network;
|
||||
extern MqttTask* tMqtt;
|
||||
extern OpenThermTask* tOt;
|
||||
extern FileData fsSettings, fsNetworkSettings;
|
||||
extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
|
||||
extern ESPTelnetStream* telnetStream;
|
||||
|
||||
|
||||
@@ -32,7 +32,8 @@ protected:
|
||||
unsigned long lastHeapInfo = 0;
|
||||
unsigned int minFreeHeap = 0;
|
||||
unsigned int minMaxFreeBlockHeap = 0;
|
||||
unsigned long restartSignalTime = 0;
|
||||
bool restartSignalReceived = false;
|
||||
unsigned long restartSignalReceivedTime = 0;
|
||||
bool heatingEnabled = false;
|
||||
unsigned long heatingDisabledTime = 0;
|
||||
PumpStartReason extPumpStartReason = PumpStartReason::NONE;
|
||||
@@ -60,31 +61,46 @@ protected:
|
||||
void loop() {
|
||||
network->loop();
|
||||
|
||||
if (fsSettings.tick() == FD_WRITE) {
|
||||
Log.sinfoln(FPSTR(L_SETTINGS), F("Updated"));
|
||||
}
|
||||
|
||||
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 (vars.actions.restart) {
|
||||
this->restartSignalReceivedTime = millis();
|
||||
this->restartSignalReceived = true;
|
||||
vars.actions.restart = false;
|
||||
this->restartSignalTime = millis();
|
||||
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Received restart signal"));
|
||||
}
|
||||
|
||||
if (!vars.states.restarting && this->restartSignalReceived && millis() - this->restartSignalReceivedTime > 5000) {
|
||||
vars.states.restarting = true;
|
||||
|
||||
// save settings
|
||||
fsSettings.updateNow();
|
||||
|
||||
// save sensors settings
|
||||
fsSensorsSettings.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."));
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Restart scheduled in 10 sec."));
|
||||
}
|
||||
|
||||
vars.states.mqtt = tMqtt->isConnected();
|
||||
vars.sensors.rssi = network->isConnected() ? WiFi.RSSI() : 0;
|
||||
vars.mqtt.connected = tMqtt->isConnected();
|
||||
vars.network.connected = network->isConnected();
|
||||
vars.network.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) {
|
||||
@@ -98,20 +114,14 @@ protected:
|
||||
this->telnetStarted = true;
|
||||
}
|
||||
|
||||
if (settings.mqtt.enable && !tMqtt->isEnabled()) {
|
||||
if (settings.mqtt.enabled && !tMqtt->isEnabled()) {
|
||||
tMqtt->enable();
|
||||
|
||||
} else if (!settings.mqtt.enable && tMqtt->isEnabled()) {
|
||||
} else if (!settings.mqtt.enabled && tMqtt->isEnabled()) {
|
||||
tMqtt->disable();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, !settings.mqtt.enabled || vars.mqtt.connected, false);
|
||||
|
||||
} else {
|
||||
if (this->telnetStarted) {
|
||||
@@ -123,13 +133,7 @@ protected:
|
||||
tMqtt->disable();
|
||||
}
|
||||
|
||||
if (settings.sensors.indoor.type == SensorType::MANUAL) {
|
||||
vars.sensors.indoor.connected = false;
|
||||
}
|
||||
|
||||
if (settings.sensors.outdoor.type == SensorType::MANUAL) {
|
||||
vars.sensors.outdoor.connected = false;
|
||||
}
|
||||
Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, false, false);
|
||||
}
|
||||
this->yield();
|
||||
|
||||
@@ -151,8 +155,9 @@ protected:
|
||||
for (Stream* stream : Log.getStreams()) {
|
||||
while (stream->available() > 0) {
|
||||
stream->read();
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -162,8 +167,9 @@ protected:
|
||||
|
||||
|
||||
// restart
|
||||
if (this->restartSignalTime > 0 && millis() - this->restartSignalTime > 10000) {
|
||||
this->restartSignalTime = 0;
|
||||
if (this->restartSignalReceived && millis() - this->restartSignalReceivedTime > 15000) {
|
||||
this->restartSignalReceived = false;
|
||||
|
||||
ESP.restart();
|
||||
}
|
||||
}
|
||||
@@ -172,8 +178,10 @@ protected:
|
||||
unsigned int freeHeap = getFreeHeap();
|
||||
unsigned int maxFreeBlockHeap = getMaxFreeBlockHeap();
|
||||
|
||||
if (!this->restartSignalTime && (freeHeap < 2048 || maxFreeBlockHeap < 2048)) {
|
||||
this->restartSignalTime = millis();
|
||||
// critical heap
|
||||
if (!vars.states.restarting && (freeHeap < 2048 || maxFreeBlockHeap < 2048)) {
|
||||
this->restartSignalReceivedTime = millis();
|
||||
vars.states.restarting = true;
|
||||
}
|
||||
|
||||
if (settings.system.logLevel < TinyLogger::Level::VERBOSE) {
|
||||
@@ -209,19 +217,22 @@ protected:
|
||||
uint8_t emergencyFlags = 0b00000000;
|
||||
|
||||
// set outdoor sensor flag
|
||||
if (settings.equitherm.enable && !vars.sensors.outdoor.connected) {
|
||||
if (settings.equitherm.enabled) {
|
||||
if (!Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::OUTDOOR_TEMP)) {
|
||||
emergencyFlags |= 0b00000001;
|
||||
}
|
||||
}
|
||||
|
||||
// set indoor sensor flag
|
||||
if (!settings.equitherm.enable && settings.pid.enable && !vars.sensors.indoor.connected) {
|
||||
// set indoor sensor flags
|
||||
if (!Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) {
|
||||
if (!settings.equitherm.enabled && settings.pid.enabled) {
|
||||
emergencyFlags |= 0b00000010;
|
||||
}
|
||||
|
||||
// set indoor sensor flag for OT native heating control
|
||||
if (settings.opentherm.nativeHeatingControl && !vars.sensors.indoor.connected) {
|
||||
if (settings.opentherm.nativeHeatingControl) {
|
||||
emergencyFlags |= 0b00000100;
|
||||
}
|
||||
}
|
||||
|
||||
// if any flags is true
|
||||
if ((emergencyFlags & 0b00001111) != 0) {
|
||||
@@ -230,10 +241,10 @@ protected:
|
||||
this->emergencyDetected = true;
|
||||
this->emergencyFlipTime = millis();
|
||||
|
||||
} else if (this->emergencyDetected && !vars.states.emergency) {
|
||||
} else if (this->emergencyDetected && !vars.emergency.state) {
|
||||
// enable emergency
|
||||
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = true;
|
||||
vars.emergency.state = true;
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled (%hhu)"), emergencyFlags);
|
||||
}
|
||||
}
|
||||
@@ -244,10 +255,10 @@ protected:
|
||||
this->emergencyDetected = false;
|
||||
this->emergencyFlipTime = millis();
|
||||
|
||||
} else if (!this->emergencyDetected && vars.states.emergency) {
|
||||
} else if (!this->emergencyDetected && vars.emergency.state) {
|
||||
// disable emergency
|
||||
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
|
||||
vars.states.emergency = false;
|
||||
vars.emergency.state = false;
|
||||
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled"));
|
||||
}
|
||||
}
|
||||
@@ -286,15 +297,15 @@ protected:
|
||||
errors[errCount++] = 2;
|
||||
}
|
||||
|
||||
if (!vars.states.otStatus) {
|
||||
if (!vars.slave.connected) {
|
||||
errors[errCount++] = 3;
|
||||
}
|
||||
|
||||
if (vars.states.fault) {
|
||||
if (vars.slave.fault.active) {
|
||||
errors[errCount++] = 4;
|
||||
}
|
||||
|
||||
if (vars.states.emergency) {
|
||||
if (vars.emergency.state) {
|
||||
errors[errCount++] = 5;
|
||||
}
|
||||
|
||||
@@ -342,7 +353,7 @@ protected:
|
||||
static unsigned long outputChangedTs = 0;
|
||||
|
||||
// input
|
||||
if (settings.cascadeControl.input.enable) {
|
||||
if (settings.cascadeControl.input.enabled) {
|
||||
if (settings.cascadeControl.input.gpio != configuredInputGpio) {
|
||||
if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
pinMode(configuredInputGpio, OUTPUT);
|
||||
@@ -393,7 +404,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
if (!settings.cascadeControl.input.enable || configuredInputGpio == GPIO_IS_NOT_CONFIGURED) {
|
||||
if (!settings.cascadeControl.input.enabled || configuredInputGpio == GPIO_IS_NOT_CONFIGURED) {
|
||||
if (!vars.cascadeControl.input) {
|
||||
vars.cascadeControl.input = true;
|
||||
|
||||
@@ -407,7 +418,7 @@ protected:
|
||||
|
||||
|
||||
// output
|
||||
if (settings.cascadeControl.output.enable) {
|
||||
if (settings.cascadeControl.output.enabled) {
|
||||
if (settings.cascadeControl.output.gpio != configuredOutputGpio) {
|
||||
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
pinMode(configuredOutputGpio, OUTPUT);
|
||||
@@ -437,13 +448,13 @@ protected:
|
||||
|
||||
if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
|
||||
bool value = false;
|
||||
if (settings.cascadeControl.output.onFault && vars.states.fault) {
|
||||
if (settings.cascadeControl.output.onFault && vars.slave.fault.active) {
|
||||
value = true;
|
||||
|
||||
} else if (settings.cascadeControl.output.onLossConnection && !vars.states.otStatus) {
|
||||
} else if (settings.cascadeControl.output.onLossConnection && !vars.slave.connected) {
|
||||
value = true;
|
||||
|
||||
} else if (settings.cascadeControl.output.onEnabledHeating && settings.heating.enable && vars.cascadeControl.input) {
|
||||
} else if (settings.cascadeControl.output.onEnabledHeating && settings.heating.enabled && vars.cascadeControl.input) {
|
||||
value = true;
|
||||
}
|
||||
|
||||
@@ -475,7 +486,7 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
if (!settings.cascadeControl.output.enable || configuredOutputGpio == GPIO_IS_NOT_CONFIGURED) {
|
||||
if (!settings.cascadeControl.output.enabled || configuredOutputGpio == GPIO_IS_NOT_CONFIGURED) {
|
||||
if (vars.cascadeControl.output) {
|
||||
vars.cascadeControl.output = false;
|
||||
|
||||
@@ -516,75 +527,75 @@ protected:
|
||||
}
|
||||
|
||||
if (configuredGpio == GPIO_IS_NOT_CONFIGURED) {
|
||||
if (vars.states.externalPump) {
|
||||
vars.states.externalPump = false;
|
||||
vars.parameters.extPumpLastEnableTime = millis();
|
||||
if (vars.externalPump.state) {
|
||||
vars.externalPump.state = false;
|
||||
vars.externalPump.lastEnabledTime = millis();
|
||||
|
||||
Log.sinfoln("EXTPUMP", F("Disabled: use = off"));
|
||||
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off"));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vars.states.heating && this->heatingEnabled) {
|
||||
if (!vars.master.heating.enabled && this->heatingEnabled) {
|
||||
this->heatingEnabled = false;
|
||||
this->heatingDisabledTime = millis();
|
||||
|
||||
} else if (vars.states.heating && !this->heatingEnabled) {
|
||||
} else if (vars.master.heating.enabled && !this->heatingEnabled) {
|
||||
this->heatingEnabled = true;
|
||||
}
|
||||
|
||||
if (!settings.externalPump.use) {
|
||||
if (vars.states.externalPump) {
|
||||
if (vars.externalPump.state) {
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
|
||||
vars.states.externalPump = false;
|
||||
vars.parameters.extPumpLastEnableTime = millis();
|
||||
vars.externalPump.state = false;
|
||||
vars.externalPump.lastEnabledTime = millis();
|
||||
|
||||
Log.sinfoln("EXTPUMP", F("Disabled: use = off"));
|
||||
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off"));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (vars.states.externalPump && !this->heatingEnabled) {
|
||||
if (vars.externalPump.state && !this->heatingEnabled) {
|
||||
if (this->extPumpStartReason == MainTask::PumpStartReason::HEATING && millis() - this->heatingDisabledTime > (settings.externalPump.postCirculationTime * 1000u)) {
|
||||
digitalWrite(configuredGpio, LOW);
|
||||
|
||||
vars.states.externalPump = false;
|
||||
vars.parameters.extPumpLastEnableTime = millis();
|
||||
vars.externalPump.state = false;
|
||||
vars.externalPump.lastEnabledTime = millis();
|
||||
|
||||
Log.sinfoln("EXTPUMP", F("Disabled: expired post circulation time"));
|
||||
Log.sinfoln(FPSTR(L_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.states.externalPump = false;
|
||||
vars.parameters.extPumpLastEnableTime = millis();
|
||||
vars.externalPump.state = false;
|
||||
vars.externalPump.lastEnabledTime = millis();
|
||||
|
||||
Log.sinfoln("EXTPUMP", F("Disabled: expired anti stuck time"));
|
||||
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired anti stuck time"));
|
||||
}
|
||||
|
||||
} else if (vars.states.externalPump && this->heatingEnabled && this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK) {
|
||||
} else if (vars.externalPump.state && this->heatingEnabled && this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK) {
|
||||
this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
|
||||
|
||||
} else if (!vars.states.externalPump && this->heatingEnabled) {
|
||||
vars.states.externalPump = true;
|
||||
} else if (!vars.externalPump.state && this->heatingEnabled) {
|
||||
vars.externalPump.state = true;
|
||||
this->externalPumpStartTime = millis();
|
||||
this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
|
||||
|
||||
digitalWrite(configuredGpio, HIGH);
|
||||
|
||||
Log.sinfoln("EXTPUMP", F("Enabled: heating on"));
|
||||
Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: heating on"));
|
||||
|
||||
} else if (!vars.states.externalPump && (vars.parameters.extPumpLastEnableTime == 0 || millis() - vars.parameters.extPumpLastEnableTime >= (settings.externalPump.antiStuckInterval * 1000ul))) {
|
||||
vars.states.externalPump = true;
|
||||
} else if (!vars.externalPump.state && (vars.externalPump.lastEnabledTime == 0 || millis() - vars.externalPump.lastEnabledTime >= (settings.externalPump.antiStuckInterval * 1000lu))) {
|
||||
vars.externalPump.state = true;
|
||||
this->externalPumpStartTime = millis();
|
||||
this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK;
|
||||
|
||||
digitalWrite(configuredGpio, HIGH);
|
||||
|
||||
Log.sinfoln("EXTPUMP", F("Enabled: anti stuck"));
|
||||
Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: anti stuck"));
|
||||
}
|
||||
}
|
||||
};
|
||||
361
src/MqttTask.h
361
src/MqttTask.h
@@ -1,3 +1,4 @@
|
||||
#include <unordered_map>
|
||||
#include <MqttClient.h>
|
||||
#include <MqttWiFiClient.h>
|
||||
#include <MqttWriter.h>
|
||||
@@ -61,10 +62,18 @@ 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;
|
||||
@@ -72,12 +81,14 @@ 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;
|
||||
|
||||
@@ -118,7 +129,7 @@ protected:
|
||||
#endif
|
||||
|
||||
this->client->onMessage([this] (void*, size_t length) {
|
||||
String topic = this->client->messageTopic();
|
||||
const String& topic = this->client->messageTopic();
|
||||
if (!length || length > 2048 || !topic.length()) {
|
||||
return;
|
||||
}
|
||||
@@ -128,7 +139,7 @@ protected:
|
||||
payload[i] = this->client->read();
|
||||
}
|
||||
|
||||
this->onMessage(topic.c_str(), payload, length);
|
||||
this->onMessage(topic, payload, length);
|
||||
});
|
||||
|
||||
// writer settings
|
||||
@@ -142,7 +153,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
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
|
||||
//this->client->poll();
|
||||
@@ -151,13 +162,13 @@ protected:
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
this->writer->setFlushEventCallback([this] (size_t, size_t) {
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
|
||||
if (this->wifiClient->connected()) {
|
||||
this->wifiClient->flush();
|
||||
}
|
||||
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
});
|
||||
#endif
|
||||
|
||||
@@ -173,9 +184,8 @@ protected:
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (settings.mqtt.interval > 120) {
|
||||
settings.mqtt.interval = 5;
|
||||
fsSettings.update();
|
||||
if (vars.states.restarting || vars.states.upgrading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->connected && !this->client->connected()) {
|
||||
@@ -186,9 +196,15 @@ 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();
|
||||
@@ -210,22 +226,44 @@ protected:
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
|
||||
// publish variables and status
|
||||
if (this->newConnection || millis() - this->prevPubVarsTime > (settings.mqtt.interval * 1000u)) {
|
||||
this->writer->publish(this->haHelper->getDeviceTopic("status").c_str(), "online", false);
|
||||
this->publishVariables(this->haHelper->getDeviceTopic("state").c_str());
|
||||
this->writer->publish(this->haHelper->getDeviceTopic(F("status")).c_str(), "online", false);
|
||||
this->publishVariables(this->haHelper->getDeviceTopic(F("state")).c_str());
|
||||
this->prevPubVarsTime = millis();
|
||||
}
|
||||
|
||||
// publish settings
|
||||
if (this->newConnection || millis() - this->prevPubSettingsTime > (settings.mqtt.interval * 10000u)) {
|
||||
this->publishSettings(this->haHelper->getDeviceTopic("settings").c_str());
|
||||
this->publishSettings(this->haHelper->getDeviceTopic(F("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) {
|
||||
@@ -239,6 +277,79 @@ 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;
|
||||
}
|
||||
@@ -254,24 +365,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("settings/set").c_str());
|
||||
this->client->subscribe(this->haHelper->getDeviceTopic("state/set").c_str());
|
||||
this->client->subscribe(this->haHelper->getDeviceTopic(F("settings/set")).c_str());
|
||||
this->client->subscribe(this->haHelper->getDeviceTopic(F("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: %u s.)"), this->client->connectError(), uptime);
|
||||
Log.swarningln(FPSTR(L_MQTT), F("Disconnected (reason: %d uptime: %lu s.)"), this->client->connectError(), uptime);
|
||||
}
|
||||
|
||||
void onMessage(const char* topic, uint8_t* payload, size_t length) {
|
||||
void onMessage(const String& 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);
|
||||
Log.strace(FPSTR(L_MQTT_MSG), F("Topic: %s\r\n> "), topic.c_str());
|
||||
if (Log.lock()) {
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
if (payload[i] == 0) {
|
||||
@@ -279,12 +390,12 @@ protected:
|
||||
} else if (payload[i] == 13) {
|
||||
continue;
|
||||
} else if (payload[i] == 10) {
|
||||
Log.print("\r\n> ");
|
||||
Log.print(F("\r\n> "));
|
||||
} else {
|
||||
Log.print((char) payload[i]);
|
||||
}
|
||||
}
|
||||
Log.print("\r\n\n");
|
||||
Log.print(F("\r\n\n"));
|
||||
Log.flush();
|
||||
Log.unlock();
|
||||
}
|
||||
@@ -302,34 +413,48 @@ protected:
|
||||
}
|
||||
doc.shrinkToFit();
|
||||
|
||||
if (this->haHelper->getDeviceTopic("state/set").equals(topic)) {
|
||||
this->writer->publish(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true);
|
||||
// delete topic
|
||||
this->writer->publish(topic.c_str(), nullptr, 0, true);
|
||||
|
||||
if (this->haHelper->getDeviceTopic(F("state/set")).equals(topic)) {
|
||||
if (jsonToVars(doc, vars)) {
|
||||
this->resetPublishedVarsTime();
|
||||
}
|
||||
|
||||
} else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) {
|
||||
this->writer->publish(this->haHelper->getDeviceTopic("settings/set").c_str(), nullptr, 0, true);
|
||||
|
||||
} else if (this->haHelper->getDeviceTopic(F("settings/set")).equals(topic)) {
|
||||
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();
|
||||
@@ -347,103 +472,98 @@ protected:
|
||||
this->haHelper->publishInputEquithermFactorT(false);
|
||||
|
||||
// states
|
||||
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);
|
||||
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);
|
||||
|
||||
// sensors
|
||||
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);
|
||||
this->haHelper->publishFaultCode();
|
||||
this->haHelper->publishDiagCode();
|
||||
this->haHelper->publishNetworkRssi(false);
|
||||
this->haHelper->publishUptime(false);
|
||||
|
||||
// buttons
|
||||
this->haHelper->publishButtonRestart(false);
|
||||
this->haHelper->publishButtonResetFault();
|
||||
this->haHelper->publishButtonResetDiagnostic();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool publishNonStaticHaEntities(bool force = false) {
|
||||
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
|
||||
static bool _noRegulators, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false;
|
||||
static bool _indoorTempControl, _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->publishStateDhw();
|
||||
this->haHelper->publishSensorDhwTemp(settings.system.unitSystem);
|
||||
this->haHelper->publishSensorDhwFlowRate(settings.system.unitSystem);
|
||||
this->haHelper->publishDhwState();
|
||||
|
||||
} else {
|
||||
this->haHelper->deleteSwitchDhw();
|
||||
this->haHelper->deleteSensorBoilerDhwMinTemp();
|
||||
this->haHelper->deleteSensorBoilerDhwMaxTemp();
|
||||
this->haHelper->deleteInputDhwMinTemp();
|
||||
this->haHelper->deleteInputDhwMaxTemp();
|
||||
this->haHelper->deleteStateDhw();
|
||||
this->haHelper->deleteSensorDhwTemp();
|
||||
this->haHelper->deleteDhwState();
|
||||
this->haHelper->deleteInputDhwTarget();
|
||||
this->haHelper->deleteClimateDhw();
|
||||
this->haHelper->deleteSensorDhwFlowRate();
|
||||
}
|
||||
|
||||
published = true;
|
||||
}
|
||||
|
||||
if (force || _noRegulators != noRegulators || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) {
|
||||
_heatingMinTemp = heatingMinTemp;
|
||||
_heatingMaxTemp = heatingMaxTemp;
|
||||
_noRegulators = noRegulators;
|
||||
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;
|
||||
|
||||
this->haHelper->publishInputHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false);
|
||||
this->haHelper->publishClimateHeating(
|
||||
settings.system.unitSystem,
|
||||
heatingMinTemp,
|
||||
heatingMaxTemp,
|
||||
noRegulators ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
|
||||
vars.master.heating.minTemp,
|
||||
vars.master.heating.maxTemp
|
||||
);
|
||||
|
||||
published = true;
|
||||
@@ -453,40 +573,11 @@ 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;
|
||||
}
|
||||
|
||||
@@ -498,6 +589,30 @@ 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);
|
||||
|
||||
1894
src/OpenThermTask.h
1894
src/OpenThermTask.h
File diff suppressed because it is too large
Load Diff
656
src/PortalTask.h
656
src/PortalTask.h
@@ -1,5 +1,5 @@
|
||||
#define PORTAL_CACHE_TIME "max-age=86400"
|
||||
#define PORTAL_CACHE (settings.system.logLevel >= TinyLogger::Level::TRACE ? nullptr : PORTAL_CACHE_TIME)
|
||||
//#define PORTAL_CACHE "max-age=86400"
|
||||
#define PORTAL_CACHE nullptr
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <Updater.h>
|
||||
@@ -17,7 +17,7 @@ using WebServer = ESP8266WebServer;
|
||||
using namespace NetworkUtils;
|
||||
|
||||
extern NetworkMgr* network;
|
||||
extern FileData fsSettings, fsNetworkSettings;
|
||||
extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
|
||||
extern MqttTask* tMqtt;
|
||||
|
||||
|
||||
@@ -72,9 +72,24 @@ protected:
|
||||
void setup() {
|
||||
this->dnsServer->setTTL(0);
|
||||
this->dnsServer->setErrorReplyCode(DNSReplyCode::NoError);
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
this->webServer->enableETag(true);
|
||||
#endif
|
||||
this->webServer->enableETag(true, [](FS &fs, const String &fName) -> const String {
|
||||
char buf[32];
|
||||
{
|
||||
MD5Builder md5;
|
||||
md5.begin();
|
||||
md5.add(fName);
|
||||
md5.add(" " BUILD_ENV " " BUILD_VERSION " " __DATE__ " " __TIME__);
|
||||
md5.calculate();
|
||||
md5.getChars(buf);
|
||||
}
|
||||
|
||||
String etag;
|
||||
etag.reserve(34);
|
||||
etag += '\"';
|
||||
etag.concat(buf, 32);
|
||||
etag += '\"';
|
||||
return etag;
|
||||
});
|
||||
|
||||
// index page
|
||||
/*auto indexPage = (new DynamicPage("/", &LittleFS, "/pages/index.html"))
|
||||
@@ -88,13 +103,13 @@ protected:
|
||||
return result;
|
||||
});
|
||||
this->webServer->addHandler(indexPage);*/
|
||||
this->webServer->addHandler(new StaticPage("/", &LittleFS, "/pages/index.html", PORTAL_CACHE));
|
||||
this->webServer->addHandler(new StaticPage("/", &LittleFS, F("/pages/index.html"), PORTAL_CACHE));
|
||||
|
||||
// dashboard page
|
||||
auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, "/pages/dashboard.html", PORTAL_CACHE))
|
||||
auto dashboardPage = (new StaticPage("/dashboard.html", &LittleFS, F("/pages/dashboard.html"), PORTAL_CACHE))
|
||||
->setBeforeSendCallback([this]() {
|
||||
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->requestAuthentication(DIGEST_AUTH);
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
this->webServer->requestAuthentication(BASIC_AUTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -103,24 +118,22 @@ protected:
|
||||
this->webServer->addHandler(dashboardPage);
|
||||
|
||||
// restart
|
||||
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);
|
||||
this->webServer->on(F("/restart.html"), HTTP_GET, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
this->webServer->requestAuthentication(BASIC_AUTH);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
vars.actions.restart = true;
|
||||
this->webServer->sendHeader("Location", "/");
|
||||
this->webServer->sendHeader(F("Location"), "/");
|
||||
this->webServer->send(302);
|
||||
});
|
||||
|
||||
// network settings page
|
||||
auto networkPage = (new StaticPage("/network.html", &LittleFS, "/pages/network.html", PORTAL_CACHE))
|
||||
auto networkPage = (new StaticPage("/network.html", &LittleFS, F("/pages/network.html"), PORTAL_CACHE))
|
||||
->setBeforeSendCallback([this]() {
|
||||
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->requestAuthentication(DIGEST_AUTH);
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
this->webServer->requestAuthentication(BASIC_AUTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -129,10 +142,10 @@ protected:
|
||||
this->webServer->addHandler(networkPage);
|
||||
|
||||
// settings page
|
||||
auto settingsPage = (new StaticPage("/settings.html", &LittleFS, "/pages/settings.html", PORTAL_CACHE))
|
||||
auto settingsPage = (new StaticPage("/settings.html", &LittleFS, F("/pages/settings.html"), PORTAL_CACHE))
|
||||
->setBeforeSendCallback([this]() {
|
||||
if (this->isAuthRequired() && !this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->requestAuthentication(DIGEST_AUTH);
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
this->webServer->requestAuthentication(BASIC_AUTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -140,11 +153,23 @@ protected:
|
||||
});
|
||||
this->webServer->addHandler(settingsPage);
|
||||
|
||||
// upgrade page
|
||||
auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, "/pages/upgrade.html", PORTAL_CACHE))
|
||||
// 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);
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
this->webServer->requestAuthentication(BASIC_AUTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
this->webServer->addHandler(sensorsPage);
|
||||
|
||||
// upgrade page
|
||||
auto upgradePage = (new StaticPage("/upgrade.html", &LittleFS, F("/pages/upgrade.html"), PORTAL_CACHE))
|
||||
->setBeforeSendCallback([this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
this->webServer->requestAuthentication(BASIC_AUTH);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -154,14 +179,19 @@ 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("Connection", "close");
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
this->webServer->sendHeader(F("Connection"), F("close"));
|
||||
this->webServer->send(401);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
})->setBeforeUpgradeCallback([](UpgradeHandler::UpgradeType type) -> bool {
|
||||
if (vars.states.restarting) {
|
||||
return false;
|
||||
}
|
||||
|
||||
vars.states.upgrading = true;
|
||||
return true;
|
||||
})->setAfterUpgradeCallback([this](const UpgradeHandler::UpgradeResult& fwResult, const UpgradeHandler::UpgradeResult& fsResult) {
|
||||
unsigned short status = 200;
|
||||
@@ -172,53 +202,57 @@ protected:
|
||||
status = 400;
|
||||
}
|
||||
|
||||
String response = "{\"firmware\": {\"status\": ";
|
||||
String response = F("{\"firmware\": {\"status\": ");
|
||||
response.concat((short int) fwResult.status);
|
||||
response.concat(", \"error\": \"");
|
||||
response.concat(F(", \"error\": \""));
|
||||
response.concat(fwResult.error);
|
||||
response.concat("\"}, \"filesystem\": {\"status\": ");
|
||||
response.concat(F("\"}, \"filesystem\": {\"status\": "));
|
||||
response.concat((short int) fsResult.status);
|
||||
response.concat(", \"error\": \"");
|
||||
response.concat(F(", \"error\": \""));
|
||||
response.concat(fsResult.error);
|
||||
response.concat("\"}}");
|
||||
this->webServer->send(status, "application/json", response);
|
||||
response.concat(F("\"}}"));
|
||||
this->webServer->send(status, F("application/json"), response);
|
||||
|
||||
vars.states.upgrading = false;
|
||||
});
|
||||
this->webServer->addHandler(upgradeHandler);
|
||||
|
||||
|
||||
// backup
|
||||
this->webServer->on("/api/backup/save", HTTP_GET, [this]() {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->on(F("/api/backup/save"), HTTP_GET, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
}
|
||||
|
||||
JsonDocument networkSettingsDoc;
|
||||
networkSettingsToJson(networkSettings, networkSettingsDoc);
|
||||
networkSettingsDoc.shrinkToFit();
|
||||
|
||||
JsonDocument settingsDoc;
|
||||
settingsToJson(settings, settingsDoc);
|
||||
settingsDoc.shrinkToFit();
|
||||
|
||||
JsonDocument doc;
|
||||
doc["network"] = networkSettingsDoc;
|
||||
doc["settings"] = settingsDoc;
|
||||
|
||||
auto networkDoc = doc[FPSTR(S_NETWORK)].to<JsonObject>();
|
||||
networkSettingsToJson(networkSettings, networkDoc);
|
||||
|
||||
auto settingsDoc = doc[FPSTR(S_SETTINGS)].to<JsonObject>();
|
||||
settingsToJson(settings, settingsDoc);
|
||||
|
||||
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
|
||||
auto sensorsettingsDoc = doc[FPSTR(S_SENSORS)][sensorId].to<JsonObject>();
|
||||
sensorSettingsToJson(sensorId, Sensors::settings[sensorId], sensorsettingsDoc);
|
||||
}
|
||||
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->webServer->sendHeader(F("Content-Disposition"), F("attachment; filename=\"backup.json\""));
|
||||
this->bufferedWebServer->send(200, "application/json", doc);
|
||||
this->bufferedWebServer->send(200, F("application/json"), doc);
|
||||
});
|
||||
|
||||
this->webServer->on("/api/backup/restore", HTTP_POST, [this]() {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->on(F("/api/backup/restore"), HTTP_POST, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
|
||||
if (vars.states.restarting) {
|
||||
return this->webServer->send(503);
|
||||
}
|
||||
|
||||
String plain = this->webServer->arg(0);
|
||||
const 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) {
|
||||
@@ -232,7 +266,6 @@ protected:
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError dErr = deserializeJson(doc, plain);
|
||||
plain.clear();
|
||||
|
||||
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
|
||||
this->webServer->send(400);
|
||||
@@ -240,13 +273,7 @@ protected:
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
if (doc["settings"] && jsonToSettings(doc["settings"], settings)) {
|
||||
vars.actions.restart = true;
|
||||
fsSettings.update();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (doc["network"] && jsonToNetworkSettings(doc["network"], networkSettings)) {
|
||||
if (!doc[FPSTR(S_NETWORK)].isNull() && jsonToNetworkSettings(doc[FPSTR(S_NETWORK)], networkSettings)) {
|
||||
fsNetworkSettings.update();
|
||||
network->setHostname(networkSettings.hostname)
|
||||
->setStaCredentials(networkSettings.sta.ssid, networkSettings.sta.password, networkSettings.sta.channel)
|
||||
@@ -257,40 +284,66 @@ 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("/api/network/settings", HTTP_GET, [this]() {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->on(F("/api/network/settings"), HTTP_GET, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
networkSettingsToJson(networkSettings, doc);
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(200, "application/json", doc);
|
||||
this->bufferedWebServer->send(200, F("application/json"), doc);
|
||||
});
|
||||
|
||||
this->webServer->on("/api/network/settings", HTTP_POST, [this]() {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->on(F("/api/network/settings"), HTTP_POST, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
|
||||
if (vars.states.restarting) {
|
||||
return this->webServer->send(503);
|
||||
}
|
||||
|
||||
String plain = this->webServer->arg(0);
|
||||
const 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) {
|
||||
@@ -304,7 +357,6 @@ protected:
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError dErr = deserializeJson(doc, plain);
|
||||
plain.clear();
|
||||
|
||||
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
|
||||
this->webServer->send(400);
|
||||
@@ -318,7 +370,7 @@ protected:
|
||||
networkSettingsToJson(networkSettings, doc);
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
|
||||
this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc);
|
||||
|
||||
if (changed) {
|
||||
doc.clear();
|
||||
@@ -339,12 +391,9 @@ protected:
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
this->webServer->on(F("/api/network/scan"), HTTP_GET, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
|
||||
auto apCount = WiFi.scanComplete();
|
||||
@@ -363,50 +412,50 @@ protected:
|
||||
|
||||
JsonDocument doc;
|
||||
for (short int i = 0; i < apCount; i++) {
|
||||
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();
|
||||
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();
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
const bss_info* info = WiFi.getScanInfoByIndex(i);
|
||||
doc[i]["auth"] = info->authmode;
|
||||
doc[i][FPSTR(S_AUTH)] = info->authmode;
|
||||
#else
|
||||
doc[i]["auth"] = WiFi.encryptionType(i);
|
||||
doc[i][FPSTR(S_AUTH)] = WiFi.encryptionType(i);
|
||||
#endif
|
||||
}
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(200, "application/json", doc);
|
||||
this->bufferedWebServer->send(200, F("application/json"), doc);
|
||||
|
||||
WiFi.scanDelete();
|
||||
});
|
||||
|
||||
|
||||
// settings
|
||||
this->webServer->on("/api/settings", HTTP_GET, [this]() {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->on(F("/api/settings"), HTTP_GET, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
}
|
||||
|
||||
JsonDocument doc;
|
||||
settingsToJson(settings, doc);
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(200, "application/json", doc);
|
||||
this->bufferedWebServer->send(200, F("application/json"), doc);
|
||||
});
|
||||
|
||||
this->webServer->on("/api/settings", HTTP_POST, [this]() {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->on(F("/api/settings"), HTTP_POST, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
|
||||
if (vars.states.restarting) {
|
||||
return this->webServer->send(503);
|
||||
}
|
||||
|
||||
String plain = this->webServer->arg(0);
|
||||
const 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) {
|
||||
@@ -420,7 +469,6 @@ protected:
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError dErr = deserializeJson(doc, plain);
|
||||
plain.clear();
|
||||
|
||||
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
|
||||
this->webServer->send(400);
|
||||
@@ -434,7 +482,7 @@ protected:
|
||||
settingsToJson(settings, doc);
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
|
||||
this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc);
|
||||
|
||||
if (changed) {
|
||||
doc.clear();
|
||||
@@ -446,23 +494,149 @@ protected:
|
||||
});
|
||||
|
||||
|
||||
// sensors list
|
||||
this->webServer->on(F("/api/sensors"), HTTP_GET, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
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_ENABLED)] = sSensor.enabled;
|
||||
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() && !this->isValidCredentials()) {
|
||||
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() && !this->isValidCredentials()) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
|
||||
if (vars.states.restarting) {
|
||||
return this->webServer->send(503);
|
||||
}
|
||||
|
||||
#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("/api/vars", HTTP_GET, [this]() {
|
||||
this->webServer->on(F("/api/vars"), HTTP_GET, [this]() {
|
||||
JsonDocument doc;
|
||||
varsToJson(vars, doc);
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(200, "application/json", doc);
|
||||
this->bufferedWebServer->send(200, F("application/json"), doc);
|
||||
});
|
||||
|
||||
this->webServer->on("/api/vars", HTTP_POST, [this]() {
|
||||
if (this->isAuthRequired()) {
|
||||
if (!this->webServer->authenticate(settings.portal.login, settings.portal.password)) {
|
||||
this->webServer->on(F("/api/vars"), HTTP_POST, [this]() {
|
||||
if (this->isAuthRequired() && !this->isValidCredentials()) {
|
||||
return this->webServer->send(401);
|
||||
}
|
||||
}
|
||||
|
||||
String plain = this->webServer->arg(0);
|
||||
const 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) {
|
||||
@@ -476,7 +650,6 @@ protected:
|
||||
|
||||
JsonDocument doc;
|
||||
DeserializationError dErr = deserializeJson(doc, plain);
|
||||
plain.clear();
|
||||
|
||||
if (dErr != DeserializationError::Ok || doc.isNull() || !doc.size()) {
|
||||
this->webServer->send(400);
|
||||
@@ -490,7 +663,7 @@ protected:
|
||||
varsToJson(vars, doc);
|
||||
doc.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
|
||||
this->bufferedWebServer->send(changed ? 201 : 200, F("application/json"), doc);
|
||||
|
||||
if (changed) {
|
||||
doc.clear();
|
||||
@@ -500,78 +673,138 @@ protected:
|
||||
}
|
||||
});
|
||||
|
||||
this->webServer->on("/api/info", HTTP_GET, [this]() {
|
||||
this->webServer->on(F("/api/info"), HTTP_GET, [this]() {
|
||||
bool isConnected = network->isConnected();
|
||||
|
||||
JsonDocument doc;
|
||||
doc["system"]["resetReason"] = getResetReason();
|
||||
doc["system"]["uptime"] = millis() / 1000ul;
|
||||
|
||||
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 docSystem = doc[FPSTR(S_SYSTEM)].to<JsonObject>();
|
||||
docSystem[FPSTR(S_RESET_REASON)] = getResetReason();
|
||||
docSystem[FPSTR(S_UPTIME)] = millis() / 1000;
|
||||
|
||||
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);
|
||||
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() : "";
|
||||
|
||||
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
|
||||
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();
|
||||
docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion();
|
||||
docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion();
|
||||
#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"];
|
||||
docBuild[FPSTR(S_CORE)] = ESP.getCoreVersion();
|
||||
docBuild[FPSTR(S_SDK)] = ESP.getSdkVersion();
|
||||
#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;
|
||||
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.shrinkToFit();
|
||||
|
||||
this->bufferedWebServer->send(200, "application/json", doc);
|
||||
this->bufferedWebServer->send(200, F("application/json"), doc);
|
||||
});
|
||||
|
||||
this->webServer->on("/api/debug", HTTP_GET, [this]() {
|
||||
this->webServer->on(F("/api/debug"), HTTP_GET, [this]() {
|
||||
JsonDocument doc;
|
||||
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);
|
||||
|
||||
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
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
auto reason = esp_reset_reason();
|
||||
@@ -582,58 +815,30 @@ protected:
|
||||
#else
|
||||
if (false) {
|
||||
#endif
|
||||
doc["crash"]["reason"] = getResetReason();
|
||||
doc["crash"]["core"] = CrashRecorder::ext.core;
|
||||
doc["crash"]["heap"] = CrashRecorder::ext.heap;
|
||||
doc["crash"]["uptime"] = CrashRecorder::ext.uptime;
|
||||
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;
|
||||
|
||||
if (CrashRecorder::backtrace.length > 0 && CrashRecorder::backtrace.length <= CrashRecorder::backtraceMaxLength) {
|
||||
String backtraceStr;
|
||||
arr2str(backtraceStr, CrashRecorder::backtrace.data, CrashRecorder::backtrace.length);
|
||||
doc["crash"]["backtrace"]["data"] = backtraceStr;
|
||||
doc["crash"]["backtrace"]["continues"] = CrashRecorder::backtrace.continues;
|
||||
docCrash[FPSTR(S_BACKTRACE)][FPSTR(S_DATA)] = backtraceStr;
|
||||
docCrash[FPSTR(S_BACKTRACE)][FPSTR(S_CONTINUES)] = CrashRecorder::backtrace.continues;
|
||||
}
|
||||
|
||||
if (CrashRecorder::epc.length > 0 && CrashRecorder::epc.length <= CrashRecorder::epcMaxLength) {
|
||||
String epcStr;
|
||||
arr2str(epcStr, CrashRecorder::epc.data, CrashRecorder::epc.length);
|
||||
doc["crash"]["epc"] = epcStr;
|
||||
docCrash[FPSTR(S_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, "application/json", doc, true);
|
||||
this->bufferedWebServer->send(200, F("application/json"), doc, true);
|
||||
});
|
||||
|
||||
|
||||
@@ -641,15 +846,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("/")) {
|
||||
this->webServer->send(200, "text/plain", F("The file system is not flashed!"));
|
||||
const String& uri = this->webServer->uri();
|
||||
if (uri.equals(F("/"))) {
|
||||
this->webServer->send(200, F("text/plain"), F("The file system is not flashed!"));
|
||||
|
||||
} else if (network->isApEnabled()) {
|
||||
this->onCaptivePortal();
|
||||
|
||||
} else {
|
||||
this->webServer->send(404, "text/plain", F("Page not found"));
|
||||
this->webServer->send(404, F("text/plain"), F("Page not found"));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -668,7 +873,7 @@ protected:
|
||||
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Started: AP up or STA connected"));
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
|
||||
} else if (this->stateWebServer() && !network->isApEnabled() && !network->isStaEnabled()) {
|
||||
@@ -676,7 +881,7 @@ protected:
|
||||
Log.straceln(FPSTR(L_PORTAL_WEBSERVER), F("Stopped: AP and STA down"));
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -686,7 +891,7 @@ protected:
|
||||
Log.straceln(FPSTR(L_PORTAL_DNSSERVER), F("Started: AP up"));
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
|
||||
} else if (this->stateDnsServer() && (!network->isApEnabled() || !this->stateWebServer())) {
|
||||
@@ -694,18 +899,27 @@ protected:
|
||||
Log.straceln(FPSTR(L_PORTAL_DNSSERVER), F("Stopped: AP down"));
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (this->stateDnsServer()) {
|
||||
this->dnsServer->processNextRequest();
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP8266
|
||||
::delay(0);
|
||||
::optimistic_yield(1000);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (this->stateWebServer()) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// Fix ERR_CONNECTION_RESET for Chrome based browsers
|
||||
auto& client = this->webServer->client();
|
||||
if (!client.getNoDelay()) {
|
||||
client.setNoDelay(true);
|
||||
}
|
||||
#endif
|
||||
|
||||
this->webServer->handleClient();
|
||||
}
|
||||
|
||||
@@ -718,29 +932,35 @@ protected:
|
||||
return !network->isApEnabled() && settings.portal.auth && strlen(settings.portal.password);
|
||||
}
|
||||
|
||||
void onCaptivePortal() {
|
||||
const String uri = this->webServer->uri();
|
||||
bool isValidCredentials() {
|
||||
return this->webServer->authenticate(settings.portal.login, settings.portal.password);
|
||||
}
|
||||
|
||||
if (uri.equals("/connecttest.txt")) {
|
||||
void onCaptivePortal() {
|
||||
const String& uri = this->webServer->uri();
|
||||
|
||||
if (uri.equals(F("/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("/wpad.dat")) {
|
||||
} else if (uri.equals(F("/wpad.dat"))) {
|
||||
this->webServer->send(404);
|
||||
|
||||
Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Send empty page with 404 code"));
|
||||
|
||||
} else if (uri.equals("/success.txt")) {
|
||||
} else if (uri.equals(F("/success.txt"))) {
|
||||
this->webServer->send(200);
|
||||
|
||||
Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Send empty page with 200 code"));
|
||||
|
||||
} else {
|
||||
String portalUrl = "http://" + network->getApIp().toString() + '/';
|
||||
String portalUrl = F("http://");
|
||||
portalUrl += network->getApIp().toString().c_str();
|
||||
portalUrl += '/';
|
||||
|
||||
this->webServer->sendHeader("Location", portalUrl.c_str());
|
||||
this->webServer->sendHeader(F("Location"), portalUrl);
|
||||
this->webServer->send(302);
|
||||
|
||||
Log.straceln(FPSTR(L_PORTAL_CAPTIVE), F("Redirect to portal page with 302 code"));
|
||||
|
||||
@@ -10,9 +10,12 @@ public:
|
||||
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
|
||||
|
||||
protected:
|
||||
float prevHeatingTarget = 0;
|
||||
float prevEtResult = 0;
|
||||
float prevPidResult = 0;
|
||||
float prevHeatingTarget = 0.0f;
|
||||
float prevEtResult = 0.0f;
|
||||
float prevPidResult = 0.0f;
|
||||
|
||||
bool indoorSensorsConnected = false;
|
||||
//bool outdoorSensorsConnected = false;
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
const char* getTaskName() override {
|
||||
@@ -29,20 +32,55 @@ protected:
|
||||
#endif
|
||||
|
||||
void loop() {
|
||||
if (!settings.pid.enable && fabs(pidRegulator.integral) > 0.01f) {
|
||||
if (vars.states.restarting || vars.states.upgrading) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
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.enable || vars.states.emergency || !vars.sensors.indoor.connected) {
|
||||
if (!settings.heating.enabled || vars.emergency.state || !this->indoorSensorsConnected) {
|
||||
settings.heating.turbo = false;
|
||||
|
||||
} else if (!settings.pid.enable && !settings.equitherm.enable) {
|
||||
} else if (!settings.pid.enabled && !settings.equitherm.enabled) {
|
||||
settings.heating.turbo = false;
|
||||
|
||||
} else if (fabs(settings.heating.target - vars.temperatures.indoor) <= 1.0f) {
|
||||
} else if (fabsf(settings.heating.target - vars.master.heating.indoorTemp) <= 1.0f) {
|
||||
settings.heating.turbo = false;
|
||||
}
|
||||
|
||||
@@ -50,45 +88,69 @@ 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;
|
||||
}
|
||||
|
||||
float newTemp = vars.states.emergency
|
||||
? settings.emergency.target
|
||||
: this->getNormalModeTemp();
|
||||
if (useHyst) {
|
||||
if (!vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target + 0.0001f >= settings.heating.hysteresis) {
|
||||
vars.master.heating.blocking = true;
|
||||
|
||||
// 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.indoorTemp - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis)) {
|
||||
vars.master.heating.blocking = false;
|
||||
}
|
||||
|
||||
if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.09f) {
|
||||
vars.parameters.heatingSetpoint = newTemp;
|
||||
} else if (vars.master.heating.blocking) {
|
||||
vars.master.heating.blocking = false;
|
||||
}
|
||||
}
|
||||
|
||||
inline float getHeatingMinSetpointTemp() {
|
||||
return settings.opentherm.nativeHeatingControl
|
||||
? vars.master.heating.minTemp
|
||||
: settings.heating.minTemp;
|
||||
}
|
||||
|
||||
float getNormalModeTemp() {
|
||||
inline float getHeatingMaxSetpointTemp() {
|
||||
return settings.opentherm.nativeHeatingControl
|
||||
? vars.master.heating.maxTemp
|
||||
: settings.heating.maxTemp;
|
||||
}
|
||||
|
||||
float getHeatingSetpointTemp() {
|
||||
float newTemp = 0;
|
||||
|
||||
if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001f) {
|
||||
if (fabsf(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.enable) {
|
||||
/*if (settings.pid.enabled) {
|
||||
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.enable) {
|
||||
if (settings.equitherm.enabled) {
|
||||
unsigned short minTemp = settings.heating.minTemp;
|
||||
unsigned short maxTemp = settings.heating.maxTemp;
|
||||
float targetTemp = settings.heating.target;
|
||||
float indoorTemp = vars.temperatures.indoor;
|
||||
float outdoorTemp = vars.temperatures.outdoor;
|
||||
float indoorTemp = vars.master.heating.indoorTemp;
|
||||
float outdoorTemp = vars.master.heating.outdoorTemp;
|
||||
|
||||
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
|
||||
minTemp = f2c(minTemp);
|
||||
@@ -98,7 +160,7 @@ protected:
|
||||
outdoorTemp = f2c(outdoorTemp);
|
||||
}
|
||||
|
||||
if (!vars.sensors.indoor.connected || settings.pid.enable) {
|
||||
if (!this->indoorSensorsConnected || settings.pid.enabled) {
|
||||
etRegulator.Kt = 0.0f;
|
||||
etRegulator.indoorTemp = 0.0f;
|
||||
|
||||
@@ -118,7 +180,7 @@ protected:
|
||||
etResult = c2f(etResult);
|
||||
}
|
||||
|
||||
if (fabs(prevEtResult - etResult) > 0.09f) {
|
||||
if (fabsf(prevEtResult - etResult) > 0.09f) {
|
||||
prevEtResult = etResult;
|
||||
newTemp += etResult;
|
||||
|
||||
@@ -130,18 +192,18 @@ protected:
|
||||
}
|
||||
|
||||
// if use pid
|
||||
if (settings.pid.enable) {
|
||||
if (settings.pid.enabled) {
|
||||
//if (vars.parameters.heatingEnabled) {
|
||||
if (settings.heating.enable && vars.sensors.indoor.connected) {
|
||||
if (settings.heating.enabled && this->indoorSensorsConnected) {
|
||||
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.temperatures.indoor;
|
||||
pidRegulator.input = vars.master.heating.indoorTemp;
|
||||
pidRegulator.setpoint = settings.heating.target;
|
||||
|
||||
if (fabs(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
|
||||
if (fabsf(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
|
||||
pidRegulator.Ki = settings.pid.i_factor;
|
||||
pidRegulator.integral = 0.0f;
|
||||
pidRegulator.getResultNow();
|
||||
@@ -150,7 +212,7 @@ protected:
|
||||
}
|
||||
|
||||
float pidResult = pidRegulator.getResultTimer();
|
||||
if (fabs(prevPidResult - pidResult) > 0.09f) {
|
||||
if (fabsf(prevPidResult - pidResult) > 0.09f) {
|
||||
prevPidResult = pidResult;
|
||||
newTemp += pidResult;
|
||||
|
||||
@@ -167,19 +229,14 @@ protected:
|
||||
}
|
||||
|
||||
// Turbo mode
|
||||
if (settings.heating.turbo && (settings.equitherm.enable || settings.pid.enable)) {
|
||||
if (settings.heating.turbo && (settings.equitherm.enabled || settings.pid.enabled)) {
|
||||
newTemp += constrain(
|
||||
settings.heating.target - vars.temperatures.indoor,
|
||||
settings.heating.target - vars.master.heating.indoorTemp,
|
||||
-3.0f,
|
||||
3.0f
|
||||
) * settings.heating.turboFactor;
|
||||
}
|
||||
|
||||
// default temp, manual mode
|
||||
if (!settings.equitherm.enable && !settings.pid.enable) {
|
||||
newTemp = settings.heating.target;
|
||||
}
|
||||
|
||||
return newTemp;
|
||||
}
|
||||
};
|
||||
|
||||
464
src/Sensors.h
Normal file
464
src/Sensors.h
Normal file
@@ -0,0 +1,464 @@
|
||||
#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,
|
||||
OT_EXHAUST_CO2 = 12,
|
||||
OT_EXHAUST_FAN_SPEED = 13,
|
||||
OT_SUPPLY_FAN_SPEED = 14,
|
||||
OT_SOLAR_STORAGE_TEMP = 15,
|
||||
OT_SOLAR_COLLECTOR_TEMP = 16,
|
||||
OT_FAN_SPEED_SETPOINT = 17,
|
||||
OT_FAN_SPEED_CURRENT = 18,
|
||||
|
||||
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,
|
||||
|
||||
POWER_FACTOR = 248,
|
||||
POWER = 249,
|
||||
FAN_SPEED = 250,
|
||||
CO2 = 251,
|
||||
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 = 100;
|
||||
//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, bool onlyEnabled = false) {
|
||||
if (settings == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t amount = 0;
|
||||
for (uint8_t id = 0; id < getMaxSensorId(); id++) {
|
||||
if (settings[id].type == type && (!onlyEnabled || settings[id].enabled)) {
|
||||
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
300
src/Settings.h
300
src/Settings.h
@@ -27,12 +27,12 @@ struct Settings {
|
||||
uint8_t logLevel = DEFAULT_LOG_LEVEL;
|
||||
|
||||
struct {
|
||||
bool enable = DEFAULT_SERIAL_ENABLE;
|
||||
bool enabled = DEFAULT_SERIAL_ENABLED;
|
||||
unsigned int baudrate = DEFAULT_SERIAL_BAUD;
|
||||
} serial;
|
||||
|
||||
struct {
|
||||
bool enable = DEFAULT_TELNET_ENABLE;
|
||||
bool enabled = DEFAULT_TELNET_ENABLED;
|
||||
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;
|
||||
unsigned int memberIdCode = 0;
|
||||
uint8_t memberId = 0;
|
||||
uint8_t flags = 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,15 +67,10 @@ struct Settings {
|
||||
bool getMinMaxTemp = true;
|
||||
bool nativeHeatingControl = false;
|
||||
bool immergasFix = false;
|
||||
|
||||
struct {
|
||||
bool enable = false;
|
||||
float factor = 0.1f;
|
||||
} filterNumValues;
|
||||
} opentherm;
|
||||
|
||||
struct {
|
||||
bool enable = false;
|
||||
bool enabled = DEFAULT_MQTT_ENABLED;
|
||||
char server[81] = DEFAULT_MQTT_SERVER;
|
||||
unsigned short port = DEFAULT_MQTT_PORT;
|
||||
char user[33] = DEFAULT_MQTT_USER;
|
||||
@@ -91,7 +86,7 @@ struct Settings {
|
||||
} emergency;
|
||||
|
||||
struct {
|
||||
bool enable = true;
|
||||
bool enabled = true;
|
||||
bool turbo = false;
|
||||
float target = DEFAULT_HEATING_TARGET_TEMP;
|
||||
float hysteresis = 0.5f;
|
||||
@@ -101,14 +96,14 @@ struct Settings {
|
||||
} heating;
|
||||
|
||||
struct {
|
||||
bool enable = true;
|
||||
bool enabled = true;
|
||||
float target = DEFAULT_DHW_TARGET_TEMP;
|
||||
byte minTemp = DEFAULT_DHW_MIN_TEMP;
|
||||
byte maxTemp = DEFAULT_DHW_MAX_TEMP;
|
||||
} dhw;
|
||||
|
||||
struct {
|
||||
bool enable = false;
|
||||
bool enabled = false;
|
||||
float p_factor = 2.0f;
|
||||
float i_factor = 0.0055f;
|
||||
float d_factor = 0.0f;
|
||||
@@ -118,28 +113,12 @@ struct Settings {
|
||||
} pid;
|
||||
|
||||
struct {
|
||||
bool enable = false;
|
||||
bool enabled = 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;
|
||||
@@ -150,14 +129,14 @@ struct Settings {
|
||||
|
||||
struct {
|
||||
struct {
|
||||
bool enable = false;
|
||||
bool enabled = false;
|
||||
byte gpio = GPIO_IS_NOT_CONFIGURED;
|
||||
byte invertState = false;
|
||||
unsigned short thresholdTime = 60;
|
||||
} input;
|
||||
|
||||
struct {
|
||||
bool enable = false;
|
||||
bool enabled = false;
|
||||
byte gpio = GPIO_IS_NOT_CONFIGURED;
|
||||
byte invertState = false;
|
||||
unsigned short thresholdTime = 60;
|
||||
@@ -170,51 +149,95 @@ 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::POWER,
|
||||
Sensors::Type::OT_CURRENT_POWER,
|
||||
}
|
||||
};
|
||||
|
||||
struct Variables {
|
||||
struct {
|
||||
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;
|
||||
bool connected = false;
|
||||
int8_t rssi = 0;
|
||||
} network;
|
||||
|
||||
struct {
|
||||
bool connected = false;
|
||||
int8_t rssi = 0;
|
||||
float battery = 0.0f;
|
||||
float humidity = 0.0f;
|
||||
} outdoor;
|
||||
} mqtt;
|
||||
|
||||
struct {
|
||||
bool connected = false;
|
||||
int8_t rssi = 0;
|
||||
float battery = 0.0f;
|
||||
float humidity = 0.0f;
|
||||
} indoor;
|
||||
} sensors;
|
||||
bool state = false;
|
||||
} emergency;
|
||||
|
||||
struct {
|
||||
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;
|
||||
bool state = false;
|
||||
unsigned long lastEnabledTime = 0;
|
||||
} externalPump;
|
||||
|
||||
struct {
|
||||
bool input = false;
|
||||
@@ -222,31 +245,130 @@ struct Variables {
|
||||
} cascadeControl;
|
||||
|
||||
struct {
|
||||
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;
|
||||
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 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 {
|
||||
float temp = 0.0f;
|
||||
uint16_t co2 = 0;
|
||||
uint16_t fanSpeed = 0;
|
||||
} exhaust;
|
||||
|
||||
struct {
|
||||
float storage = 0.0f;
|
||||
float collector = 0.0f;
|
||||
} solar;
|
||||
|
||||
struct {
|
||||
uint8_t setpoint = 0;
|
||||
uint8_t current = 0;
|
||||
uint16_t supply = 0;
|
||||
} fanSpeed;
|
||||
|
||||
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;
|
||||
|
||||
struct {
|
||||
bool restart = false;
|
||||
bool resetFault = false;
|
||||
bool resetDiagnostic = false;
|
||||
} actions;
|
||||
|
||||
struct {
|
||||
bool restarting = false;
|
||||
bool upgrading = false;
|
||||
} states;
|
||||
} vars;
|
||||
@@ -2,10 +2,6 @@
|
||||
#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
|
||||
@@ -22,6 +18,14 @@
|
||||
#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
|
||||
@@ -30,16 +34,16 @@
|
||||
#define BUILD_ENV "undefined"
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_SERIAL_ENABLE
|
||||
#define DEFAULT_SERIAL_ENABLE true
|
||||
#ifndef DEFAULT_SERIAL_ENABLED
|
||||
#define DEFAULT_SERIAL_ENABLED true
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_SERIAL_BAUD
|
||||
#define DEFAULT_SERIAL_BAUD 115200
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_TELNET_ENABLE
|
||||
#define DEFAULT_TELNET_ENABLE true
|
||||
#ifndef DEFAULT_TELNET_ENABLED
|
||||
#define DEFAULT_TELNET_ENABLED true
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_TELNET_PORT
|
||||
@@ -86,6 +90,10 @@
|
||||
#define DEFAULT_PORTAL_PASSWORD ""
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_MQTT_ENABLED
|
||||
#define DEFAULT_MQTT_ENABLED false
|
||||
#endif
|
||||
|
||||
#ifndef DEFAULT_MQTT_SERVER
|
||||
#define DEFAULT_MQTT_SERVER ""
|
||||
#endif
|
||||
@@ -130,6 +138,10 @@
|
||||
#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
|
||||
@@ -141,22 +153,14 @@
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <driver/gpio.h>
|
||||
#elif !defined(GPIO_IS_VALID_GPIO)
|
||||
#define GPIO_IS_VALID_GPIO(gpioNum) (gpioNum >= 0 && gpioNum <= 16)
|
||||
#define GPIO_IS_VALID_GPIO(gpioNum) (gpioNum >= 0 && gpioNum <= 17)
|
||||
#endif
|
||||
|
||||
#define GPIO_IS_VALID(gpioNum) (gpioNum != GPIO_IS_NOT_CONFIGURED && GPIO_IS_VALID_GPIO(gpioNum))
|
||||
|
||||
enum class SensorType : byte {
|
||||
BOILER_OUTDOOR = 0,
|
||||
BOILER_RETURN = 4,
|
||||
MANUAL = 1,
|
||||
DS18B20 = 2,
|
||||
BLUETOOTH = 3
|
||||
};
|
||||
|
||||
enum class UnitSystem : byte {
|
||||
METRIC,
|
||||
IMPERIAL
|
||||
enum class UnitSystem : uint8_t {
|
||||
METRIC = 0,
|
||||
IMPERIAL = 1
|
||||
};
|
||||
|
||||
char buffer[255];
|
||||
98
src/main.cpp
98
src/main.cpp
@@ -1,13 +1,19 @@
|
||||
#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 "defines.h"
|
||||
#include "strings.h"
|
||||
|
||||
#include <TinyLogger.h>
|
||||
#include <NetworkMgr.h>
|
||||
#include "CrashRecorder.h"
|
||||
#include "Sensors.h"
|
||||
#include "Settings.h"
|
||||
#include "utils.h"
|
||||
|
||||
@@ -31,10 +37,13 @@
|
||||
using namespace NetworkUtils;
|
||||
|
||||
// Vars
|
||||
FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000);
|
||||
FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000);
|
||||
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);
|
||||
|
||||
// Tasks
|
||||
MqttTask* tMqtt;
|
||||
@@ -47,6 +56,9 @@ MainTask* tMain;
|
||||
|
||||
void setup() {
|
||||
CrashRecorder::init();
|
||||
Sensors::setMaxSensors(SENSORS_AMOUNT);
|
||||
Sensors::settings = sensorsSettings;
|
||||
Sensors::results = sensorsResults;
|
||||
LittleFS.begin();
|
||||
|
||||
Log.setLevel(TinyLogger::Level::VERBOSE);
|
||||
@@ -64,10 +76,14 @@ 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"));
|
||||
@@ -86,7 +102,27 @@ void setup() {
|
||||
break;
|
||||
}
|
||||
|
||||
// settings
|
||||
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
|
||||
switch (fsSettings.read()) {
|
||||
case FD_FS_ERR:
|
||||
Log.swarningln(FPSTR(L_SETTINGS), F("Filesystem error, load default"));
|
||||
@@ -112,8 +148,8 @@ void setup() {
|
||||
break;
|
||||
}
|
||||
|
||||
// logs
|
||||
if (!settings.system.serial.enable) {
|
||||
// Logs settings
|
||||
if (!settings.system.serial.enabled) {
|
||||
Serial.end();
|
||||
Log.clearStreams();
|
||||
|
||||
@@ -125,7 +161,7 @@ void setup() {
|
||||
Log.addStream(&Serial);
|
||||
}
|
||||
|
||||
if (settings.system.telnet.enable) {
|
||||
if (settings.system.telnet.enabled) {
|
||||
telnetStream = new ESPTelnetStream;
|
||||
telnetStream->setKeepAliveInterval(500);
|
||||
Log.addStream(telnetStream);
|
||||
@@ -135,34 +171,34 @@ void setup() {
|
||||
Log.setLevel(static_cast<TinyLogger::Level>(settings.system.logLevel));
|
||||
}
|
||||
|
||||
// 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
|
||||
);
|
||||
//
|
||||
// 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;
|
||||
}
|
||||
|
||||
// tasks
|
||||
//
|
||||
// Make tasks
|
||||
tMqtt = new MqttTask(false, 500);
|
||||
Scheduler.start(tMqtt);
|
||||
|
||||
tOt = new OpenThermTask(true, 750);
|
||||
Scheduler.start(tOt);
|
||||
|
||||
tSensors = new SensorsTask(true, EXT_SENSORS_INTERVAL);
|
||||
tSensors = new SensorsTask(true, 1000);
|
||||
Scheduler.start(tSensors);
|
||||
|
||||
tRegulator = new RegulatorTask(true, 10000);
|
||||
|
||||
169
src/strings.h
169
src/strings.h
@@ -15,15 +15,180 @@ 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_SENSORS_OUTDOOR[] PROGMEM = "SENSORS.OUTDOOR";
|
||||
const char L_SENSORS_INDOOR[] PROGMEM = "SENSORS.INDOOR";
|
||||
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";
|
||||
|
||||
1420
src/utils.h
1420
src/utils.h
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -49,6 +49,12 @@
|
||||
<glyph glyph-name="wifi-strength-2-1"
|
||||
unicode="wifi_strength_2"
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917M511.4866651896334 682.8808891701171C642.3423366997788 682.8808891701171 771.0661477733722 646.2236788332648 882.7410693389065 578.4516956956384L745.9183864006797 408.8089514068742C693.0644313319532 438.2194346552781 612.505281564586 469.7607793428413 511.4866651896334 469.7607793428413C410.0414769278917 469.7607793428413 329.9088990473136 437.7928627684892 277.0549439785871 408.8089514068743L139.3801162641751 578.8782675824273C251.9071826058945 646.6502507200537 380.6309936794878 682.8808891701171 511.4866651896334 682.8808891701171z" />
|
||||
<glyph glyph-name="error"
|
||||
unicode=""
|
||||
horiz-adv-x="1024" d="M512 810.667C747.605 810.667 938.667 619.605 938.667 384C938.667 148.395 747.6049999999999 -42.6669999999999 511.9999999999999 -42.6669999999999C276.395 -42.6669999999999 85.333 148.3950000000001 85.333 384.0000000000001C85.333 619.605 276.395 810.667 512 810.667zM512 725.333C323.7970000000001 725.333 170.667 572.203 170.667 384C170.667 195.797 323.797 42.6669999999999 512 42.6669999999999C700.203 42.6669999999999 853.3330000000001 195.7969999999999 853.3330000000001 384C853.3330000000001 572.203 700.2030000000001 725.3330000000001 512 725.3330000000001zM609.835 542.165L670.1650000000001 481.835L572.33 384L670.1650000000001 286.165L609.835 225.8349999999999L512 323.67L414.165 225.8349999999999L353.8350000000001 286.165L451.67 384L353.8350000000001 481.835L414.165 542.165L512 444.33L609.835 542.165z" />
|
||||
<glyph glyph-name="error-1"
|
||||
unicode="error"
|
||||
horiz-adv-x="1024" d="M512 810.667C747.605 810.667 938.667 619.605 938.667 384C938.667 148.395 747.6049999999999 -42.6669999999999 511.9999999999999 -42.6669999999999C276.395 -42.6669999999999 85.333 148.3950000000001 85.333 384.0000000000001C85.333 619.605 276.395 810.667 512 810.667zM512 725.333C323.7970000000001 725.333 170.667 572.203 170.667 384C170.667 195.797 323.797 42.6669999999999 512 42.6669999999999C700.203 42.6669999999999 853.3330000000001 195.7969999999999 853.3330000000001 384C853.3330000000001 572.203 700.2030000000001 725.3330000000001 512 725.3330000000001zM609.835 542.165L670.1650000000001 481.835L572.33 384L670.1650000000001 286.165L609.835 225.8349999999999L512 323.67L414.165 225.8349999999999L353.8350000000001 286.165L451.67 384L353.8350000000001 481.835L414.165 542.165L512 444.33L609.835 542.165z" />
|
||||
<glyph glyph-name="wifi-strength-3"
|
||||
unicode=""
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917M511.4866651896334 682.8808891701171C642.3423366997788 682.8808891701171 771.0661477733722 646.2236788332648 882.7410693389065 578.4516956956384L800.0500591349871 474.8756459947375C735.688653096887 512.385001107775 634.2434648351453 555.0092228727087 511.4866651896334 555.0092228727087C383.614998892225 555.0092228727087 284.7272439564316 512.385001107775 222.0701274707015 476.5809345445006L139.3801162641751 578.8782675824273C251.9071826058945 646.6502507200537 380.6309936794878 682.8808891701171 511.4866651896334 682.8808891701171z" />
|
||||
@@ -67,12 +73,24 @@
|
||||
<glyph glyph-name="wifi-strength-4-1"
|
||||
unicode="wifi_strength_4"
|
||||
horiz-adv-x="1022.9733303792667" d="M511.4866651896334 768.1283337025917C332.0397604864727 768.1283337025917 157.7087214326013 708.0286505428097 16.1967447318741 597.6324456402496C187.9713494571903 381.9559014844185 336.3024823621839 198.2472739029395 511.4866651896334 -20.4132754656288C685.3921313541088 196.1154134663874 862.7071756207173 416.9078232715078 1008.0553023103668 597.6324456402497C866.1177527202434 708.0286505428097 691.3591427821901 768.1283337025917 511.4866651896334 768.1283337025917z" />
|
||||
<glyph glyph-name="success"
|
||||
unicode=""
|
||||
horiz-adv-x="1024" d="M512 810.667C276.36 810.667 85.333 619.64 85.333 384C85.333 148.36 276.3590000000001 -42.6669999999999 512 -42.6669999999999C747.64 -42.6669999999999 938.667 148.3590000000002 938.667 384.0000000000001C938.667 619.64 747.64 810.667 512 810.667zM512 42.667C323.79 42.667 170.667 195.789 170.667 384.0000000000001C170.667 572.21 323.789 725.3330000000001 512 725.3330000000001C700.21 725.3330000000001 853.3330000000001 572.211 853.3330000000001 384.0000000000001C853.3330000000001 195.7900000000001 700.211 42.667 512 42.667zM672.672 536.437L733.005 476.104L469.333 211.328L311.167 369.495L371.5 429.828L469.333 331.995L672.673 536.438z" />
|
||||
<glyph glyph-name="success-1"
|
||||
unicode="success"
|
||||
horiz-adv-x="1024" d="M512 810.667C276.36 810.667 85.333 619.64 85.333 384C85.333 148.36 276.3590000000001 -42.6669999999999 512 -42.6669999999999C747.64 -42.6669999999999 938.667 148.3590000000002 938.667 384.0000000000001C938.667 619.64 747.64 810.667 512 810.667zM512 42.667C323.79 42.667 170.667 195.789 170.667 384.0000000000001C170.667 572.21 323.789 725.3330000000001 512 725.3330000000001C700.21 725.3330000000001 853.3330000000001 572.211 853.3330000000001 384.0000000000001C853.3330000000001 195.7900000000001 700.211 42.667 512 42.667zM672.672 536.437L733.005 476.104L469.333 211.328L311.167 369.495L371.5 429.828L469.333 331.995L672.673 536.438z" />
|
||||
<glyph glyph-name="up"
|
||||
unicode=""
|
||||
horiz-adv-x="893.7641930109211" d="M69.6258266430383 317.8562626928574L25.3366188643721 362.1454704715236C6.5835308860179 380.8985584498777 6.5835308860179 411.2227007127483 25.3366188643721 429.7762877551625L412.9669373956711 817.6021172036826C431.7200253740253 836.3552051820368 462.0441676368958 836.3552051820368 480.59775467931 817.6021172036826L868.2280732106092 429.9717986723836C886.9811611889633 411.2187106940295 886.9811611889633 380.894568431159 868.2280732106092 362.3409813887447L823.938865431943 318.0517736100786C804.9862765176489 299.0991846957845 774.0636314469585 299.4981865676644 755.5100444045443 318.8497773538383L526.6824708814356 559.0528942442421V-14.3127956471388C526.6824708814356 -40.8464201271506 505.3358707358623 -62.1930202727239 478.8022462558506 -62.1930202727239H414.9619467550705C388.4283222750588 -62.1930202727239 367.0817221294855 -40.8464201271506 367.0817221294855 -14.3127956471388V559.0528942442422L138.0546476704369 318.6542664366173C119.5010606280227 299.1031747145034 88.5784155573324 298.7041728426234 69.6258266430383 317.8562626928575z" />
|
||||
<glyph glyph-name="up-1"
|
||||
unicode="up"
|
||||
horiz-adv-x="893.7641930109211" d="M69.6258266430383 317.8562626928574L25.3366188643721 362.1454704715236C6.5835308860179 380.8985584498777 6.5835308860179 411.2227007127483 25.3366188643721 429.7762877551625L412.9669373956711 817.6021172036826C431.7200253740253 836.3552051820368 462.0441676368958 836.3552051820368 480.59775467931 817.6021172036826L868.2280732106092 429.9717986723836C886.9811611889633 411.2187106940295 886.9811611889633 380.894568431159 868.2280732106092 362.3409813887447L823.938865431943 318.0517736100786C804.9862765176489 299.0991846957845 774.0636314469585 299.4981865676644 755.5100444045443 318.8497773538383L526.6824708814356 559.0528942442421V-14.3127956471388C526.6824708814356 -40.8464201271506 505.3358707358623 -62.1930202727239 478.8022462558506 -62.1930202727239H414.9619467550705C388.4283222750588 -62.1930202727239 367.0817221294855 -40.8464201271506 367.0817221294855 -14.3127956471388V559.0528942442422L138.0546476704369 318.6542664366173C119.5010606280227 299.1031747145034 88.5784155573324 298.7041728426234 69.6258266430383 317.8562626928575z" />
|
||||
<glyph glyph-name="alarm"
|
||||
unicode=""
|
||||
horiz-adv-x="1024" d="M512 810.667C747.22 810.667 938.667 619.22 938.667 384C938.667 148.78 747.2199999999999 -42.6669999999999 511.9999999999999 -42.6669999999999C276.78 -42.667 85.333 148.78 85.333 384C85.333 619.22 276.78 810.667 512 810.667zM512 725.333C323.082 725.333 170.667 572.918 170.667 384C170.667 195.082 323.082 42.6669999999999 512 42.6669999999999C700.918 42.6669999999999 853.3330000000001 195.0819999999999 853.3330000000001 384C853.3330000000001 572.918 700.9180000000001 725.3330000000001 512 725.3330000000001zM512 266.581C542.476 266.581 565.333 244.054 565.333 213.3340000000001S542.476 160.085 512 160.085C480.83 160.085 458.667 182.6130000000001 458.667 214.015C458.667 244.053 481.524 266.581 512 266.581zM554.667 640V341.3330000000001H469.333V640H554.667z" />
|
||||
<glyph glyph-name="alarm-1"
|
||||
unicode="alarm"
|
||||
horiz-adv-x="1024" d="M512 810.667C747.22 810.667 938.667 619.22 938.667 384C938.667 148.78 747.2199999999999 -42.6669999999999 511.9999999999999 -42.6669999999999C276.78 -42.667 85.333 148.78 85.333 384C85.333 619.22 276.78 810.667 512 810.667zM512 725.333C323.082 725.333 170.667 572.918 170.667 384C170.667 195.082 323.082 42.6669999999999 512 42.6669999999999C700.918 42.6669999999999 853.3330000000001 195.0819999999999 853.3330000000001 384C853.3330000000001 572.918 700.9180000000001 725.3330000000001 512 725.3330000000001zM512 266.581C542.476 266.581 565.333 244.054 565.333 213.3340000000001S542.476 160.085 512 160.085C480.83 160.085 458.667 182.6130000000001 458.667 214.015C458.667 244.053 481.524 266.581 512 266.581zM554.667 640V341.3330000000001H469.333V640H554.667z" />
|
||||
</font>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 27 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -74,8 +74,9 @@
|
||||
|
||||
"section": {
|
||||
"control": "Control",
|
||||
"states": "States and sensors",
|
||||
"otDiag": "OpenTherm diagnostic"
|
||||
"states": "States",
|
||||
"sensors": "Sensors",
|
||||
"diag": "OpenTherm diagnostic"
|
||||
},
|
||||
|
||||
"thermostat": {
|
||||
@@ -86,39 +87,45 @@
|
||||
"turbo": "Turbo mode"
|
||||
},
|
||||
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
|
||||
"sensors": {
|
||||
"values": {
|
||||
"temp": "Temperature",
|
||||
"humidity": "Humidity",
|
||||
"battery": "Battery",
|
||||
"rssi": "RSSI"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -161,6 +168,87 @@
|
||||
}
|
||||
},
|
||||
|
||||
"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)",
|
||||
"powerFactor": "Power (in percent)",
|
||||
"power": "Power (in kWt)",
|
||||
"fanSpeed": "Fan speed",
|
||||
"co2": "CO2",
|
||||
"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",
|
||||
"otExhaustCo2": "OpenTherm, exhaust CO2",
|
||||
"otExhaustFanSpeed": "OpenTherm, exhaust fan speed",
|
||||
"otSupplyFanSpeed": "OpenTherm, supply fan speed",
|
||||
"otSolarStorageTemp": "OpenTherm, solar storage temp",
|
||||
"otSolarCollectorTemp": "OpenTherm, solar collector temp",
|
||||
"otFanSpeedSetpoint": "OpenTherm, setpoint fan speed",
|
||||
"otFanSpeedCurrent": "OpenTherm, current fan speed",
|
||||
|
||||
"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",
|
||||
@@ -176,8 +264,6 @@
|
||||
"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"
|
||||
},
|
||||
@@ -207,11 +293,11 @@
|
||||
"statusLedGpio": "Status LED GPIO",
|
||||
"logLevel": "Log level",
|
||||
"serial": {
|
||||
"enable": "Enable Serial port",
|
||||
"enable": "Enabled Serial port",
|
||||
"baud": "Serial port baud rate"
|
||||
},
|
||||
"telnet": {
|
||||
"enable": "Enable Telnet",
|
||||
"enable": "Enabled Telnet",
|
||||
"port": {
|
||||
"title": "Telnet port",
|
||||
"note": "Default: 23"
|
||||
@@ -256,16 +342,9 @@
|
||||
"inGpio": "In GPIO",
|
||||
"outGpio": "Out GPIO",
|
||||
"ledGpio": "RX LED GPIO",
|
||||
"memberIdCode": "Master MemberID code",
|
||||
"memberId": "Master member ID",
|
||||
"flags": "Master flags",
|
||||
"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\"."
|
||||
@@ -274,17 +353,6 @@
|
||||
"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",
|
||||
@@ -315,20 +383,6 @@
|
||||
"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",
|
||||
@@ -340,14 +394,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": "Enable input",
|
||||
"enable": "Enabled 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": "Enable output",
|
||||
"enable": "Enabled output",
|
||||
"gpio": "GPIO",
|
||||
"invertState": "Invert GPIO state",
|
||||
"thresholdTime": "State change threshold time <small>(sec)</small>",
|
||||
@@ -374,7 +428,7 @@
|
||||
|
||||
"note": {
|
||||
"disclaimer1": "After a successful upgrade the filesystem, ALL settings will be reset to default values! Save backup before upgrading.",
|
||||
"disclaimer2": "After a successful upgrade, the device will automatically reboot after 10 seconds."
|
||||
"disclaimer2": "After a successful upgrade, the device will automatically reboot after 15 seconds."
|
||||
},
|
||||
|
||||
"settingsFile": "Settings file",
|
||||
|
||||
@@ -74,8 +74,9 @@
|
||||
|
||||
"section": {
|
||||
"control": "Управление",
|
||||
"states": "Состояние и сенсоры",
|
||||
"otDiag": "Диагностика OpenTherm"
|
||||
"states": "Состояние",
|
||||
"sensors": "Сенсоры",
|
||||
"diag": "Диагностика OpenTherm"
|
||||
},
|
||||
|
||||
"thermostat": {
|
||||
@@ -86,39 +87,45 @@
|
||||
"turbo": "Турбо"
|
||||
},
|
||||
|
||||
"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": "Темп. выхлопных газов"
|
||||
"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": "ГВС, температура обратки"
|
||||
},
|
||||
|
||||
"sensors": {
|
||||
"values": {
|
||||
"temp": "Температура",
|
||||
"humidity": "Влажность",
|
||||
"battery": "Уровень заряда",
|
||||
"rssi": "RSSI"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -161,6 +168,87 @@
|
||||
}
|
||||
},
|
||||
|
||||
"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": "Уровень модуляции (в процентах)",
|
||||
"powerFactor": "Мощность (в процентах)",
|
||||
"power": "Мощность (в кВт)",
|
||||
"fanSpeed": "Скорость вентилятора",
|
||||
"co2": "CO2",
|
||||
"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, текущая мощность",
|
||||
"otExhaustCo2": "OpenTherm, CO2 вытяжного воздуха",
|
||||
"otExhaustFanSpeed": "OpenTherm, скорость вытяжного вентилятора",
|
||||
"otSupplyFanSpeed": "OpenTherm, скорость приточного вентилятора",
|
||||
"otSolarStorageTemp": "OpenTherm, темп. бойлера солн. коллектора",
|
||||
"otSolarCollectorTemp": "OpenTherm, темп. солн. коллектора",
|
||||
"otFanSpeedSetpoint": "OpenTherm, установленная мощн. вентилятора",
|
||||
"otFanSpeedCurrent": "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": "Настройки",
|
||||
@@ -176,8 +264,6 @@
|
||||
"pid": "Настройки ПИД",
|
||||
"ot": "Настройки OpenTherm",
|
||||
"mqtt": "Настройки MQTT",
|
||||
"outdorSensor": "Настройки наружного датчика температуры",
|
||||
"indoorSensor": "Настройки внутреннего датчика температуры",
|
||||
"extPump": "Настройки дополнительного насоса",
|
||||
"cascadeControl": "Настройки каскадного управления"
|
||||
},
|
||||
@@ -231,21 +317,7 @@
|
||||
"title": "Целевая температура",
|
||||
"note": "<b>Важно:</b> <u>Целевая температура в помещении</u>, если включена ОТ опция <i>«Передать управление отоплением котлу»</i>.<br />Во всех остальных случаях <u>целевая температура теплоносителя</u>."
|
||||
},
|
||||
"treshold": "Пороговое время включения <small>(сек)</small>",
|
||||
|
||||
"events": {
|
||||
"desc": "События",
|
||||
"network": "При отключении сети",
|
||||
"mqtt": "При отключении MQTT",
|
||||
"indoorSensorDisconnect": "При потере связи с датчиком внутренней темп.",
|
||||
"outdoorSensorDisconnect": "При потере связи с датчиком наружной темп."
|
||||
},
|
||||
|
||||
"regulators": {
|
||||
"desc": "Используемые регуляторы",
|
||||
"equitherm": "ПЗА <small>(требуется внешний (DS18B20) или подключенный к котлу датчик <u>наружной</u> температуры)</small>",
|
||||
"pid": "ПИД <small>(требуется внешний (DS18B20) датчик <u>внутренней</u> температуры)</small>"
|
||||
}
|
||||
"treshold": "Пороговое время включения <small>(сек)</small>"
|
||||
},
|
||||
|
||||
"equitherm": {
|
||||
@@ -270,16 +342,9 @@
|
||||
"inGpio": "Вход GPIO",
|
||||
"outGpio": "Выход GPIO",
|
||||
"ledGpio": "RX LED GPIO",
|
||||
"memberIdCode": "Master MemberID код",
|
||||
"memberId": "Master member ID",
|
||||
"flags": "Master flags",
|
||||
"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": "Это значение соответствует уровню модуляции котла 0–1%. Обычно можно найти в спецификации котла как \"минимальная полезная тепловая мощность\"."
|
||||
@@ -288,17 +353,6 @@
|
||||
"title": "Макс. мощность котла <small>(кВт)</small>",
|
||||
"note": "<b>0</b> - попробовать определить автоматически. Обычно можно найти в спецификации котла как \"максимальная полезная тепловая мощность\"."
|
||||
},
|
||||
"fnv": {
|
||||
"desc": "Фильтрация числовых значений",
|
||||
"enable": {
|
||||
"title": "Включить фильтрацию",
|
||||
"note": "Может быть полезно, если на графиках много резкого шума. В качестве фильтра используется \"бегущее среднее\"."
|
||||
},
|
||||
"factor": {
|
||||
"title": "Коэфф. фильтрации",
|
||||
"note": "Чем меньше коэф., тем плавнее и <u>дольше</u> изменение числовых значений."
|
||||
}
|
||||
},
|
||||
|
||||
"options": {
|
||||
"desc": "Опции",
|
||||
@@ -329,20 +383,6 @@
|
||||
"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 реле",
|
||||
@@ -366,7 +406,7 @@
|
||||
"invertState": "Инвертировать состояние GPIO",
|
||||
"thresholdTime": "Пороговое время изменения состояния <small>(сек)</small>",
|
||||
"events": {
|
||||
"title": "События",
|
||||
"desc": "События",
|
||||
"onFault": "Если состояние fault (ошибки) активно",
|
||||
"onLossConnection": "Если соединение по OpenTherm потеряно",
|
||||
"onEnabledHeating": "Если отопление включено"
|
||||
@@ -388,7 +428,7 @@
|
||||
|
||||
"note": {
|
||||
"disclaimer1": "После успешного обновления файловой системы ВСЕ настройки будут сброшены на стандартные! Создайте резервную копию ПЕРЕД обновлением.",
|
||||
"disclaimer2": "После успешного обновления устройство автоматически перезагрузится через 10 секунд."
|
||||
"disclaimer2": "После успешного обновления устройство автоматически перезагрузится через 15 секунд."
|
||||
},
|
||||
|
||||
"settingsFile": "Файл настроек",
|
||||
|
||||
@@ -4,19 +4,20 @@
|
||||
<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" />
|
||||
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">
|
||||
<li>
|
||||
<a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</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>
|
||||
@@ -42,31 +43,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="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 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>
|
||||
<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-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-control">
|
||||
<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="tHeatEnabled" value="true">
|
||||
<label htmlFor="tHeatEnabled" data-i18n>dashboard.thermostat.enable</label>
|
||||
|
||||
<input type="checkbox" role="switch" id="thermostat-heating-turbo" value="true">
|
||||
<label htmlFor="thermostat-heating-turbo" data-i18n>dashboard.thermostat.turbo</label>
|
||||
<input type="checkbox" role="switch" id="tHeatTurbo" value="true">
|
||||
<label htmlFor="tHeatTurbo" 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="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 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>
|
||||
<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-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-control">
|
||||
<input type="checkbox" role="switch" id="thermostat-dhw-enabled" value="true">
|
||||
<label htmlFor="thermostat-dhw-enabled" data-i18n>dashboard.thermostat.enable</label>
|
||||
<input type="checkbox" role="switch" id="tDhwEnabled" value="true">
|
||||
<label htmlFor="tDhwEnabled" data-i18n>dashboard.thermostat.enable</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,132 +80,116 @@
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.ot</th>
|
||||
<td><input type="radio" id="ot-connected" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mNetworkConnected</th>
|
||||
<td><i class="mNetworkConnected"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.mqtt</th>
|
||||
<td><input type="radio" id="mqtt-connected" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mMqttConnected</th>
|
||||
<td><i class="mMqttConnected"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.emergency</th>
|
||||
<td><input type="radio" id="ot-emergency" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mEmergencyState</th>
|
||||
<td><i class="mEmergencyState"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.heating</th>
|
||||
<td><input type="radio" id="ot-heating" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mExtPumpState</th>
|
||||
<td><i class="mExtPumpState"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.dhw</th>
|
||||
<td><input type="radio" id="ot-dhw" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mCascadeControlInput</th>
|
||||
<td><i class="mCascadeControlInput"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.flame</th>
|
||||
<td><input type="radio" id="ot-flame" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mCascadeControlOutput</th>
|
||||
<td><i class="mCascadeControlOutput"></i></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.states.sConnected</th>
|
||||
<td><i class="sConnected"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.fault</th>
|
||||
<td><input type="radio" id="ot-fault" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.sFlame</th>
|
||||
<td><i class="sFlame"></i></td>
|
||||
</tr>
|
||||
|
||||
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.states.sFaultActive</th>
|
||||
<td><i class="sFaultActive"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.diag</th>
|
||||
<td><input type="radio" id="ot-diagnostic" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.sFaultCode</th>
|
||||
<td><b class="sFaultCode"></b></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.extpump</th>
|
||||
<td><input type="radio" id="ot-external-pump" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.sDiagActive</th>
|
||||
<td><i class="sDiagActive"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorConnected</th>
|
||||
<td><input type="radio" id="outdoor-sensor-connected" aria-invalid="false" checked disabled /></td>
|
||||
<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><i class="mHeatEnabled"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorRssi</th>
|
||||
<td><b id="outdoor-sensor-rssi"></b> <span data-i18n>dbm</span></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mHeatBlocking</th>
|
||||
<td><i class="mHeatBlocking"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorHumidity</th>
|
||||
<td><b id="outdoor-sensor-humidity"></b> %</td>
|
||||
<th scope="row" data-i18n>dashboard.states.sHeatActive</th>
|
||||
<td><i class="sHeatActive"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.outdoorSensorBattery</th>
|
||||
<td><b id="outdoor-sensor-battery"></b> %</td>
|
||||
<th scope="row" data-i18n>dashboard.states.mHeatTargetTemp</th>
|
||||
<td><b class="mHeatTargetTemp"></b> <span class="tempUnit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorConnected</th>
|
||||
<td><input type="radio" id="indoor-sensor-connected" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mHeatSetpointTemp</th>
|
||||
<td><b class="mHeatSetpointTemp"></b> <span class="tempUnit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.cascadeControlInput</th>
|
||||
<td><input type="radio" id="cc-input" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mHeatCurrTemp</th>
|
||||
<td><b class="mHeatCurrTemp"></b> <span class="tempUnit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.cascadeControlOutput</th>
|
||||
<td><input type="radio" id="cc-output" aria-invalid="false" checked disabled /></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mHeatRetTemp</th>
|
||||
<td><b class="mHeatRetTemp"></b> <span class="tempUnit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorRssi</th>
|
||||
<td><b id="indoor-sensor-rssi"></b> <span data-i18n>dbm</span></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mHeatIndoorTemp</th>
|
||||
<td><b class="mHeatIndoorTemp"></b> <span class="tempUnit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorHumidity</th>
|
||||
<td><b id="indoor-sensor-humidity"></b> %</td>
|
||||
<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><i class="mDhwEnabled"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.indoorSensorBattery</th>
|
||||
<td><b id="indoor-sensor-battery"></b> %</td>
|
||||
<th scope="row" data-i18n>dashboard.states.sDhwActive</th>
|
||||
<td><i class="sDhwActive"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.modulation</th>
|
||||
<td><b id="ot-modulation"></b> %</td>
|
||||
<th scope="row" data-i18n>dashboard.states.mDhwTargetTemp</th>
|
||||
<td><b class="mDhwTargetTemp"></b> <span class="tempUnit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>dashboard.state.pressure</th>
|
||||
<td><b id="ot-pressure"></b> <span class="pressure-unit"></span></td>
|
||||
<th scope="row" data-i18n>dashboard.states.mDhwCurrTemp</th>
|
||||
<td><b class="mDhwCurrTemp"></b> <span class="tempUnit"></span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<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>
|
||||
<th scope="row" data-i18n>dashboard.states.mDhwRetTemp</th>
|
||||
<td><b class="mDhwRetTemp"></b> <span class="tempUnit"></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -213,15 +198,36 @@
|
||||
<hr />
|
||||
|
||||
<details>
|
||||
<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>
|
||||
<summary><b data-i18n>dashboard.section.sensors</b></summary>
|
||||
<table>
|
||||
<tbody class="sensors">
|
||||
<tr class="sensor template hidden">
|
||||
<td style="width: 1.35rem">
|
||||
<span class="sStatusContainer">
|
||||
<i class="sStatus"></i>
|
||||
</span>
|
||||
</td>
|
||||
<th scope="row" class="sName"></th>
|
||||
<td class="sValue"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</details>
|
||||
|
||||
<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>
|
||||
</details>
|
||||
</div>
|
||||
</article>
|
||||
@@ -238,7 +244,7 @@
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script src="/static/app.js?{BUILD_TIME}"></script>
|
||||
<script>
|
||||
let modifiedTime = null;
|
||||
let noRegulators;
|
||||
@@ -259,7 +265,7 @@
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
lang.build();
|
||||
|
||||
document.querySelector('#thermostat-heating-minus').addEventListener('click', (event) => {
|
||||
document.querySelector('#tHeatActionMinus').addEventListener('click', (event) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
@@ -278,10 +284,10 @@
|
||||
newSettings.heating.target = minTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-heating-target', newSettings.heating.target);
|
||||
setValue('#tHeatTargetTemp', newSettings.heating.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-heating-plus').addEventListener('click', (event) => {
|
||||
document.querySelector('#tHeatActionPlus').addEventListener('click', (event) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
@@ -300,10 +306,10 @@
|
||||
newSettings.heating.target = maxTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-heating-target', newSettings.heating.target);
|
||||
setValue('#tHeatTargetTemp', newSettings.heating.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-dhw-minus').addEventListener('click', (event) => {
|
||||
document.querySelector('#tDhwActionMinus').addEventListener('click', (event) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
@@ -315,10 +321,10 @@
|
||||
newSettings.dhw.target = prevSettings.dhw.minTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-dhw-target', newSettings.dhw.target);
|
||||
setValue('#tDhwTargetTemp', newSettings.dhw.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-dhw-plus').addEventListener('click', (event) => {
|
||||
document.querySelector('#tDhwActionPlus').addEventListener('click', (event) => {
|
||||
if (!prevSettings) {
|
||||
return;
|
||||
}
|
||||
@@ -330,25 +336,27 @@
|
||||
newSettings.dhw.target = prevSettings.dhw.maxTemp;
|
||||
}
|
||||
|
||||
setValue('#thermostat-dhw-target', newSettings.dhw.target);
|
||||
setValue('#tDhwTargetTemp', newSettings.dhw.target);
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-heating-enabled').addEventListener('change', (event) => {
|
||||
document.querySelector('#tHeatEnabled').addEventListener('change', (event) => {
|
||||
modifiedTime = Date.now();
|
||||
newSettings.heating.enable = event.currentTarget.checked;
|
||||
newSettings.heating.enabled = event.currentTarget.checked;
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-heating-turbo').addEventListener('change', (event) => {
|
||||
document.querySelector('#tHeatTurbo').addEventListener('change', (event) => {
|
||||
modifiedTime = Date.now();
|
||||
newSettings.heating.turbo = event.currentTarget.checked;
|
||||
});
|
||||
|
||||
document.querySelector('#thermostat-dhw-enabled').addEventListener('change', (event) => {
|
||||
document.querySelector('#tDhwEnabled').addEventListener('change', (event) => {
|
||||
modifiedTime = Date.now();
|
||||
newSettings.dhw.enable = event.currentTarget.checked;
|
||||
newSettings.dhw.enabled = event.currentTarget.checked;
|
||||
});
|
||||
|
||||
setTimeout(async function onLoadPage() {
|
||||
let unitSystem = null;
|
||||
|
||||
if (modifiedTime) {
|
||||
if ((Date.now() - modifiedTime) < 5000) {
|
||||
setTimeout(onLoadPage, 1000);
|
||||
@@ -361,10 +369,10 @@
|
||||
// settings
|
||||
try {
|
||||
let modified = prevSettings && (
|
||||
(prevSettings.heating.enable != newSettings.heating.enable)
|
||||
(prevSettings.heating.enabled != newSettings.heating.enabled)
|
||||
|| (prevSettings.heating.turbo != newSettings.heating.turbo)
|
||||
|| (prevSettings.heating.target != newSettings.heating.target)
|
||||
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enable != newSettings.dhw.enable)
|
||||
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.enabled != newSettings.dhw.enabled)
|
||||
|| (prevSettings.opentherm.dhwPresent && prevSettings.dhw.target != newSettings.dhw.target)
|
||||
);
|
||||
|
||||
@@ -372,24 +380,30 @@
|
||||
console.log(newSettings);
|
||||
}
|
||||
|
||||
let parameters = { cache: 'no-cache' };
|
||||
let parameters = {
|
||||
method: "GET",
|
||||
cache: "no-cache",
|
||||
credentials: "include"
|
||||
};
|
||||
|
||||
if (modified) {
|
||||
parameters.method = "POST";
|
||||
parameters.body = JSON.stringify(newSettings);
|
||||
}
|
||||
|
||||
const response = await fetch('/api/settings', parameters);
|
||||
const response = await fetch("/api/settings", parameters);
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enable && !result.pid.enable;
|
||||
noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enabled && !result.pid.enabled;
|
||||
prevSettings = result;
|
||||
newSettings.heating.enable = result.heating.enable;
|
||||
unitSystem = result.system.unitSystem;
|
||||
newSettings.heating.enabled = result.heating.enabled;
|
||||
newSettings.heating.turbo = result.heating.turbo;
|
||||
newSettings.heating.target = result.heating.target;
|
||||
newSettings.dhw.enable = result.dhw.enable;
|
||||
newSettings.dhw.enabled = result.dhw.enabled;
|
||||
newSettings.dhw.target = result.dhw.target;
|
||||
|
||||
if (result.opentherm.dhwPresent) {
|
||||
@@ -398,16 +412,16 @@
|
||||
hide('#thermostat-dhw');
|
||||
}
|
||||
|
||||
setCheckboxValue('#thermostat-heating-enabled', result.heating.enable);
|
||||
setCheckboxValue('#thermostat-heating-turbo', result.heating.turbo);
|
||||
setValue('#thermostat-heating-target', result.heating.target);
|
||||
setCheckboxValue('#tHeatEnabled', result.heating.enabled);
|
||||
setCheckboxValue('#tHeatTurbo', result.heating.turbo);
|
||||
setValue('#tHeatTargetTemp', result.heating.target);
|
||||
|
||||
setCheckboxValue('#thermostat-dhw-enabled', result.dhw.enable);
|
||||
setValue('#thermostat-dhw-target', result.dhw.target);
|
||||
setCheckboxValue('#tDhwEnabled', result.dhw.enabled);
|
||||
setValue('#tDhwTargetTemp', result.dhw.target);
|
||||
|
||||
setValue('.temp-unit', temperatureUnit(result.system.unitSystem));
|
||||
setValue('.pressure-unit', pressureUnit(result.system.unitSystem));
|
||||
setValue('.volume-unit', volumeUnit(result.system.unitSystem));
|
||||
setValue('.tempUnit', temperatureUnit(unitSystem));
|
||||
setValue('.pressureUnit', pressureUnit(unitSystem));
|
||||
setValue('.volumeUnit', volumeUnit(unitSystem));
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -415,79 +429,206 @@
|
||||
|
||||
// vars
|
||||
try {
|
||||
const response = await fetch('/api/vars', { cache: 'no-cache' });
|
||||
const response = await fetch("/api/vars", {
|
||||
cache: "no-cache",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
setValue('#thermostat-heating-current', noRegulators ? result.temperatures.heating : result.temperatures.indoor);
|
||||
setValue('#thermostat-dhw-current', result.temperatures.dhw);
|
||||
|
||||
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);
|
||||
// Graph
|
||||
setValue('#tHeatCurrentTemp', result.master.heating.indoorTempControl
|
||||
? result.master.heating.indoorTemp
|
||||
: result.master.heating.currentTemp
|
||||
);
|
||||
setValue('#tDhwCurrentTemp', result.master.dhw.currentTemp);
|
||||
|
||||
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('#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(
|
||||
'#ot-fault-code',
|
||||
result.sensors.faultCode
|
||||
? (result.sensors.faultCode + " (0x" + dec2hex(result.sensors.faultCode) + ")")
|
||||
: "-"
|
||||
// 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);
|
||||
|
||||
setStatus(
|
||||
'.sConnected',
|
||||
result.slave.connected ? "success" : "error",
|
||||
result.slave.connected ? "green" : "red"
|
||||
);
|
||||
setState('.sFlame', result.slave.flame);
|
||||
|
||||
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);
|
||||
|
||||
setStatus(
|
||||
'.sFaultActive',
|
||||
result.slave.fault.active ? "success" : "error",
|
||||
result.slave.fault.active ? "red" : "green"
|
||||
);
|
||||
setValue(
|
||||
'#ot-diag-code',
|
||||
result.sensors.diagnosticCode
|
||||
? (result.sensors.diagnosticCode + " (0x" + dec2hex(result.sensors.diagnosticCode) + ")")
|
||||
'.sFaultCode',
|
||||
result.slave.fault.active
|
||||
? `${result.slave.fault.code} (0x${dec2hex(result.slave.fault.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);
|
||||
setStatus(
|
||||
'.sDiagActive',
|
||||
result.slave.diag.active ? "success" : "error",
|
||||
result.slave.diag.active ? "red" : "green"
|
||||
);
|
||||
setValue(
|
||||
'.sDiagCode',
|
||||
result.slave.diag.active
|
||||
? `${result.slave.diag.code} (0x${dec2hex(result.slave.diag.code)})`
|
||||
: "-"
|
||||
);
|
||||
|
||||
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);
|
||||
|
||||
setValue('#slave-member-id', result.parameters.slaveMemberId);
|
||||
setValue('#slave-vendor', memberIdToVendor(result.parameters.slaveMemberId));
|
||||
// MASTER
|
||||
setState('.mHeatEnabled', result.master.heating.enabled);
|
||||
setStatus(
|
||||
'.mHeatBlocking',
|
||||
result.master.heating.blocking ? "success" : "error",
|
||||
result.master.heating.blocking ? "red" : "green"
|
||||
);
|
||||
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);
|
||||
|
||||
setStatus(
|
||||
'.mNetworkConnected',
|
||||
result.master.network.connected ? "success" : "error",
|
||||
result.master.network.connected ? "green" : "red"
|
||||
);
|
||||
setState('.mMqttConnected', result.master.mqtt.connected);
|
||||
setStatus(
|
||||
'.mEmergencyState',
|
||||
result.master.emergency.state ? "success" : "error",
|
||||
result.master.emergency.state ? "red" : "green"
|
||||
);
|
||||
setState('.mExtPumpState', result.master.externalPump.state);
|
||||
setState('.mCascadeControlInput', result.master.cascadeControl.input);
|
||||
setState('.mCascadeControlOutput', result.master.cascadeControl.output);
|
||||
|
||||
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) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
// sensors
|
||||
try {
|
||||
const response = await fetch("/api/sensors?detailed=1", {
|
||||
cache: "no-cache",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Response not valid");
|
||||
}
|
||||
|
||||
const container = document.querySelector(".sensors");
|
||||
const templateNode = container.querySelector(".template");
|
||||
const result = await response.json();
|
||||
|
||||
for (const sensorId in result) {
|
||||
let sensorNode = container.querySelector(`.sensor[data-id='${sensorId}']`);
|
||||
if (sensorNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
sensorNode = templateNode.cloneNode(true);
|
||||
sensorNode.dataset.id = sensorId;
|
||||
sensorNode.classList.remove("template");
|
||||
container.appendChild(sensorNode);
|
||||
}
|
||||
|
||||
for (const sensorId in result) {
|
||||
const sensorNode = container.querySelector(`.sensor[data-id='${sensorId}']`);
|
||||
if (!sensorNode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const sData = result[sensorId];
|
||||
if (!sData.enabled || sData.purpose == 255) {
|
||||
sensorNode.classList.toggle("hidden", true);
|
||||
continue;
|
||||
}
|
||||
|
||||
sensorNode.classList.toggle("hidden", false);
|
||||
|
||||
setStatus(
|
||||
".sStatus",
|
||||
sData.connected ? "success" : "error",
|
||||
sData.connected ? "green" : "red",
|
||||
sensorNode
|
||||
);
|
||||
setValue(".sName", sData.name, sensorNode);
|
||||
setValue(".sValue", "", sensorNode);
|
||||
|
||||
const statusNode = sensorNode.querySelector(`.sStatusContainer`);
|
||||
if (statusNode) {
|
||||
statusNode.dataset.tooltip = `${sData.signalQuality}%`;
|
||||
}
|
||||
|
||||
if (sData.value !== undefined) {
|
||||
const sUnit = purposeUnit(sData.purpose, unitSystem);
|
||||
appendValue(".sValue", `<b>${sData.value.toFixed(2)}</b> ${sUnit !== null ? sUnit : ``}`, `<br />`, sensorNode);
|
||||
}
|
||||
|
||||
if (sData.temperature !== undefined) {
|
||||
const sUnit = temperatureUnit(unitSystem);
|
||||
appendValue(".sValue", `${i18n('dashboard.sensors.values.temp')}: <b>${sData.temperature.toFixed(2)}</b> ${sUnit !== null ? sUnit : ``}`, `<br />`, sensorNode);
|
||||
}
|
||||
|
||||
if (sData.humidity !== undefined) {
|
||||
appendValue(".sValue", `${i18n('dashboard.sensors.values.humidity')}: <b>${sData.humidity.toFixed(2)}</b> %`, `<br />`, sensorNode);
|
||||
}
|
||||
|
||||
if (sData.battery !== undefined) {
|
||||
appendValue(".sValue", `${i18n('dashboard.sensors.values.battery')}: <b>${sData.battery.toFixed(2)}</b> %`, `<br />`, sensorNode);
|
||||
}
|
||||
|
||||
if (sData.rssi !== undefined) {
|
||||
appendValue(".sValue", `${i18n('dashboard.sensors.values.rssi')}: <b>${sData.rssi.toFixed(0)}</b> ${i18n('dbm')}`, `<br />`, sensorNode);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
setTimeout(onLoadPage, 10000);
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
@@ -4,19 +4,20 @@
|
||||
<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" />
|
||||
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">
|
||||
<li>
|
||||
<a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</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>
|
||||
@@ -48,7 +49,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>network.wifi.connected</th>
|
||||
<td><input type="radio" id="network-connected" aria-invalid="false" checked disabled /></td>
|
||||
<td><i id="network-connected"></i></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row" data-i18n>network.wifi.ssid</th>
|
||||
@@ -139,9 +140,10 @@
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="grid">
|
||||
<div class="grid" style="grid-template-columns: repeat(auto-fit,minmax(12rem,1fr)) !important;">
|
||||
<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>
|
||||
@@ -160,7 +162,7 @@
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script src="/static/app.js?{BUILD_TIME}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
@@ -168,7 +170,11 @@
|
||||
|
||||
setTimeout(async function onLoadPage() {
|
||||
try {
|
||||
const response = await fetch('/api/info', { cache: 'no-cache' });
|
||||
const response = await fetch("/api/info", {
|
||||
cache: "no-cache",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
@@ -183,7 +189,11 @@
|
||||
|
||||
setValue('#network-hostname', result.network.hostname);
|
||||
setValue('#network-mac', result.network.mac);
|
||||
setState('#network-connected', result.network.connected);
|
||||
setStatus(
|
||||
'#network-connected',
|
||||
result.network.connected ? "success" : "error",
|
||||
result.network.connected ? "green" : "red"
|
||||
);
|
||||
setValue('#network-ssid', result.network.ssid);
|
||||
setValue('#network-signal', result.network.signalQuality);
|
||||
setValue('#network-ip', result.network.ip);
|
||||
|
||||
@@ -4,19 +4,20 @@
|
||||
<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" />
|
||||
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">
|
||||
<li>
|
||||
<a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</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>
|
||||
@@ -171,7 +172,7 @@
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script src="/static/app.js?{BUILD_TIME}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
@@ -198,7 +199,11 @@
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/network/settings', { cache: 'no-cache' });
|
||||
const response = await fetch("/api/network/settings", {
|
||||
cache: "no-cache",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
|
||||
321
src_data/pages/sensors.html
Normal file
321
src_data/pages/sensors.html
Normal file
@@ -0,0 +1,321 @@
|
||||
<!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>
|
||||
<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="248" data-i18n>sensors.purposes.powerFactor</option>
|
||||
<option value="249" data-i18n>sensors.purposes.power</option>
|
||||
<option value="250" data-i18n>sensors.purposes.fanSpeed</option>
|
||||
<option value="251" data-i18n>sensors.purposes.co2</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="12" data-i18n>sensors.types.otExhaustCo2</option>
|
||||
<option value="13" data-i18n>sensors.types.otExhaustFanSpeed</option>
|
||||
<option value="14" data-i18n>sensors.types.otSupplyFanSpeed</option>
|
||||
<option value="15" data-i18n>sensors.types.otSolarStorageTemp</option>
|
||||
<option value="16" data-i18n>sensors.types.otSolarCollectorTemp</option>
|
||||
<option value="17" data-i18n>sensors.types.otFanSpeedSetpoint</option>
|
||||
<option value="18" data-i18n>sensors.types.otFanSpeedCurrent</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",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
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",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
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>
|
||||
@@ -4,19 +4,20 @@
|
||||
<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" />
|
||||
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">
|
||||
<li>
|
||||
<a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</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>
|
||||
@@ -95,12 +96,12 @@
|
||||
<legend data-i18n>settings.section.diag</legend>
|
||||
|
||||
<label for="system-serial-enable">
|
||||
<input type="checkbox" id="system-serial-enable" name="system[serial][enable]" value="true">
|
||||
<input type="checkbox" id="system-serial-enable" name="system[serial][enabled]" 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][enable]" value="true">
|
||||
<input type="checkbox" id="system-telnet-enable" name="system[telnet][enabled]" value="true">
|
||||
<span data-i18n>settings.system.telnet.enable</span>
|
||||
</label>
|
||||
|
||||
@@ -245,7 +246,7 @@
|
||||
<form action="/api/settings" id="equitherm-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="equitherm-enable">
|
||||
<input type="checkbox" id="equitherm-enable" name="equitherm[enable]" value="true">
|
||||
<input type="checkbox" id="equitherm-enable" name="equitherm[enabled]" value="true">
|
||||
<span data-i18n>settings.enable</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
@@ -282,7 +283,7 @@
|
||||
<form action="/api/settings" id="pid-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="pid-enable">
|
||||
<input type="checkbox" id="pid-enable" name="pid[enable]" value="true">
|
||||
<input type="checkbox" id="pid-enable" name="pid[enabled]" value="true">
|
||||
<span data-i18n>settings.enable</span>
|
||||
</label>
|
||||
</fieldset>
|
||||
@@ -371,8 +372,13 @@
|
||||
|
||||
<div class="grid">
|
||||
<label for="opentherm-member-id-code">
|
||||
<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>
|
||||
<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>
|
||||
</label>
|
||||
|
||||
<label for="opentherm-max-modulation">
|
||||
@@ -450,44 +456,6 @@
|
||||
<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>
|
||||
@@ -503,7 +471,7 @@
|
||||
<form action="/api/settings" id="mqtt-settings" class="hidden">
|
||||
<fieldset>
|
||||
<label for="mqtt-enable">
|
||||
<input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true">
|
||||
<input type="checkbox" id="mqtt-enable" name="mqtt[enabled]" value="true">
|
||||
<span data-i18n>settings.enable</span>
|
||||
</label>
|
||||
|
||||
@@ -556,120 +524,6 @@
|
||||
|
||||
<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>
|
||||
@@ -720,9 +574,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][enable]" value="true">
|
||||
<input type="checkbox" id="cc-input-enable" name="cascadeControl[input][enabled]" value="true">
|
||||
<span data-i18n>settings.cascadeControl.input.enable</span>
|
||||
<br>
|
||||
<br />
|
||||
<small data-i18n>settings.cascadeControl.input.desc</small>
|
||||
</label>
|
||||
|
||||
@@ -748,9 +602,9 @@
|
||||
|
||||
<fieldset>
|
||||
<label for="cc-output-enable">
|
||||
<input type="checkbox" id="cc-output-enable" name="cascadeControl[output][enable]" value="true">
|
||||
<input type="checkbox" id="cc-output-enable" name="cascadeControl[output][enabled]" value="true">
|
||||
<span data-i18n>settings.cascadeControl.output.enable</span>
|
||||
<br>
|
||||
<br />
|
||||
<small data-i18n>settings.cascadeControl.output.desc</small>
|
||||
</label>
|
||||
|
||||
@@ -809,19 +663,18 @@
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script src="/static/app.js?{BUILD_TIME}"></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.enable);
|
||||
setCheckboxValue('#system-serial-enable', data.system.serial.enabled);
|
||||
setSelectValue('#system-serial-baudrate', data.system.serial.baudrate);
|
||||
setCheckboxValue('#system-telnet-enable', data.system.telnet.enable);
|
||||
setCheckboxValue('#system-telnet-enable', data.system.telnet.enabled);
|
||||
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 : '');
|
||||
@@ -838,10 +691,9 @@
|
||||
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-code', data.opentherm.memberIdCode);
|
||||
setInputValue('#opentherm-member-id', data.opentherm.memberId);
|
||||
setInputValue('#opentherm-flags', data.opentherm.flags);
|
||||
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);
|
||||
@@ -854,12 +706,10 @@
|
||||
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.enable);
|
||||
setCheckboxValue('#mqtt-enable', data.mqtt.enabled);
|
||||
setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery);
|
||||
setInputValue('#mqtt-server', data.mqtt.server);
|
||||
setInputValue('#mqtt-port', data.mqtt.port);
|
||||
@@ -869,20 +719,6 @@
|
||||
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 : '');
|
||||
@@ -892,12 +728,12 @@
|
||||
setBusy('#extpump-settings-busy', '#extpump-settings', false);
|
||||
|
||||
// Cascade control
|
||||
setCheckboxValue('#cc-input-enable', data.cascadeControl.input.enable);
|
||||
setCheckboxValue('#cc-input-enable', data.cascadeControl.input.enabled);
|
||||
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.enable);
|
||||
setCheckboxValue('#cc-output-enable', data.cascadeControl.output.enabled);
|
||||
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);
|
||||
@@ -948,20 +784,20 @@
|
||||
setBusy('#emergency-settings-busy', '#emergency-settings', false);
|
||||
|
||||
// Equitherm
|
||||
setCheckboxValue('#equitherm-enable', data.equitherm.enable);
|
||||
setCheckboxValue('#equitherm-enable', data.equitherm.enabled);
|
||||
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.enable);
|
||||
setCheckboxValue('#pid-enable', data.pid.enabled);
|
||||
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.enable ? (data.system.unitSystem == 0 ? -100 : -146) : (data.system.unitSystem == 0 ? 0 : 32),
|
||||
"min": data.equitherm.enabled ? (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, {
|
||||
@@ -972,7 +808,11 @@
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/settings', { cache: 'no-cache' });
|
||||
const response = await fetch("/api/settings", {
|
||||
cache: "no-cache",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Response not valid');
|
||||
}
|
||||
@@ -989,8 +829,6 @@
|
||||
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);
|
||||
|
||||
|
||||
@@ -4,19 +4,20 @@
|
||||
<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">
|
||||
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header class="container">
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">
|
||||
<li>
|
||||
<a href="/">
|
||||
<div class="logo" data-i18n>logo</div>
|
||||
</a></li>
|
||||
</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>
|
||||
@@ -97,7 +98,7 @@
|
||||
</small>
|
||||
</footer>
|
||||
|
||||
<script src="/static/app.js"></script>
|
||||
<script src="/static/app.js?{BUILD_TIME}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const lang = new Lang(document.getElementById('lang'));
|
||||
|
||||
@@ -32,7 +32,7 @@ class Lang {
|
||||
}
|
||||
|
||||
if (!this.localeIsSupported(this.defaultLocale)) {
|
||||
const selected = this.switcher.selectedIndex ?? 0;
|
||||
const selected = this.switcher.selectedIndex ? this.switcher.selectedIndex : 0;
|
||||
this.defaultLocale = this.switcher.options[selected].value;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ class Lang {
|
||||
}
|
||||
|
||||
async fetchTranslations(locale) {
|
||||
const response = await fetch(`/static/locales/${locale}.json`);
|
||||
const response = await fetch(`/static/locales/${locale}.json?{BUILD_TIME}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.values instanceof Object) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
|
||||
const setupForm = (formSelector, onResultCallback = null, noCastItems = []) => {
|
||||
const form = document.querySelector(formSelector);
|
||||
if (!form) {
|
||||
return;
|
||||
@@ -10,13 +10,13 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
|
||||
})
|
||||
});
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
const url = form.action;
|
||||
let button = form.querySelector('button[type="submit"]');
|
||||
let defaultText;
|
||||
|
||||
form.addEventListener('submit', async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
if (button) {
|
||||
defaultText = button.textContent;
|
||||
button.textContent = i18n("button.wait");
|
||||
@@ -60,10 +60,11 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
|
||||
}
|
||||
|
||||
let response = await fetch(url, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: form2json(fd, noCastItems)
|
||||
});
|
||||
@@ -86,7 +87,7 @@ function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
|
||||
});
|
||||
}
|
||||
|
||||
function setupNetworkScanForm(formSelector, tableSelector) {
|
||||
const setupNetworkScanForm = (formSelector, tableSelector) => {
|
||||
const form = document.querySelector(formSelector);
|
||||
if (!form) {
|
||||
console.error("form not found");
|
||||
@@ -132,7 +133,7 @@ function setupNetworkScanForm(formSelector, tableSelector) {
|
||||
let row = tbody.insertRow(-1);
|
||||
row.classList.add("network");
|
||||
row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid);
|
||||
row.onclick = function () {
|
||||
row.onclick = () => {
|
||||
const input = document.querySelector('input#sta-ssid');
|
||||
const ssid = this.getAttribute('data-ssid');
|
||||
if (!input || !ssid) {
|
||||
@@ -143,8 +144,8 @@ function setupNetworkScanForm(formSelector, tableSelector) {
|
||||
input.focus();
|
||||
};
|
||||
|
||||
row.insertCell().textContent = "#" + (i + 1);
|
||||
row.insertCell().innerHTML = result[i].hidden ? ("<i>" + result[i].bssid + "</i>") : result[i].ssid;
|
||||
row.insertCell().textContent = `#${i + 1}`;
|
||||
row.insertCell().innerHTML = result[i].hidden ? `<i>${result[i].bssid}</i>` : result[i].ssid;
|
||||
|
||||
// info cell
|
||||
let infoCell = row.insertCell();
|
||||
@@ -164,7 +165,7 @@ function setupNetworkScanForm(formSelector, tableSelector) {
|
||||
}
|
||||
|
||||
let signalQualityContainer = document.createElement("span");
|
||||
signalQualityContainer.setAttribute('data-tooltip', result[i].signalQuality + "%");
|
||||
signalQualityContainer.setAttribute('data-tooltip', `${result[i].signalQuality}%`);
|
||||
signalQualityContainer.appendChild(signalQualityIcon);
|
||||
infoCell.appendChild(signalQualityContainer);
|
||||
|
||||
@@ -218,7 +219,10 @@ function setupNetworkScanForm(formSelector, tableSelector) {
|
||||
attempts--;
|
||||
|
||||
try {
|
||||
let response = await fetch(url, { cache: 'no-cache' });
|
||||
let response = await fetch(url, {
|
||||
cache: "no-cache",
|
||||
credentials: "include"
|
||||
});
|
||||
|
||||
if (response.status == 200) {
|
||||
await onSuccess(response);
|
||||
@@ -246,7 +250,7 @@ function setupNetworkScanForm(formSelector, tableSelector) {
|
||||
onSubmitFn();
|
||||
}
|
||||
|
||||
function setupRestoreBackupForm(formSelector) {
|
||||
const setupRestoreBackupForm = (formSelector) => {
|
||||
const form = document.querySelector(formSelector);
|
||||
if (!form) {
|
||||
return;
|
||||
@@ -266,7 +270,7 @@ function setupRestoreBackupForm(formSelector) {
|
||||
button.setAttribute('aria-busy', true);
|
||||
}
|
||||
|
||||
const onSuccess = (response) => {
|
||||
const onSuccess = () => {
|
||||
if (button) {
|
||||
button.textContent = i18n('button.restored');
|
||||
button.classList.add('success');
|
||||
@@ -280,7 +284,7 @@ function setupRestoreBackupForm(formSelector) {
|
||||
}
|
||||
};
|
||||
|
||||
const onFailed = (response) => {
|
||||
const onFailed = () => {
|
||||
if (button) {
|
||||
button.textContent = i18n('button.error');
|
||||
button.classList.add('failed');
|
||||
@@ -302,35 +306,82 @@ function setupRestoreBackupForm(formSelector) {
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.readAsText(files[0]);
|
||||
reader.onload = async function () {
|
||||
reader.onload = async (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.target.result);
|
||||
console.log("Backup: ", data);
|
||||
|
||||
if (data.settings != undefined) {
|
||||
let response = await fetch(url, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: reader.result
|
||||
body: JSON.stringify({"settings": data.settings})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
onSuccess(response);
|
||||
|
||||
} else {
|
||||
onFailed(response);
|
||||
if (!response.ok) {
|
||||
onFailed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
credentials: "include",
|
||||
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",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({"network": data.network})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
onFailed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
onSuccess();
|
||||
|
||||
} catch (err) {
|
||||
onFailed(false);
|
||||
onFailed();
|
||||
}
|
||||
};
|
||||
reader.onerror = function () {
|
||||
reader.onerror = () => {
|
||||
console.log(reader.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function setupUpgradeForm(formSelector) {
|
||||
const setupUpgradeForm = (formSelector) => {
|
||||
const form = document.querySelector(formSelector);
|
||||
if (!form) {
|
||||
return;
|
||||
@@ -383,7 +434,7 @@ function setupUpgradeForm(formSelector) {
|
||||
resItem.classList.add('failed');
|
||||
|
||||
if (result.firmware.error != "") {
|
||||
resItem.textContent += ": " + result.firmware.error;
|
||||
resItem.textContent += `: ${result.firmware.error}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -401,7 +452,7 @@ function setupUpgradeForm(formSelector) {
|
||||
resItem.classList.add('failed');
|
||||
|
||||
if (result.filesystem.error != "") {
|
||||
resItem.textContent += ": " + result.filesystem.error;
|
||||
resItem.textContent += `: ${result.filesystem.error}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,8 +503,9 @@ function setupUpgradeForm(formSelector) {
|
||||
try {
|
||||
let fd = new FormData(form);
|
||||
let response = await fetch(url, {
|
||||
method: 'POST',
|
||||
cache: 'no-cache',
|
||||
method: "POST",
|
||||
cache: "no-cache",
|
||||
credentials: "include",
|
||||
body: fd
|
||||
});
|
||||
|
||||
@@ -471,19 +523,23 @@ function setupUpgradeForm(formSelector) {
|
||||
}
|
||||
|
||||
|
||||
function setBusy(busySelector, contentSelector, value) {
|
||||
const setBusy = (busySelector, contentSelector, value, parent = undefined) => {
|
||||
if (!value) {
|
||||
hide(busySelector);
|
||||
show(contentSelector);
|
||||
hide(busySelector, parent);
|
||||
show(contentSelector, parent);
|
||||
|
||||
} else {
|
||||
show(busySelector);
|
||||
hide(contentSelector);
|
||||
show(busySelector, parent);
|
||||
hide(contentSelector, parent);
|
||||
}
|
||||
}
|
||||
|
||||
function setState(selector, value) {
|
||||
let item = document.querySelector(selector);
|
||||
const setAriaState = (selector, value, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let item = parent.querySelector(selector);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
@@ -491,8 +547,44 @@ function setState(selector, value) {
|
||||
item.setAttribute('aria-invalid', !value);
|
||||
}
|
||||
|
||||
function setValue(selector, value) {
|
||||
let items = document.querySelectorAll(selector);
|
||||
const setStatus = (selector, state, color = undefined, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let item = parent.querySelector(selector);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
item.classList.forEach(cName => {
|
||||
if (cName.indexOf("icons-") === 0) {
|
||||
item.classList.remove(cName);
|
||||
}
|
||||
});
|
||||
|
||||
item.classList.add(`icons-${state}`);
|
||||
|
||||
if (color !== undefined) {
|
||||
item.classList.add(`icons-color-${color}`);
|
||||
}
|
||||
}
|
||||
|
||||
const setState = (selector, state, parent = undefined) => {
|
||||
return setStatus(
|
||||
selector,
|
||||
state ? "success" : "error",
|
||||
state ? "green" : "gray",
|
||||
parent
|
||||
);
|
||||
}
|
||||
|
||||
const setValue = (selector, value, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let items = parent.querySelectorAll(selector);
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
@@ -502,8 +594,36 @@ function setValue(selector, value) {
|
||||
}
|
||||
}
|
||||
|
||||
function setCheckboxValue(selector, value) {
|
||||
let item = document.querySelector(selector);
|
||||
const appendValue = (selector, value, nl = null, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let items = parent.querySelectorAll(selector);
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let item of items) {
|
||||
if (item.innerHTML.trim().length > 0) {
|
||||
if (nl !== null) {
|
||||
item.innerHTML += nl;
|
||||
}
|
||||
|
||||
item.innerHTML += value;
|
||||
|
||||
} else {
|
||||
item.innerHTML = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const setCheckboxValue = (selector, value, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let item = parent.querySelector(selector);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
@@ -511,8 +631,12 @@ function setCheckboxValue(selector, value) {
|
||||
item.checked = value;
|
||||
}
|
||||
|
||||
function setRadioValue(selector, value) {
|
||||
let items = document.querySelectorAll(selector);
|
||||
const setRadioValue = (selector, value, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let items = parent.querySelectorAll(selector);
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
@@ -522,8 +646,12 @@ function setRadioValue(selector, value) {
|
||||
}
|
||||
}
|
||||
|
||||
function setInputValue(selector, value, attrs = {}) {
|
||||
let items = document.querySelectorAll(selector);
|
||||
const setInputValue = (selector, value, attrs = {}, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let items = parent.querySelectorAll(selector);
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
@@ -539,8 +667,12 @@ function setInputValue(selector, value, attrs = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
function setSelectValue(selector, value) {
|
||||
let item = document.querySelector(selector);
|
||||
const setSelectValue = (selector, value, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let item = parent.querySelector(selector);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
@@ -550,8 +682,12 @@ function setSelectValue(selector, value) {
|
||||
}
|
||||
}
|
||||
|
||||
function show(selector) {
|
||||
let items = document.querySelectorAll(selector);
|
||||
const show = (selector, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let items = parent.querySelectorAll(selector);
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
@@ -563,8 +699,12 @@ function show(selector) {
|
||||
}
|
||||
}
|
||||
|
||||
function hide(selector) {
|
||||
let items = document.querySelectorAll(selector);
|
||||
const hide = (selector, parent = undefined) => {
|
||||
if (parent === undefined) {
|
||||
parent = document;
|
||||
}
|
||||
|
||||
let items = parent.querySelectorAll(selector);
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
@@ -582,28 +722,51 @@ function unit2str(unitSystem, units = {}, defaultValue = '?') {
|
||||
: defaultValue;
|
||||
}
|
||||
|
||||
function temperatureUnit(unitSystem) {
|
||||
const temperatureUnit = (unitSystem) => {
|
||||
return unit2str(unitSystem, {
|
||||
0: "°C",
|
||||
1: "°F"
|
||||
});
|
||||
}
|
||||
|
||||
function pressureUnit(unitSystem) {
|
||||
const pressureUnit = (unitSystem) => {
|
||||
return unit2str(unitSystem, {
|
||||
0: "bar",
|
||||
1: "psi"
|
||||
});
|
||||
}
|
||||
|
||||
function volumeUnit(unitSystem) {
|
||||
const volumeUnit = (unitSystem) => {
|
||||
return unit2str(unitSystem, {
|
||||
0: "L",
|
||||
1: "gal"
|
||||
});
|
||||
}
|
||||
|
||||
function memberIdToVendor(memberId) {
|
||||
const purposeUnit = (purpose, unitSystem) => {
|
||||
const tUnit = temperatureUnit(unitSystem);
|
||||
|
||||
return unit2str(purpose, {
|
||||
0: tUnit,
|
||||
1: tUnit,
|
||||
2: tUnit,
|
||||
3: tUnit,
|
||||
4: tUnit,
|
||||
5: tUnit,
|
||||
6: `${volumeUnit(unitSystem)}/${i18n('time.min')}`,
|
||||
7: tUnit,
|
||||
8: "%",
|
||||
248: "%",
|
||||
249: i18n('kw'),
|
||||
250: "RPM",
|
||||
251: "ppm",
|
||||
252: pressureUnit(unitSystem),
|
||||
253: "%",
|
||||
254: tUnit
|
||||
}, null);
|
||||
}
|
||||
|
||||
const 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 = {
|
||||
@@ -681,7 +844,7 @@ function form2json(data, noCastItems = []) {
|
||||
function dec2hex(i) {
|
||||
let hex = parseInt(i).toString(16);
|
||||
if (hex.length % 2 != 0) {
|
||||
hex = "0" + hex;
|
||||
hex = `0${hex}`;
|
||||
}
|
||||
|
||||
return hex.toUpperCase();
|
||||
|
||||
@@ -71,6 +71,11 @@ pre {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
:nth-last-child(1 of table tr:not(.hidden)) th,
|
||||
:nth-last-child(1 of table tr:not(.hidden)) td {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
.hidden {
|
||||
display: none !important;
|
||||
@@ -201,3 +206,19 @@ tr.network:hover {
|
||||
[data-tooltip]:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]) {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
.icons-color-green {
|
||||
color: var(--pico-form-element-valid-active-border-color);
|
||||
}
|
||||
|
||||
.icons-color-yellow {
|
||||
color: #c89048;
|
||||
}
|
||||
|
||||
.icons-color-gray {
|
||||
color: var(--pico-form-element-placeholder-color);
|
||||
}
|
||||
|
||||
.icons-color-red {
|
||||
color: var(--pico-form-element-invalid-active-border-color);
|
||||
}
|
||||
@@ -7,8 +7,8 @@
|
||||
font-family: "Icons";
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url("/static/fonts/iconly.eot?1718563596894");
|
||||
src: url("/static/fonts/iconly.eot?#iefix") format("embedded-opentype"), url("/static/fonts/iconly.woff2?1718563596894") format("woff2"), url("/static/fonts/iconly.woff?1718563596894") format("woff"), url("/static/fonts/iconly.ttf?1718563596894") format("truetype"), url("/static/fonts/iconly.svg?1718563596894#Icons") format("svg");
|
||||
src: url("/static/fonts/iconly.eot?1732913937966");
|
||||
src: url("/static/fonts/iconly.eot?#iefix") format("embedded-opentype"), url("/static/fonts/iconly.woff2?1732913937966") format("woff2"), url("/static/fonts/iconly.woff?1732913937966") format("woff"), url("/static/fonts/iconly.ttf?1732913937966") format("truetype"), url("/static/fonts/iconly.svg?1732913937966#Icons") format("svg");
|
||||
}
|
||||
|
||||
[class="icons"], [class^="icons-"], [class*=" icons-"] {
|
||||
@@ -51,6 +51,10 @@
|
||||
content: "\e006";
|
||||
}
|
||||
|
||||
.icons-error:before {
|
||||
content: "\e007";
|
||||
}
|
||||
|
||||
.icons-wifi-strength-3:before {
|
||||
content: "\e008";
|
||||
}
|
||||
@@ -63,6 +67,14 @@
|
||||
content: "\e00a";
|
||||
}
|
||||
|
||||
.icons-success:before {
|
||||
content: "\e00b";
|
||||
}
|
||||
|
||||
.icons-up:before {
|
||||
content: "\e00c";
|
||||
}
|
||||
|
||||
.icons-alarm:before {
|
||||
content: "\e00d";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user