mirror of
https://github.com/Laxilef/OTGateway.git
synced 2026-05-20 08:18:15 +05:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bdfa793c49 | |||
| 25280bf9b1 | |||
| 9c2e420ec7 | |||
| af2d4624b3 | |||
| 14d2262d2f | |||
| db99746ee9 |
@@ -0,0 +1,152 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
paths-ignore:
|
||||||
|
- "assets/**"
|
||||||
|
- "tools/**"
|
||||||
|
- "**/*.md"
|
||||||
|
- "LICENSE"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- "assets/**"
|
||||||
|
- "tools/**"
|
||||||
|
- "**/*.md"
|
||||||
|
- "LICENSE"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
prepare:
|
||||||
|
name: Prepare (get envs)
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
envs: ${{ steps.envs.outputs.envs }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Python
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install PlatformIO Core
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade platformio
|
||||||
|
pip install --upgrade intelhex
|
||||||
|
|
||||||
|
- name: Get environments
|
||||||
|
id: envs
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
ENVS=$(
|
||||||
|
pio project config --json-output \
|
||||||
|
| jq -c '
|
||||||
|
.[]
|
||||||
|
| select(type == "array")
|
||||||
|
| .[0]
|
||||||
|
| select(startswith("env:"))
|
||||||
|
| sub("^env:"; "")
|
||||||
|
' | jq -s -c '.'
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "envs=$ENVS" >> "$GITHUB_OUTPUT"
|
||||||
|
echo "$ENVS"
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Build ${{ matrix.env }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: prepare
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
env: ${{ fromJson(needs.prepare.outputs.envs) }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Python
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v5
|
||||||
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
|
||||||
|
- name: Install PlatformIO Core
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade platformio
|
||||||
|
pip install --upgrade intelhex
|
||||||
|
|
||||||
|
- name: Full clean
|
||||||
|
run: |
|
||||||
|
pio run -e ${{ matrix.env }} -t fullclean
|
||||||
|
|
||||||
|
- name: Build firmware & filesystem
|
||||||
|
env:
|
||||||
|
PIOENV: ${{ matrix.env }}
|
||||||
|
run: |
|
||||||
|
pio run -e ${{ matrix.env }}
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.env }}
|
||||||
|
if-no-files-found: error
|
||||||
|
path: |
|
||||||
|
build/*.bin
|
||||||
|
build/*.elf
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Release Dev
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
if: ${{ github.repository_owner == 'Laxilef' && github.ref == 'refs/heads/async' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Download artifacts
|
||||||
|
uses: actions/download-artifact@v5
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
- name: Delete old release assets
|
||||||
|
uses: mknejp/delete-release-assets@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: dev
|
||||||
|
fail-if-no-assets: false
|
||||||
|
fail-if-no-release: false
|
||||||
|
assets: |
|
||||||
|
*.bin
|
||||||
|
*.elf
|
||||||
|
|
||||||
|
- name: Publish Dev release
|
||||||
|
uses: softprops/action-gh-release@v3
|
||||||
|
with:
|
||||||
|
tag_name: dev
|
||||||
|
target_commitish: async
|
||||||
|
name: Dev (unstable)
|
||||||
|
prerelease: true
|
||||||
|
make_latest: false
|
||||||
|
generate_release_notes: true
|
||||||
|
files: |
|
||||||
|
artifacts/*.bin
|
||||||
|
artifacts/*.elf
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-json:
|
||||||
|
name: Validate JSON
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Install jq
|
||||||
|
run: sudo apt-get update && sudo apt-get install -y jq
|
||||||
|
|
||||||
|
- name: Validate
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
echo "Searching JSON files in src_data..."
|
||||||
|
|
||||||
|
files=$(find src_data -type f -iname "*.json")
|
||||||
|
|
||||||
|
if [ -z "$files" ]; then
|
||||||
|
echo "No JSON files found."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
for file in $files; do
|
||||||
|
echo "Validating: $file"
|
||||||
|
|
||||||
|
if ! jq empty "$file" >/dev/null 2>&1; then
|
||||||
|
echo "❌ Invalid JSON: $file"
|
||||||
|
failed=1
|
||||||
|
else
|
||||||
|
echo "✅ Valid JSON: $file"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$failed" -ne 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "JSON validation failed."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "All JSON files are valid."
|
||||||
|
|
||||||
|
cpplint:
|
||||||
|
name: cpplint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Cache
|
||||||
|
uses: actions/cache@v5
|
||||||
|
with:
|
||||||
|
key: ${{ runner.os }}-cpplint
|
||||||
|
path: ~/.cache/pip
|
||||||
|
|
||||||
|
- name: Python
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: "3.x"
|
||||||
|
|
||||||
|
- name: Install cpplint
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install --upgrade cpplint
|
||||||
|
|
||||||
|
- name: cpplint
|
||||||
|
run: |
|
||||||
|
FILTERS="-legal/copyright,\
|
||||||
|
-runtime/int,\
|
||||||
|
-runtime/references,\
|
||||||
|
-runtime/indentation_namespace,\
|
||||||
|
-runtime/arrays,\
|
||||||
|
-runtime/printf,\
|
||||||
|
-whitespace/line_length,\
|
||||||
|
-whitespace/braces,\
|
||||||
|
-whitespace/comments,\
|
||||||
|
-whitespace/indent,\
|
||||||
|
-whitespace/newline,\
|
||||||
|
-whitespace/parens,\
|
||||||
|
-whitespace/blank_line,\
|
||||||
|
-readability/braces,\
|
||||||
|
-readability/todo,\
|
||||||
|
-readability/namespace,\
|
||||||
|
-build/header_guard,\
|
||||||
|
-build/include_subdir,\
|
||||||
|
-build/include_what_you_use,\
|
||||||
|
-build/namespaces,\
|
||||||
|
-build/c++11"
|
||||||
|
|
||||||
|
cpplint \
|
||||||
|
--repository=. \
|
||||||
|
--recursive \
|
||||||
|
--quiet \
|
||||||
|
--filter="$FILTERS" \
|
||||||
|
include lib src
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
class BufferedWebServer {
|
class BufferedWebServer {
|
||||||
public:
|
public:
|
||||||
BufferedWebServer(WebServer* webServer, size_t bufferSize = 64) {
|
explicit BufferedWebServer(WebServer* webServer, size_t bufferSize = 64) {
|
||||||
this->webServer = webServer;
|
this->webServer = webServer;
|
||||||
this->bufferSize = bufferSize;
|
this->bufferSize = bufferSize;
|
||||||
this->buffer = (uint8_t*)malloc(bufferSize * sizeof(*this->buffer));
|
this->buffer = static_cast<uint8_t*>(malloc(bufferSize * sizeof(*this->buffer)));
|
||||||
}
|
}
|
||||||
|
|
||||||
~BufferedWebServer() {
|
~BufferedWebServer() {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public:
|
|||||||
typedef std::function<void(unsigned long, uint8_t)> BeforeSendRequestCallback;
|
typedef std::function<void(unsigned long, uint8_t)> BeforeSendRequestCallback;
|
||||||
typedef std::function<void(unsigned long, unsigned long, OpenThermResponseStatus, uint8_t)> AfterSendRequestCallback;
|
typedef std::function<void(unsigned long, unsigned long, OpenThermResponseStatus, uint8_t)> AfterSendRequestCallback;
|
||||||
|
|
||||||
CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false, bool alwaysReceive = false) : OpenTherm(inPin, outPin, isSlave, alwaysReceive) {}
|
explicit CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false, bool alwaysReceive = false) : OpenTherm(inPin, outPin, isSlave, alwaysReceive) {}
|
||||||
~CustomOpenTherm() {}
|
~CustomOpenTherm() {}
|
||||||
|
|
||||||
CustomOpenTherm* setDelayCallback(DelayCallback callback = nullptr) {
|
CustomOpenTherm* setDelayCallback(DelayCallback callback = nullptr) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#ifndef PROGMEM
|
#ifndef PROGMEM
|
||||||
#define PROGMEM
|
#define PROGMEM // NOLINT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char HA_ENTITY_BINARY_SENSOR[] PROGMEM = "binary_sensor";
|
const char HA_ENTITY_BINARY_SENSOR[] PROGMEM = "binary_sensor";
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ public:
|
|||||||
typedef std::function<void(const char*, size_t, size_t, bool)> PublishEventCallback;
|
typedef std::function<void(const char*, size_t, size_t, bool)> PublishEventCallback;
|
||||||
typedef std::function<void(size_t, size_t)> FlushEventCallback;
|
typedef std::function<void(size_t, size_t)> FlushEventCallback;
|
||||||
|
|
||||||
MqttWriter(MqttClient* client, size_t bufferSize = 64) {
|
explicit MqttWriter(MqttClient* client, size_t bufferSize = 64) {
|
||||||
this->client = client;
|
this->client = client;
|
||||||
this->bufferSize = bufferSize;
|
this->bufferSize = bufferSize;
|
||||||
this->buffer = (uint8_t*) malloc(bufferSize * sizeof(*this->buffer));
|
this->buffer = static_cast<uint8_t*>(malloc(bufferSize * sizeof(*this->buffer)));
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
this->mutex = new std::mutex();
|
this->mutex = new std::mutex();
|
||||||
|
|||||||
@@ -1,224 +0,0 @@
|
|||||||
#include <FS.h>
|
|
||||||
|
|
||||||
|
|
||||||
class DynamicPage : public RequestHandler {
|
|
||||||
public:
|
|
||||||
typedef std::function<bool(HTTPMethod, const String&)> CanHandleCallback;
|
|
||||||
typedef std::function<bool()> BeforeSendCallback;
|
|
||||||
typedef std::function<String(const char*)> TemplateCallback;
|
|
||||||
|
|
||||||
DynamicPage(const char* uri, FS* fs, const char* path, const char* cacheHeader = nullptr) {
|
|
||||||
this->uri = uri;
|
|
||||||
this->fs = fs;
|
|
||||||
this->path = path;
|
|
||||||
this->cacheHeader = cacheHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicPage* setCanHandleCallback(CanHandleCallback callback = nullptr) {
|
|
||||||
this->canHandleCallback = callback;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicPage* setBeforeSendCallback(BeforeSendCallback callback = nullptr) {
|
|
||||||
this->beforeSendCallback = callback;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
DynamicPage* setTemplateCallback(TemplateCallback callback = nullptr) {
|
|
||||||
this->templateCallback = callback;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_ESP32)
|
|
||||||
bool canHandle(WebServer &server, HTTPMethod method, const String &uri) override {
|
|
||||||
return this->canHandle(method, uri);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
bool canHandle(HTTPMethod method, const String& uri) override {
|
|
||||||
return uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool handle(WebServer& server, HTTPMethod method, const String& uri) override {
|
|
||||||
if (!this->canHandle(method, uri)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->beforeSendCallback && !this->beforeSendCallback()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
File file = this->fs->open(this->path, "r");
|
|
||||||
if (!file) {
|
|
||||||
return false;
|
|
||||||
|
|
||||||
} else if (file.isDirectory()) {
|
|
||||||
file.close();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this->cacheHeader != nullptr) {
|
|
||||||
server.sendHeader(F("Cache-Control"), this->cacheHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP8266
|
|
||||||
if (!server.chunkedResponseModeStart(200, F("text/html"))) {
|
|
||||||
server.send(505, F("text/html"), F("HTTP1.1 required"));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
|
|
||||||
server.send(200, "text/html", emptyString);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
uint8_t* argStartPos = nullptr;
|
|
||||||
uint8_t* argEndPos = nullptr;
|
|
||||||
uint8_t argName[16];
|
|
||||||
size_t sizeArgName = 0;
|
|
||||||
bool argNameProcess = false;
|
|
||||||
while (file.available()) {
|
|
||||||
uint8_t buf[64];
|
|
||||||
size_t length = file.read(buf, sizeof(buf));
|
|
||||||
size_t offset = 0;
|
|
||||||
|
|
||||||
if (argNameProcess) {
|
|
||||||
argEndPos = (uint8_t*) memchr(buf, '}', length);
|
|
||||||
|
|
||||||
if (argEndPos != nullptr) {
|
|
||||||
size_t fullSizeArgName = sizeArgName + (argEndPos - buf);
|
|
||||||
if (fullSizeArgName < sizeof(argName)) {
|
|
||||||
// copy full arg name
|
|
||||||
if (argEndPos - buf > 0) {
|
|
||||||
memcpy(argName + sizeArgName, buf, argEndPos - buf);
|
|
||||||
}
|
|
||||||
argName[fullSizeArgName] = '\0';
|
|
||||||
|
|
||||||
// send arg value
|
|
||||||
String argValue = this->templateCallback((const char*) argName);
|
|
||||||
if (argValue.length()) {
|
|
||||||
server.sendContent(argValue.c_str());
|
|
||||||
|
|
||||||
} else if (fullSizeArgName > 0) {
|
|
||||||
server.sendContent("{");
|
|
||||||
server.sendContent((const char*) argName);
|
|
||||||
server.sendContent("}");
|
|
||||||
}
|
|
||||||
|
|
||||||
offset = size_t(argEndPos - buf + 1);
|
|
||||||
sizeArgName = 0;
|
|
||||||
argNameProcess = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argNameProcess) {
|
|
||||||
server.sendContent("{");
|
|
||||||
|
|
||||||
if (sizeArgName > 0) {
|
|
||||||
argName[sizeArgName] = '\0';
|
|
||||||
server.sendContent((const char*) argName);
|
|
||||||
}
|
|
||||||
|
|
||||||
argNameProcess = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
do {
|
|
||||||
uint8_t* currentBuf = buf + offset;
|
|
||||||
size_t currentLength = length - offset;
|
|
||||||
|
|
||||||
argStartPos = (uint8_t*) memchr(currentBuf, '{', currentLength);
|
|
||||||
|
|
||||||
// send all content
|
|
||||||
if (argStartPos == nullptr) {
|
|
||||||
if (currentLength > 0) {
|
|
||||||
server.sendContent((const char*) currentBuf, currentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
argEndPos = (uint8_t*) memchr(argStartPos, '}', length - (argStartPos - buf));
|
|
||||||
if (argEndPos != nullptr) {
|
|
||||||
sizeArgName = argEndPos - argStartPos - 1;
|
|
||||||
|
|
||||||
// send all content if arg len > space
|
|
||||||
if (sizeArgName >= sizeof(argName)) {
|
|
||||||
if (currentLength > 0) {
|
|
||||||
server.sendContent((const char*) currentBuf, currentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// arg name
|
|
||||||
memcpy(argName, argStartPos + 1, sizeArgName);
|
|
||||||
argName[sizeArgName] = '\0';
|
|
||||||
|
|
||||||
// send arg value
|
|
||||||
String argValue = this->templateCallback((const char*) argName);
|
|
||||||
if (argValue.length()) {
|
|
||||||
// send content before var
|
|
||||||
if (argStartPos - buf > 0) {
|
|
||||||
server.sendContent((const char*) currentBuf, argStartPos - buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
server.sendContent(argValue.c_str());
|
|
||||||
|
|
||||||
} else {
|
|
||||||
server.sendContent((const char*) currentBuf, argEndPos - currentBuf + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
offset = size_t(argEndPos - currentBuf + 1);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
sizeArgName = length - size_t(argStartPos - currentBuf) - 1;
|
|
||||||
|
|
||||||
// send all content if arg len > space
|
|
||||||
if (sizeArgName >= sizeof(argName)) {
|
|
||||||
if (currentLength) {
|
|
||||||
server.sendContent((const char*) currentBuf, currentLength);
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// send content before var
|
|
||||||
if (argStartPos - buf > 0) {
|
|
||||||
server.sendContent((const char*) currentBuf, argStartPos - buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy arg name chunk
|
|
||||||
if (sizeArgName > 0) {
|
|
||||||
memcpy(argName, argStartPos + 1, sizeArgName);
|
|
||||||
}
|
|
||||||
|
|
||||||
argNameProcess = true;
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} while(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP8266
|
|
||||||
server.chunkedResponseFinalize();
|
|
||||||
#else
|
|
||||||
server.sendContent(emptyString);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
FS* fs = nullptr;
|
|
||||||
CanHandleCallback canHandleCallback;
|
|
||||||
BeforeSendCallback beforeSendCallback;
|
|
||||||
TemplateCallback templateCallback;
|
|
||||||
const char* uri = nullptr;
|
|
||||||
const char* path = nullptr;
|
|
||||||
const char* cacheHeader = nullptr;
|
|
||||||
};
|
|
||||||
@@ -29,9 +29,9 @@ public:
|
|||||||
typedef std::function<bool(AsyncWebServerRequest *request, UpgradeType)> BeforeUpgradeCallback;
|
typedef std::function<bool(AsyncWebServerRequest *request, UpgradeType)> BeforeUpgradeCallback;
|
||||||
typedef std::function<void(AsyncWebServerRequest *request, const UpgradeResult&, const UpgradeResult&)> AfterUpgradeCallback;
|
typedef std::function<void(AsyncWebServerRequest *request, const UpgradeResult&, const UpgradeResult&)> AfterUpgradeCallback;
|
||||||
|
|
||||||
UpgradeHandler(AsyncURIMatcher uri) : uri(uri) {}
|
explicit UpgradeHandler(AsyncURIMatcher uri) : uri(uri) {}
|
||||||
|
|
||||||
bool canHandle(AsyncWebServerRequest *request) const override final {
|
bool canHandle(AsyncWebServerRequest *request) const final {
|
||||||
if (!request->isHTTP()) {
|
if (!request->isHTTP()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -51,7 +51,7 @@ public:
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleRequest(AsyncWebServerRequest *request) override final {
|
void handleRequest(AsyncWebServerRequest *request) final {
|
||||||
if (this->afterUpgradeCallback) {
|
if (this->afterUpgradeCallback) {
|
||||||
this->afterUpgradeCallback(request, this->firmwareResult, this->filesystemResult);
|
this->afterUpgradeCallback(request, this->firmwareResult, this->filesystemResult);
|
||||||
}
|
}
|
||||||
@@ -63,7 +63,7 @@ public:
|
|||||||
this->filesystemResult.error.clear();
|
this->filesystemResult.error.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleUpload(AsyncWebServerRequest *request, const String &fileName, size_t index, uint8_t *data, size_t dataLength, bool isFinal) override final {
|
void handleUpload(AsyncWebServerRequest *request, const String &fileName, size_t index, uint8_t *data, size_t dataLength, bool isFinal) final {
|
||||||
UpgradeResult* result = nullptr;
|
UpgradeResult* result = nullptr;
|
||||||
|
|
||||||
if (!request->hasParam(asyncsrv::T_name, true, true)) {
|
if (!request->hasParam(asyncsrv::T_name, true, true)) {
|
||||||
@@ -194,7 +194,7 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isRequestHandlerTrivial() const override final {
|
bool isRequestHandlerTrivial() const final {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -35,9 +35,9 @@ namespace CrashRecorder {
|
|||||||
} ext_t;
|
} ext_t;
|
||||||
|
|
||||||
|
|
||||||
__NOINIT_ATTR volatile static backtrace_t backtrace;
|
__NOINIT_ATTR volatile static backtrace_t backtrace; // NOLINT
|
||||||
__NOINIT_ATTR volatile static epc_t epc;
|
__NOINIT_ATTR volatile static epc_t epc; // NOLINT
|
||||||
__NOINIT_ATTR volatile static ext_t ext;
|
__NOINIT_ATTR volatile static ext_t ext; // NOLINT
|
||||||
|
|
||||||
uint8_t backtraceMaxLength = sizeof(backtrace.data) / sizeof(*backtrace.data);
|
uint8_t backtraceMaxLength = sizeof(backtrace.data) / sizeof(*backtrace.data);
|
||||||
uint8_t epcMaxLength = sizeof(epc.data) / sizeof(*epc.data);
|
uint8_t epcMaxLength = sizeof(epc.data) / sizeof(*epc.data);
|
||||||
@@ -98,7 +98,7 @@ extern "C" void custom_crash_callback(struct rst_info *info, uint32_t stack, uin
|
|||||||
CrashRecorder::backtrace.continues = false;
|
CrashRecorder::backtrace.continues = false;
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
for (uint32_t i = stack; i < stack_end; i += 4) {
|
for (uint32_t i = stack; i < stack_end; i += 4) {
|
||||||
value = *((uint32_t*) i);
|
value = *((uint32_t*) i); // NOLINT
|
||||||
|
|
||||||
// keep only addresses in code area
|
// keep only addresses in code area
|
||||||
if ((value >= 0x40000000) && (value < 0x40300000)) {
|
if ((value >= 0x40000000) && (value < 0x40300000)) {
|
||||||
|
|||||||
+4
-4
@@ -10,7 +10,7 @@ extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
|
|||||||
|
|
||||||
class MainTask : public Task {
|
class MainTask : public Task {
|
||||||
public:
|
public:
|
||||||
MainTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {
|
explicit MainTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {
|
||||||
this->blinker = new Blinker();
|
this->blinker = new Blinker();
|
||||||
|
|
||||||
network->setDelayCallback([this](unsigned int time) {
|
network->setDelayCallback([this](unsigned int time) {
|
||||||
@@ -225,7 +225,7 @@ protected:
|
|||||||
uint8_t availableSensors = 0;
|
uint8_t availableSensors = 0;
|
||||||
|
|
||||||
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) {
|
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) {
|
||||||
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY);
|
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY, settings.heating.indoorTempAvgType);
|
||||||
if (value < lowTemp) {
|
if (value < lowTemp) {
|
||||||
lowTemp = value;
|
lowTemp = value;
|
||||||
}
|
}
|
||||||
@@ -253,7 +253,7 @@ protected:
|
|||||||
|
|
||||||
if (availableSensors) {
|
if (availableSensors) {
|
||||||
if (vars.master.heating.freezing) {
|
if (vars.master.heating.freezing) {
|
||||||
if (lowTemp - (float) settings.heating.freezeProtection.highTemp + 0.0001f >= 0.0f) {
|
if (lowTemp - static_cast<float>(settings.heating.freezeProtection.highTemp) + 0.0001f >= 0.0f) {
|
||||||
vars.master.heating.freezing = false;
|
vars.master.heating.freezing = false;
|
||||||
|
|
||||||
Log.sinfoln(
|
Log.sinfoln(
|
||||||
@@ -264,7 +264,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if ((float) settings.heating.freezeProtection.lowTemp - lowTemp + 0.0001f >= 0.0f) {
|
if (static_cast<float>(settings.heating.freezeProtection.lowTemp) - lowTemp + 0.0001f >= 0.0f) {
|
||||||
vars.master.heating.freezing = true;
|
vars.master.heating.freezing = true;
|
||||||
|
|
||||||
if (!settings.heating.enabled) {
|
if (!settings.heating.enabled) {
|
||||||
|
|||||||
+2
-2
@@ -8,7 +8,7 @@ extern FileData fsSettings;
|
|||||||
|
|
||||||
class MqttTask : public Task {
|
class MqttTask : public Task {
|
||||||
public:
|
public:
|
||||||
MqttTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {
|
explicit MqttTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {
|
||||||
this->wifiClient = new MqttWiFiClient();
|
this->wifiClient = new MqttWiFiClient();
|
||||||
this->client = new MqttClient(this->wifiClient);
|
this->client = new MqttClient(this->wifiClient);
|
||||||
this->writer = new MqttWriter(this->client, 256);
|
this->writer = new MqttWriter(this->client, 256);
|
||||||
@@ -443,7 +443,7 @@ protected:
|
|||||||
} else if (payload[i] == 10) {
|
} else if (payload[i] == 10) {
|
||||||
Log.print(F("\r\n> "));
|
Log.print(F("\r\n> "));
|
||||||
} else {
|
} else {
|
||||||
Log.print((char) payload[i]);
|
Log.print(static_cast<char>(payload[i]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.print(F("\r\n\n"));
|
Log.print(F("\r\n\n"));
|
||||||
|
|||||||
+7
-7
@@ -3,7 +3,7 @@ extern FileData fsSettings;
|
|||||||
|
|
||||||
class OpenThermTask : public Task {
|
class OpenThermTask : public Task {
|
||||||
public:
|
public:
|
||||||
OpenThermTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
|
explicit OpenThermTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
|
||||||
|
|
||||||
~OpenThermTask() {
|
~OpenThermTask() {
|
||||||
delete this->instance;
|
delete this->instance;
|
||||||
@@ -1401,7 +1401,7 @@ protected:
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (vars.master.heating.overheat) {
|
if (vars.master.heating.overheat) {
|
||||||
if ((float) settings.heating.overheatProtection.lowTemp - highTemp + 0.0001f >= 0.0f) {
|
if (static_cast<float>(settings.heating.overheatProtection.lowTemp) - highTemp + 0.0001f >= 0.0f) {
|
||||||
vars.master.heating.overheat = false;
|
vars.master.heating.overheat = false;
|
||||||
|
|
||||||
Log.sinfoln(
|
Log.sinfoln(
|
||||||
@@ -1411,7 +1411,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if (vars.slave.heating.active) {
|
} else if (vars.slave.heating.active) {
|
||||||
if (highTemp - (float) settings.heating.overheatProtection.highTemp + 0.0001f >= 0.0f) {
|
if (highTemp - static_cast<float>(settings.heating.overheatProtection.highTemp) + 0.0001f >= 0.0f) {
|
||||||
vars.master.heating.overheat = true;
|
vars.master.heating.overheat = true;
|
||||||
|
|
||||||
Log.swarningln(
|
Log.swarningln(
|
||||||
@@ -1441,7 +1441,7 @@ protected:
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (vars.master.dhw.overheat) {
|
if (vars.master.dhw.overheat) {
|
||||||
if ((float) settings.dhw.overheatProtection.lowTemp - highTemp + 0.0001f >= 0.0f) {
|
if (static_cast<float>(settings.dhw.overheatProtection.lowTemp) - highTemp + 0.0001f >= 0.0f) {
|
||||||
vars.master.dhw.overheat = false;
|
vars.master.dhw.overheat = false;
|
||||||
|
|
||||||
Log.sinfoln(
|
Log.sinfoln(
|
||||||
@@ -1451,7 +1451,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else if (vars.slave.dhw.active) {
|
} else if (vars.slave.dhw.active) {
|
||||||
if (highTemp - (float) settings.dhw.overheatProtection.highTemp + 0.0001f >= 0.0f) {
|
if (highTemp - static_cast<float>(settings.dhw.overheatProtection.highTemp) + 0.0001f >= 0.0f) {
|
||||||
vars.master.dhw.overheat = true;
|
vars.master.dhw.overheat = true;
|
||||||
|
|
||||||
Log.swarningln(
|
Log.swarningln(
|
||||||
@@ -2432,7 +2432,7 @@ protected:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float value = (float) CustomOpenTherm::getInt(response);
|
const float value = CustomOpenTherm::getInt(response);
|
||||||
if (!isValidTemp(value, settings.opentherm.unitSystem, -40, 500)) {
|
if (!isValidTemp(value, settings.opentherm.unitSystem, -40, 500)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -2456,7 +2456,7 @@ protected:
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const float value = (float) CustomOpenTherm::getInt(response);
|
const float value = CustomOpenTherm::getInt(response);
|
||||||
if (value <= 0) {
|
if (value <= 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-3
@@ -18,7 +18,7 @@ extern WebSerial* webSerial;
|
|||||||
|
|
||||||
class PortalTask : public LeanTask {
|
class PortalTask : public LeanTask {
|
||||||
public:
|
public:
|
||||||
PortalTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {
|
explicit PortalTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {
|
||||||
this->webServer = new AsyncWebServer(80);
|
this->webServer = new AsyncWebServer(80);
|
||||||
this->dnsServer = new DNSServer();
|
this->dnsServer = new DNSServer();
|
||||||
}
|
}
|
||||||
@@ -810,9 +810,13 @@ protected:
|
|||||||
|
|
||||||
static void getFilename(char* filename, size_t maxSizeFilename, const char* type) {
|
static void getFilename(char* filename, size_t maxSizeFilename, const char* type) {
|
||||||
const time_t now = time(nullptr);
|
const time_t now = time(nullptr);
|
||||||
const tm* localNow = localtime(&now);
|
|
||||||
|
tm localNow {};
|
||||||
|
localtime_r(&now, &localNow);
|
||||||
|
|
||||||
char localNowValue[20];
|
char localNowValue[20];
|
||||||
strftime(localNowValue, sizeof(localNowValue), PSTR("%Y-%m-%d-%H-%M-%S"), localNow);
|
strftime(localNowValue, sizeof(localNowValue), PSTR("%Y-%m-%d-%H-%M-%S"), &localNow);
|
||||||
|
|
||||||
snprintf_P(filename, maxSizeFilename, PSTR("%s_%s_%s.json"), networkSettings.hostname, localNowValue, type);
|
snprintf_P(filename, maxSizeFilename, PSTR("%s_%s_%s.json"), networkSettings.hostname, localNowValue, type);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
+1
-1
@@ -5,7 +5,7 @@ GyverPID pidRegulator(0, 0, 0);
|
|||||||
|
|
||||||
class RegulatorTask : public LeanTask {
|
class RegulatorTask : public LeanTask {
|
||||||
public:
|
public:
|
||||||
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
|
explicit RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
float prevHeatingTarget = 0.0f;
|
float prevHeatingTarget = 0.0f;
|
||||||
|
|||||||
+39
-10
@@ -77,6 +77,12 @@ public:
|
|||||||
RSSI = 3
|
RSSI = 3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class AverageType : uint8_t {
|
||||||
|
MEAN = 0,
|
||||||
|
MINIMUM = 1,
|
||||||
|
MAXIMUM = 2
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
bool enabled = false;
|
bool enabled = false;
|
||||||
char name[33];
|
char name[33];
|
||||||
@@ -330,39 +336,62 @@ public:
|
|||||||
return updated;
|
return updated;
|
||||||
}
|
}
|
||||||
|
|
||||||
static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, bool onlyConnected = true) {
|
static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, const AverageType avgType = AverageType::MEAN, const bool onlyConnected = true, const float defaultValue = NAN) {
|
||||||
if (settings == nullptr || results == nullptr) {
|
if (settings == nullptr || results == nullptr) {
|
||||||
return 0.0f;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t valueId = (uint8_t) valueType;
|
uint8_t valueId = (uint8_t) valueType;
|
||||||
if (!isValidValueId(valueId)) {
|
if (!isValidValueId(valueId)) {
|
||||||
return 0.0f;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
float value = 0.0f;
|
float value = 0.0f;
|
||||||
uint8_t amount = 0;
|
uint8_t amount = 0;
|
||||||
|
|
||||||
|
if (avgType == AverageType::MEAN) {
|
||||||
|
float sum = 0.0f;
|
||||||
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
|
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
|
||||||
auto& sSensor = settings[id];
|
auto& sSensor = settings[id];
|
||||||
auto& rSensor = results[id];
|
auto& rSensor = results[id];
|
||||||
|
|
||||||
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
|
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
|
||||||
value += rSensor.values[valueId];
|
sum += rSensor.values[valueId];
|
||||||
amount++;
|
amount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!amount) {
|
value = amount == 1 ? sum : (sum / amount);
|
||||||
return 0.0f;
|
|
||||||
|
|
||||||
} else if (amount == 1) {
|
} else if (avgType == AverageType::MINIMUM) {
|
||||||
return value;
|
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
|
||||||
|
auto& sSensor = settings[id];
|
||||||
|
auto& rSensor = results[id];
|
||||||
|
|
||||||
} else {
|
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
|
||||||
return value / amount;
|
if (amount == 0 || rSensor.values[valueId] < value) {
|
||||||
|
value = rSensor.values[valueId];
|
||||||
|
amount++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (avgType == AverageType::MAXIMUM) {
|
||||||
|
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
|
||||||
|
auto& sSensor = settings[id];
|
||||||
|
auto& rSensor = results[id];
|
||||||
|
|
||||||
|
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
|
||||||
|
if (amount == 0 || rSensor.values[valueId] > value) {
|
||||||
|
value = rSensor.values[valueId];
|
||||||
|
amount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return amount > 0 ? value : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
static bool existsConnectedSensorsByPurpose(Purpose purpose) {
|
static bool existsConnectedSensorsByPurpose(Purpose purpose) {
|
||||||
if (settings == nullptr || results == nullptr) {
|
if (settings == nullptr || results == nullptr) {
|
||||||
|
|||||||
+47
-11
@@ -74,7 +74,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool parseAtcData(const NimBLEAdvertisedDevice* device, uint8_t sensorId) {
|
static bool parseAtcData(const NimBLEAdvertisedDevice* device, uint8_t sensorId) {
|
||||||
NimBLEUUID serviceUuid((uint16_t) 0x181A);
|
NimBLEUUID serviceUuid(static_cast<uint16_t>(0x181A));
|
||||||
|
|
||||||
auto serviceData = device->getServiceData(serviceUuid);
|
auto serviceData = device->getServiceData(serviceUuid);
|
||||||
if (!serviceData.size()) {
|
if (!serviceData.size()) {
|
||||||
@@ -125,7 +125,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool parsePvvxData(const NimBLEAdvertisedDevice* device, uint8_t sensorId) {
|
static bool parsePvvxData(const NimBLEAdvertisedDevice* device, uint8_t sensorId) {
|
||||||
NimBLEUUID serviceUuid((uint16_t) 0x181A);
|
NimBLEUUID serviceUuid(static_cast<uint16_t>(0x181A));
|
||||||
|
|
||||||
auto serviceData = device->getServiceData(serviceUuid);
|
auto serviceData = device->getServiceData(serviceUuid);
|
||||||
if (!serviceData.size()) {
|
if (!serviceData.size()) {
|
||||||
@@ -178,7 +178,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
static bool parseBTHomeData(const NimBLEAdvertisedDevice* device, uint8_t sensorId) {
|
static bool parseBTHomeData(const NimBLEAdvertisedDevice* device, uint8_t sensorId) {
|
||||||
NimBLEUUID serviceUuid((uint16_t) 0xFCD2);
|
NimBLEUUID serviceUuid(static_cast<uint16_t>(0xFCD2));
|
||||||
|
|
||||||
auto serviceData = device->getServiceData(serviceUuid);
|
auto serviceData = device->getServiceData(serviceUuid);
|
||||||
if (!serviceData.size()) {
|
if (!serviceData.size()) {
|
||||||
@@ -304,7 +304,7 @@ public:
|
|||||||
|
|
||||||
class SensorsTask : public LeanTask {
|
class SensorsTask : public LeanTask {
|
||||||
public:
|
public:
|
||||||
SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {
|
explicit SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {
|
||||||
this->gpioLastPollingTime.reserve(2);
|
this->gpioLastPollingTime.reserve(2);
|
||||||
|
|
||||||
// OneWire
|
// OneWire
|
||||||
@@ -423,14 +423,50 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void updateMasterValues() {
|
void updateMasterValues() {
|
||||||
vars.master.heating.outdoorTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::OUTDOOR_TEMP, Sensors::ValueType::PRIMARY);
|
vars.master.heating.indoorTemp = Sensors::getMeanValueByPurpose(
|
||||||
vars.master.heating.indoorTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY);
|
Sensors::Purpose::INDOOR_TEMP,
|
||||||
|
Sensors::ValueType::PRIMARY,
|
||||||
|
settings.heating.indoorTempAvgType,
|
||||||
|
true,
|
||||||
|
0.0f
|
||||||
|
);
|
||||||
|
vars.master.heating.outdoorTemp = Sensors::getMeanValueByPurpose(
|
||||||
|
Sensors::Purpose::OUTDOOR_TEMP,
|
||||||
|
Sensors::ValueType::PRIMARY,
|
||||||
|
settings.heating.outdoorTempAvgType,
|
||||||
|
true,
|
||||||
|
0.0f
|
||||||
|
);
|
||||||
|
|
||||||
vars.master.heating.currentTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_TEMP, Sensors::ValueType::PRIMARY);
|
vars.master.heating.currentTemp = Sensors::getMeanValueByPurpose(
|
||||||
vars.master.heating.returnTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::HEATING_RETURN_TEMP, Sensors::ValueType::PRIMARY);
|
Sensors::Purpose::HEATING_TEMP,
|
||||||
|
Sensors::ValueType::PRIMARY,
|
||||||
|
Sensors::AverageType::MEAN,
|
||||||
|
true,
|
||||||
|
0.0f
|
||||||
|
);
|
||||||
|
vars.master.heating.returnTemp = Sensors::getMeanValueByPurpose(
|
||||||
|
Sensors::Purpose::HEATING_RETURN_TEMP,
|
||||||
|
Sensors::ValueType::PRIMARY,
|
||||||
|
Sensors::AverageType::MEAN,
|
||||||
|
true,
|
||||||
|
0.0f
|
||||||
|
);
|
||||||
|
|
||||||
vars.master.dhw.currentTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::DHW_TEMP, Sensors::ValueType::PRIMARY);
|
vars.master.dhw.currentTemp = Sensors::getMeanValueByPurpose(
|
||||||
vars.master.dhw.returnTemp = Sensors::getMeanValueByPurpose(Sensors::Purpose::DHW_RETURN_TEMP, Sensors::ValueType::PRIMARY);
|
Sensors::Purpose::DHW_TEMP,
|
||||||
|
Sensors::ValueType::PRIMARY,
|
||||||
|
Sensors::AverageType::MEAN,
|
||||||
|
true,
|
||||||
|
0.0f
|
||||||
|
);
|
||||||
|
vars.master.dhw.returnTemp = Sensors::getMeanValueByPurpose(
|
||||||
|
Sensors::Purpose::DHW_RETURN_TEMP,
|
||||||
|
Sensors::ValueType::PRIMARY,
|
||||||
|
Sensors::AverageType::MEAN,
|
||||||
|
true,
|
||||||
|
0.0f
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void makeDallasInstances() {
|
void makeDallasInstances() {
|
||||||
@@ -794,7 +830,7 @@ protected:
|
|||||||
}
|
}
|
||||||
|
|
||||||
const float sensorResistance = value > 1
|
const float sensorResistance = value > 1
|
||||||
? DEFAULT_NTC_REF_RESISTANCE / (DEFAULT_NTC_VREF / (float) value - 1.0f)
|
? DEFAULT_NTC_REF_RESISTANCE / (DEFAULT_NTC_VREF / static_cast<float>(value) - 1.0f)
|
||||||
: 0.0f;
|
: 0.0f;
|
||||||
const float rawTemp = 1.0f / (
|
const float rawTemp = 1.0f / (
|
||||||
1.0f / (DEFAULT_NTC_NOMINAL_TEMP + 273.15f) +
|
1.0f / (DEFAULT_NTC_NOMINAL_TEMP + 273.15f) +
|
||||||
|
|||||||
@@ -107,6 +107,8 @@ struct Settings {
|
|||||||
uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP;
|
uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP;
|
||||||
uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP;
|
uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP;
|
||||||
uint8_t maxModulation = 100;
|
uint8_t maxModulation = 100;
|
||||||
|
Sensors::AverageType indoorTempAvgType = Sensors::AverageType::MEAN;
|
||||||
|
Sensors::AverageType outdoorTempAvgType = Sensors::AverageType::MEAN;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
|
|||||||
+1
-1
@@ -147,7 +147,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef PROGMEM
|
#ifndef PROGMEM
|
||||||
#define PROGMEM
|
#define PROGMEM // NOLINT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#ifndef PROGMEM
|
#ifndef PROGMEM
|
||||||
#define PROGMEM
|
#define PROGMEM // NOLINT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const char L_SETTINGS[] PROGMEM = "SETTINGS";
|
const char L_SETTINGS[] PROGMEM = "SETTINGS";
|
||||||
@@ -115,6 +115,7 @@ const char S_IGNORE_DIAG_STATE[] PROGMEM = "ignoreDiagState";
|
|||||||
const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix";
|
const char S_IMMERGAS_FIX[] PROGMEM = "immergasFix";
|
||||||
const char S_ALWAYS_SEND_INDOOR_TEMP[] PROGMEM = "alwaysSendIndoorTemp";
|
const char S_ALWAYS_SEND_INDOOR_TEMP[] PROGMEM = "alwaysSendIndoorTemp";
|
||||||
const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp";
|
const char S_INDOOR_TEMP[] PROGMEM = "indoorTemp";
|
||||||
|
const char S_INDOOR_TEMP_AVG_TYPE[] PROGMEM = "indoorTempAvgType";
|
||||||
const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl";
|
const char S_INDOOR_TEMP_CONTROL[] PROGMEM = "indoorTempControl";
|
||||||
const char S_IN_GPIO[] PROGMEM = "inGpio";
|
const char S_IN_GPIO[] PROGMEM = "inGpio";
|
||||||
const char S_INPUT[] PROGMEM = "input";
|
const char S_INPUT[] PROGMEM = "input";
|
||||||
@@ -155,6 +156,7 @@ const char S_ON_LOSS_CONNECTION[] PROGMEM = "onLossConnection"
|
|||||||
const char S_OPENTHERM[] PROGMEM = "opentherm";
|
const char S_OPENTHERM[] PROGMEM = "opentherm";
|
||||||
const char S_OPTIONS[] PROGMEM = "options";
|
const char S_OPTIONS[] PROGMEM = "options";
|
||||||
const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
|
const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
|
||||||
|
const char S_OUTDOOR_TEMP_AVG_TYPE[] PROGMEM = "outdoorTempAvgType";
|
||||||
const char S_OUT_GPIO[] PROGMEM = "outGpio";
|
const char S_OUT_GPIO[] PROGMEM = "outGpio";
|
||||||
const char S_OUTPUT[] PROGMEM = "output";
|
const char S_OUTPUT[] PROGMEM = "output";
|
||||||
const char S_OVERHEAT[] PROGMEM = "overheat";
|
const char S_OVERHEAT[] PROGMEM = "overheat";
|
||||||
|
|||||||
+40
-2
@@ -139,7 +139,7 @@ inline bool isValidTemp(const float value, UnitSystem unit, const float min = 0.
|
|||||||
|
|
||||||
float roundf(float value, uint8_t decimals = 2) {
|
float roundf(float value, uint8_t decimals = 2) {
|
||||||
if (decimals == 0) {
|
if (decimals == 0) {
|
||||||
return (int)(value + 0.5f);
|
return static_cast<int>(value + 0.5f);
|
||||||
|
|
||||||
} else if (abs(value) < 0.00000001f) {
|
} else if (abs(value) < 0.00000001f) {
|
||||||
return 0.0f;
|
return 0.0f;
|
||||||
@@ -147,7 +147,7 @@ float roundf(float value, uint8_t decimals = 2) {
|
|||||||
|
|
||||||
float multiplier = pow10(decimals);
|
float multiplier = pow10(decimals);
|
||||||
value += 0.5f / multiplier * (value < 0.0f ? -1.0f : 1.0f);
|
value += 0.5f / multiplier * (value < 0.0f ? -1.0f : 1.0f);
|
||||||
return (int)(value * multiplier) / multiplier;
|
return static_cast<int>(value * multiplier) / multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline size_t getTotalHeap() {
|
inline size_t getTotalHeap() {
|
||||||
@@ -498,6 +498,8 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
|
|||||||
heating[FPSTR(S_MIN_TEMP)] = src.heating.minTemp;
|
heating[FPSTR(S_MIN_TEMP)] = src.heating.minTemp;
|
||||||
heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp;
|
heating[FPSTR(S_MAX_TEMP)] = src.heating.maxTemp;
|
||||||
heating[FPSTR(S_MAX_MODULATION)] = src.heating.maxModulation;
|
heating[FPSTR(S_MAX_MODULATION)] = src.heating.maxModulation;
|
||||||
|
heating[FPSTR(S_INDOOR_TEMP_AVG_TYPE)] = static_cast<uint8_t>(src.heating.indoorTempAvgType);
|
||||||
|
heating[FPSTR(S_OUTDOOR_TEMP_AVG_TYPE)] = static_cast<uint8_t>(src.heating.outdoorTempAvgType);
|
||||||
|
|
||||||
auto heatingOverheatProtection = heating[FPSTR(S_OVERHEAT_PROTECTION)].to<JsonObject>();
|
auto heatingOverheatProtection = heating[FPSTR(S_OVERHEAT_PROTECTION)].to<JsonObject>();
|
||||||
heatingOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.heating.overheatProtection.highTemp;
|
heatingOverheatProtection[FPSTR(S_HIGH_TEMP)] = src.heating.overheatProtection.highTemp;
|
||||||
@@ -1393,6 +1395,42 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!src[FPSTR(S_HEATING)][FPSTR(S_INDOOR_TEMP_AVG_TYPE)].isNull()) {
|
||||||
|
uint8_t value = src[FPSTR(S_HEATING)][FPSTR(S_INDOOR_TEMP_AVG_TYPE)].as<uint8_t>();
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
case static_cast<uint8_t>(Sensors::AverageType::MEAN):
|
||||||
|
case static_cast<uint8_t>(Sensors::AverageType::MINIMUM):
|
||||||
|
case static_cast<uint8_t>(Sensors::AverageType::MAXIMUM):
|
||||||
|
if (static_cast<uint8_t>(dst.heating.indoorTempAvgType) != value) {
|
||||||
|
dst.heating.indoorTempAvgType = static_cast<Sensors::AverageType>(value);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!src[FPSTR(S_HEATING)][FPSTR(S_OUTDOOR_TEMP_AVG_TYPE)].isNull()) {
|
||||||
|
uint8_t value = src[FPSTR(S_HEATING)][FPSTR(S_OUTDOOR_TEMP_AVG_TYPE)].as<uint8_t>();
|
||||||
|
|
||||||
|
switch (value) {
|
||||||
|
case static_cast<uint8_t>(Sensors::AverageType::MEAN):
|
||||||
|
case static_cast<uint8_t>(Sensors::AverageType::MINIMUM):
|
||||||
|
case static_cast<uint8_t>(Sensors::AverageType::MAXIMUM):
|
||||||
|
if (static_cast<uint8_t>(dst.heating.outdoorTempAvgType) != value) {
|
||||||
|
dst.heating.outdoorTempAvgType = static_cast<Sensors::AverageType>(value);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].isNull()) {
|
if (!src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].isNull()) {
|
||||||
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].as<unsigned char>();
|
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_OVERHEAT_PROTECTION)][FPSTR(S_HIGH_TEMP)].as<unsigned char>();
|
||||||
|
|
||||||
|
|||||||
@@ -291,6 +291,11 @@
|
|||||||
"min": "最低温度",
|
"min": "最低温度",
|
||||||
"max": "最高温度"
|
"max": "最高温度"
|
||||||
},
|
},
|
||||||
|
"avgType": {
|
||||||
|
"mean": "平均温度",
|
||||||
|
"min": "最低温度",
|
||||||
|
"max": "最高温度"
|
||||||
|
},
|
||||||
"maxModulation": "最大调制范围",
|
"maxModulation": "最大调制范围",
|
||||||
"ohProtection": {
|
"ohProtection": {
|
||||||
"title": "超温保护",
|
"title": "超温保护",
|
||||||
@@ -352,7 +357,15 @@
|
|||||||
"set0target": "设置空目标"
|
"set0target": "设置空目标"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"turboFactor": "Turbo 模式系数"
|
"turboFactor": "Turbo 模式系数",
|
||||||
|
"indoorTempAvgType": {
|
||||||
|
"title": "室内温度平均类型",
|
||||||
|
"desc": "使用两个或更多室内温度传感器时可能有用(使用 «Equitherm» 和/或 «PID» 时)。"
|
||||||
|
},
|
||||||
|
"outdoorTempAvgType": {
|
||||||
|
"title": "室外温度平均类型",
|
||||||
|
"desc": "使用两个或更多室外温度传感器时可能有用(使用 «Equitherm» 时)。"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"emergency": {
|
"emergency": {
|
||||||
"desc": "紧急模式会在以下情况自动激活(当PID或气候补偿无法计算热媒设定值时):<br />启用气候补偿但室外温度传感器断开连接;<br />启用PID或 OpenTherm 选项中启用<i>原生供暖控制</i>但室内温度传感器断开连接。<br /><b>注意:</b> 网络故障或MQTT 服务器连接故障时,类型为<i>通过MQTT/API手动控制</i>的传感器将显示为断开连接状态。",
|
"desc": "紧急模式会在以下情况自动激活(当PID或气候补偿无法计算热媒设定值时):<br />启用气候补偿但室外温度传感器断开连接;<br />启用PID或 OpenTherm 选项中启用<i>原生供暖控制</i>但室内温度传感器断开连接。<br /><b>注意:</b> 网络故障或MQTT 服务器连接故障时,类型为<i>通过MQTT/API手动控制</i>的传感器将显示为断开连接状态。",
|
||||||
|
|||||||
@@ -291,6 +291,11 @@
|
|||||||
"min": "Minimum temperature",
|
"min": "Minimum temperature",
|
||||||
"max": "Maximum temperature"
|
"max": "Maximum temperature"
|
||||||
},
|
},
|
||||||
|
"avgType": {
|
||||||
|
"mean": "Mean temperature",
|
||||||
|
"min": "Minimum temperature",
|
||||||
|
"max": "Maximum temperature"
|
||||||
|
},
|
||||||
"maxModulation": "Max modulation level",
|
"maxModulation": "Max modulation level",
|
||||||
"ohProtection": {
|
"ohProtection": {
|
||||||
"title": "Overheating protection",
|
"title": "Overheating protection",
|
||||||
@@ -352,7 +357,15 @@
|
|||||||
"set0target": "Set null target"
|
"set0target": "Set null target"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"turboFactor": "Turbo mode coeff."
|
"turboFactor": "Turbo mode coeff.",
|
||||||
|
"indoorTempAvgType": {
|
||||||
|
"title": "Indoor temp. averaging type",
|
||||||
|
"desc": "May be useful when using two or more indoor temp. sensors (when using «Equitherm» and/or «PID»)."
|
||||||
|
},
|
||||||
|
"outdoorTempAvgType": {
|
||||||
|
"title": "Outdoor temp. averaging type",
|
||||||
|
"desc": "May be useful when using two or more outdoor temp. sensors (when using «Equitherm»)."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"emergency": {
|
"emergency": {
|
||||||
"desc": "Emergency mode is activated automatically when «PID» or «Equitherm» cannot calculate the heat carrier setpoint:<br />- if «Equitherm» is enabled and the outdoor temperature sensor is disconnected;<br />- if «PID» or OT option <i>«Native heating control»</i> is enabled and the indoor temperature sensor is disconnected.<br /><b>Note:</b> On network fault or MQTT fault, sensors with <i>«Manual via MQTT/API»</i> type will be in DISCONNECTED state.",
|
"desc": "Emergency mode is activated automatically when «PID» or «Equitherm» cannot calculate the heat carrier setpoint:<br />- if «Equitherm» is enabled and the outdoor temperature sensor is disconnected;<br />- if «PID» or OT option <i>«Native heating control»</i> is enabled and the indoor temperature sensor is disconnected.<br /><b>Note:</b> On network fault or MQTT fault, sensors with <i>«Manual via MQTT/API»</i> type will be in DISCONNECTED state.",
|
||||||
|
|||||||
@@ -291,6 +291,11 @@
|
|||||||
"min": "Temperatura minima",
|
"min": "Temperatura minima",
|
||||||
"max": "Temperatura massima"
|
"max": "Temperatura massima"
|
||||||
},
|
},
|
||||||
|
"avgType": {
|
||||||
|
"mean": "Temperatura media",
|
||||||
|
"min": "Temperatura minima",
|
||||||
|
"max": "Temperatura massima"
|
||||||
|
},
|
||||||
"maxModulation": "Max livello modulazione",
|
"maxModulation": "Max livello modulazione",
|
||||||
"ohProtection": {
|
"ohProtection": {
|
||||||
"title": "Protezione contro il surriscaldamento",
|
"title": "Protezione contro il surriscaldamento",
|
||||||
@@ -352,7 +357,15 @@
|
|||||||
"set0target": "Imposta target nullo"
|
"set0target": "Imposta target nullo"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"turboFactor": "Turbo mode coeff."
|
"turboFactor": "Turbo mode coeff.",
|
||||||
|
"indoorTempAvgType": {
|
||||||
|
"title": "Tipo di media temperatura interna",
|
||||||
|
"desc": "Utile con due o più sensori di temperatura interna (quando si usa «Equitherm» e/o «PID»)."
|
||||||
|
},
|
||||||
|
"outdoorTempAvgType": {
|
||||||
|
"title": "Tipo di media temperatura esterna",
|
||||||
|
"desc": "Utile con due o più sensori di temperatura esterna (quando si usa «Equitherm»)."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"emergency": {
|
"emergency": {
|
||||||
"desc": "Il modo emergenza è attivato automaticamente quando «PID» o «Equitherm» non possono calcolare il setpoint:<br />- se «Equitherm» è attivato e il sensore della temperatura esternare è disconnesso;<br />- se «PID» o l'opzione OT <i>«Impostazioni riscaldamento native»</i> è attiva e il sensore di temperatura interno è disconnesso.<br /><b>Nota:</b> In mancanza di rete o MQTT, sensore di tipo <i>«Manuale via MQTT/API»</i> è in stato Disconnesso.",
|
"desc": "Il modo emergenza è attivato automaticamente quando «PID» o «Equitherm» non possono calcolare il setpoint:<br />- se «Equitherm» è attivato e il sensore della temperatura esternare è disconnesso;<br />- se «PID» o l'opzione OT <i>«Impostazioni riscaldamento native»</i> è attiva e il sensore di temperatura interno è disconnesso.<br /><b>Nota:</b> In mancanza di rete o MQTT, sensore di tipo <i>«Manuale via MQTT/API»</i> è in stato Disconnesso.",
|
||||||
|
|||||||
@@ -291,6 +291,11 @@
|
|||||||
"min": "Minimumtemperatuur",
|
"min": "Minimumtemperatuur",
|
||||||
"max": "Maximumtemperatuur"
|
"max": "Maximumtemperatuur"
|
||||||
},
|
},
|
||||||
|
"avgType": {
|
||||||
|
"mean": "Gemiddelde temperatuur",
|
||||||
|
"min": "Minimum temperatuur",
|
||||||
|
"max": "Maximum temperatuur"
|
||||||
|
},
|
||||||
"maxModulation": "Max. modulatieniveau",
|
"maxModulation": "Max. modulatieniveau",
|
||||||
"ohProtection": {
|
"ohProtection": {
|
||||||
"title": "Oververhittingsbeveiliging",
|
"title": "Oververhittingsbeveiliging",
|
||||||
@@ -352,7 +357,15 @@
|
|||||||
"set0target": "Stel null target in"
|
"set0target": "Stel null target in"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"turboFactor": "Turbomodus coëff."
|
"turboFactor": "Turbomodus coëff.",
|
||||||
|
"indoorTempAvgType": {
|
||||||
|
"title": "Binnentemperatuur gemiddelde type",
|
||||||
|
"desc": "Nuttig bij twee of meer binnentemperatuursensoren (bij gebruik van «Equitherm» en/of «PID»)."
|
||||||
|
},
|
||||||
|
"outdoorTempAvgType": {
|
||||||
|
"title": "Buitentemperatuur gemiddelde type",
|
||||||
|
"desc": "Nuttig bij twee of meer buitensensoren (bij gebruik van «Equitherm»)."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"emergency": {
|
"emergency": {
|
||||||
"desc": "Noodmodus wordt automatisch geactiveerd wanneer «PID» of «Equitherm» het instelpunt van de warmtedrager niet kan berekenen:<br />- als «Equitherm» is ingeschakeld en de buitentemperatuursensor is losgekoppeld;<br />- als «PID» of OT-optie <i>«Natuurlijke verwarmingsregeling»</i> is ingeschakeld en de binnentemperatuursensor is losgekoppeld.<br /><b>Let op:</b> Bij een netwerk- of MQTT-storing krijgen sensoren van het type <i>«Handmatig via MQTT/API»</i> de status ONVERBONDEN.",
|
"desc": "Noodmodus wordt automatisch geactiveerd wanneer «PID» of «Equitherm» het instelpunt van de warmtedrager niet kan berekenen:<br />- als «Equitherm» is ingeschakeld en de buitentemperatuursensor is losgekoppeld;<br />- als «PID» of OT-optie <i>«Natuurlijke verwarmingsregeling»</i> is ingeschakeld en de binnentemperatuursensor is losgekoppeld.<br /><b>Let op:</b> Bij een netwerk- of MQTT-storing krijgen sensoren van het type <i>«Handmatig via MQTT/API»</i> de status ONVERBONDEN.",
|
||||||
|
|||||||
@@ -291,6 +291,11 @@
|
|||||||
"min": "Мин. температура",
|
"min": "Мин. температура",
|
||||||
"max": "Макс. температура"
|
"max": "Макс. температура"
|
||||||
},
|
},
|
||||||
|
"avgType": {
|
||||||
|
"mean": "Средняя температура",
|
||||||
|
"min": "Минимальная температура",
|
||||||
|
"max": "Максимальная температура"
|
||||||
|
},
|
||||||
"maxModulation": "Макс. уровень модуляции",
|
"maxModulation": "Макс. уровень модуляции",
|
||||||
"ohProtection": {
|
"ohProtection": {
|
||||||
"title": "Защита от перегрева",
|
"title": "Защита от перегрева",
|
||||||
@@ -352,7 +357,15 @@
|
|||||||
"set0target": "Установить 0 в качестве целевой темп."
|
"set0target": "Установить 0 в качестве целевой темп."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"turboFactor": "Коэфф. турбо режима"
|
"turboFactor": "Коэфф. турбо режима",
|
||||||
|
"indoorTempAvgType": {
|
||||||
|
"title": "Тип усреднения внутренней темп.",
|
||||||
|
"desc": "Полезно при использовании двух и более датчиков внутренней температуры (при использовании «Equitherm» и/или «PID»)."
|
||||||
|
},
|
||||||
|
"outdoorTempAvgType": {
|
||||||
|
"title": "Тип усреднения наружнной темп.",
|
||||||
|
"desc": "Полезно при использовании двух и более датчиков наружной температуры (при использовании «Equitherm»)."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"emergency": {
|
"emergency": {
|
||||||
"desc": "Аварийный режим активируется автоматически, если «ПИД» или «ПЗА» не могут рассчитать уставку теплоносителя:<br />- если «ПЗА» включен и датчик наружной температуры отключен;<br />- если включен «ПИД» или OT опция <i>«Передать управление отоплением котлу»</i> и датчик внутренней температуры отключен.<br /><b>Примечание:</b> При сбое сети или MQTT датчики с типом <i>«Вручную через MQTT/API»</i> будут находиться в состоянии ОТКЛЮЧЕН.",
|
"desc": "Аварийный режим активируется автоматически, если «ПИД» или «ПЗА» не могут рассчитать уставку теплоносителя:<br />- если «ПЗА» включен и датчик наружной температуры отключен;<br />- если включен «ПИД» или OT опция <i>«Передать управление отоплением котлу»</i> и датчик внутренней температуры отключен.<br /><b>Примечание:</b> При сбое сети или MQTT датчики с типом <i>«Вручную через MQTT/API»</i> будут находиться в состоянии ОТКЛЮЧЕН.",
|
||||||
|
|||||||
@@ -198,6 +198,28 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<label>
|
||||||
|
<span data-i18n>settings.heating.indoorTempAvgType.title</span>
|
||||||
|
<select name="heating[indoorTempAvgType]">
|
||||||
|
<option value="0" data-i18n>settings.avgType.mean</option>
|
||||||
|
<option value="1" data-i18n>settings.avgType.min</option>
|
||||||
|
<option value="2" data-i18n>settings.avgType.max</option>
|
||||||
|
</select>
|
||||||
|
<small data-i18n>settings.heating.indoorTempAvgType.desc</small>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label>
|
||||||
|
<span data-i18n>settings.heating.outdoorTempAvgType.title</span>
|
||||||
|
<select name="heating[outdoorTempAvgType]">
|
||||||
|
<option value="0" data-i18n>settings.avgType.mean</option>
|
||||||
|
<option value="1" data-i18n>settings.avgType.min</option>
|
||||||
|
<option value="2" data-i18n>settings.avgType.max</option>
|
||||||
|
</select>
|
||||||
|
<small data-i18n>settings.heating.outdoorTempAvgType.desc</small>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
@@ -1171,6 +1193,8 @@
|
|||||||
setSelectValue("[name='heating[hysteresis][action]']", data.heating.hysteresis.action);
|
setSelectValue("[name='heating[hysteresis][action]']", data.heating.hysteresis.action);
|
||||||
setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor);
|
setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor);
|
||||||
setInputValue("[name='heating[maxModulation]']", data.heating.maxModulation);
|
setInputValue("[name='heating[maxModulation]']", data.heating.maxModulation);
|
||||||
|
setSelectValue("[name='heating[indoorTempAvgType]']", data.heating.indoorTempAvgType);
|
||||||
|
setSelectValue("[name='heating[outdoorTempAvgType]']", data.heating.outdoorTempAvgType);
|
||||||
setInputValue("[name='heating[overheatProtection][highTemp]']", data.heating.overheatProtection.highTemp, {
|
setInputValue("[name='heating[overheatProtection][highTemp]']", data.heating.overheatProtection.highTemp, {
|
||||||
"min": 0,
|
"min": 0,
|
||||||
"max": data.system.unitSystem == 0 ? 100 : 212
|
"max": data.system.unitSystem == 0 ? 100 : 212
|
||||||
|
|||||||
Reference in New Issue
Block a user