Compare commits

..
14 Commits
55 changed files with 4963 additions and 6512 deletions

No files matched your search

-173
View File
@@ -1,173 +0,0 @@
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
env:
# Keep Python within the versions supported by pioarduino.
PYTHON_VERSION: "3.13"
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: ${{ env.PYTHON_VERSION }}
- 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: ${{ env.PYTHON_VERSION }}
- 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: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Move dev tag
run: |
git fetch --tags
git tag -f dev
git push origin refs/tags/dev --force
- 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: Clear release body
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release edit dev --notes ""
- 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
-110
View File
@@ -1,110 +0,0 @@
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
+22
View File
@@ -0,0 +1,22 @@
name: PlatformIO Dependabot
on:
workflow_dispatch: # option to manually trigger the workflow
schedule:
# Runs every day at 00:00
- cron: "0 0 * * *"
permissions:
contents: write
pull-requests: write
jobs:
dependabot:
runs-on: ubuntu-latest
name: run PlatformIO Dependabot
steps:
- name: Checkout
uses: actions/checkout@v4
- name: run PlatformIO Dependabot
uses: peterus/platformio_dependabot@v1.2.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
+1 -1
View File
@@ -7,7 +7,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v11
- uses: actions/stale@v9
with:
stale-issue-message: >
This issue is stale because it has been open 15 days with no activity. Remove stale label or comment or this will be closed in 5 days.
+1 -1
View File
@@ -1,5 +1,4 @@
.pio
.dummy
.vscode
.PVS-Studio
build/*
@@ -11,4 +10,5 @@ package-lock.json
*.lock
sdkconfig.*
CMakeLists.txt
!sdkconfig.defaults
!.gitkeep
+1 -10
View File
@@ -2,7 +2,6 @@
[![GitHub version](https://img.shields.io/github/release/Laxilef/OTGateway.svg?include_prereleases)](https://github.com/Laxilef/OTGateway/releases)
[![GitHub download](https://img.shields.io/github/downloads/Laxilef/OTGateway/total.svg)](https://github.com/Laxilef/OTGateway/releases/latest)
[![Translations](https://hosted.weblate.org/widget/otgateway/svg-badge.svg)](https://hosted.weblate.org/engage/otgateway/)
[![License](https://img.shields.io/github/license/Laxilef/OTGateway.svg)](LICENSE.txt)
[![Telegram](https://img.shields.io/badge/Telegram-Channel-33A8E3)](https://t.me/otgateway)
@@ -72,16 +71,8 @@ All available information and instructions can be found in the wiki:
* [Connection](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#connection)
* [Leds on board](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#leds-on-board)
## Translations
The project uses [Weblate](https://hosted.weblate.org/new-lang/otgateway/) to manage translations of the interface.<br />
Anyone is welcome to add their native language as a [new localization](https://hosted.weblate.org/new-lang/otgateway/portal/). All contributions are welcome!
<a href="https://hosted.weblate.org/engage/otgateway/">
<img src="https://hosted.weblate.org/widget/otgateway/multi-auto.svg" alt="Translation status" />
</a>
## Gratitude
* To the developers of the libraries used: [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library), [ArduinoJson](https://github.com/bblanchon/ArduinoJson), [NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino), [ArduinoMqttClient](https://github.com/arduino-libraries/ArduinoMqttClient), [ESPTelnet](https://github.com/LennartHennigs/ESPTelnet), [FileData](https://github.com/GyverLibs/FileData), [GyverPID](https://github.com/GyverLibs/GyverPID), [GyverBlinker](https://github.com/GyverLibs/GyverBlinker), [OneWireNg](https://github.com/pstolarz/OneWireNg) & [OneWire](https://github.com/PaulStoffregen/OneWire)
* To the developers of the libraries used: [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library), [ESP8266Scheduler](https://github.com/nrwiersma/ESP8266Scheduler), [ArduinoJson](https://github.com/bblanchon/ArduinoJson), [NimBLE-Arduino](https://github.com/h2zero/NimBLE-Arduino), [ArduinoMqttClient](https://github.com/arduino-libraries/ArduinoMqttClient), [ESPTelnet](https://github.com/LennartHennigs/ESPTelnet), [FileData](https://github.com/GyverLibs/FileData), [GyverPID](https://github.com/GyverLibs/GyverPID), [GyverBlinker](https://github.com/GyverLibs/GyverBlinker), [FileData](https://github.com/GyverLibs/FileData), [OneWireNg](https://github.com/pstolarz/OneWireNg) & [OneWire](https://github.com/PaulStoffregen/OneWire)
* To the [PlatformIO](https://platformio.org/) Team
* To the team and contributors of the [pioarduino](https://github.com/pioarduino/platform-espressif32) project
* To the [BrowserStack](https://www.browserstack.com/) team. This project is tested with BrowserStack.
+4 -7
View File
@@ -1,5 +1,5 @@
# Blueprint for reporting indoor/outdoor temperature to OpenTherm Gateway from any home assistant sensor
# Updated: 26.01.2026
# Updated: 03.09.2024
blueprint:
name: Report temp to OpenTherm Gateway
@@ -15,6 +15,7 @@ blueprint:
multiple: false
filter:
- domain: sensor
device_class: temperature
target_entity:
name: Target entity
description: "Usually ``number.opentherm_indoor_temp`` or ``number.opentherm_outdoor_temp``"
@@ -37,12 +38,8 @@ condition:
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
action:
- if:
- condition: or
conditions:
- condition: template
value_template: "{{ (states(source_entity)|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.01 }}"
- condition: template
value_template: "{{ (as_timestamp(now()) - as_timestamp(states[target_entity].last_updated)) | int(0) > 300 }}"
- condition: template
value_template: "{{ (states(source_entity)|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.01 }}"
then:
- service: number.set_value
data:
@@ -1,5 +1,5 @@
# Blueprint for reporting temperature to OpenTherm Gateway from home assistant weather integration
# Updated: 26.01.2026
# Updated: 03.09.2024
blueprint:
name: Report temp to OpenTherm Gateway from Weather
@@ -37,12 +37,8 @@ condition:
value_template: "{{ states(source_entity) != 'unavailable' and states(target_entity) != 'unavailable' }}"
action:
- if:
- condition: or
conditions:
- condition: template
value_template: "{{ (state_attr(source_entity, 'temperature')|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.1 }}"
- condition: template
value_template: "{{ (as_timestamp(now()) - as_timestamp(states[target_entity].last_updated)) | int(0) > 300 }}"
- condition: template
value_template: "{{ (state_attr(source_entity, 'temperature')|float(0) - states(target_entity)|float(0)) | abs | round(2) >= 0.1 }}"
then:
- service: number.set_value
data:
+22 -2
View File
@@ -1,9 +1,9 @@
class BufferedWebServer {
public:
explicit BufferedWebServer(WebServer* webServer, size_t bufferSize = 64) {
BufferedWebServer(WebServer* webServer, size_t bufferSize = 64) {
this->webServer = webServer;
this->bufferSize = bufferSize;
this->buffer = static_cast<uint8_t*>(malloc(bufferSize * sizeof(*this->buffer)));
this->buffer = (uint8_t*)malloc(bufferSize * sizeof(*this->buffer));
}
~BufferedWebServer() {
@@ -14,8 +14,17 @@ public:
void send(int code, T contentType, const JsonVariantConst content, bool pretty = false) {
auto contentLength = pretty ? measureJsonPretty(content) : measureJson(content);
#ifdef ARDUINO_ARCH_ESP8266
if (!this->webServer->chunkedResponseModeStart(code, contentType)) {
this->webServer->send(505, F("text/html"), F("HTTP1.1 required"));
return;
}
this->webServer->setContentLength(contentLength);
#else
this->webServer->setContentLength(contentLength);
this->webServer->send(code, contentType, emptyString);
#endif
if (pretty) {
serializeJsonPretty(content, *this);
@@ -26,7 +35,11 @@ public:
this->flush();
#ifdef ARDUINO_ARCH_ESP8266
this->webServer->chunkedResponseFinalize();
#else
this->webServer->sendContent(emptyString);
#endif
}
size_t write(uint8_t c) {
@@ -65,6 +78,10 @@ public:
return;
}
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
#endif
auto& client = this->webServer->client();
if (client.connected()) {
this->webServer->sendContent((const char*)this->buffer, this->bufferPos);
@@ -72,6 +89,9 @@ public:
this->bufferPos = 0;
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
#endif
}
protected:
+33 -71
View File
@@ -4,10 +4,10 @@
class CustomOpenTherm : public OpenTherm {
public:
typedef std::function<void(unsigned int)> DelayCallback;
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, byte)> BeforeSendRequestCallback;
typedef std::function<void(unsigned long, unsigned long, OpenThermResponseStatus, byte)> AfterSendRequestCallback;
explicit CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false, bool alwaysReceive = false) : OpenTherm(inPin, outPin, isSlave, alwaysReceive) {}
CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false, bool alwaysReceive = false) : OpenTherm(inPin, outPin, isSlave, alwaysReceive) {}
~CustomOpenTherm() {}
CustomOpenTherm* setDelayCallback(DelayCallback callback = nullptr) {
@@ -70,89 +70,51 @@ public:
}
}
inline auto sendBoilerReset() {
return this->sendRequestCode(1);
}
inline auto sendServiceReset() {
return this->sendRequestCode(10);
}
inline auto sendWaterFilling() {
return this->sendRequestCode(2);
}
bool sendRequestCode(const uint8_t requestCode) {
bool sendBoilerReset() {
unsigned int data = 1;
data <<= 8;
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::RemoteRequest,
static_cast<unsigned int>(requestCode) << 8
data
));
if (!isValidResponse(response) || !isValidResponseId(response, OpenThermMessageID::RemoteRequest)) {
return false;
}
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
}
const uint8_t responseRequestCode = (response & 0xFFFF) >> 8;
const uint8_t responseCode = response & 0xFF;
if (responseRequestCode != requestCode || responseCode < 128) {
return false;
}
// reset
this->sendRequest(buildRequest(
bool sendServiceReset() {
unsigned int data = 10;
data <<= 8;
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::RemoteRequest,
0u << 8
data
));
return true;
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
}
bool getStr(OpenThermMessageID id, char* buffer, uint16_t length = 50) {
if (buffer == nullptr || length == 0) {
return false;
}
bool sendWaterFilling() {
unsigned int data = 2;
data <<= 8;
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::RemoteRequest,
data
));
unsigned long response;
uint8_t index = 0;
uint8_t maxIndex = 255;
while (index <= maxIndex && index < length) {
response = this->sendRequest(buildRequest(
OpenThermMessageType::READ_DATA,
id,
static_cast<unsigned int>(index) << 8
));
if (!isValidResponse(response) || !isValidResponseId(response, id)) {
break;
}
const uint8_t character = response & 0xFF;
if (character == 0) {
break;
}
if (index == 0) {
maxIndex = (response & 0xFFFF) >> 8;
}
buffer[index++] = static_cast<char>(character);
}
buffer[index] = '\0';
return index > 0;
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
}
static bool isCh2Active(unsigned long response) {
return response & 0x20;
static bool isCh2Active(unsigned long response)
{
return response & 0x20;
}
static bool isValidResponseId(unsigned long response, OpenThermMessageID id) {
const uint8_t responseId = (response >> 16) & 0xFF;
byte responseId = (response >> 16) & 0xFF;
return static_cast<uint8_t>(id) == responseId;
return (byte)id == responseId;
}
static uint8_t getResponseMessageTypeId(unsigned long response) {
@@ -163,10 +125,10 @@ public:
uint8_t msgType = getResponseMessageTypeId(response);
switch (msgType) {
case static_cast<uint8_t>(OpenThermMessageType::READ_ACK):
case static_cast<uint8_t>(OpenThermMessageType::WRITE_ACK):
case static_cast<uint8_t>(OpenThermMessageType::DATA_INVALID):
case static_cast<uint8_t>(OpenThermMessageType::UNKNOWN_DATA_ID):
case (uint8_t) OpenThermMessageType::READ_ACK:
case (uint8_t) OpenThermMessageType::WRITE_ACK:
case (uint8_t) OpenThermMessageType::DATA_INVALID:
case (uint8_t) OpenThermMessageType::UNKNOWN_DATA_ID:
return CustomOpenTherm::messageTypeToString(
static_cast<OpenThermMessageType>(msgType)
);
+8 -19
View File
@@ -53,23 +53,23 @@ public:
return false;
}
doc[HA_DEVICE][HA_IDENTIFIERS][0] = this->devicePrefix;
doc[HA_DEVICE][HA_SW_VERSION] = this->deviceVersion;
doc[FPSTR(HA_DEVICE)][FPSTR(HA_IDENTIFIERS)][0] = this->devicePrefix;
doc[FPSTR(HA_DEVICE)][FPSTR(HA_SW_VERSION)] = this->deviceVersion;
if (this->deviceManufacturer != nullptr) {
doc[HA_DEVICE][HA_MANUFACTURER] = this->deviceManufacturer;
doc[FPSTR(HA_DEVICE)][FPSTR(HA_MANUFACTURER)] = this->deviceManufacturer;
}
if (this->deviceModel != nullptr) {
doc[HA_DEVICE][HA_MODEL] = this->deviceModel;
doc[FPSTR(HA_DEVICE)][FPSTR(HA_MODEL)] = this->deviceModel;
}
if (this->deviceName != nullptr) {
doc[HA_DEVICE][HA_NAME] = this->deviceName;
doc[FPSTR(HA_DEVICE)][FPSTR(HA_NAME)] = this->deviceName;
}
if (this->deviceConfigUrl != nullptr) {
doc[HA_DEVICE][HA_CONF_URL] = this->deviceConfigUrl;
doc[FPSTR(HA_DEVICE)][FPSTR(HA_CONF_URL)] = this->deviceConfigUrl;
}
bool result = this->writer->publish(topic, doc, true);
@@ -110,7 +110,7 @@ public:
topic.concat(this->devicePrefix);
topic.concat(nameSeparator);
topic.concat(name);
topic.concat("/config");
topic.concat(F("/config"));
return topic;
}
@@ -147,19 +147,8 @@ public:
return topic;
}
template <class DT, class VT>
String getEntityIdWithPrefix(DT domain, VT value, char separator = '_') {
String topic = "";
topic.concat(domain);
topic.concat('.');
topic.concat(this->devicePrefix);
topic.concat(separator);
topic.concat(value);
return topic;
}
template <class T>
String getUniqueIdWithPrefix(T value, char separator = '_') {
String getObjectIdWithPrefix(T value, char separator = '_') {
String topic = "";
topic.concat(this->devicePrefix);
topic.concat(separator);
+74 -72
View File
@@ -1,75 +1,77 @@
#pragma once
#ifndef PROGMEM
#define PROGMEM
#endif
const char HA_ENTITY_BINARY_SENSOR[] = "binary_sensor";
const char HA_ENTITY_BUTTON[] = "button";
const char HA_ENTITY_FAN[] = "fan";
const char HA_ENTITY_CLIMATE[] = "climate";
const char HA_ENTITY_NUMBER[] = "number";
const char HA_ENTITY_SELECT[] = "select";
const char HA_ENTITY_SENSOR[] = "sensor";
const char HA_ENTITY_SWITCH[] = "switch";
const char HA_ENTITY_BINARY_SENSOR[] PROGMEM = "binary_sensor";
const char HA_ENTITY_BUTTON[] PROGMEM = "button";
const char HA_ENTITY_FAN[] PROGMEM = "fan";
const char HA_ENTITY_CLIMATE[] PROGMEM = "climate";
const char HA_ENTITY_NUMBER[] PROGMEM = "number";
const char HA_ENTITY_SELECT[] PROGMEM = "select";
const char HA_ENTITY_SENSOR[] PROGMEM = "sensor";
const char HA_ENTITY_SWITCH[] PROGMEM = "switch";
// https://www.home-assistant.io/integrations/mqtt/#supported-abbreviations-in-mqtt-discovery-messages
const char HA_DEFAULT_ENTITY_ID[] = "def_ent_id"; // "default_entity_id "
const char HA_DEVICE[] = "dev"; // "device"
const char HA_IDENTIFIERS[] = "ids"; // "identifiers"
const char HA_SW_VERSION[] = "sw"; // "sw_version"
const char HA_MANUFACTURER[] = "mf"; // "manufacturer"
const char HA_MODEL[] = "mdl"; // "model"
const char HA_NAME[] = "name";
const char HA_CONF_URL[] = "cu"; // "configuration_url"
const char HA_COMMAND_TOPIC[] = "cmd_t"; // "command_topic"
const char HA_COMMAND_TEMPLATE[] = "cmd_tpl"; // "command_template"
const char HA_ENABLED_BY_DEFAULT[] = "en"; // "enabled_by_default"
const char HA_UNIQUE_ID[] = "uniq_id"; // "unique_id"
const char HA_ENTITY_CATEGORY[] = "ent_cat"; // "entity_category"
const char HA_ENTITY_CATEGORY_DIAGNOSTIC[] = "diagnostic";
const char HA_ENTITY_CATEGORY_CONFIG[] = "config";
const char HA_STATE_TOPIC[] = "stat_t"; // "state_topic"
const char HA_VALUE_TEMPLATE[] = "val_tpl"; // "value_template"
const char HA_OPTIONS[] = "ops"; // "options"
const char HA_AVAILABILITY[] = "avty"; // "availability"
const char HA_AVAILABILITY_MODE[] = "avty_mode"; // "availability_mode"
const char HA_TOPIC[] = "t"; // "topic"
const char HA_DEVICE_CLASS[] = "dev_cla"; // "device_class"
const char HA_UNIT_OF_MEASUREMENT[] = "unit_of_meas"; // "unit_of_measurement"
const char HA_UNIT_OF_MEASUREMENT_C[] = "°C";
const char HA_UNIT_OF_MEASUREMENT_F[] = "°F";
const char HA_UNIT_OF_MEASUREMENT_PERCENT[] = "%";
const char HA_UNIT_OF_MEASUREMENT_L_MIN[] = "L/min";
const char HA_UNIT_OF_MEASUREMENT_GAL_MIN[] = "gal/min";
const char HA_ICON[] = "ic"; // "icon"
const char HA_MIN[] = "min";
const char HA_MAX[] = "max";
const char HA_STEP[] = "step";
const char HA_MODE[] = "mode";
const char HA_MODE_BOX[] = "box";
const char HA_STATE_ON[] = "stat_on"; // "state_on"
const char HA_STATE_OFF[] = "stat_off"; // "state_off"
const char HA_PAYLOAD_ON[] = "pl_on"; // "payload_on"
const char HA_PAYLOAD_OFF[] = "pl_off"; // "payload_off"
const char HA_STATE_CLASS[] = "stat_cla"; // "state_class"
const char HA_STATE_CLASS_MEASUREMENT[] = "measurement";
const char HA_EXPIRE_AFTER[] = "exp_aft"; // "expire_after"
const char HA_CURRENT_TEMPERATURE_TOPIC[] = "curr_temp_t"; // "current_temperature_topic"
const char HA_CURRENT_TEMPERATURE_TEMPLATE[] = "curr_temp_tpl"; // "current_temperature_template"
const char HA_TEMPERATURE_COMMAND_TOPIC[] = "temp_cmd_t"; // "temperature_command_topic"
const char HA_TEMPERATURE_COMMAND_TEMPLATE[] = "temp_cmd_tpl"; // "temperature_command_template"
const char HA_TEMPERATURE_STATE_TOPIC[] = "temp_stat_t"; // "temperature_state_topic"
const char HA_TEMPERATURE_STATE_TEMPLATE[] = "temp_stat_tpl"; // "temperature_state_template"
const char HA_TEMPERATURE_UNIT[] = "temp_unit"; // "temperature_unit"
const char HA_MODE_COMMAND_TOPIC[] = "mode_cmd_t"; // "mode_command_topic"
const char HA_MODE_COMMAND_TEMPLATE[] = "mode_cmd_tpl"; // "mode_command_template"
const char HA_MODE_STATE_TOPIC[] = "mode_stat_t"; // "mode_state_topic"
const char HA_MODE_STATE_TEMPLATE[] = "mode_stat_tpl"; // "mode_state_template"
const char HA_MODES[] = "modes";
const char HA_ACTION_TOPIC[] = "act_t"; // "action_topic"
const char HA_ACTION_TEMPLATE[] = "act_tpl"; // "action_template"
const char HA_MIN_TEMP[] = "min_temp";
const char HA_MAX_TEMP[] = "max_temp";
const char HA_TEMP_STEP[] = "temp_step";
const char HA_PRESET_MODE_COMMAND_TOPIC[] = "pr_mode_cmd_t"; // "preset_mode_command_topic"
const char HA_PRESET_MODE_COMMAND_TEMPLATE[] = "pr_mode_cmd_tpl"; // "preset_mode_command_template"
const char HA_PRESET_MODE_STATE_TOPIC[] = "pr_mode_stat_t"; // "preset_mode_state_topic"
const char HA_PRESET_MODE_VALUE_TEMPLATE[] = "pr_mode_val_tpl"; // "preset_mode_value_template"
const char HA_PRESET_MODES[] = "pr_modes"; // "preset_modes"
const char HA_DEVICE[] PROGMEM = "device";
const char HA_IDENTIFIERS[] PROGMEM = "identifiers";
const char HA_SW_VERSION[] PROGMEM = "sw_version";
const char HA_MANUFACTURER[] PROGMEM = "manufacturer";
const char HA_MODEL[] PROGMEM = "model";
const char HA_NAME[] PROGMEM = "name";
const char HA_CONF_URL[] PROGMEM = "configuration_url";
const char HA_COMMAND_TOPIC[] PROGMEM = "command_topic";
const char HA_COMMAND_TEMPLATE[] PROGMEM = "command_template";
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";
const char HA_AVAILABILITY[] PROGMEM = "availability";
const char HA_AVAILABILITY_MODE[] PROGMEM = "availability_mode";
const char HA_TOPIC[] PROGMEM = "topic";
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";
const char HA_TEMPERATURE_COMMAND_TOPIC[] PROGMEM = "temperature_command_topic";
const char HA_TEMPERATURE_COMMAND_TEMPLATE[] PROGMEM = "temperature_command_template";
const char HA_TEMPERATURE_STATE_TOPIC[] PROGMEM = "temperature_state_topic";
const char HA_TEMPERATURE_STATE_TEMPLATE[] PROGMEM = "temperature_state_template";
const char HA_TEMPERATURE_UNIT[] PROGMEM = "temperature_unit";
const char HA_MODE_COMMAND_TOPIC[] PROGMEM = "mode_command_topic";
const char HA_MODE_COMMAND_TEMPLATE[] PROGMEM = "mode_command_template";
const char HA_MODE_STATE_TOPIC[] PROGMEM = "mode_state_topic";
const char HA_MODE_STATE_TEMPLATE[] PROGMEM = "mode_state_template";
const char HA_MODES[] PROGMEM = "modes";
const char HA_ACTION_TOPIC[] PROGMEM = "action_topic";
const char HA_ACTION_TEMPLATE[] PROGMEM = "action_template";
const char HA_MIN_TEMP[] PROGMEM = "min_temp";
const char HA_MAX_TEMP[] PROGMEM = "max_temp";
const char HA_TEMP_STEP[] PROGMEM = "temp_step";
const char HA_PRESET_MODE_COMMAND_TOPIC[] PROGMEM = "preset_mode_command_topic";
const char HA_PRESET_MODE_COMMAND_TEMPLATE[] PROGMEM = "preset_mode_command_template";
const char HA_PRESET_MODE_STATE_TOPIC[] PROGMEM = "preset_mode_state_topic";
const char HA_PRESET_MODE_VALUE_TEMPLATE[] PROGMEM = "preset_mode_value_template";
const char HA_PRESET_MODES[] PROGMEM = "preset_modes";
+24
View File
@@ -0,0 +1,24 @@
#include <WiFiClient.h>
class MqttWiFiClient : public WiFiClient {
public:
#ifdef ARDUINO_ARCH_ESP8266
void flush() override {
if (this->connected()) {
WiFiClient::flush(0);
}
}
void stop() override {
this->abort();
}
#endif
#ifdef ARDUINO_ARCH_ESP32
void setSync(bool) {}
bool getSync() {
return false;
}
#endif
};
+18 -2
View File
@@ -1,7 +1,9 @@
#pragma once
#include <Arduino.h>
#include <MqttClient.h>
#ifdef ARDUINO_ARCH_ESP32
#include <mutex>
#endif
class MqttWriter {
@@ -10,18 +12,22 @@ public:
typedef std::function<void(const char*, size_t, size_t, bool)> PublishEventCallback;
typedef std::function<void(size_t, size_t)> FlushEventCallback;
explicit MqttWriter(MqttClient* client, size_t bufferSize = 64) {
MqttWriter(MqttClient* client, size_t bufferSize = 64) {
this->client = client;
this->bufferSize = bufferSize;
this->buffer = static_cast<uint8_t*>(malloc(bufferSize * sizeof(*this->buffer)));
this->buffer = (uint8_t*) malloc(bufferSize * sizeof(*this->buffer));
#ifdef ARDUINO_ARCH_ESP32
this->mutex = new std::mutex();
#endif
}
~MqttWriter() {
free(this->buffer);
#ifdef ARDUINO_ARCH_ESP32
delete this->mutex;
#endif
}
MqttWriter* setYieldCallback(YieldCallback callback = nullptr) {
@@ -43,9 +49,15 @@ public:
}
bool lock() {
#ifdef ARDUINO_ARCH_ESP32
if (!this->mutex->try_lock()) {
return false;
}
#else
if (this->isLocked()) {
return false;
}
#endif
this->locked = true;
this->writeAfterLock = 0;
@@ -60,7 +72,9 @@ public:
void unlock() {
this->locked = false;
#if defined(ARDUINO_ARCH_ESP32)
this->mutex->unlock();
#endif
}
bool publish(const char* topic, const JsonVariantConst doc, bool retained = false) {
@@ -198,7 +212,9 @@ protected:
size_t bufferSize = 64;
size_t bufferPos = 0;
bool locked = false;
#ifdef ARDUINO_ARCH_ESP32
mutable std::mutex* mutex;
#endif
unsigned long lockedTime = 0;
size_t writeAfterLock = 0;
};
+73
View File
@@ -4,7 +4,11 @@ using namespace NetworkUtils;
void NetworkConnection::setup(bool useDhcp) {
setUseDhcp(useDhcp);
#if defined(ARDUINO_ARCH_ESP8266)
wifi_set_event_handler_cb(NetworkConnection::onEvent);
#elif defined(ARDUINO_ARCH_ESP32)
WiFi.onEvent(NetworkConnection::onEvent);
#endif
}
void NetworkConnection::reset() {
@@ -25,6 +29,57 @@ NetworkConnection::DisconnectReason NetworkConnection::getDisconnectReason() {
return disconnectReason;
}
#if defined(ARDUINO_ARCH_ESP8266)
void NetworkConnection::onEvent(System_Event_t *event) {
switch (event->event) {
case EVENT_STAMODE_CONNECTED:
status = useDhcp ? Status::CONNECTING : Status::CONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::NONE;
break;
case EVENT_STAMODE_GOT_IP:
status = Status::CONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::NONE;
break;
case EVENT_STAMODE_DHCP_TIMEOUT:
status = Status::DISCONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::DHCP_TIMEOUT;
break;
case EVENT_STAMODE_DISCONNECTED:
status = Status::DISCONNECTED;
rawDisconnectReason = event->event_info.disconnected.reason;
disconnectReason = convertDisconnectReason(event->event_info.disconnected.reason);
// https://github.com/esp8266/Arduino/blob/d5eb265f78bff9deb7063d10030a02d021c8c66c/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp#L231
if ((wifi_station_get_connect_status() == STATION_GOT_IP) && !wifi_station_get_reconnect_policy()) {
wifi_station_disconnect();
}
break;
case EVENT_STAMODE_AUTHMODE_CHANGE:
// https://github.com/esp8266/Arduino/blob/d5eb265f78bff9deb7063d10030a02d021c8c66c/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp#L241
{
auto& src = event->event_info.auth_change;
if ((src.old_mode != AUTH_OPEN) && (src.new_mode == AUTH_OPEN)) {
status = Status::DISCONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::OTHER;
wifi_station_disconnect();
}
}
break;
default:
break;
}
}
#elif defined(ARDUINO_ARCH_ESP32)
void NetworkConnection::onEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) {
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
@@ -56,9 +111,26 @@ void NetworkConnection::onEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
break;
}
}
#endif
NetworkConnection::DisconnectReason NetworkConnection::convertDisconnectReason(uint8_t reason) {
switch (reason) {
#if defined(ARDUINO_ARCH_ESP8266)
case REASON_BEACON_TIMEOUT:
return DisconnectReason::BEACON_TIMEOUT;
case REASON_NO_AP_FOUND:
return DisconnectReason::NO_AP_FOUND;
case REASON_AUTH_FAIL:
return DisconnectReason::AUTH_FAIL;
case REASON_ASSOC_FAIL:
return DisconnectReason::ASSOC_FAIL;
case REASON_HANDSHAKE_TIMEOUT:
return DisconnectReason::HANDSHAKE_TIMEOUT;
#elif defined(ARDUINO_ARCH_ESP32)
case WIFI_REASON_BEACON_TIMEOUT:
return DisconnectReason::BEACON_TIMEOUT;
@@ -73,6 +145,7 @@ NetworkConnection::DisconnectReason NetworkConnection::convertDisconnectReason(u
case WIFI_REASON_HANDSHAKE_TIMEOUT:
return DisconnectReason::HANDSHAKE_TIMEOUT;
#endif
default:
return DisconnectReason::OTHER;
+10 -1
View File
@@ -1,4 +1,9 @@
#include <WiFi.h>
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include "lwip/etharp.h"
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#endif
namespace NetworkUtils {
struct NetworkConnection {
@@ -29,7 +34,11 @@ namespace NetworkUtils {
static void reset();
static Status getStatus();
static DisconnectReason getDisconnectReason();
#if defined(ARDUINO_ARCH_ESP8266)
static void onEvent(System_Event_t *evt);
#elif defined(ARDUINO_ARCH_ESP32)
static void onEvent(WiFiEvent_t event, WiFiEventInfo_t info);
#endif
protected:
static DisconnectReason convertDisconnectReason(uint8_t reason);
+124 -62
View File
@@ -1,5 +1,10 @@
#if defined(ARDUINO_ARCH_ESP8266)
#include <ESP8266WiFi.h>
#include "lwip/etharp.h"
#elif defined(ARDUINO_ARCH_ESP32)
#include <WiFi.h>
#include <esp_wifi.h>
#endif
#include <NetworkConnection.h>
namespace NetworkUtils {
@@ -30,7 +35,7 @@ namespace NetworkUtils {
return this;
}
NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, uint8_t channel = 0) {
NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, byte channel = 0) {
this->apName = ssid;
this->apPassword = password;
this->apChannel = channel;
@@ -38,7 +43,7 @@ namespace NetworkUtils {
return this;
}
NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, uint8_t channel = 0) {
NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, byte channel = 0) {
this->staSsid = ssid;
this->staPassword = password;
this->staChannel = channel;
@@ -135,30 +140,51 @@ namespace NetworkUtils {
return this->staPassword;
}
uint8_t getStaChannel() {
byte getStaChannel() {
return this->staChannel;
}
bool resetWifi() {
// set policy manual for work 13 ch
{
#ifdef ARDUINO_ARCH_ESP8266
wifi_country_t country = {"JP", 1, 14, WIFI_COUNTRY_POLICY_MANUAL};
wifi_set_country(&country);
#elif defined(ARDUINO_ARCH_ESP32)
const wifi_country_t country = {"JP", 1, 14, CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER, WIFI_COUNTRY_POLICY_MANUAL};
esp_wifi_set_country(&country);
#endif
}
WiFi.persistent(false);
#if ESP_ARDUINO_VERSION_MAJOR < 3
#if !defined(ESP_ARDUINO_VERSION_MAJOR) || ESP_ARDUINO_VERSION_MAJOR < 3
WiFi.setAutoConnect(false);
#endif
WiFi.setAutoReconnect(false);
#ifdef ARDUINO_ARCH_ESP8266
WiFi.setSleepMode(WIFI_NONE_SLEEP);
#elif defined(ARDUINO_ARCH_ESP32)
WiFi.setSleep(USE_BLE ? WIFI_PS_MIN_MODEM : WIFI_PS_NONE);
#endif
WiFi.softAPdisconnect();
#ifdef ARDUINO_ARCH_ESP8266
/*if (wifi_softap_dhcps_status() == DHCP_STARTED) {
wifi_softap_dhcps_stop();
}*/
#endif
WiFi.disconnect(false, true);
#ifdef ARDUINO_ARCH_ESP8266
/*if (wifi_station_dhcpc_status() == DHCP_STARTED) {
wifi_station_dhcpc_stop();
}*/
#if ESP_ARDUINO_VERSION_MAJOR < 3
wifi_station_dhcpc_set_maxtry(5);
#endif
#if defined(ARDUINO_ARCH_ESP32) && ESP_ARDUINO_VERSION_MAJOR < 3
// Nothing. Because memory leaks when turn off WiFi on ESP32 SDK < 3.0.0
return true;
#else
@@ -180,6 +206,12 @@ namespace NetworkUtils {
NetworkConnection::reset();
} else {
/*#ifdef ARDUINO_ARCH_ESP8266
if (wifi_station_dhcpc_status() == DHCP_STARTED) {
wifi_station_dhcpc_stop();
}
#endif*/
this->disconnect();
}
@@ -189,12 +221,14 @@ namespace NetworkUtils {
this->delayCallback(250);
#ifdef ARDUINO_ARCH_ESP32
if (this->setWifiHostname(this->hostname)) {
Log.straceln(L_NETWORK, "Set hostname '%s': success", this->hostname);
Log.straceln(FPSTR(L_NETWORK), F("Set hostname '%s': success"), this->hostname);
} else {
Log.serrorln(L_NETWORK, "Set hostname '%s': fail", this->hostname);
Log.serrorln(FPSTR(L_NETWORK), F("Set hostname '%s': fail"), this->hostname);
}
#endif
if (!WiFi.mode((WiFiMode_t)(WiFi.getMode() | WIFI_STA))) {
return false;
@@ -202,8 +236,21 @@ namespace NetworkUtils {
this->delayCallback(250);
#ifdef ARDUINO_ARCH_ESP8266
if (this->setWifiHostname(this->hostname)) {
Log.straceln(FPSTR(L_NETWORK), F("Set hostname '%s': success"), this->hostname);
} else {
Log.serrorln(FPSTR(L_NETWORK), F("Set hostname '%s': fail"), this->hostname);
}
this->delayCallback(250);
#endif
#ifdef ARDUINO_ARCH_ESP32
WiFi.setScanMethod(WIFI_ALL_CHANNEL_SCAN);
WiFi.setSortMethod(WIFI_CONNECT_AP_BY_SIGNAL);
#endif
if (!this->useDhcp) {
WiFi.begin(this->staSsid, this->staPassword, this->staChannel, nullptr, false);
@@ -228,26 +275,30 @@ namespace NetworkUtils {
}
void disconnect() {
#ifdef ARDUINO_ARCH_ESP32
WiFi.disconnectAsync(false, true);
const unsigned long start = millis();
while (WiFi.isConnected() && (millis() - start) < 5000) {
this->delayCallback(100);
}
#else
WiFi.disconnect(false, true);
#endif
}
void loop() {
if (this->reconnectFlag) {
this->delayCallback(5000);
Log.sinfoln(L_NETWORK, "Reconnecting...");
Log.sinfoln(FPSTR(L_NETWORK), F("Reconnecting..."));
this->reconnectFlag = false;
this->disconnect();
NetworkConnection::reset();
this->delayCallback(1000);
} else if (this->isConnected() && !this->hasStaCredentials()) {
Log.sinfoln(L_NETWORK, "Reset");
Log.sinfoln(FPSTR(L_NETWORK), F("Reset"));
this->resetWifi();
NetworkConnection::reset();
this->delayCallback(1000);
@@ -258,8 +309,8 @@ namespace NetworkUtils {
this->connected = true;
Log.sinfoln(
L_NETWORK,
"Connected, downtime: %lu s., IP: %s, RSSI: %hhd",
FPSTR(L_NETWORK),
F("Connected, downtime: %lu s., IP: %s, RSSI: %hhd"),
(millis() - this->disconnectedTime) / 1000,
WiFi.localIP().toString().c_str(),
WiFi.RSSI()
@@ -267,51 +318,58 @@ namespace NetworkUtils {
}
if (this->isApEnabled() && millis() - this->connectedTime > this->reconnectInterval && !this->hasApClients()) {
Log.sinfoln(L_NETWORK, "Stop AP because STA connected");
Log.sinfoln(FPSTR(L_NETWORK), F("Stop AP because STA connected"));
WiFi.mode(WIFI_STA);
return;
}
#ifdef ARDUINO_ARCH_ESP8266
if (millis() - this->prevArpGratuitous > 60000) {
this->stationKeepAliveNow();
this->prevArpGratuitous = millis();
}
#endif
} else {
if (this->connected) {
this->disconnectedTime = millis();
this->connected = false;
Log.sinfoln(
L_NETWORK,
"Disconnected, reason: %d, uptime: %lu s.",
FPSTR(L_NETWORK),
F("Disconnected, reason: %d, uptime: %lu s."),
NetworkConnection::getDisconnectReason(),
(millis() - this->connectedTime) / 1000
);
}
if (!this->hasStaCredentials() && !this->isApEnabled()) {
Log.sinfoln(L_NETWORK, "No STA credentials, start AP");
Log.sinfoln(FPSTR(L_NETWORK), F("No STA credentials, start AP"));
WiFi.mode(WIFI_AP_STA);
this->delayCallback(250);
WiFi.softAP(this->apName, this->apPassword, this->apChannel);
} else if (!this->isApEnabled() && millis() - this->disconnectedTime > this->failedConnectTimeout) {
Log.sinfoln(L_NETWORK, "Disconnected for a long time, start AP");
Log.sinfoln(FPSTR(L_NETWORK), F("Disconnected for a long time, start AP"));
WiFi.mode(WIFI_AP_STA);
this->delayCallback(250);
WiFi.softAP(this->apName, this->apPassword, this->apChannel);
} else if (this->isConnecting() && millis() - this->prevReconnectingTime > this->resetConnectionTimeout) {
Log.swarningln(L_NETWORK, "Connection timeout, reset wifi...");
Log.swarningln(FPSTR(L_NETWORK), F("Connection timeout, reset wifi..."));
this->resetWifi();
NetworkConnection::reset();
this->delayCallback(250);
} else if (!this->isConnecting() && this->hasStaCredentials() && (!this->prevReconnectingTime || millis() - this->prevReconnectingTime > this->reconnectInterval)) {
Log.sinfoln(L_NETWORK, "Try connect...");
Log.sinfoln(FPSTR(L_NETWORK), F("Try connect..."));
NetworkConnection::reset();
if (!this->connect(true, this->connectionTimeout)) {
Log.straceln(L_NETWORK, "Connection failed. Status: %d, reason: %d, raw reason: %d", NetworkConnection::getStatus(), NetworkConnection::getDisconnectReason(), NetworkConnection::rawDisconnectReason);
Log.straceln(FPSTR(L_NETWORK), F("Connection failed. Status: %d, reason: %d, raw reason: %d"), NetworkConnection::getStatus(), NetworkConnection::getDisconnectReason(), NetworkConnection::rawDisconnectReason);
}
this->prevReconnectingTime = millis();
@@ -319,50 +377,10 @@ namespace NetworkUtils {
}
}
static uint8_t rssiToSignalQuality(short int rssi) {
static byte rssiToSignalQuality(short int rssi) {
return constrain(map(rssi, -100, -50, 0, 100), 0, 100);
}
static bool isHostnameValid(const char* value) {
if (value == nullptr) {
return false;
}
size_t len = strlen(value);
if (len == 0 || len > 24 || value[0] == '-' || value[len - 1] == '-') {
return false;
}
for (size_t i = 0; i < len; i++) {
const char symbol = value[i];
// Lowercase ASCII letters.
if (symbol >= 'a' && symbol <= 'z') {
continue;
}
// Uppercase ASCII letters.
if (symbol >= 'A' && symbol <= 'Z') {
continue;
}
// Decimal digits.
if (symbol >= '0' && symbol <= '9') {
continue;
}
// Hyphens are allowed inside the hostname.
if (symbol == '-') {
continue;
}
// All other characters are invalid.
return false;
}
return true;
}
protected:
const unsigned int reconnectInterval = 15000;
const unsigned int failedConnectTimeout = 185000;
@@ -379,11 +397,11 @@ namespace NetworkUtils {
const char* hostname = "esp";
const char* apName = "ESP";
const char* apPassword = nullptr;
uint8_t apChannel = 1;
byte apChannel = 1;
const char* staSsid = nullptr;
const char* staPassword = nullptr;
uint8_t staChannel = 0;
byte staChannel = 0;
bool useDhcp = true;
IPAddress staticIp;
@@ -393,6 +411,7 @@ namespace NetworkUtils {
bool connected = false;
bool reconnectFlag = false;
unsigned long prevArpGratuitous = 0;
unsigned long prevReconnectingTime = 0;
unsigned long connectedTime = 0;
unsigned long disconnectedTime = 0;
@@ -410,5 +429,48 @@ namespace NetworkUtils {
return WiFi.setHostname(hostname);
}
#ifdef ARDUINO_ARCH_ESP8266
/**
* @brief
* https://github.com/arendst/Tasmota/blob/e6515883f0ee5451931b6280ff847b117de5a231/tasmota/tasmota_support/support_wifi.ino#L1196
*/
static void stationKeepAliveNow(void) {
for (netif* interface = netif_list; interface != nullptr; interface = interface->next) {
if (
(interface->flags & NETIF_FLAG_LINK_UP)
&& (interface->flags & NETIF_FLAG_UP)
&& interface->num == STATION_IF
&& (!ip4_addr_isany_val(*netif_ip4_addr(interface)))
) {
etharp_gratuitous(interface);
break;
}
}
}
#endif
/**
* @brief check RFC compliance
*
* @param value
* @return true
* @return false
*/
static bool isHostnameValid(const char* value) {
size_t len = strlen(value);
if (len > 24) {
return false;
} else if (value[len - 1] == '-') {
return false;
}
for (size_t i = 0; i < len; i++) {
if (!isalnum(value[i]) && value[i] != '-') {
return false;
}
}
return true;
}
};
}
+224
View File
@@ -0,0 +1,224 @@
#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;
};
+13 -7
View File
@@ -28,9 +28,11 @@ public:
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 method == HTTP_GET && uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri));
@@ -52,17 +54,17 @@ public:
}
}
if (!this->eTag.isEmpty() && 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;
}
}
if (!this->path.endsWith(mimeTable[gz].endsWith) && !this->fs->exists(path)) {
String pathWithGz = this->path + mimeTable[gz].endsWith;
if (!this->path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !this->fs->exists(path)) {
String pathWithGz = this->path + FPSTR(mimeTable[gz].endsWith);
if (this->fs->exists(pathWithGz)) {
this->path += mimeTable[gz].endsWith;
this->path += FPSTR(mimeTable[gz].endsWith);
}
}
@@ -76,14 +78,18 @@ 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("ETag", this->eTag);
server.sendHeader(F("ETag"), this->eTag);
}
server.streamFile(file, "text/html", 200);
#if defined(ARDUINO_ARCH_ESP8266)
server.streamFile(file, F("text/html"), method);
#else
server.streamFile(file, F("text/html"), 200);
#endif
return true;
}
+98 -90
View File
@@ -1,6 +1,6 @@
#include <Arduino.h>
class UpgradeHandler : public AsyncWebHandler {
class UpgradeHandler : public RequestHandler {
public:
enum class UpgradeType {
FIRMWARE = 0,
@@ -12,7 +12,7 @@ public:
NO_FILE,
SUCCESS,
PROHIBITED,
SIZE_MISMATCH,
ABORTED,
ERROR_ON_START,
ERROR_ON_WRITE,
ERROR_ON_FINISH
@@ -22,21 +22,27 @@ public:
UpgradeType type;
UpgradeStatus status;
String error;
size_t progress = 0;
size_t size = 0;
} UpgradeResult;
typedef std::function<bool(AsyncWebServerRequest *request, UpgradeType)> BeforeUpgradeCallback;
typedef std::function<void(AsyncWebServerRequest *request, const UpgradeResult&, const UpgradeResult&)> AfterUpgradeCallback;
typedef std::function<bool(HTTPMethod, const String&)> CanHandleCallback;
typedef std::function<bool(const String&)> CanUploadCallback;
typedef std::function<bool(UpgradeType)> BeforeUpgradeCallback;
typedef std::function<void(const UpgradeResult&, const UpgradeResult&)> AfterUpgradeCallback;
explicit UpgradeHandler(AsyncURIMatcher uri) : uri(uri) {}
UpgradeHandler(const char* uri) {
this->uri = uri;
}
bool canHandle(AsyncWebServerRequest *request) const final {
if (!request->isHTTP()) {
return false;
}
UpgradeHandler* setCanHandleCallback(CanHandleCallback callback = nullptr) {
this->canHandleCallback = callback;
return this->uri.matches(request);
return this;
}
UpgradeHandler* setCanUploadCallback(CanUploadCallback callback = nullptr) {
this->canUploadCallback = callback;
return this;
}
UpgradeHandler* setBeforeUpgradeCallback(BeforeUpgradeCallback callback = nullptr) {
@@ -51,9 +57,29 @@ public:
return this;
}
void handleRequest(AsyncWebServerRequest *request) final {
#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 method == HTTP_POST && uri.equals(this->uri) && (!this->canHandleCallback || this->canHandleCallback(method, uri));
}
#if defined(ARDUINO_ARCH_ESP32)
bool canUpload(WebServer &server, const String &uri) override {
return this->canUpload(uri);
}
#endif
bool canUpload(const String& uri) override {
return uri.equals(this->uri) && (!this->canUploadCallback || this->canUploadCallback(uri));
}
bool handle(WebServer& server, HTTPMethod method, const String& uri) override {
if (this->afterUpgradeCallback) {
this->afterUpgradeCallback(request, this->firmwareResult, this->filesystemResult);
this->afterUpgradeCallback(this->firmwareResult, this->filesystemResult);
}
this->firmwareResult.status = UpgradeStatus::NONE;
@@ -61,147 +87,129 @@ public:
this->filesystemResult.status = UpgradeStatus::NONE;
this->filesystemResult.error.clear();
return true;
}
void handleUpload(AsyncWebServerRequest *request, const String &fileName, size_t index, uint8_t *data, size_t dataLength, bool isFinal) final {
UpgradeResult* result = nullptr;
if (!request->hasParam(asyncsrv::T_name, true, true)) {
// Missing content-disposition 'name' parameter
return;
}
const auto& pName = request->getParam(asyncsrv::T_name, true, true)->value();
if (pName.equals("fw")) {
void upload(WebServer& server, const String& uri, HTTPUpload& upload) override {
UpgradeResult* result;
if (upload.name.equals(F("firmware"))) {
result = &this->firmwareResult;
if (!index) {
result->progress = 0;
result->size = request->hasParam("fw_size", true)
? request->getParam("fw_size", true)->value().toInt()
: 0;
}
} else if (pName.equals("fs")) {
} else if (upload.name.equals(F("filesystem"))) {
result = &this->filesystemResult;
if (!index) {
result->progress = 0;
result->size = request->hasParam("fs_size", true)
? request->getParam("fs_size", true)->value().toInt()
: 0;
}
} else {
// Unknown parameter name
return;
}
// check result status
if (result->status != UpgradeStatus::NONE) {
return;
}
if (this->beforeUpgradeCallback && !this->beforeUpgradeCallback(request, result->type)) {
if (this->beforeUpgradeCallback && !this->beforeUpgradeCallback(result->type)) {
result->status = UpgradeStatus::PROHIBITED;
return;
}
if (!fileName.length()) {
if (!upload.filename.length()) {
result->status = UpgradeStatus::NO_FILE;
return;
}
if (!index) {
if (upload.status == UPLOAD_FILE_START) {
// reset
if (Update.isRunning()) {
Update.end(false);
Update.clearError();
}
// try begin
bool begin = false;
#ifdef ARDUINO_ARCH_ESP8266
Update.runAsync(true);
if (result->type == UpgradeType::FIRMWARE) {
begin = Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000, U_FLASH);
} else if (result->type == UpgradeType::FILESYSTEM) {
close_all_fs();
begin = Update.begin((size_t)FS_end - (size_t)FS_start, U_FS);
}
#elif defined(ARDUINO_ARCH_ESP32)
if (result->type == UpgradeType::FIRMWARE) {
begin = Update.begin(UPDATE_SIZE_UNKNOWN, U_FLASH);
} else if (result->type == UpgradeType::FILESYSTEM) {
begin = Update.begin(UPDATE_SIZE_UNKNOWN, U_SPIFFS);
}
#endif
if (!begin || Update.hasError()) {
result->status = UpgradeStatus::ERROR_ON_START;
#ifdef ARDUINO_ARCH_ESP8266
result->error = Update.getErrorString();
#else
result->error = Update.errorString();
#endif
Log.serrorln(L_PORTAL_OTA, "File '%s', on start: %s", fileName.c_str(), result->error.c_str());
Log.serrorln(FPSTR(L_PORTAL_OTA), F("File '%s', on start: %s"), upload.filename.c_str(), result->error.c_str());
return;
}
Log.sinfoln(L_PORTAL_OTA, "File '%s', started", fileName.c_str());
}
Log.sinfoln(FPSTR(L_PORTAL_OTA), F("File '%s', started"), upload.filename.c_str());
if (dataLength) {
if (Update.write(data, dataLength) != dataLength) {
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.end(false);
result->status = UpgradeStatus::ERROR_ON_WRITE;
#ifdef ARDUINO_ARCH_ESP8266
result->error = Update.getErrorString();
#else
result->error = Update.errorString();
#endif
Log.serrorln(
L_PORTAL_OTA, "File '%s', on write %d bytes, %d of %d bytes",
fileName.c_str(),
dataLength,
result->progress + dataLength,
result->size
FPSTR(L_PORTAL_OTA),
F("File '%s', on writing %d bytes: %s"),
upload.filename.c_str(), upload.totalSize, result->error.c_str()
);
return;
} else {
Log.sinfoln(FPSTR(L_PORTAL_OTA), F("File '%s', writed %d bytes"), upload.filename.c_str(), upload.totalSize);
}
result->progress += dataLength;
Log.sinfoln(
L_PORTAL_OTA, "File '%s', write %d bytes, %d of %d bytes",
fileName.c_str(),
dataLength,
result->progress,
result->size
);
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) {
result->status = UpgradeStatus::SUCCESS;
if (result->size > 0) {
if (result->progress > result->size || (isFinal && result->progress < result->size)) {
Update.end(false);
result->status = UpgradeStatus::SIZE_MISMATCH;
Log.sinfoln(FPSTR(L_PORTAL_OTA), F("File '%s': finish"), upload.filename.c_str());
Log.serrorln(
L_PORTAL_OTA, "File '%s', size mismatch: %d of %d bytes",
fileName.c_str(),
result->progress,
result->size
);
return;
}
}
if (isFinal) {
if (!Update.end(true)) {
} else {
result->status = UpgradeStatus::ERROR_ON_FINISH;
#ifdef ARDUINO_ARCH_ESP8266
result->error = Update.getErrorString();
#else
result->error = Update.errorString();
#endif
Log.serrorln(L_PORTAL_OTA, "File '%s', on finish: %s", fileName.c_str(), result->error.c_str());
return;
Log.serrorln(FPSTR(L_PORTAL_OTA), F("File '%s', on finish: %s"), upload.filename.c_str(), result->error);
}
result->status = UpgradeStatus::SUCCESS;
Log.sinfoln(L_PORTAL_OTA, "File '%s': finish", fileName.c_str());
}
}
} else if (upload.status == UPLOAD_FILE_ABORTED) {
Update.end(false);
result->status = UpgradeStatus::ABORTED;
bool isRequestHandlerTrivial() const final {
return false;
Log.serrorln(FPSTR(L_PORTAL_OTA), F("File '%s': aborted"), upload.filename.c_str());
}
}
protected:
CanHandleCallback canHandleCallback;
CanUploadCallback canUploadCallback;
BeforeUpgradeCallback beforeUpgradeCallback;
AfterUpgradeCallback afterUpgradeCallback;
AsyncURIMatcher uri;
const char* uri = nullptr;
UpgradeResult firmwareResult{UpgradeType::FIRMWARE, UpgradeStatus::NONE};
UpgradeResult filesystemResult{UpgradeType::FILESYSTEM, UpgradeStatus::NONE};
+3 -3
View File
@@ -4,12 +4,12 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"cssnano": "^9.3.2",
"cssnano-preset-advanced": "^9.2.2",
"cssnano": "^7.0.2",
"cssnano-preset-advanced": "^7.0.2",
"gulp": "^5.0.0",
"gulp-concat": "^2.6.1",
"gulp-gzip": "^1.4.2",
"gulp-html-minifier-terser": "^8.0.0",
"gulp-html-minifier-terser": "^7.1.0",
"gulp-jsonminify": "^1.1.0",
"gulp-postcss": "^10.0.0",
"gulp-terser": "^2.1.0",
+317 -213
View File
@@ -1,235 +1,339 @@
; PlatformIO Project Configuration File
;
; Build options: build flags, source filter
; Upload options: custom upload port, speed and extra flags
; Library options: dependencies, extra library storages
; Advanced options: extra scripting
;
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[platformio]
;extra_configs = secrets.ini
extra_configs = secrets.default.ini
;core_dir = .pio
;extra_configs = secrets.ini
extra_configs = secrets.default.ini
core_dir = .pio
[env]
version = 1.6.0-async
framework = arduino
lib_deps = ESP32Async/AsyncTCP@^3.5.0
ESP32Async/ESPAsyncWebServer@^3.12.1
mathieucarbou/MycilaWebSerial@^8.2.3
bblanchon/ArduinoJson@^7.4.3
;ihormelnyk/OpenTherm Library@^1.1.5
https://github.com/Laxilef/opentherm_library#esp32_timer
arduino-libraries/ArduinoMqttClient@^0.1.8
gyverlibs/FileData@^1.0.3
gyverlibs/GyverPID@^3.3.2
gyverlibs/GyverBlinker@^1.1.1
;pstolarz/OneWireNg@^0.14.1
https://github.com/pstolarz/OneWireNg.git
milesburton/DallasTemperature@^4.0.6
https://github.com/Laxilef/esp32DHT#idf5
;laxilef/TinyLogger@^1.1.1
https://github.com/Laxilef/TinyLogger#custom_handlers
lib_ignore = OneWire
build_type = ${secrets.build_type}
build_flags = ;-mtext-section-literals
-O2
-Wno-deprecated-declarations
-D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1
-D WSL_CUSTOM_PAGE
;-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 CONFIG_ASYNC_TCP_STACK_SIZE=4096
-D ARDUINOJSON_USE_DOUBLE=0
-D ARDUINOJSON_USE_LONG_LONG=0
-D TINYLOGGER_GLOBAL
-D DHT_TASK_STACK_SIZE=4096
-D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled}
-D DEFAULT_SERIAL_BAUD=${secrets.serial_baud}
-D DEFAULT_WEBSERIAL_ENABLED=${secrets.webserial_enabled}
-D DEFAULT_LOG_LEVEL=${secrets.log_level}
-D DEFAULT_HOSTNAME='"${secrets.hostname}"'
-D DEFAULT_AP_SSID='"${secrets.ap_ssid}"'
-D DEFAULT_AP_PASSWORD='"${secrets.ap_password}"'
-D DEFAULT_STA_SSID='"${secrets.sta_ssid}"'
-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}"'
-D DEFAULT_MQTT_PASSWORD='"${secrets.mqtt_password}"'
-D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"'
upload_speed = 921600
monitor_speed = 115200
;monitor_filters = direct
monitor_filters = esp32_exception_decoder
board_build.flash_mode = dio
board_build.filesystem = littlefs
check_tool = ;pvs-studio
check_flags = ;pvs-studio: --analysis-mode=4 --exclude-path=./.pio/libdeps
version = 1.5.5
framework = arduino
lib_deps =
bblanchon/ArduinoJson@^7.3.0
;ihormelnyk/OpenTherm Library@^1.1.5
https://github.com/Laxilef/opentherm_library#esp32_timer
arduino-libraries/ArduinoMqttClient@^0.1.8
lennarthennigs/ESP Telnet@^2.2
gyverlibs/FileData@^1.0.2
gyverlibs/GyverPID@^3.3.2
gyverlibs/GyverBlinker@^1.1.1
https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg
laxilef/TinyLogger@^1.1.1
build_type = ${secrets.build_type}
build_flags =
-mtext-section-literals
-D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1
;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial
-D BUILD_VERSION='"${this.version}"'
-D BUILD_ENV='"$PIOENV"'
-D DEFAULT_SERIAL_ENABLED=${secrets.serial_enabled}
-D DEFAULT_SERIAL_BAUD=${secrets.serial_baud}
-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}"'
-D DEFAULT_AP_SSID='"${secrets.ap_ssid}"'
-D DEFAULT_AP_PASSWORD='"${secrets.ap_password}"'
-D DEFAULT_STA_SSID='"${secrets.sta_ssid}"'
-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}"'
-D DEFAULT_MQTT_PASSWORD='"${secrets.mqtt_password}"'
-D DEFAULT_MQTT_PREFIX='"${secrets.mqtt_prefix}"'
upload_speed = 921600
monitor_speed = 115200
;monitor_filters = direct
monitor_filters =
esp32_exception_decoder
esp8266_exception_decoder
board_build.flash_mode = dio
board_build.filesystem = littlefs
; Defaults
[esp8266_defaults]
platform = espressif8266@^4.2.1
lib_deps =
${env.lib_deps}
nrwiersma/ESP8266Scheduler@^1.2
lib_ignore =
extra_scripts =
post:tools/build.py
build_type = ${env.build_type}
build_flags =
${env.build_flags}
-D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
-D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
board_build.ldscript = eagle.flash.4m1m.ld
[esp32_defaults]
platform = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.312-1/platform-espressif32.zip
platform_packages = ${env.platform_packages}
board_build.partitions = esp32_partitions.csv
lib_deps = ${env.lib_deps}
laxilef/ESP32Scheduler@^1.0.1
nimble_lib = https://github.com/h2zero/NimBLE-Arduino
lib_ignore = ${env.lib_ignore}
BluetoothSerial
SimpleBLE
ESP RainMaker
RainMaker
ESP Insights
Insights
Zigbee
Matter
OpenThread
dsp
custom_component_remove = espressif/esp_hosted
espressif/esp_wifi_remote
espressif/esp-dsp
espressif/esp_modem
espressif/esp_rainmaker
espressif/rmaker_common
espressif/esp_insights
espressif/esp_diag_data_store
espressif/esp_diagnostics
espressif/libsodium
espressif/esp-modbus
espressif/esp-cbor
espressif/esp-sr
espressif/esp32-camera
espressif/qrcode
espressif/esp-zboss-lib
espressif/esp-zigbee-lib
chmorgan/esp-libhelix-mp3
extra_scripts = pre:tools/add_build_commit.py
post:tools/esp32.py
post:tools/build.py
build_type = ${env.build_type}
build_flags = ${env.build_flags}
-D MYNEWT_VAL_BLE_MAX_CONNECTIONS=1
-D MYNEWT_VAL_BLE_STORE_MAX_BONDS=1
-D MYNEWT_VAL_BLE_ROLE_CENTRAL=0
-D CORE_DEBUG_LEVEL=0
-Wl,--wrap=esp_panic_handler
check_tool = ${env.check_tool}
check_flags = ${env.check_flags}
;platform = espressif32@^6.7
;platform = https://github.com/platformio/platform-espressif32.git
;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/54.03.20/platform-espressif32.zip
platform_packages =
board_build.partitions = esp32_partitions.csv
lib_deps =
${env.lib_deps}
laxilef/ESP32Scheduler@^1.0.1
nimble_lib = h2zero/NimBLE-Arduino@^2.1.0
lib_ignore =
extra_scripts =
post:tools/esp32.py
post:tools/build.py
build_type = ${env.build_type}
build_flags =
${env.build_flags}
-D CORE_DEBUG_LEVEL=0
-Wl,--wrap=esp_panic_handler
; Boards
[env:d1_mini]
platform = ${esp8266_defaults.platform}
board = d1_mini
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags =
${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4
-D DEFAULT_OT_OUT_GPIO=5
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15
[env:d1_mini_lite]
platform = ${esp8266_defaults.platform}
board = d1_mini_lite
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags =
${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4
-D DEFAULT_OT_OUT_GPIO=5
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15
[env:d1_mini_pro]
platform = ${esp8266_defaults.platform}
board = d1_mini_pro
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags =
${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=4
-D DEFAULT_OT_OUT_GPIO=5
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15
[env:nodemcu_8266]
platform = ${esp8266_defaults.platform}
board = nodemcuv2
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_type = ${esp8266_defaults.build_type}
build_flags =
${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=13
-D DEFAULT_OT_OUT_GPIO=15
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=4
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=16
[env:s2_mini]
extends = esp32_defaults
board = lolin_s2_mini
build_unflags = -DARDUINO_USB_MODE=1
build_flags = ${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D DEFAULT_OT_IN_GPIO=33
-D DEFAULT_OT_OUT_GPIO=35
-D DEFAULT_SENSOR_OUTDOOR_GPIO=9
-D DEFAULT_SENSOR_INDOOR_GPIO=7
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=12
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_s2_mini
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = ${esp32_defaults.lib_deps}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-DARDUINO_USB_MODE=1
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D DEFAULT_OT_IN_GPIO=33
-D DEFAULT_OT_OUT_GPIO=35
-D DEFAULT_SENSOR_OUTDOOR_GPIO=9
-D DEFAULT_SENSOR_INDOOR_GPIO=7
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=12
[env:s3_mini]
extends = esp32_defaults
board = lolin_s3_mini
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
build_unflags = -DARDUINO_USB_MODE=1
build_flags = ${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D MYNEWT_VAL_BLE_EXT_ADV=1
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=35
-D DEFAULT_OT_OUT_GPIO=36
-D DEFAULT_SENSOR_OUTDOOR_GPIO=13
-D DEFAULT_SENSOR_INDOOR_GPIO=12
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=10
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_s3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-DARDUINO_USB_MODE=1
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D CONFIG_BT_NIMBLE_EXT_ADV=1
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=35
-D DEFAULT_OT_OUT_GPIO=36
-D DEFAULT_SENSOR_OUTDOOR_GPIO=13
-D DEFAULT_SENSOR_INDOOR_GPIO=12
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=10
[env:c3_mini]
extends = esp32_defaults
board = lolin_c3_mini
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
;build_unflags = -mtext-section-literals
build_flags = ${esp32_defaults.build_flags}
-D MYNEWT_VAL_BLE_EXT_ADV=1
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=8
-D DEFAULT_OT_OUT_GPIO=10
-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
-D DEFAULT_SENSOR_INDOOR_GPIO=1
-D DEFAULT_STATUS_LED_GPIO=4
-D DEFAULT_OT_RX_LED_GPIO=5
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_c3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-mtext-section-literals
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D CONFIG_BT_NIMBLE_EXT_ADV=1
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=8
-D DEFAULT_OT_OUT_GPIO=10
-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
-D DEFAULT_SENSOR_INDOOR_GPIO=1
-D DEFAULT_STATUS_LED_GPIO=4
-D DEFAULT_OT_RX_LED_GPIO=5
[env:nodemcu_32]
extends = esp32_defaults
board = nodemcu-32s
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
build_flags = ${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=16
-D DEFAULT_OT_OUT_GPIO=4
-D DEFAULT_SENSOR_OUTDOOR_GPIO=15
-D DEFAULT_SENSOR_INDOOR_GPIO=26
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19
[env:nodemcu_32_160mhz]
extends = env:nodemcu_32
board_build.f_cpu = 160000000L ; set frequency to 160MHz
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = nodemcu-32s
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=16
-D DEFAULT_OT_OUT_GPIO=4
-D DEFAULT_SENSOR_OUTDOOR_GPIO=15
-D DEFAULT_SENSOR_INDOOR_GPIO=26
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19
[env:d1_mini32]
extends = esp32_defaults
board = wemos_d1_mini32
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
build_flags = ${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=21
-D DEFAULT_OT_OUT_GPIO=22
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=18
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = wemos_d1_mini32
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=21
-D DEFAULT_OT_OUT_GPIO=22
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=18
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19
[env:esp32_c6]
extends = esp32_defaults
board = esp32-c6-devkitc-1
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
;build_unflags = -mtext-section-literals
build_flags = ${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=15
-D DEFAULT_OT_OUT_GPIO=23
-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
-D DEFAULT_SENSOR_INDOOR_GPIO=0
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=10
custom_sdkconfig = '# CONFIG_OPENTHREAD_ENABLED is not set'
'# CONFIG_ZB_ENABLED is not set'
'# CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT is not set'
'# CONFIG_LWIP_IPV6 is not set'
'# CONFIG_LWIP_IPV6_AUTOCONFIG is not set'
'# CONFIG_LWIP_PPP_SUPPORT is not set'
'# CONFIG_LIBC_OPTIMIZED_MISALIGNED_ACCESS is not set'
platform = ${esp32_defaults.platform}
framework = arduino, espidf
platform_packages = ${esp32_defaults.platform_packages}
board = esp32-c6-devkitm-1
board_build.partitions = ${esp32_defaults.board_build.partitions}
board_build.embed_txtfiles =
managed_components/espressif__esp_insights/server_certs/https_server.crt
managed_components/espressif__esp_rainmaker/server_certs/rmaker_mqtt_server.crt
managed_components/espressif__esp_rainmaker/server_certs/rmaker_claim_service_server.crt
managed_components/espressif__esp_rainmaker/server_certs/rmaker_ota_server.crt
lib_deps = ${esp32_defaults.lib_deps}
lib_ignore =
${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-mtext-section-literals
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=15
-D DEFAULT_OT_OUT_GPIO=23
-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
-D DEFAULT_SENSOR_INDOOR_GPIO=0
-D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=10
[env:otthing]
extends = esp32_defaults
board = lolin_c3_mini
lib_deps = ${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
;build_unflags = -mtext-section-literals
build_flags = ${esp32_defaults.build_flags}
-D MYNEWT_VAL_BLE_EXT_ADV=1
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=3
-D DEFAULT_OT_OUT_GPIO=1
;-D DEFAULT_SENSOR_OUTDOOR_GPIO=0
;-D DEFAULT_SENSOR_INDOOR_GPIO=1
-D DEFAULT_STATUS_LED_GPIO=8
-D DEFAULT_OT_RX_LED_GPIO=2
-D OT_BYPASS_RELAY_GPIO=20
platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_c3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps =
${esp32_defaults.lib_deps}
${esp32_defaults.nimble_lib}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-mtext-section-literals
build_type = ${esp32_defaults.build_type}
build_flags =
${esp32_defaults.build_flags}
-D CONFIG_BT_NIMBLE_EXT_ADV=1
-D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=3
-D DEFAULT_OT_OUT_GPIO=1
; -D DEFAULT_SENSOR_OUTDOOR_GPIO=0
; -D DEFAULT_SENSOR_INDOOR_GPIO=1
-D DEFAULT_STATUS_LED_GPIO=8
-D DEFAULT_OT_RX_LED_GPIO=2
-D OT_BYPASS_RELAY_GPIO=20
+33
View File
@@ -0,0 +1,33 @@
# Source:
# https://github.com/pioarduino/platform-espressif32/tree/main/examples/espidf-arduino-h2zero-BLE_scan
CONFIG_FREERTOS_HZ=1000
CONFIG_MBEDTLS_PSK_MODES=y
CONFIG_MBEDTLS_KEY_EXCHANGE_PSK=y
CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_COMPILER_OPTIMIZATION_SIZE=y
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE=y
#
# BT config
CONFIG_BT_ENABLED=y
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n
CONFIG_BTDM_CTRL_MODE_BTDM=n
CONFIG_BT_BLUEDROID_ENABLED=n
CONFIG_BT_NIMBLE_ENABLED=y
#
# Arduino Configuration
CONFIG_AUTOSTART_ARDUINO=y
CONFIG_ARDUINO_SELECTIVE_COMPILATION=y
CONFIG_ARDUINO_SELECTIVE_Zigbee=n
CONFIG_ARDUINO_SELECTIVE_Matter=n
CONFIG_ARDUINO_SELECTIVE_WiFiProv=n
CONFIG_ARDUINO_SELECTIVE_BLE=n
CONFIG_ARDUINO_SELECTIVE_BluetoothSerial=n
CONFIG_ARDUINO_SELECTIVE_SimpleBLE=n
CONFIG_ARDUINO_SELECTIVE_RainMaker=n
CONFIG_ARDUINO_SELECTIVE_OpenThread=n
CONFIG_ARDUINO_SELECTIVE_Insights=n
+2 -1
View File
@@ -3,7 +3,8 @@ build_type = release
serial_enabled = true
serial_baud = 115200
webserial_enabled = true
telnet_enabled = true
telnet_port = 23
log_level = 5
hostname = opentherm
+66 -4
View File
@@ -1,7 +1,20 @@
#pragma once
#include <Arduino.h>
#include "esp_err.h"
#ifdef ARDUINO_ARCH_ESP32
#include "esp_err.h"
#endif
#ifdef ARDUINO_ARCH_ESP8266
extern "C" {
#include <user_interface.h>
}
// https://github.com/espressif/ESP8266_RTOS_SDK/blob/master/components/esp8266/include/esp_attr.h
#define _COUNTER_STRINGIFY(COUNTER) #COUNTER
#define _SECTION_ATTR_IMPL(SECTION, COUNTER) __attribute__((section(SECTION "." _COUNTER_STRINGIFY(COUNTER))))
#define __NOINIT_ATTR _SECTION_ATTR_IMPL(".noinit", __COUNTER__)
#endif
namespace CrashRecorder {
typedef struct {
@@ -22,13 +35,14 @@ namespace CrashRecorder {
} ext_t;
__NOINIT_ATTR volatile static backtrace_t backtrace; // NOLINT
__NOINIT_ATTR volatile static epc_t epc; // NOLINT
__NOINIT_ATTR volatile static ext_t ext; // NOLINT
__NOINIT_ATTR volatile static backtrace_t backtrace;
__NOINIT_ATTR volatile static epc_t epc;
__NOINIT_ATTR volatile static ext_t ext;
uint8_t backtraceMaxLength = sizeof(backtrace.data) / sizeof(*backtrace.data);
uint8_t epcMaxLength = sizeof(epc.data) / sizeof(*epc.data);
#ifdef ARDUINO_ARCH_ESP32
void IRAM_ATTR panicHandler(arduino_panic_info_t *info, void *arg) {;
ext.core = info->core;
ext.heap = ESP.getFreeHeap();
@@ -55,6 +69,7 @@ namespace CrashRecorder {
epc.length = 0;
}
}
#endif
void init() {
if (backtrace.length > backtraceMaxLength) {
@@ -65,6 +80,53 @@ namespace CrashRecorder {
epc.length = 0;
}
#ifdef ARDUINO_ARCH_ESP32
set_arduino_panic_handler(panicHandler, nullptr);
#endif
}
}
#ifdef ARDUINO_ARCH_ESP8266
extern "C" void custom_crash_callback(struct rst_info *info, uint32_t stack, uint32_t stack_end) {
uint8_t _length = 0;
CrashRecorder::ext.core = 0;
CrashRecorder::ext.heap = ESP.getFreeHeap();
CrashRecorder::ext.uptime = millis() / 1000u;
// Backtrace
CrashRecorder::backtrace.continues = false;
uint32_t value;
for (uint32_t i = stack; i < stack_end; i += 4) {
value = *((uint32_t*) i);
// keep only addresses in code area
if ((value >= 0x40000000) && (value < 0x40300000)) {
if (_length >= CrashRecorder::backtraceMaxLength) {
CrashRecorder::backtrace.continues = true;
break;
}
CrashRecorder::backtrace.data[_length++] = value;
}
}
CrashRecorder::backtrace.length = _length;
// EPC
_length = 0;
if (info->epc1 > 0) {
CrashRecorder::epc.data[_length++] = info->epc1;
}
if (info->epc2 > 0) {
CrashRecorder::epc.data[_length++] = info->epc2;
}
if (info->epc3 > 0) {
CrashRecorder::epc.data[_length++] = info->epc3;
}
CrashRecorder::epc.length = _length;
}
#endif
+784 -808
View File
File diff suppressed because it is too large. Load diff
+97 -111
View File
@@ -6,11 +6,12 @@ extern NetworkMgr* network;
extern MqttTask* tMqtt;
extern OpenThermTask* tOt;
extern FileData fsNetworkSettings, fsSettings, fsSensorsSettings;
extern ESPTelnetStream* telnetStream;
class MainTask : public Task {
public:
explicit MainTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {
MainTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {
this->blinker = new Blinker();
network->setDelayCallback([this](unsigned int time) {
@@ -39,17 +40,17 @@ protected:
PumpStartReason extPumpStartReason = PumpStartReason::NONE;
unsigned long externalPumpStartTime = 0;
bool ntpStarted = false;
bool telnetStarted = false;
bool emergencyDetected = false;
unsigned long emergencyFlipTime = 0;
bool freezeDetected = false;
unsigned long freezeDetectedTime = 0;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Main";
}
uint32_t getTaskStackSize() override {
return 6000;
}
/*BaseType_t getTaskCore() override {
return 1;
}*/
@@ -57,6 +58,7 @@ protected:
int getTaskPriority() override {
return 3;
}
#endif
void setup() {}
@@ -64,15 +66,15 @@ protected:
network->loop();
if (fsNetworkSettings.tick() == FD_WRITE) {
Log.sinfoln(L_NETWORK_SETTINGS, "Updated");
Log.sinfoln(FPSTR(L_NETWORK_SETTINGS), F("Updated"));
}
if (fsSettings.tick() == FD_WRITE) {
Log.sinfoln(L_SETTINGS, "Updated");
Log.sinfoln(FPSTR(L_SETTINGS), F("Updated"));
}
if (fsSensorsSettings.tick() == FD_WRITE) {
Log.sinfoln(L_SENSORS_SETTINGS, "Updated");
Log.sinfoln(FPSTR(L_SENSORS_SETTINGS), F("Updated"));
}
if (vars.actions.restart) {
@@ -80,7 +82,7 @@ protected:
this->restartSignalReceived = true;
vars.actions.restart = false;
Log.sinfoln(L_MAIN, "Received restart signal");
Log.sinfoln(FPSTR(L_MAIN), F("Received restart signal"));
}
if (!vars.states.restarting && this->restartSignalReceived && millis() - this->restartSignalReceivedTime > 5000) {
@@ -97,16 +99,16 @@ protected:
fsNetworkSettings.write();
}
Log.sinfoln(L_MAIN, "Restart scheduled in 10 sec.");
Log.sinfoln(FPSTR(L_MAIN), F("Restart scheduled in 10 sec."));
}
vars.mqtt.connected = tMqtt->isConnected();
vars.network.connected = network->isConnected();
vars.network.rssi = network->isConnected() ? WiFi.RSSI() : 0;
if (settings.system.logLevel >= TinyLoggerLevel::SILENT && settings.system.logLevel <= TinyLoggerLevel::VERBOSE) {
if (settings.system.logLevel >= TinyLogger::Level::SILENT && settings.system.logLevel <= TinyLogger::Level::VERBOSE) {
if (Log.getLevel() != settings.system.logLevel) {
Log.setLevel(static_cast<TinyLoggerLevel>(settings.system.logLevel));
Log.setLevel(static_cast<TinyLogger::Level>(settings.system.logLevel));
}
}
@@ -121,6 +123,11 @@ protected:
}
}
if (!this->telnetStarted && telnetStream != nullptr) {
telnetStream->begin(23, false);
this->telnetStarted = true;
}
if (settings.mqtt.enabled && !tMqtt->isEnabled()) {
tMqtt->enable();
@@ -128,11 +135,18 @@ protected:
tMqtt->disable();
}
Sensors::setConnectionStatusByType(Sensors::Type::MANUAL, !settings.mqtt.enabled || vars.mqtt.connected, false);
} else {
if (this->ntpStarted) {
this->ntpStarted = false;
}
if (this->telnetStarted) {
telnetStream->stop();
this->telnetStarted = false;
}
if (tMqtt->isEnabled()) {
tMqtt->disable();
}
@@ -146,10 +160,23 @@ protected:
}
this->ledStatus();
// telnet
if (this->telnetStarted) {
this->yield();
telnetStream->loop();
this->yield();
}
// anti memory leak
while (Serial.available() > 0) {
Serial.read();
for (Stream* stream : Log.getStreams()) {
while (stream->available() > 0) {
stream->read();
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
#endif
}
}
// heap info
@@ -188,7 +215,7 @@ protected:
vars.states.restarting = true;
}
if (settings.system.logLevel < TinyLoggerLevel::VERBOSE) {
if (settings.system.logLevel < TinyLogger::Level::VERBOSE) {
return;
}
@@ -208,8 +235,8 @@ protected:
if (millis() - this->lastHeapInfo > 20000 || minFreeHeapDiff > 0 || minMaxFreeBlockHeapDiff > 0) {
Log.sverboseln(
L_MAIN,
"Free heap size: %u of %u bytes (min: %u, diff: %u), max free block: %u (min: %u, diff: %u, frag: %hhu%%)",
FPSTR(L_MAIN),
F("Free heap size: %u of %u bytes (min: %u, diff: %u), max free block: %u (min: %u, diff: %u, frag: %hhu%%)"),
freeHeap, getTotalHeap(), this->minFreeHeap, minFreeHeapDiff, maxFreeBlockHeap, this->minMaxFreeBlockHeap, minMaxFreeBlockHeapDiff, getHeapFrag()
);
this->lastHeapInfo = millis();
@@ -218,12 +245,12 @@ protected:
void heating() {
// freeze protection
{
if (!settings.heating.enabled) {
float lowTemp = 255.0f;
uint8_t availableSensors = 0;
if (Sensors::existsConnectedSensorsByPurpose(Sensors::Purpose::INDOOR_TEMP)) {
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY, settings.heating.indoorTempAvgType);
auto value = Sensors::getMeanValueByPurpose(Sensors::Purpose::INDOOR_TEMP, Sensors::ValueType::PRIMARY);
if (value < lowTemp) {
lowTemp = value;
}
@@ -249,40 +276,29 @@ protected:
availableSensors++;
}
if (availableSensors) {
if (vars.master.heating.freezing) {
if (lowTemp - static_cast<float>(settings.heating.freezeProtection.highTemp) + 0.0001f >= 0.0f) {
vars.master.heating.freezing = false;
if (availableSensors && lowTemp <= settings.heating.freezeProtection.lowTemp) {
if (!this->freezeDetected) {
this->freezeDetected = true;
this->freezeDetectedTime = millis();
Log.sinfoln(
L_MAIN,
"No freezing detected. Current low temp: %.2f, threshold (high): %hhu",
lowTemp, settings.heating.freezeProtection.highTemp
);
}
} else if (millis() - this->freezeDetectedTime > (settings.heating.freezeProtection.thresholdTime * 1000)) {
this->freezeDetected = false;
settings.heating.enabled = true;
fsSettings.update();
} else {
if (static_cast<float>(settings.heating.freezeProtection.lowTemp) - lowTemp + 0.0001f >= 0.0f) {
vars.master.heating.freezing = true;
if (!settings.heating.enabled) {
settings.heating.enabled = true;
fsSettings.update();
}
Log.sinfoln(
L_MAIN,
"Freezing detected! Current low temp: %.2f, threshold (low): %hhu",
lowTemp, settings.heating.freezeProtection.lowTemp
);
}
Log.sinfoln(
FPSTR(L_MAIN),
F("Heating turned on by freeze protection, current low temp: %.2f, threshold: %hhu"),
lowTemp, settings.heating.freezeProtection.lowTemp
);
}
} else if (vars.master.heating.freezing) {
vars.master.heating.freezing = false;
Log.sinfoln(L_MAIN, "No sensors available, freeze protection unavailable!");
} else if (this->freezeDetected) {
this->freezeDetected = false;
}
} else if (this->freezeDetected) {
this->freezeDetected = false;
}
}
@@ -303,7 +319,7 @@ protected:
emergencyFlags |= 0b00000010;
}
if (settings.opentherm.options.nativeOTC) {
if (settings.opentherm.options.nativeHeatingControl) {
emergencyFlags |= 0b00000100;
}
}
@@ -319,7 +335,7 @@ protected:
// enable emergency
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
vars.emergency.state = true;
Log.sinfoln(L_MAIN, "Emergency mode enabled (%hhu)", emergencyFlags);
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode enabled (%hhu)"), emergencyFlags);
}
}
@@ -333,7 +349,7 @@ protected:
// disable emergency
if (millis() - this->emergencyFlipTime > (settings.emergency.tresholdTime * 1000)) {
vars.emergency.state = false;
Log.sinfoln(L_MAIN, "Emergency mode disabled");
Log.sinfoln(FPSTR(L_MAIN), F("Emergency mode disabled"));
}
}
}
@@ -433,19 +449,19 @@ protected:
pinMode(configuredInputGpio, OUTPUT);
digitalWrite(configuredInputGpio, LOW);
Log.sinfoln(L_CASCADE_INPUT, "Deinitialized on GPIO %hhu", configuredInputGpio);
Log.sinfoln(FPSTR(L_CASCADE_INPUT), F("Deinitialized on GPIO %hhu"), configuredInputGpio);
}
if (GPIO_IS_VALID(settings.cascadeControl.input.gpio)) {
configuredInputGpio = settings.cascadeControl.input.gpio;
pinMode(configuredInputGpio, INPUT);
Log.sinfoln(L_CASCADE_INPUT, "Initialized on GPIO %hhu", configuredInputGpio);
Log.sinfoln(FPSTR(L_CASCADE_INPUT), F("Initialized on GPIO %hhu"), configuredInputGpio);
} else if (configuredInputGpio != GPIO_IS_NOT_CONFIGURED) {
configuredInputGpio = GPIO_IS_NOT_CONFIGURED;
Log.swarningln(L_CASCADE_INPUT, "Failed initialize: GPIO %hhu is not valid!", configuredInputGpio);
Log.swarningln(FPSTR(L_CASCADE_INPUT), F("Failed initialize: GPIO %hhu is not valid!"), configuredInputGpio);
}
}
@@ -466,9 +482,9 @@ protected:
vars.cascadeControl.input = value;
Log.sinfoln(
L_CASCADE_INPUT,
"State changed to %s",
value ? "TRUE" : "FALSE"
FPSTR(L_CASCADE_INPUT),
F("State changed to %s"),
value ? F("TRUE") : F("FALSE")
);
}
@@ -483,9 +499,9 @@ protected:
vars.cascadeControl.input = true;
Log.sinfoln(
L_CASCADE_INPUT,
"Disabled, state changed to %s",
vars.cascadeControl.input ? "TRUE" : "FALSE"
FPSTR(L_CASCADE_INPUT),
F("Disabled, state changed to %s"),
vars.cascadeControl.input ? F("TRUE") : F("FALSE")
);
}
}
@@ -498,7 +514,7 @@ protected:
pinMode(configuredOutputGpio, OUTPUT);
digitalWrite(configuredOutputGpio, LOW);
Log.sinfoln(L_CASCADE_OUTPUT, "Deinitialized on GPIO %hhu", configuredOutputGpio);
Log.sinfoln(FPSTR(L_CASCADE_OUTPUT), F("Deinitialized on GPIO %hhu"), configuredOutputGpio);
}
if (GPIO_IS_VALID(settings.cascadeControl.output.gpio)) {
@@ -511,12 +527,12 @@ protected:
: LOW
);
Log.sinfoln(L_CASCADE_OUTPUT, "Initialized on GPIO %hhu", configuredOutputGpio);
Log.sinfoln(FPSTR(L_CASCADE_OUTPUT), F("Initialized on GPIO %hhu"), configuredOutputGpio);
} else if (configuredOutputGpio != GPIO_IS_NOT_CONFIGURED) {
configuredOutputGpio = GPIO_IS_NOT_CONFIGURED;
Log.swarningln(L_CASCADE_OUTPUT, "Failed initialize: GPIO %hhu is not valid!", configuredOutputGpio);
Log.swarningln(FPSTR(L_CASCADE_OUTPUT), F("Failed initialize: GPIO %hhu is not valid!"), configuredOutputGpio);
}
}
@@ -548,9 +564,9 @@ protected:
);
Log.sinfoln(
L_CASCADE_OUTPUT,
"State changed to %s",
value ? "TRUE" : "FALSE"
FPSTR(L_CASCADE_OUTPUT),
F("State changed to %s"),
value ? F("TRUE") : F("FALSE")
);
}
@@ -574,9 +590,9 @@ protected:
}
Log.sinfoln(
L_CASCADE_OUTPUT,
"Disabled, state changed to %s",
vars.cascadeControl.output ? "TRUE" : "FALSE"
FPSTR(L_CASCADE_OUTPUT),
F("Disabled, state changed to %s"),
vars.cascadeControl.output ? F("TRUE") : F("FALSE")
);
}
}
@@ -593,12 +609,7 @@ protected:
if (GPIO_IS_VALID(settings.externalPump.gpio)) {
configuredGpio = settings.externalPump.gpio;
pinMode(configuredGpio, OUTPUT);
digitalWrite(
configuredGpio,
settings.externalPump.invertState
? HIGH
: LOW
);
digitalWrite(configuredGpio, LOW);
} else if (configuredGpio != GPIO_IS_NOT_CONFIGURED) {
configuredGpio = GPIO_IS_NOT_CONFIGURED;
@@ -610,7 +621,7 @@ protected:
vars.externalPump.state = false;
vars.externalPump.lastEnabledTime = millis();
Log.sinfoln(L_EXTPUMP, "Disabled: use = off");
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off"));
}
return;
@@ -626,17 +637,12 @@ protected:
if (!settings.externalPump.use) {
if (vars.externalPump.state) {
digitalWrite(
configuredGpio,
settings.externalPump.invertState
? HIGH
: LOW
);
digitalWrite(configuredGpio, LOW);
vars.externalPump.state = false;
vars.externalPump.lastEnabledTime = millis();
Log.sinfoln(L_EXTPUMP, "Disabled: use = off");
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: use = off"));
}
return;
@@ -644,30 +650,20 @@ protected:
if (vars.externalPump.state && !this->heatingEnabled) {
if (this->extPumpStartReason == MainTask::PumpStartReason::HEATING && millis() - this->heatingDisabledTime > (settings.externalPump.postCirculationTime * 1000u)) {
digitalWrite(
configuredGpio,
settings.externalPump.invertState
? HIGH
: LOW
);
digitalWrite(configuredGpio, LOW);
vars.externalPump.state = false;
vars.externalPump.lastEnabledTime = millis();
Log.sinfoln(L_EXTPUMP, "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,
settings.externalPump.invertState
? HIGH
: LOW
);
digitalWrite(configuredGpio, LOW);
vars.externalPump.state = false;
vars.externalPump.lastEnabledTime = millis();
Log.sinfoln(L_EXTPUMP, "Disabled: expired anti stuck time");
Log.sinfoln(FPSTR(L_EXTPUMP), F("Disabled: expired anti stuck time"));
}
} else if (vars.externalPump.state && this->heatingEnabled && this->extPumpStartReason == MainTask::PumpStartReason::ANTISTUCK) {
@@ -678,28 +674,18 @@ protected:
this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::HEATING;
digitalWrite(
configuredGpio,
settings.externalPump.invertState
? LOW
: HIGH
);
digitalWrite(configuredGpio, HIGH);
Log.sinfoln(L_EXTPUMP, "Enabled: heating on");
Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: heating on"));
} else if (!vars.externalPump.state && (vars.externalPump.lastEnabledTime == 0 || millis() - vars.externalPump.lastEnabledTime >= (settings.externalPump.antiStuckInterval * 1000lu))) {
vars.externalPump.state = true;
this->externalPumpStartTime = millis();
this->extPumpStartReason = MainTask::PumpStartReason::ANTISTUCK;
digitalWrite(
configuredGpio,
settings.externalPump.invertState
? LOW
: HIGH
);
digitalWrite(configuredGpio, HIGH);
Log.sinfoln(L_EXTPUMP, "Enabled: anti stuck");
Log.sinfoln(FPSTR(L_EXTPUMP), F("Enabled: anti stuck"));
}
}
};
+74 -58
View File
@@ -1,6 +1,6 @@
#include <unordered_map>
#include <MqttClient.h>
#include <WiFiClient.h>
#include <MqttWiFiClient.h>
#include <MqttWriter.h>
#include "HaHelper.h"
@@ -8,8 +8,8 @@ extern FileData fsSettings;
class MqttTask : public Task {
public:
explicit MqttTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {
this->wifiClient = new WiFiClient();
MqttTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {
this->wifiClient = new MqttWiFiClient();
this->client = new MqttClient(this->wifiClient);
this->writer = new MqttWriter(this->client, 256);
this->haHelper = new HaHelper();
@@ -45,13 +45,13 @@ public:
Task::disable();
Log.sinfoln(L_MQTT, "Disabled");
Log.sinfoln(FPSTR(L_MQTT), F("Disabled"));
}
void enable() {
Task::enable();
Log.sinfoln(L_MQTT, "Enabled");
Log.sinfoln(FPSTR(L_MQTT), F("Enabled"));
}
inline bool isConnected() {
@@ -75,11 +75,10 @@ public:
}
protected:
WiFiClient* wifiClient = nullptr;
MqttWiFiClient* wifiClient = nullptr;
MqttClient* client = nullptr;
HaHelper* haHelper = nullptr;
MqttWriter* writer = nullptr;
char configUrl[64] = {};
UnitSystem currentUnitSystem = UnitSystem::METRIC;
bool currentHomeAssistantDiscovery = false;
std::unordered_map<uint8_t, Sensors::Settings> queueReconfigureSensors;
@@ -93,6 +92,7 @@ protected:
bool connected = false;
bool newConnection = false;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Mqtt";
}
@@ -104,17 +104,28 @@ protected:
int getTaskPriority() override {
return 2;
}
#endif
inline bool isReadyForSend() {
return millis() - this->connectedTime > this->readyForSendTime;
}
void setup() {
Log.sinfoln(L_MQTT, "Started");
Log.sinfoln(FPSTR(L_MQTT), F("Started"));
// wificlient settings
#ifdef ARDUINO_ARCH_ESP8266
this->wifiClient->setSync(true);
this->wifiClient->setNoDelay(true);
#endif
// client settings
this->client->setTxPayloadSize(256);
#ifdef ARDUINO_ARCH_ESP8266
this->client->setConnectionTimeout(1000);
#else
this->client->setConnectionTimeout(3000);
#endif
this->client->onMessage([this] (void*, size_t length) {
const String& topic = this->client->messageTopic();
@@ -131,17 +142,35 @@ protected:
});
// writer settings
#ifdef ARDUINO_ARCH_ESP32
this->writer->setYieldCallback([this] {
this->delay(10);
});
#endif
this->writer->setPublishEventCallback([this] (const char* topic, size_t written, size_t length, bool result) {
Log.straceln(L_MQTT, "%s publish %u of %u bytes to topic: %s", result ? "Successfully" : "Failed", written, length, topic);
Log.straceln(FPSTR(L_MQTT), F("%s publish %u of %u bytes to topic: %s"), result ? F("Successfully") : F("Failed"), written, length, topic);
#ifdef ARDUINO_ARCH_ESP8266
::optimistic_yield(1000);
#endif
//this->client->poll();
this->delay(250);
});
#ifdef ARDUINO_ARCH_ESP8266
this->writer->setFlushEventCallback([this] (size_t, size_t) {
::optimistic_yield(1000);
if (this->wifiClient->connected()) {
this->wifiClient->flush();
}
::optimistic_yield(1000);
});
#endif
// ha helper settings
this->haHelper->setDevicePrefix(settings.mqtt.prefix);
this->haHelper->setDeviceVersion(BUILD_VERSION);
@@ -149,8 +178,8 @@ protected:
this->haHelper->setDeviceName(PROJECT_NAME);
this->haHelper->setWriter(this->writer);
snprintf(this->configUrl, sizeof(this->configUrl), CONFIG_URL, WiFi.localIP().toString().c_str());
this->haHelper->setDeviceConfigUrl(this->configUrl);
sprintf(buffer, CONFIG_URL, WiFi.localIP().toString().c_str());
this->haHelper->setDeviceConfigUrl(buffer);
}
void loop() {
@@ -163,7 +192,7 @@ protected:
this->onDisconnect();
} else if (!this->connected && millis() - this->lastReconnectTime >= MQTT_RECONNECT_INTERVAL) {
Log.sinfoln(L_MQTT, "Connecting to %s:%u...", settings.mqtt.server, settings.mqtt.port);
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();
@@ -172,8 +201,8 @@ protected:
this->client->setKeepAliveInterval(settings.mqtt.interval * 10000);
this->client->setId(networkSettings.hostname);
this->client->setUsernamePassword(settings.mqtt.user, settings.mqtt.password);
this->client->beginWill(this->haHelper->getDeviceTopic("status").c_str(), 7, true, 1);
this->client->print("offline");
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();
@@ -195,16 +224,20 @@ protected:
return;
}
#ifdef ARDUINO_ARCH_ESP8266
::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();
}
@@ -245,7 +278,7 @@ protected:
// rebuilding ha configs
for (auto& [sensorId, prevSettings] : this->queueReconfigureSensors) {
Log.sinfoln(L_MQTT_HA, "Rebuilding config for sensor #%hhu '%s'", sensorId, prevSettings.name);
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) {
@@ -259,14 +292,6 @@ protected:
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::RSSI);
break;
case Sensors::Type::DHT11:
case Sensors::Type::DHT22:
this->haHelper->deleteConnectionDynamicSensor(prevSettings);
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::TEMPERATURE);
this->haHelper->deleteDynamicSensor(prevSettings, Sensors::ValueType::HUMIDITY);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->deleteConnectionDynamicSensor(prevSettings);
this->haHelper->deleteSignalQualityDynamicSensor(prevSettings);
@@ -294,14 +319,6 @@ protected:
this->haHelper->publishDynamicSensor(sSettings, Sensors::ValueType::RSSI, settings.system.unitSystem, false);
break;
case Sensors::Type::DHT11:
case Sensors::Type::DHT22:
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);
break;
case Sensors::Type::DALLAS_TEMP:
this->haHelper->publishConnectionDynamicSensor(sSettings);
this->haHelper->publishSignalQualityDynamicSensor(sSettings, false);
@@ -324,9 +341,9 @@ protected:
if (prevSettings.type == Sensors::Type::MANUAL) {
this->client->unsubscribe(
this->haHelper->getDeviceTopic(
"sensors",
F("sensors"),
Sensors::makeObjectId(prevSettings.name).c_str(),
"set"
F("set")
).c_str()
);
}
@@ -341,9 +358,9 @@ protected:
if (sSettings.type == Sensors::Type::MANUAL) {
this->client->subscribe(
this->haHelper->getDeviceTopic(
"sensors",
F("sensors"),
Sensors::makeObjectId(sSettings.name).c_str(),
"set"
F("set")
).c_str()
);
}
@@ -361,10 +378,10 @@ protected:
this->connectedTime = millis();
this->newConnection = true;
unsigned long downtime = (millis() - this->disconnectedTime) / 1000;
Log.sinfoln(L_MQTT, "Connected (downtime: %u s.)", downtime);
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());
// subscribe to manual sensors
for (uint8_t sensorId = 0; sensorId <= Sensors::getMaxSensorId(); sensorId++) {
@@ -379,9 +396,9 @@ protected:
this->client->subscribe(
this->haHelper->getDeviceTopic(
"sensors",
F("sensors"),
Sensors::makeObjectId(sSettings.name).c_str(),
"set"
F("set")
).c_str()
);
}
@@ -391,7 +408,7 @@ protected:
this->disconnectedTime = millis();
unsigned long uptime = (millis() - this->connectedTime) / 1000;
Log.swarningln(L_MQTT, "Disconnected (reason: %d uptime: %lu s.)", this->client->connectError(), uptime);
Log.swarningln(FPSTR(L_MQTT), F("Disconnected (reason: %d uptime: %lu s.)"), this->client->connectError(), uptime);
}
void onMessage(const String& topic, uint8_t* payload, size_t length) {
@@ -399,8 +416,8 @@ protected:
return;
}
if (settings.system.logLevel >= TinyLoggerLevel::TRACE) {
Log.strace(L_MQTT_MSG, "Topic: %s\r\n> ", topic.c_str());
if (settings.system.logLevel >= TinyLogger::Level::TRACE) {
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) {
@@ -408,12 +425,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(static_cast<char>(payload[i]));
Log.print((char) payload[i]);
}
}
Log.print("\r\n\n");
Log.print(F("\r\n\n"));
Log.flush();
Log.unlock();
}
@@ -422,11 +439,11 @@ protected:
JsonDocument doc;
DeserializationError dErr = deserializeJson(doc, payload, length);
if (dErr != DeserializationError::Ok) {
Log.swarningln(L_MQTT_MSG, "Error on deserialization: %s", dErr.c_str());
Log.swarningln(FPSTR(L_MQTT_MSG), F("Error on deserialization: %s"), dErr.f_str());
return;
} else if (doc.isNull() || !doc.size()) {
Log.swarningln(L_MQTT_MSG, "Not valid json");
Log.swarningln(FPSTR(L_MQTT_MSG), F("Not valid json"));
return;
}
doc.shrinkToFit();
@@ -434,22 +451,22 @@ protected:
// delete topic
this->writer->publish(topic.c_str(), nullptr, 0, true);
if (this->haHelper->getDeviceTopic("state/set").equals(topic)) {
if (this->haHelper->getDeviceTopic(F("state/set")).equals(topic)) {
if (jsonToVars(doc, vars)) {
this->resetPublishedVarsTime();
}
} else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) {
} 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("sensors/");
const String& sensorsTopic = this->haHelper->getDeviceTopic(F("sensors/"));
auto stLength = sensorsTopic.length();
if (topic.startsWith(sensorsTopic) && topic.endsWith("/set")) {
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());
@@ -469,7 +486,6 @@ protected:
void publishHaEntities() {
// heating
this->haHelper->publishSwitchHeatingTurbo(false);
this->haHelper->publishSwitchHeatingHysteresis();
this->haHelper->publishInputHeatingHysteresis(settings.system.unitSystem);
this->haHelper->publishInputHeatingTurboFactor(false);
this->haHelper->publishInputHeatingMinTemp(settings.system.unitSystem);
@@ -542,7 +558,7 @@ protected:
}
bool publishNonStaticHaEntities(bool force = false) {
static uint8_t _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
static bool _indoorTempControl, _dhwSupport = false;
bool published = false;
@@ -616,7 +632,7 @@ protected:
return this->writer->publish(
this->haHelper->getDeviceTopic(
"sensors",
F("sensors"),
Sensors::makeObjectId(sSettings.name).c_str()
).c_str(),
doc,
+223 -391
View File
File diff suppressed because it is too large. Load diff
+644 -424
View File
File diff suppressed because it is too large. Load diff
+20 -31
View File
@@ -5,7 +5,7 @@ GyverPID pidRegulator(0, 0, 0);
class RegulatorTask : public LeanTask {
public:
explicit RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
protected:
float prevHeatingTarget = 0.0f;
@@ -15,14 +15,11 @@ protected:
bool indoorSensorsConnected = false;
//bool outdoorSensorsConnected = false;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Regulator";
}
uint32_t getTaskStackSize() override {
return 5000;
}
/*BaseType_t getTaskCore() override {
return 1;
}*/
@@ -30,6 +27,7 @@ protected:
int getTaskPriority() override {
return 4;
}
#endif
void loop() {
if (vars.states.restarting || vars.states.upgrading) {
@@ -39,7 +37,7 @@ protected:
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.options.nativeOTC) {
if (settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeHeatingControl) {
vars.master.heating.indoorTempControl = true;
vars.master.heating.minTemp = THERMOSTAT_INDOOR_MIN_TEMP;
vars.master.heating.maxTemp = THERMOSTAT_INDOOR_MAX_TEMP;
@@ -53,7 +51,7 @@ protected:
if (!settings.pid.enabled && fabsf(pidRegulator.integral) > 0.01f) {
pidRegulator.integral = 0.0f;
Log.sinfoln(L_REGULATOR_PID, "Integral sum has been reset");
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
this->turbo();
@@ -85,22 +83,22 @@ protected:
}
if (!settings.heating.turbo) {
Log.sinfoln(L_REGULATOR, "Turbo mode auto disabled");
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
}
}
void hysteresis() {
bool useHyst = false;
if (settings.heating.hysteresis.enabled && this->indoorSensorsConnected) {
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeOTC;
if (settings.heating.hysteresis > 0.01f && this->indoorSensorsConnected) {
useHyst = settings.equitherm.enabled || settings.pid.enabled || settings.opentherm.options.nativeHeatingControl;
}
if (useHyst) {
if (!vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target + 0.0001f >= settings.heating.hysteresis.value) {
if (!vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target + 0.0001f >= settings.heating.hysteresis) {
vars.master.heating.blocking = true;
} else if (vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis.value)) {
} else if (vars.master.heating.blocking && vars.master.heating.indoorTemp - settings.heating.target - 0.0001f <= -(settings.heating.hysteresis)) {
vars.master.heating.blocking = false;
}
@@ -110,13 +108,13 @@ protected:
}
inline float getHeatingMinSetpointTemp() {
return settings.opentherm.options.nativeOTC
return settings.opentherm.options.nativeHeatingControl
? vars.master.heating.minTemp
: settings.heating.minTemp;
}
inline float getHeatingMaxSetpointTemp() {
return settings.opentherm.options.nativeOTC
return settings.opentherm.options.nativeHeatingControl
? vars.master.heating.maxTemp
: settings.heating.maxTemp;
}
@@ -126,18 +124,18 @@ protected:
if (fabsf(prevHeatingTarget - settings.heating.target) > 0.0001f) {
prevHeatingTarget = settings.heating.target;
Log.sinfoln(L_REGULATOR, "New target: %.2f", settings.heating.target);
Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target);
/*if (settings.pid.enabled) {
pidRegulator.integral = 0.0f;
Log.sinfoln(L_REGULATOR_PID, "Integral sum has been reset");
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}*/
}
if (vars.emergency.state) {
return settings.emergency.target;
} else if (settings.opentherm.options.nativeOTC) {
} else if (settings.opentherm.options.nativeHeatingControl) {
return settings.heating.target;
} else if (!settings.equitherm.enabled && !settings.pid.enabled) {
@@ -177,7 +175,7 @@ protected:
prevEtResult = etResult;
newTemp += etResult;
Log.sinfoln(L_REGULATOR_EQUITHERM, "New result: %.2f", etResult);
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New result: %.2f"), etResult);
} else {
newTemp += prevEtResult;
@@ -202,12 +200,11 @@ protected:
pidRegulator.integral = 0.0f;
pidRegulator.getResultNow();
Log.sinfoln(L_REGULATOR_PID, "Integral sum has been reset");
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}*/
float error = pidRegulator.setpoint - pidRegulator.input;
bool hasDeadband = settings.pid.deadband.enabled
&& (error > -(settings.pid.deadband.thresholdHigh))
bool hasDeadband = (error > -(settings.pid.deadband.thresholdHigh))
&& (error < settings.pid.deadband.thresholdLow);
if (hasDeadband) {
@@ -221,8 +218,8 @@ protected:
prevPidResult = pidResult;
newTemp += pidResult;
Log.sinfoln(L_REGULATOR_PID, "New result: %.2f", pidResult);
Log.straceln(L_REGULATOR_PID, "Integral: %.2f", pidRegulator.integral);
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New result: %.2f"), pidResult);
Log.straceln(FPSTR(L_REGULATOR_PID), F("Integral: %.2f"), pidRegulator.integral);
} else {
newTemp += prevPidResult;
@@ -242,14 +239,6 @@ protected:
) * settings.heating.turboFactor;
}
// If freezing, set temperature to no lower than low temp provided by freeze protection
if (vars.master.heating.freezing) {
newTemp = fmaxf(
newTemp,
static_cast<float>(settings.heating.freezeProtection.lowTemp)
);
}
return newTemp;
}
};
+27 -93
View File
@@ -1,6 +1,4 @@
#pragma once
#include <errno.h>
#include <stdlib.h>
class Sensors {
protected:
@@ -36,13 +34,10 @@ public:
OT_DHW_BURNER_HOURS = 24,
OT_HEATING_PUMP_HOURS = 25,
OT_DHW_PUMP_HOURS = 26,
OT_COOLING_HOURS = 27,
NTC_10K_TEMP = 50,
DALLAS_TEMP = 51,
BLUETOOTH = 52,
DHT11 = 53,
DHT22 = 54,
HEATING_SETPOINT_TEMP = 253,
MANUAL = 254,
@@ -79,12 +74,6 @@ public:
RSSI = 3
};
enum class AverageType : uint8_t {
MEAN = 0,
MINIMUM = 1,
MAXIMUM = 2
};
typedef struct {
bool enabled = false;
char name[33];
@@ -123,26 +112,10 @@ public:
return maxSensors > 1 ? (maxSensors - 1) : 0;
}
template <class T>
static bool isValidSensorId(const T id) {
static inline bool isValidSensorId(const uint8_t id) {
return id >= 0 && id <= getMaxSensorId();
}
static bool parseId(const char* value, uint8_t& sensorId) {
if (!isDigit(value)) {
return false;
}
errno = 0;
const unsigned long parsed = strtoul(value, nullptr, 10);
if (errno == ERANGE || !isValidSensorId(parsed)) {
return false;
}
sensorId = static_cast<uint8_t>(parsed);
return true;
}
static inline bool isValidValueId(const uint8_t id) {
return id >= (uint8_t) ValueType::TEMPERATURE && id <= (uint8_t) ValueType::RSSI;
}
@@ -176,7 +149,7 @@ public:
static int16_t getIdByName(const char* name) {
if (settings == nullptr) {
return -1;
return 0;
}
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
@@ -190,7 +163,7 @@ public:
static int16_t getIdByObjectId(const char* objectId) {
if (settings == nullptr) {
return -1;
return 0;
}
String refObjectId;
@@ -253,13 +226,13 @@ public:
rSensor.connected = true;
Log.snoticeln(
L_SENSORS, "#%hhu '%s' new status: CONNECTED",
FPSTR(L_SENSORS), F("#%hhu '%s' new status: CONNECTED"),
sensorId, sSensor.name
);
}
Log.snoticeln(
L_SENSORS, "#%hhu '%s' new value %hhu: %.2f, compensated: %.2f, raw: %.2f",
FPSTR(L_SENSORS), F("#%hhu '%s' new value %hhu: %.2f, compensated: %.2f, raw: %.2f"),
sensorId, sSensor.name, valueId, rSensor.values[valueId], compensatedValue, value
);
@@ -316,8 +289,8 @@ public:
if (rSensor.connected != status) {
Log.snoticeln(
L_SENSORS, "#%hhu '%s' new status: %s",
sensorId, sSensor.name, status ? "CONNECTED" : "DISCONNECTED"
FPSTR(L_SENSORS), F("#%hhu '%s' new status: %s"),
sensorId, sSensor.name, status ? F("CONNECTED") : F("DISCONNECTED")
);
rSensor.connected = status;
@@ -354,66 +327,43 @@ public:
return updated;
}
static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, const AverageType avgType = AverageType::MEAN, const bool onlyConnected = true, const float defaultValue = NAN) {
static float getMeanValueByPurpose(Purpose purpose, const ValueType valueType, bool onlyConnected = true) {
if (settings == nullptr || results == nullptr) {
return defaultValue;
return 0;
}
uint8_t valueId = (uint8_t) valueType;
if (!isValidValueId(valueId)) {
return defaultValue;
return false;
}
float value = 0.0f;
uint8_t amount = 0;
if (avgType == AverageType::MEAN) {
float sum = 0.0f;
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
auto& sSensor = settings[id];
auto& rSensor = results[id];
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
auto& sSensor = settings[id];
auto& rSensor = results[id];
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
sum += rSensor.values[valueId];
amount++;
}
}
value = amount == 1 ? sum : (sum / amount);
} else if (avgType == AverageType::MINIMUM) {
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++;
}
}
}
} 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++;
}
}
if (sSensor.purpose == purpose && (!onlyConnected || rSensor.connected)) {
value += rSensor.values[valueId];
amount++;
}
}
return amount > 0 ? value : defaultValue;
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 false;
return 0;
}
for (uint8_t id = 0; id <= getMaxSensorId(); id++) {
@@ -427,7 +377,7 @@ public:
static String& cleanName(String& value, char space = ' ') {
// only valid symbols
for (size_t pos = 0; pos < value.length(); pos++) {
for (uint8_t pos = 0; pos < value.length(); pos++) {
char symbol = value.charAt(pos);
// 0..9
@@ -517,22 +467,6 @@ public:
static uint8_t bluetoothRssiToQuality(int rssi) {
return constrain(map(rssi, -110, -50, 0, 100), 0, 100);;
}
private:
static constexpr bool isDigit(const char* value) {
if (value == nullptr || *value == '\0') {
return false;
}
while (*value != '\0') {
if (*value < '0' || *value > '9') {
return false;
}
value++;
}
return true;
}
};
uint8_t Sensors::maxSensors = 0;
+600 -546
View File
File diff suppressed because it is too large. Load diff
+22 -40
View File
@@ -12,13 +12,13 @@ struct NetworkSettings {
struct {
char ssid[33] = DEFAULT_AP_SSID;
char password[65] = DEFAULT_AP_PASSWORD;
uint8_t channel = 6;
byte channel = 6;
} ap;
struct {
char ssid[33] = DEFAULT_STA_SSID;
char password[65] = DEFAULT_STA_PASSWORD;
uint8_t channel = 0;
byte channel = 0;
} sta;
} networkSettings;
@@ -32,8 +32,9 @@ struct Settings {
} serial;
struct {
bool enabled = DEFAULT_WEBSERIAL_ENABLED;
} webSerial;
bool enabled = DEFAULT_TELNET_ENABLED;
unsigned short port = DEFAULT_TELNET_PORT;
} telnet;
struct {
char server[49] = "pool.ntp.org";
@@ -41,7 +42,7 @@ struct Settings {
} ntp;
UnitSystem unitSystem = UnitSystem::METRIC;
uint8_t statusLedGpio = DEFAULT_STATUS_LED_GPIO;
byte statusLedGpio = DEFAULT_STATUS_LED_GPIO;
} system;
struct {
@@ -53,14 +54,11 @@ struct Settings {
struct {
UnitSystem unitSystem = UnitSystem::METRIC;
uint8_t inGpio = DEFAULT_OT_IN_GPIO;
uint8_t outGpio = DEFAULT_OT_OUT_GPIO;
uint8_t rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
byte inGpio = DEFAULT_OT_IN_GPIO;
byte outGpio = DEFAULT_OT_OUT_GPIO;
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
uint8_t memberId = 0;
uint8_t flags = 0;
ChannelDisablingAction heatingDisablingAction = ChannelDisablingAction::OFF_AND_ZERO_SETPOINT;
ChannelDisablingAction dhwDisablingAction = ChannelDisablingAction::OFF_AND_ZERO_SETPOINT;
ChannelDisablingAction ch2DisablingAction = ChannelDisablingAction::OFF_AND_ZERO_SETPOINT;
float minPower = 0.0f;
float maxPower = 0.0f;
@@ -80,8 +78,7 @@ struct Settings {
bool autoFaultReset = false;
bool autoDiagReset = false;
bool setDateAndTime = false;
bool alwaysSendIndoorTemp = true;
bool nativeOTC = false;
bool nativeHeatingControl = false;
bool immergasFix = false;
} options;
} opentherm;
@@ -106,17 +103,11 @@ struct Settings {
bool enabled = true;
bool turbo = false;
float target = DEFAULT_HEATING_TARGET_TEMP;
float hysteresis = 0.5f;
float turboFactor = 7.5f;
uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP;
uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP;
byte minTemp = DEFAULT_HEATING_MIN_TEMP;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
uint8_t maxModulation = 100;
Sensors::AverageType indoorTempAvgType = Sensors::AverageType::MEAN;
Sensors::AverageType outdoorTempAvgType = Sensors::AverageType::MEAN;
struct {
bool enabled = true;
float value = 0.5f;
} hysteresis;
struct {
uint8_t highTemp = 95;
@@ -124,16 +115,16 @@ struct Settings {
} overheatProtection;
struct {
uint8_t highTemp = 15;
uint8_t lowTemp = 10;
unsigned short thresholdTime = 600;
} freezeProtection;
} heating;
struct {
bool enabled = true;
float target = DEFAULT_DHW_TARGET_TEMP;
uint8_t minTemp = DEFAULT_DHW_MIN_TEMP;
uint8_t maxTemp = DEFAULT_DHW_MAX_TEMP;
byte minTemp = DEFAULT_DHW_MIN_TEMP;
byte maxTemp = DEFAULT_DHW_MAX_TEMP;
uint8_t maxModulation = 100;
struct {
@@ -171,8 +162,7 @@ struct Settings {
struct {
bool use = false;
uint8_t gpio = DEFAULT_EXT_PUMP_GPIO;
bool invertState = false;
byte gpio = DEFAULT_EXT_PUMP_GPIO;
unsigned short postCirculationTime = 600;
unsigned int antiStuckInterval = 2592000;
unsigned short antiStuckTime = 300;
@@ -181,15 +171,15 @@ struct Settings {
struct {
struct {
bool enabled = false;
uint8_t gpio = GPIO_IS_NOT_CONFIGURED;
bool invertState = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
unsigned short thresholdTime = 60;
} input;
struct {
bool enabled = false;
uint8_t gpio = GPIO_IS_NOT_CONFIGURED;
bool invertState = false;
byte gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false;
unsigned short thresholdTime = 60;
bool onFault = true;
bool onLossConnection = true;
@@ -307,7 +297,6 @@ struct Variables {
bool enabled = false;
bool indoorTempControl = false;
bool overheat = false;
bool freezing = false;
float setpointTemp = 0.0f;
float targetTemp = 0.0f;
float currentTemp = 0.0f;
@@ -341,14 +330,10 @@ struct Variables {
bool connected = false;
bool flame = false;
bool cooling = false;
float pressure = 0.0f;
float heatExchangerTemp = 0.0f;
struct {
bool active = false;
uint8_t setpoint = 0;
} cooling;
struct {
bool active = false;
uint8_t code = 0;
@@ -393,7 +378,6 @@ struct Variables {
uint16_t dhwBurnerStarts = 0;
uint16_t heatingPumpStarts = 0;
uint16_t dhwPumpStarts = 0;
uint16_t coolingHours = 0;
uint16_t burnerHours = 0;
uint16_t dhwBurnerHours = 0;
uint16_t heatingPumpHours = 0;
@@ -404,7 +388,6 @@ struct Variables {
bool active = false;
bool enabled = false;
float targetTemp = 0.0f;
float roomTargetTemp = 0.0f;
float currentTemp = 0.0f;
float returnTemp = 0.0f;
float indoorTemp = 0.0f;
@@ -429,7 +412,6 @@ struct Variables {
bool active = false;
bool enabled = false;
float targetTemp = 0.0f;
float roomTargetTemp = 0.0f;
float currentTemp = 0.0f;
float indoorTemp = 0.0f;
} ch2;
+17 -13
View File
@@ -30,10 +30,6 @@
#define BUILD_VERSION "0.0.0"
#endif
#ifndef BUILD_COMMIT
#define BUILD_COMMIT "undefined"
#endif
#ifndef BUILD_ENV
#define BUILD_ENV "undefined"
#endif
@@ -46,8 +42,12 @@
#define DEFAULT_SERIAL_BAUD 115200
#endif
#ifndef DEFAULT_WEBSERIAL_ENABLED
#define DEFAULT_WEBSERIAL_ENABLED true
#ifndef DEFAULT_TELNET_ENABLED
#define DEFAULT_TELNET_ENABLED true
#endif
#ifndef DEFAULT_TELNET_PORT
#define DEFAULT_TELNET_PORT 23
#endif
#ifndef USE_BLE
@@ -75,7 +75,7 @@
#endif
#ifndef DEFAULT_LOG_LEVEL
#define DEFAULT_LOG_LEVEL TinyLoggerLevel::VERBOSE
#define DEFAULT_LOG_LEVEL TinyLogger::Level::VERBOSE
#endif
#ifndef DEFAULT_STATUS_LED_GPIO
@@ -146,7 +146,15 @@
#define DEFAULT_EXT_PUMP_GPIO GPIO_IS_NOT_CONFIGURED
#endif
#include <driver/gpio.h>
#ifndef PROGMEM
#define PROGMEM
#endif
#ifdef ARDUINO_ARCH_ESP32
#include <driver/gpio.h>
#elif !defined(GPIO_IS_VALID_GPIO)
#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))
@@ -155,8 +163,4 @@ enum class UnitSystem : uint8_t {
IMPERIAL = 1
};
enum class ChannelDisablingAction : uint8_t {
OFF = 0,
ZERO_SETPOINT = 1,
OFF_AND_ZERO_SETPOINT = 2
};
char buffer[255];
+3
View File
@@ -0,0 +1,3 @@
dependencies:
idf: ">=5.3.2"
h2zero/esp-nimble-cpp: ">=2.2.1"
+39 -33
View File
@@ -1,13 +1,11 @@
#define ARDUINOJSON_USE_DOUBLE 0
#define ARDUINOJSON_USE_LONG_LONG 0
#include <Arduino.h>
#if !defined(ARDUINO_ARCH_ESP32)
#error Only ESP32 boards are supported
#endif
#include <ArduinoJson.h>
#include <FileData.h>
#include <LittleFS.h>
#include <MycilaWebSerial.h>
#include <ESPTelnetStream.h>
#include "defines.h"
#include "strings.h"
@@ -19,7 +17,13 @@
#include "Settings.h"
#include "utils.h"
#include <ESP32Scheduler.h>
#if defined(ARDUINO_ARCH_ESP32)
#include <ESP32Scheduler.h>
#elif defined(ARDUINO_ARCH_ESP8266)
#include <Scheduler.h>
#else
#error Wrong board. Supported boards: esp8266, esp32
#endif
#include <Task.h>
#include <LeanTask.h>
@@ -33,7 +37,7 @@
using namespace NetworkUtils;
// Vars
WebSerial* webSerial = nullptr;
ESPTelnetStream* telnetStream = nullptr;
NetworkMgr* network = nullptr;
Sensors::Result sensorsResults[SENSORS_AMOUNT];
@@ -57,7 +61,7 @@ void setup() {
Sensors::results = sensorsResults;
LittleFS.begin();
Log.setLevel(TinyLoggerLevel::VERBOSE);
Log.setLevel(TinyLogger::Level::VERBOSE);
Log.setServiceTemplate("\033[1m[%s]\033[22m");
Log.setLevelTemplate("\033[1m[%s]\033[22m");
Log.setMsgPrefix("\033[m ");
@@ -75,24 +79,24 @@ void setup() {
#if ARDUINO_USB_MODE
Serial.setTxBufferSize(512);
#endif
Log.addHandler(&Serial);
Log.addStream(&Serial);
Log.print("\n\n\r");
//
// Network settings
switch (fsNetworkSettings.read()) {
case FD_FS_ERR:
Log.swarningln(L_NETWORK_SETTINGS, "Filesystem error, load default");
Log.swarningln(FPSTR(L_NETWORK_SETTINGS), F("Filesystem error, load default"));
break;
case FD_FILE_ERR:
Log.swarningln(L_NETWORK_SETTINGS, "Bad data, load default");
Log.swarningln(FPSTR(L_NETWORK_SETTINGS), F("Bad data, load default"));
break;
case FD_WRITE:
Log.sinfoln(L_NETWORK_SETTINGS, "Not found, load default");
Log.sinfoln(FPSTR(L_NETWORK_SETTINGS), F("Not found, load default"));
break;
case FD_ADD:
case FD_READ:
Log.sinfoln(L_NETWORK_SETTINGS, "Loaded");
Log.sinfoln(FPSTR(L_NETWORK_SETTINGS), F("Loaded"));
break;
default:
break;
@@ -127,20 +131,20 @@ void setup() {
// Settings
switch (fsSettings.read()) {
case FD_FS_ERR:
Log.swarningln(L_SETTINGS, "Filesystem error, load default");
Log.swarningln(FPSTR(L_SETTINGS), F("Filesystem error, load default"));
break;
case FD_FILE_ERR:
Log.swarningln(L_SETTINGS, "Bad data, load default");
Log.swarningln(FPSTR(L_SETTINGS), F("Bad data, load default"));
break;
case FD_WRITE:
Log.sinfoln(L_SETTINGS, "Not found, load default");
Log.sinfoln(FPSTR(L_SETTINGS), F("Not found, load default"));
break;
case FD_ADD:
case FD_READ:
Log.sinfoln(L_SETTINGS, "Loaded");
Log.sinfoln(FPSTR(L_SETTINGS), F("Loaded"));
if (strcmp(SETTINGS_VALID_VALUE, settings.validationValue) != 0) {
Log.swarningln(L_SETTINGS, "Not valid, set default and restart...");
Log.swarningln(FPSTR(L_SETTINGS), F("Not valid, set default and restart..."));
fsSettings.reset();
::delay(5000);
ESP.restart();
@@ -159,41 +163,41 @@ void setup() {
// Logs settings
if (!settings.system.serial.enabled) {
Serial.end();
Log.clearHandlers();
Log.clearStreams();
} else if (settings.system.serial.baudrate != 115200) {
Serial.end();
Log.clearHandlers();
Log.clearStreams();
Serial.begin(settings.system.serial.baudrate);
Log.addHandler(&Serial);
Log.addStream(&Serial);
}
if (settings.system.webSerial.enabled) {
webSerial = new WebSerial();
webSerial->setBuffer(100);
Log.addHandler(webSerial);
if (settings.system.telnet.enabled) {
telnetStream = new ESPTelnetStream;
telnetStream->setKeepAliveInterval(500);
Log.addStream(telnetStream);
}
if (settings.system.logLevel >= TinyLoggerLevel::SILENT && settings.system.logLevel <= TinyLoggerLevel::VERBOSE) {
Log.setLevel(static_cast<TinyLoggerLevel>(settings.system.logLevel));
if (settings.system.logLevel >= TinyLogger::Level::SILENT && settings.system.logLevel <= TinyLogger::Level::VERBOSE) {
Log.setLevel(static_cast<TinyLogger::Level>(settings.system.logLevel));
}
//
// Sensors settings
switch (fsSensorsSettings.read()) {
case FD_FS_ERR:
Log.swarningln(L_SENSORS, "Filesystem error, load default");
Log.swarningln(FPSTR(L_SENSORS), F("Filesystem error, load default"));
break;
case FD_FILE_ERR:
Log.swarningln(L_SENSORS, "Bad data, load default");
Log.swarningln(FPSTR(L_SENSORS), F("Bad data, load default"));
break;
case FD_WRITE:
Log.sinfoln(L_SENSORS, "Not found, load default");
Log.sinfoln(FPSTR(L_SENSORS), F("Not found, load default"));
break;
case FD_ADD:
case FD_READ:
Log.sinfoln(L_SENSORS, "Loaded");
Log.sinfoln(FPSTR(L_SENSORS), F("Loaded"));
default:
break;
}
@@ -212,7 +216,7 @@ void setup() {
tRegulator = new RegulatorTask(true, 10000);
Scheduler.start(tRegulator);
tPortal = new PortalTask(true, 10);
tPortal = new PortalTask(true, 0);
Scheduler.start(tPortal);
tMain = new MainTask(true, 100);
@@ -222,5 +226,7 @@ void setup() {
}
void loop() {
#if defined(ARDUINO_ARCH_ESP32)
vTaskDelete(NULL);
#endif
}
+214 -222
View File
@@ -1,226 +1,218 @@
#pragma once
#ifndef PROGMEM
#define PROGMEM
#endif
const char L_SETTINGS[] = "SETTINGS";
const char L_SETTINGS_OT[] = "SETTINGS.OT";
const char L_SETTINGS_DHW[] = "SETTINGS.DHW";
const char L_SETTINGS_HEATING[] = "SETTINGS.HEATING";
const char L_NETWORK[] = "NETWORK";
const char L_NETWORK_SETTINGS[] = "NETWORK.SETTINGS";
const char L_PORTAL_WEBSERVER[] = "PORTAL.WEBSERVER";
const char L_PORTAL_DNSSERVER[] = "PORTAL.DNSSERVER";
const char L_PORTAL_CAPTIVE[] = "PORTAL.CAPTIVE";
const char L_PORTAL_OTA[] = "PORTAL.OTA";
const char L_MAIN[] = "MAIN";
const char L_MQTT[] = "MQTT";
const char L_MQTT_HA[] = "MQTT.HA";
const char L_MQTT_MSG[] = "MQTT.MSG";
const char L_OT[] = "OT";
const char L_OT_DHW[] = "OT.DHW";
const char L_OT_HEATING[] = "OT.HEATING";
const char L_OT_CH2[] = "OT.CH2";
const char L_SENSORS[] = "SENSORS";
const char L_SENSORS_SETTINGS[] = "SENSORS.SETTINGS";
const char L_SENSORS_DALLAS[] = "SENSORS.DALLAS";
const char L_SENSORS_DHT[] = "SENSORS.DHT";
const char L_SENSORS_NTC[] = "SENSORS.NTC";
const char L_SENSORS_BLE[] = "SENSORS.BLE";
const char L_REGULATOR[] = "REGULATOR";
const char L_REGULATOR_PID[] = "REGULATOR.PID";
const char L_REGULATOR_EQUITHERM[] = "REGULATOR.EQUITHERM";
const char L_CASCADE_INPUT[] = "CASCADE.INPUT";
const char L_CASCADE_OUTPUT[] = "CASCADE.OUTPUT";
const char L_EXTPUMP[] = "EXTPUMP";
const char L_SETTINGS[] PROGMEM = "SETTINGS";
const char L_SETTINGS_OT[] PROGMEM = "SETTINGS.OT";
const char L_SETTINGS_DHW[] PROGMEM = "SETTINGS.DHW";
const char L_SETTINGS_HEATING[] PROGMEM = "SETTINGS.HEATING";
const char L_NETWORK[] PROGMEM = "NETWORK";
const char L_NETWORK_SETTINGS[] PROGMEM = "NETWORK.SETTINGS";
const char L_PORTAL_WEBSERVER[] PROGMEM = "PORTAL.WEBSERVER";
const char L_PORTAL_DNSSERVER[] PROGMEM = "PORTAL.DNSSERVER";
const char L_PORTAL_CAPTIVE[] PROGMEM = "PORTAL.CAPTIVE";
const char L_PORTAL_OTA[] PROGMEM = "PORTAL.OTA";
const char L_MAIN[] PROGMEM = "MAIN";
const char L_MQTT[] PROGMEM = "MQTT";
const char L_MQTT_HA[] PROGMEM = "MQTT.HA";
const char L_MQTT_MSG[] PROGMEM = "MQTT.MSG";
const char L_OT[] PROGMEM = "OT";
const char L_OT_DHW[] PROGMEM = "OT.DHW";
const char L_OT_HEATING[] PROGMEM = "OT.HEATING";
const char L_OT_CH2[] PROGMEM = "OT.CH2";
const char L_SENSORS[] PROGMEM = "SENSORS";
const char L_SENSORS_SETTINGS[] PROGMEM = "SENSORS.SETTINGS";
const char L_SENSORS_DALLAS[] PROGMEM = "SENSORS.DALLAS";
const char L_SENSORS_NTC[] PROGMEM = "SENSORS.NTC";
const char L_SENSORS_BLE[] PROGMEM = "SENSORS.BLE";
const char L_REGULATOR[] PROGMEM = "REGULATOR";
const char L_REGULATOR_PID[] PROGMEM = "REGULATOR.PID";
const char L_REGULATOR_EQUITHERM[] PROGMEM = "REGULATOR.EQUITHERM";
const char L_CASCADE_INPUT[] PROGMEM = "CASCADE.INPUT";
const char L_CASCADE_OUTPUT[] PROGMEM = "CASCADE.OUTPUT";
const char L_EXTPUMP[] PROGMEM = "EXTPUMP";
const char S_ACTIONS[] = "actions";
const char S_ACTIVE[] = "active";
const char S_ADDRESS[] = "address";
const char S_ANTI_STUCK_INTERVAL[] = "antiStuckInterval";
const char S_ANTI_STUCK_TIME[] = "antiStuckTime";
const char S_AP[] = "ap";
const char S_APP_VERSION[] = "appVersion";
const char S_AUTH[] = "auth";
const char S_AUTO_DIAG_RESET[] = "autoDiagReset";
const char S_AUTO_FAULT_RESET[] = "autoFaultReset";
const char S_BACKTRACE[] = "backtrace";
const char S_BATTERY[] = "battery";
const char S_BAUDRATE[] = "baudrate";
const char S_BLOCKING[] = "blocking";
const char S_BSSID[] = "bssid";
const char S_BUILD[] = "build";
const char S_CASCADE_CONTROL[] = "cascadeControl";
const char S_CHANNEL[] = "channel";
const char S_CH2_ALWAYS_ENABLED[] = "ch2AlwaysEnabled";
const char S_CH2_DISABLING_ACTION[] = "ch2DisablingAction";
const char S_CHIP[] = "chip";
const char S_CODE[] = "code";
const char S_COMMIT[] = "commit";
const char S_CONNECTED[] = "connected";
const char S_CONTINUES[] = "continues";
const char S_COOLING[] = "cooling";
const char S_COOLING_SUPPORT[] = "coolingSupport";
const char S_CORE[] = "core";
const char S_CORES[] = "cores";
const char S_CRASH[] = "crash";
const char S_CURRENT_TEMP[] = "currentTemp";
const char S_DATA[] = "data";
const char S_DATE[] = "date";
const char S_DEADBAND[] = "deadband";
const char S_DHW[] = "dhw";
const char S_DHW_BLOCKING[] = "dhwBlocking";
const char S_DHW_DISABLING_ACTION[] = "dhwDisablingAction";
const char S_DHW_STATE_AS_DHW_BLOCKING[] = "dhwStateAsDhwBlocking";
const char S_DHW_SUPPORT[] = "dhwSupport";
const char S_DHW_TO_CH2[] = "dhwToCh2";
const char S_DIAG[] = "diag";
const char S_DNS[] = "dns";
const char S_DT[] = "dt";
const char S_D_FACTOR[] = "d_factor";
const char S_D_MULTIPLIER[] = "d_multiplier";
const char S_EMERGENCY[] = "emergency";
const char S_ENABLED[] = "enabled";
const char S_ENV[] = "env";
const char S_EPC[] = "epc";
const char S_EQUITHERM[] = "equitherm";
const char S_EXPONENT[] = "exponent";
const char S_EXTERNAL_PUMP[] = "externalPump";
const char S_FACTOR[] = "factor";
const char S_FAULT[] = "fault";
const char S_FREEZE_PROTECTION[] = "freezeProtection";
const char S_FREEZING[] = "freezing";
const char S_FILTERING[] = "filtering";
const char S_FILTERING_FACTOR[] = "filteringFactor";
const char S_FLAGS[] = "flags";
const char S_FLAME[] = "flame";
const char S_FLASH[] = "flash";
const char S_FREE[] = "free";
const char S_FREQ[] = "freq";
const char S_GATEWAY[] = "gateway";
const char S_GET_MIN_MAX_TEMP[] = "getMinMaxTemp";
const char S_GPIO[] = "gpio";
const char S_HEAP[] = "heap";
const char S_HEATING[] = "heating";
const char S_HEATING_DISABLING_ACTION[] = "heatingDisablingAction";
const char S_HEATING_TO_CH2[] = "heatingToCh2";
const char S_HEATING_STATE_TO_SUMMER_WINTER_MODE[] = "heatingStateToSummerWinterMode";
const char S_HIDDEN[] = "hidden";
const char S_HIGH_TEMP[] = "highTemp";
const char S_HOME_ASSISTANT_DISCOVERY[] = "homeAssistantDiscovery";
const char S_HOSTNAME[] = "hostname";
const char S_HUMIDITY[] = "humidity";
const char S_HYSTERESIS[] = "hysteresis";
const char S_ID[] = "id";
const char S_IGNORE_DIAG_STATE[] = "ignoreDiagState";
const char S_IMMERGAS_FIX[] = "immergasFix";
const char S_ALWAYS_SEND_INDOOR_TEMP[] = "alwaysSendIndoorTemp";
const char S_INDOOR_TEMP[] = "indoorTemp";
const char S_INDOOR_TEMP_AVG_TYPE[] = "indoorTempAvgType";
const char S_INDOOR_TEMP_CONTROL[] = "indoorTempControl";
const char S_IN_GPIO[] = "inGpio";
const char S_INPUT[] = "input";
const char S_INTERVAL[] = "interval";
const char S_INVERT_STATE[] = "invertState";
const char S_IP[] = "ip";
const char S_I_FACTOR[] = "i_factor";
const char S_I_MULTIPLIER[] = "i_multiplier";
const char S_LOGIN[] = "login";
const char S_LOG_LEVEL[] = "logLevel";
const char S_LOW_TEMP[] = "lowTemp";
const char S_MAC[] = "mac";
const char S_MASTER[] = "master";
const char S_MAX[] = "max";
const char S_MAX_FREE_BLOCK[] = "maxFreeBlock";
const char S_MAX_MODULATION[] = "maxModulation";
const char S_MAX_POWER[] = "maxPower";
const char S_MAX_TEMP[] = "maxTemp";
const char S_MAX_TEMP_SYNC_WITH_TARGET_TEMP[] = "maxTempSyncWithTargetTemp";
const char S_MDNS[] = "mdns";
const char S_MEMBER_ID[] = "memberId";
const char S_MIN[] = "min";
const char S_MIN_FREE[] = "minFree";
const char S_MIN_MAX_FREE_BLOCK[] = "minMaxFreeBlock";
const char S_MIN_POWER[] = "minPower";
const char S_MIN_TEMP[] = "minTemp";
const char S_MODEL[] = "model";
const char S_MODULATION[] = "modulation";
const char S_MQTT[] = "mqtt";
const char S_NAME[] = "name";
const char S_NATIVE_OTC[] = "nativeOTC";
const char S_NETWORK[] = "network";
const char S_NTP[] = "ntp";
const char S_OFFSET[] = "offset";
const char S_ON_ENABLED_HEATING[] = "onEnabledHeating";
const char S_ON_FAULT[] = "onFault";
const char S_ON_LOSS_CONNECTION[] = "onLossConnection";
const char S_OPENTHERM[] = "opentherm";
const char S_OPTIONS[] = "options";
const char S_OUTDOOR_TEMP[] = "outdoorTemp";
const char S_OUTDOOR_TEMP_AVG_TYPE[] = "outdoorTempAvgType";
const char S_OUT_GPIO[] = "outGpio";
const char S_OUTPUT[] = "output";
const char S_OVERHEAT[] = "overheat";
const char S_OVERHEAT_PROTECTION[] = "overheatProtection";
const char S_PASSWORD[] = "password";
const char S_PID[] = "pid";
const char S_PORT[] = "port";
const char S_PORTAL[] = "portal";
const char S_POST_CIRCULATION_TIME[] = "postCirculationTime";
const char S_POWER[] = "power";
const char S_PREFIX[] = "prefix";
const char S_PROTOCOL_VERSION[] = "protocolVersion";
const char S_PURPOSE[] = "purpose";
const char S_PSRAM[] = "psram";
const char S_P_FACTOR[] = "p_factor";
const char S_P_MULTIPLIER[] = "p_multiplier";
const char S_REAL_SIZE[] = "realSize";
const char S_REASON[] = "reason";
const char S_RESET_DIAGNOSTIC[] = "resetDiagnostic";
const char S_RESET_FAULT[] = "resetFault";
const char S_RESET_REASON[] = "resetReason";
const char S_RESTART[] = "restart";
const char S_RETURN_TEMP[] = "returnTemp";
const char S_REV[] = "rev";
const char S_RSSI[] = "rssi";
const char S_RX_LED_GPIO[] = "rxLedGpio";
const char S_SDK[] = "sdk";
const char S_SENSORS[] = "sensors";
const char S_SERIAL[] = "serial";
const char S_SERVER[] = "server";
const char S_SETTINGS[] = "settings";
const char S_SET_DATE_AND_TIME[] = "setDateAndTime";
const char S_SHIFT[] = "shift";
const char S_SIGNAL_QUALITY[] = "signalQuality";
const char S_SIZE[] = "size";
const char S_SLAVE[] = "slave";
const char S_SLOPE[] = "slope";
const char S_SSID[] = "ssid";
const char S_STA[] = "sta";
const char S_STATE[] = "state";
const char S_STATIC_CONFIG[] = "staticConfig";
const char S_STATUS_LED_GPIO[] = "statusLedGpio";
const char S_SETPOINT[] = "setpoint";
const char S_SETPOINT_TEMP[] = "setpointTemp";
const char S_SUBNET[] = "subnet";
const char S_SUMMER_WINTER_MODE[] = "summerWinterMode";
const char S_SYSTEM[] = "system";
const char S_TARGET[] = "target";
const char S_TARGET_DIFF_FACTOR[] = "targetDiffFactor";
const char S_TARGET_TEMP[] = "targetTemp";
const char S_TEMPERATURE[] = "temperature";
const char S_THRESHOLD_HIGH[] = "thresholdHigh";
const char S_THRESHOLD_LOW[] = "thresholdLow";
const char S_THRESHOLD_TIME[] = "thresholdTime";
const char S_TIMEZONE[] = "timezone";
const char S_TOTAL[] = "total";
const char S_TRESHOLD_TIME[] = "tresholdTime";
const char S_TURBO[] = "turbo";
const char S_TURBO_FACTOR[] = "turboFactor";
const char S_TYPE[] = "type";
const char S_UNIT_SYSTEM[] = "unitSystem";
const char S_UPTIME[] = "uptime";
const char S_USE[] = "use";
const char S_USE_DHCP[] = "useDhcp";
const char S_USER[] = "user";
const char S_VALUE[] = "value";
const char S_VERSION[] = "version";
const char S_WEBSERIAL[] = "webSerial";
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_AUTO_DIAG_RESET[] PROGMEM = "autoDiagReset";
const char S_AUTO_FAULT_RESET[] PROGMEM = "autoFaultReset";
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_CH2_ALWAYS_ENABLED[] PROGMEM = "ch2AlwaysEnabled";
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_COOLING[] PROGMEM = "cooling";
const char S_COOLING_SUPPORT[] PROGMEM = "coolingSupport";
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_DEADBAND[] PROGMEM = "deadband";
const char S_DHW[] PROGMEM = "dhw";
const char S_DHW_BLOCKING[] PROGMEM = "dhwBlocking";
const char S_DHW_STATE_AS_DHW_BLOCKING[] PROGMEM = "dhwStateAsDhwBlocking";
const char S_DHW_SUPPORT[] PROGMEM = "dhwSupport";
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_D_MULTIPLIER[] PROGMEM = "d_multiplier";
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_EXPONENT[] PROGMEM = "exponent";
const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump";
const char S_FACTOR[] PROGMEM = "factor";
const char S_FAULT[] PROGMEM = "fault";
const char S_FREEZE_PROTECTION[] PROGMEM = "freezeProtection";
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_TO_CH2[] PROGMEM = "heatingToCh2";
const char S_HEATING_STATE_TO_SUMMER_WINTER_MODE[] PROGMEM = "heatingStateToSummerWinterMode";
const char S_HIDDEN[] PROGMEM = "hidden";
const char S_HIGH_TEMP[] PROGMEM = "highTemp";
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_IGNORE_DIAG_STATE[] PROGMEM = "ignoreDiagState";
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_I_MULTIPLIER[] PROGMEM = "i_multiplier";
const char S_LOGIN[] PROGMEM = "login";
const char S_LOG_LEVEL[] PROGMEM = "logLevel";
const char S_LOW_TEMP[] PROGMEM = "lowTemp";
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_MAX_TEMP_SYNC_WITH_TARGET_TEMP[] PROGMEM = "maxTempSyncWithTargetTemp";
const char S_MDNS[] PROGMEM = "mdns";
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_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_NTP[] PROGMEM = "ntp";
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_OPTIONS[] PROGMEM = "options";
const char S_OUTDOOR_TEMP[] PROGMEM = "outdoorTemp";
const char S_OUT_GPIO[] PROGMEM = "outGpio";
const char S_OUTPUT[] PROGMEM = "output";
const char S_OVERHEAT[] PROGMEM = "overheat";
const char S_OVERHEAT_PROTECTION[] PROGMEM = "overheatProtection";
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_P_MULTIPLIER[] PROGMEM = "p_multiplier";
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_SET_DATE_AND_TIME[] PROGMEM = "setDateAndTime";
const char S_SHIFT[] PROGMEM = "shift";
const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality";
const char S_SIZE[] PROGMEM = "size";
const char S_SLAVE[] PROGMEM = "slave";
const char S_SLOPE[] PROGMEM = "slope";
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_DIFF_FACTOR[] PROGMEM = "targetDiffFactor";
const char S_TARGET_TEMP[] PROGMEM = "targetTemp";
const char S_TELNET[] PROGMEM = "telnet";
const char S_TEMPERATURE[] PROGMEM = "temperature";
const char S_THRESHOLD_HIGH[] PROGMEM = "thresholdHigh";
const char S_THRESHOLD_LOW[] PROGMEM = "thresholdLow";
const char S_THRESHOLD_TIME[] PROGMEM = "thresholdTime";
const char S_TIMEZONE[] PROGMEM = "timezone";
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_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";
+576 -660
View File
File diff suppressed because it is too large. Load diff
+77 -126
View File
@@ -8,21 +8,15 @@
"issues": "问题与反馈",
"releases": "发行版"
},
"units": {
"dbm": "dBm",
"kw": "kW",
"time": {
"days": "天",
"hours": "小时",
"min": "分",
"sec": "秒",
"dbm": "dBm",
"mhz": "MHz",
"kw": "kW",
"rpm": "RPM",
"ppm": "ppm",
"byte": "byte",
"mbyte": "MB",
"liter": "升",
"gallon": "加仑"
"sec": "秒"
},
"button": {
"upgrade": "固件升级",
"restart": "重启",
@@ -37,25 +31,26 @@
"success": "成功",
"error": "错误"
},
"index": {
"title": "OpenTherm Gateway",
"section": {
"network": "网络",
"system": "系统"
},
"system": {
"build": {
"title": "Build",
"version": "固件版本",
"commit": "Commit",
"date": "日期",
"core": "内核版本",
"sdk": "SDK"
},
"uptime": "运行时间",
"memory": {
"heap": "内存 (heap)",
"psram": "内存 (PSRAM)",
"title": "可用内存",
"maxFreeBlock": "max free block",
"min": "min"
},
@@ -72,22 +67,26 @@
"lastResetReason": "上次重置原因"
}
},
"dashboard": {
"name": "仪表盘",
"title": "仪表盘 - OpenTherm Gateway",
"section": {
"control": "调节",
"states": "状态",
"sensors": "传感器",
"diag": "OpenTherm 诊断"
},
"thermostat": {
"heating": "供暖",
"dhw": "生活热水",
"currentTemp": "当前温度",
"temp.current": "当前温度",
"enable": "启用",
"turbo": "Turbo 模式"
},
"notify": {
"fault": {
"title": "锅炉报警状态已激活!",
@@ -99,6 +98,7 @@
},
"reset": "点击复位"
},
"states": {
"mNetworkConnected": "网络连接状态",
"mMqttConnected": "MQTT服务器连接状态",
@@ -106,18 +106,18 @@
"mExtPumpState": "外置循环泵",
"mCascadeControlInput": "Cascade 控制 (input)",
"mCascadeControlOutput": "Cascade 控制 (output)",
"sConnected": "OpenTherm 通讯状态",
"sFlame": "火焰",
"sCoolingActive": "制冷",
"sCoolingSetpoint": "冷却设定点",
"sCooling": "制冷",
"sFaultActive": "报警状态",
"sFaultCode": "报警代码",
"sDiagActive": "诊断状态",
"sDiagCode": "诊断代码",
"mHeatEnabled": "供暖功能已启用",
"mHeatBlocking": "供暖",
"mHeatOverheat": "供暖超热保护",
"mHeatFreezing": "防冻保护",
"sHeatActive": "供暖激活状态",
"mHeatSetpointTemp": "供暖供水设定温度",
"mHeatTargetTemp": "供暖供水目标温度",
@@ -125,6 +125,7 @@
"mHeatRetTemp": "供暖回水温度",
"mHeatIndoorTemp": "供暖,室内温度",
"mHeatOutdoorTemp": "供暖,室外温度",
"mDhwEnabled": "生活热水功能已启用",
"mDhwOverheat": "生活热水超热保护",
"sDhwActive": "生活热水激活",
@@ -132,6 +133,7 @@
"mDhwCurrTemp": "生活热水当前出水温度",
"mDhwRetTemp": "生活热水回水温度"
},
"sensors": {
"values": {
"temp": "温度",
@@ -141,19 +143,23 @@
}
}
},
"network": {
"title": "网络 - OpenTherm Gateway",
"name": "网络设置",
"section": {
"static": "静态设置",
"availableNetworks": "可用网络",
"staSettings": "WiFi 设置",
"apSettings": "AP 设置"
},
"scan": {
"pos": "#",
"info": "Info"
},
"wifi": {
"ssid": "SSID",
"password": "密码",
@@ -161,6 +167,7 @@
"signal": "信号强度",
"connected": "已连接"
},
"params": {
"hostname": "Hostname",
"dhcp": "自动 (DHCP)",
@@ -170,15 +177,16 @@
"gateway": "网关",
"dns": "DNS 服务器"
},
"sta": {
"channel": {
"note": "自动选择设置为0"
}
"channel.note": "自动选择设置为0"
}
},
"sensors": {
"title": "传感器设置 - OpenTherm Gateway",
"name": "传感器设置",
"enabled": "启用",
"sensorName": {
"title": "传感器名称",
@@ -234,12 +242,10 @@
"otDhwBurnerHours": "OpenTherm, number of burner operating hours (DHW)",
"otHeatingPumpHours": "OpenTherm, number of pump operating hours (heating)",
"otDhwPumpHours": "OpenTherm, number of pump operating hours (DHW)",
"otCoolingHours": "OpenTherm, number of cooling hours",
"ntcTemp": "NTC 传感器",
"dallasTemp": "DALLAS 传感器",
"bluetooth": "BLE 传感器",
"dht11": "DHT11 传感器",
"dht22": "DHT22 传感器",
"heatSetpointTemp": "Heating, setpoint temp",
"manual": "通过 MQTT/API 手动配置",
"notConfigured": "未配置"
@@ -266,9 +272,11 @@
}
}
},
"settings": {
"title": "设置 - OpenTherm Gateway",
"name": "设置",
"section": {
"portal": "Portal 设置",
"system": "系统设置",
@@ -283,21 +291,18 @@
"extPump": "外置循环泵设置",
"cascadeControl": "Cascade 级联控制设置"
},
"enable": "启用",
"note": {
"restart": "更改这些设置后,必须重启设备以使变更生效",
"blankNotUse": "空白 - 未使用",
"bleDevice": "BLE设备仅支持搭载BLE功能的特定ESP32开发板使用!"
},
"temp": {
"min": "最低温度",
"max": "最高温度"
},
"avgType": {
"mean": "平均温度",
"min": "最低温度",
"max": "最高温度"
},
"maxModulation": "最大调制范围",
"ohProtection": {
"title": "超温保护",
@@ -313,22 +318,18 @@
},
"freezeProtection": {
"title": "防冻保护",
"desc": "如果热载体或室内温度低于 <b>低温</b>,加热将被强制开启。",
"highTemp": {
"title": "高温阈值",
"note": "防冻保护激活后系统恢复正常模式的阈值"
},
"lowTemp": {
"title": "低温阈值",
"note": "强制开启加热的阈值"
}
"desc": "当热媒或室内温度在<b>等待时间</b> 内降至<b>低温阈值</b>以下时,系统将强制启动加热功能。",
"lowTemp": "低温阈值",
"thresholdTime": "等待时间<small>(秒)</small>"
},
"portal": {
"login": "登录",
"password": "密码",
"auth": "需身份验证",
"mdns": "使用 mDNS"
},
"system": {
"unit": "单位",
"metric": "公制 <small>(摄氏度、升、巴)</small>",
@@ -339,8 +340,12 @@
"enable": "启用串口",
"baud": "串口波特率"
},
"webSerial": {
"enable": "启用 WebSerial"
"telnet": {
"enable": "启用 Telnet",
"port": {
"title": "Telnet 端口",
"note": "默认值:23"
}
},
"ntp": {
"server": "NTP服务器",
@@ -348,53 +353,31 @@
"timezonePresets": "选择预设配置..."
}
},
"heating": {
"hyst": {
"title": "滞回",
"desc": "滞回有助于维持设定的室内温度(在使用«Equitherm»和/或«PID»时)。强制禁用加热当<code>current indoor > target + value</code>,启用加热当<code>current indoor < (target - value)</code>。",
"value": "值 <small>(以度为单位)</small>"
},
"turboFactor": "Turbo 模式系数",
"indoorTempAvgType": {
"title": "室内温度平均类型",
"desc": "使用两个或更多室内温度传感器时可能有用(使用 «Equitherm» 和/或 «PID» 时)。"
},
"outdoorTempAvgType": {
"title": "室外温度平均类型",
"desc": "使用两个或更多室外温度传感器时可能有用(使用 «Equitherm» 时)。"
}
"hyst": "滞后值<small>(单位:度)</small>",
"turboFactor": "Turbo 模式系数"
},
"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>的传感器将显示为断开连接状态。",
"target": {
"title": "目标温度",
"note": "<b>重要提示:</b> 若启用OpenTherm选项 <i>«原生供暖控制»</i>,此处设定值为<u>目标室内温度</u>;<br />其他所有情况下,此处设定值为 <u>目标热媒出水温度</u>."
},
"treshold": "阈值时间 <small>(秒)</small>"
},
"equitherm": {
"slope": {
"title": "斜率",
"note": "热损失补偿。主要调谐参数。"
},
"exponent": {
"title": "指数",
"note": "散热器效率。典型值:<code>1.1</code> - 地板采暖,<code>1.2</code> - 铸铁,<code>1.3</code> - 面板散热器,<code>1.4</code> - 对流器。"
},
"shift": {
"title": "偏移",
"note": "补偿额外热损失(例如,在管道中)或额外热源。"
},
"targetDiffFactor": {
"title": "T 因子",
"note": "如果启用 PID,则不使用。将目标和当前室内温度之间的差值添加到设定点:<code>setpoint = setpoint + ((target - indoor) * T)</code>。"
},
"chart": {
"targetTemp": "目标室内温度",
"setpointTemp": "热载体温度",
"outdoorTemp": "室外温度"
"n": "N 系数",
"k": "K 系数",
"t": {
"title": "T 系数",
"note": "启用PID时此参数无效"
}
},
"pid": {
"p": "P 系数",
"i": "I 系数",
@@ -414,17 +397,8 @@
"thresholdLow": "Threshold low"
}
},
"ot": {
"disablingAction": {
"title": "回路关闭方式",
"desc": "无论因何原因关闭回路,包括过热,均采用所选的关闭方式。<br />某些锅炉会在关闭回路时停止循环泵,此时可尝试将目标温度设为 <b>0</b>,以使循环泵的延时运转功能正常工作。",
"heating": "采暖回路",
"dhw": "生活热水回路",
"ch2": "回路 2",
"off": "仅关闭回路",
"zeroSetpoint": "将目标温度设为 <b>0</b>",
"offAndZeroSetpoint": "关闭回路并将目标温度设为 <b>0</b>"
},
"advanced": "高级设置",
"inGpio": "In GPIO",
"outGpio": "Out GPIO",
@@ -437,8 +411,9 @@
},
"maxPower": {
"title": "最大锅炉功率 <small>(kW)</small>",
"note": "<b>0</b> - 自动检测,通常在锅炉参数设置中的\"最大有效热输出\"。"
"note": "<b>0</b> - 自动检测,通常在锅炉参数设置中的\"最大有效热输出\"。 "
},
"options": {
"title": "选项(附加设置)",
"desc": "附加设置选项可调整锅炉的运行逻辑。由于协议未完整记录所有选项,同一选项在不同锅炉上可能产生不同效果。<br /><b>注意:</b>若系统运行正常,无需修改设置。",
@@ -457,14 +432,15 @@
"autoFaultReset": "自动报警复位 <small>(不推荐!)</small>",
"autoDiagReset": "自动诊断复位 <small>(不推荐!)</small>",
"setDateAndTime": "同步设置锅炉日期与时间",
"immergasFix": "针对Immergas锅炉的兼容性修复",
"alwaysSendIndoorTemp": "向锅炉发送当前室内温度"
"immergasFix": "针对Immergas锅炉的兼容性修复"
},
"nativeOTC": {
"title": "原生热载体温度计算模式",
"note": "仅在锅炉处于 OTC 模式时<u>才</u>工作:需要并接受目标室内温度,并基于内置曲线模式自行调节热载体温度。与 PID 和 Equitherm 不兼容。"
"nativeHeating": {
"title": "原生锅炉供暖控制",
"note": "<u>注意:</u> 仅适用于锅炉需接收目标室温并自主调节载热介质温度的场景,与固件中的PID及Equithermq气候补偿功能不兼容。"
}
},
"mqtt": {
"homeAssistantDiscovery": "Home Assistant 自动发现",
"server": "服务器地址",
@@ -474,14 +450,15 @@
"prefix": "Prefix 前缀",
"interval": "发布间隔 <small>(秒)</small>"
},
"extPump": {
"use": "使用外置循环泵",
"gpio": "继电器 GPIO引脚",
"invertState": "切换 GPIO 状态",
"postCirculationTime": "后循环时间 <small>(分钟)</small>",
"antiStuckInterval": "防卡死间隔时间<small>(天)</small>",
"antiStuckTime": "防卡死运行时长<small>(分钟)</small>"
},
"cascadeControl": {
"input": {
"desc": "仅当另一台锅炉发生故障时启用本锅炉加热。另一台锅炉的控制器需在故障发生时切换GPIO输入状态以触发本功能。",
@@ -505,49 +482,23 @@
}
}
},
"logs": {
"title": "日志 - OpenTherm Gateway",
"name": "日志",
"desc": "实时设备日志。",
"status": {
"connecting": "正在连接...",
"connected": "已连接",
"disconnected": "已断开连接"
},
"autoscroll": "自动滚动",
"limitHistory": "最多保留 1000 条日志",
"historyNote": "不限制历史记录会占用更多浏览器内存。下载包含所有保留的日志,包括被筛选器隐藏的日志。",
"filter": "筛选",
"search": {
"title": "搜索",
"previous": "上一个",
"next": "下一个"
},
"level": {
"note": "此级别适用于设备日志(Serial 和 WebSerial)。下方的筛选器仅影响显示。",
"loadError": "无法加载日志级别,请重试。",
"saveError": "无法保存日志级别,请检查连接后重试。"
},
"clear": "清空",
"download": "下载日志"
},
"upgrade": {
"title": "固件升级 - OpenTherm Gateway",
"name": "固件升级",
"section": {
"backupAndRestore": {
"title": "备份与恢复",
"desc": "本功能支持备份和恢复全部设置"
},
"upgrade": {
"title": "升级",
"desc": "本模块支持升级设备的固件与系统文件。<br />可从以下地址下载最新版本 <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases page</a> 。"
}
"backupAndRestore": "备份与恢复",
"backupAndRestore.desc": "本功能支持备份和恢复全部设置",
"upgrade": "升级",
"upgrade.desc": "本模块支持升级设备的固件与系统文件。<br />可从以下地址下载最新版本 <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases page</a> 。"
},
"note": {
"disclaimer1": "升级系统文件成功后,所有设置将恢复为默认值!升级前请务必备份配置。",
"disclaimer2": "升级成功后,设备将在15秒后自动重启。"
},
"settingsFile": "设置文件",
"fw": "Firmware",
"fs": "Filesystem"
+72 -106
View File
@@ -8,21 +8,15 @@
"issues": "Issues & questions",
"releases": "Releases"
},
"units": {
"dbm": "dBm",
"kw": "kW",
"time": {
"days": "d.",
"hours": "h.",
"min": "min.",
"sec": "sec.",
"dbm": "dBm",
"mhz": "MHz",
"kw": "kW",
"rpm": "RPM",
"ppm": "ppm",
"byte": "byte",
"mbyte": "MB",
"liter": "L",
"gallon": "gal"
"sec": "sec."
},
"button": {
"upgrade": "Upgrade",
"restart": "Restart",
@@ -37,25 +31,26 @@
"success": "Success",
"error": "Error"
},
"index": {
"title": "OpenTherm Gateway",
"section": {
"network": "Network",
"system": "System"
},
"system": {
"build": {
"title": "Build",
"version": "Version",
"commit": "Commit",
"date": "Date",
"core": "Core",
"sdk": "SDK"
},
"uptime": "Uptime",
"memory": {
"heap": "Memory (heap)",
"psram": "Memory (PSRAM)",
"title": "Free memory",
"maxFreeBlock": "max free block",
"min": "min"
},
@@ -72,22 +67,26 @@
"lastResetReason": "Last reset reason"
}
},
"dashboard": {
"name": "Dashboard",
"title": "Dashboard - OpenTherm Gateway",
"section": {
"control": "Control",
"states": "States",
"sensors": "Sensors",
"diag": "OpenTherm diagnostic"
},
"thermostat": {
"heating": "Heating",
"dhw": "DHW",
"currentTemp": "Current",
"temp.current": "Current",
"enable": "Enable",
"turbo": "Turbo mode"
},
"notify": {
"fault": {
"title": "Boiler Fault state is active!",
@@ -99,6 +98,7 @@
},
"reset": "Try reset"
},
"states": {
"mNetworkConnected": "Network connection",
"mMqttConnected": "MQTT connection",
@@ -106,18 +106,18 @@
"mExtPumpState": "External pump",
"mCascadeControlInput": "Cascade control (input)",
"mCascadeControlOutput": "Cascade control (output)",
"sConnected": "OpenTherm connection",
"sFlame": "Flame",
"sCoolingActive": "Cooling",
"sCoolingSetpoint": "Cooling setpoint",
"sCooling": "Cooling",
"sFaultActive": "Fault",
"sFaultCode": "Fault code",
"sDiagActive": "Diagnostic",
"sDiagCode": "Diagnostic code",
"mHeatEnabled": "Heating enabled",
"mHeatBlocking": "Heating blocked",
"mHeatOverheat": "Heating overheat",
"mHeatFreezing": "Heating freezing",
"sHeatActive": "Heating active",
"mHeatSetpointTemp": "Heating setpoint temp",
"mHeatTargetTemp": "Heating target temp",
@@ -125,6 +125,7 @@
"mHeatRetTemp": "Heating return temp",
"mHeatIndoorTemp": "Heating, indoor temp",
"mHeatOutdoorTemp": "Heating, outdoor temp",
"mDhwEnabled": "DHW enabled",
"mDhwOverheat": "DHW overheat",
"sDhwActive": "DHW active",
@@ -132,6 +133,7 @@
"mDhwCurrTemp": "DHW current temp",
"mDhwRetTemp": "DHW return temp"
},
"sensors": {
"values": {
"temp": "Temperature",
@@ -141,19 +143,23 @@
}
}
},
"network": {
"title": "Network - OpenTherm Gateway",
"name": "Network settings",
"section": {
"static": "Static settings",
"availableNetworks": "Available networks",
"staSettings": "WiFi settings",
"apSettings": "AP settings"
},
"scan": {
"pos": "#",
"info": "Info"
},
"wifi": {
"ssid": "SSID",
"password": "Password",
@@ -161,6 +167,7 @@
"signal": "Signal",
"connected": "Connected"
},
"params": {
"hostname": "Hostname",
"dhcp": "Use DHCP",
@@ -170,15 +177,16 @@
"gateway": "Gateway",
"dns": "DNS"
},
"sta": {
"channel": {
"note": "set 0 for auto select"
}
"channel.note": "set 0 for auto select"
}
},
"sensors": {
"title": "Sensors settings - OpenTherm Gateway",
"name": "Sensors settings",
"enabled": "Enabled",
"sensorName": {
"title": "Sensor name",
@@ -234,12 +242,10 @@
"otDhwBurnerHours": "OpenTherm, number of burner operating hours (DHW)",
"otHeatingPumpHours": "OpenTherm, number of pump operating hours (heating)",
"otDhwPumpHours": "OpenTherm, number of pump operating hours (DHW)",
"otCoolingHours": "OpenTherm, number of cooling hours",
"ntcTemp": "NTC sensor",
"dallasTemp": "DALLAS sensor",
"bluetooth": "BLE sensor",
"dht11": "DHT11 sensor",
"dht22": "DHT22 sensor",
"heatSetpointTemp": "Heating, setpoint temp",
"manual": "Manual via MQTT/API",
"notConfigured": "Not configured"
@@ -266,9 +272,11 @@
}
}
},
"settings": {
"title": "Settings - OpenTherm Gateway",
"name": "Settings",
"section": {
"portal": "Portal settings",
"system": "System settings",
@@ -283,21 +291,18 @@
"extPump": "External pump settings",
"cascadeControl": "Cascade control settings"
},
"enable": "Enable",
"note": {
"restart": "After changing these settings, the device must be restarted for the changes to take effect.",
"blankNotUse": "blank - not use",
"bleDevice": "BLE device can be used <u>only</u> with some ESP32 boards with BLE support!"
},
"temp": {
"min": "Minimum temperature",
"max": "Maximum temperature"
},
"avgType": {
"mean": "Mean temperature",
"min": "Minimum temperature",
"max": "Maximum temperature"
},
"maxModulation": "Max modulation level",
"ohProtection": {
"title": "Overheating protection",
@@ -313,22 +318,18 @@
},
"freezeProtection": {
"title": "Freeze protection",
"desc": "Heating will be forced to turn on if the heat carrier or indoor temperature drops below <b>Low temperature</b>.",
"highTemp": {
"title": "High temperature threshold",
"note": "Threshold when the system returns to normal mode after freeze protection activation"
},
"lowTemp": {
"title": "Low temperature threshold",
"note": "Threshold when heating is forced to turn on"
}
"desc": "Heating will be forced to turn on if the heat carrier or indoor temperature drops below <b>Low temperature</b> during <b>Waiting time</b>.",
"lowTemp": "Low temperature threshold",
"thresholdTime": "Waiting time <small>(sec)</small>"
},
"portal": {
"login": "Login",
"password": "Password",
"auth": "Require authentication",
"mdns": "Use mDNS"
},
"system": {
"unit": "Unit system",
"metric": "Metric <small>(celsius, liters, bar)</small>",
@@ -339,8 +340,12 @@
"enable": "Enabled Serial port",
"baud": "Serial port baud rate"
},
"webSerial": {
"enable": "Enabled WebSerial"
"telnet": {
"enable": "Enabled Telnet",
"port": {
"title": "Telnet port",
"note": "Default: 23"
}
},
"ntp": {
"server": "NTP server",
@@ -348,30 +353,22 @@
"timezonePresets": "Select preset..."
}
},
"heating": {
"hyst": {
"title": "Hysteresis",
"desc": "Hysteresis is useful for maintaining a set indoor temp (when using «Equitherm» and/or «PID»). Forces disable heating when <code>current indoor > target + value</code> and enable heating when <code>current indoor < (target - value)</code>.",
"value": "Value <small>(in degrees)</small>"
},
"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»)."
}
"hyst": "Hysteresis <small>(in degrees)</small>",
"turboFactor": "Turbo mode coeff."
},
"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.",
"target": {
"title": "Target temperature",
"note": "<b>Important:</b> <u>Target indoor temperature</u> if OT option <i>«Native heating control»</i> is enabled.<br />In all other cases, the <u>target heat carrier temperature</u>."
},
"treshold": "Treshold time <small>(sec)</small>"
},
"equitherm": {
"slope": {
"title": "Slope",
@@ -379,7 +376,7 @@
},
"exponent": {
"title": "Exponent",
"note": "Radiator efficiency. Typical values: <code>1.1</code> - Floor heating, <code>1.2</code> - Cast iron, <code>1.3</code> - Panel radiators, <code>1.4</code> - Convectors."
"note": "Radiator efficiency. Typical values: 1.1 - Floor heating, 1.2 - Cast iron, 1.3 - Panel radiators, 1.4 - Convectors."
},
"shift": {
"title": "Shift",
@@ -387,7 +384,7 @@
},
"targetDiffFactor": {
"title": "T factor",
"note": "Not used if PID is enabled. Adds to the setpoint the difference between the target and current indoor temp: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
"note": "Not used if PID is enabled"
},
"chart": {
"targetTemp": "Target indoor temperature",
@@ -395,6 +392,7 @@
"outdoorTemp": "Outdoor temperature"
}
},
"pid": {
"p": "P factor",
"i": "I factor",
@@ -414,17 +412,8 @@
"thresholdLow": "Threshold low"
}
},
"ot": {
"disablingAction": {
"title": "Circuit disabling action",
"desc": "The selected action applies whenever the circuit is disabled, including overheating.<br />Some boilers stop the circulation pump when the circuit is disabled. In this case, you can try setting the target temperature to <b>0</b> so that the pump overrun works correctly.",
"heating": "Heating circuit",
"dhw": "DHW circuit",
"ch2": "Circuit 2",
"off": "Disable circuit only",
"zeroSetpoint": "Set the target temperature to <b>0</b>",
"offAndZeroSetpoint": "Disable circuit and set the target temperature to <b>0</b>"
},
"advanced": "Advanced Settings",
"inGpio": "In GPIO",
"outGpio": "Out GPIO",
@@ -439,6 +428,7 @@
"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\"."
},
"options": {
"title": "Options (additional settings)",
"desc": "Options can change the logic of the boiler. Not all options are documented in the protocol, so the same option can have different effects on different boilers.<br /><b>Note:</b> There is no need to change anything if everything works well.",
@@ -457,14 +447,15 @@
"autoFaultReset": "Auto fault reset <small>(not recommended!)</small>",
"autoDiagReset": "Auto diag reset <small>(not recommended!)</small>",
"setDateAndTime": "Set date & time on boiler",
"immergasFix": "Fix for Immergas boilers",
"alwaysSendIndoorTemp": "Send current indoor temp to boiler"
"immergasFix": "Fix for Immergas boilers"
},
"nativeOTC": {
"title": "Native OTC mode",
"note": "Works <u>ONLY</u> if the boiler is in OTC mode: requires and accepts the target indoor temperature and self-regulates the heat carrier temperature based on the built-in curves mode. Incompatible with PID and Equitherm."
"nativeHeating": {
"title": "Native heating control (boiler)",
"note": "Works <u>ONLY</u> if the boiler requires the desired room temperature and regulates the temperature of the coolant itself. Not compatible with PID and Equitherm regulators in firmware."
}
},
"mqtt": {
"homeAssistantDiscovery": "Home Assistant Discovery",
"server": "Server",
@@ -474,14 +465,15 @@
"prefix": "Prefix",
"interval": "Publish interval <small>(sec)</small>"
},
"extPump": {
"use": "Use external pump",
"gpio": "Relay GPIO",
"invertState": "Invert GPIO state",
"postCirculationTime": "Post circulation time <small>(min)</small>",
"antiStuckInterval": "Anti stuck interval <small>(days)</small>",
"antiStuckTime": "Anti stuck time <small>(min)</small>"
},
"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.",
@@ -505,49 +497,23 @@
}
}
},
"logs": {
"title": "Logs - OpenTherm Gateway",
"name": "Logs",
"desc": "Device logs in real time.",
"status": {
"connecting": "Connecting...",
"connected": "Connected",
"disconnected": "Disconnected"
},
"autoscroll": "Autoscroll",
"limitHistory": "Keep at most 1000 entries",
"historyNote": "Unlimited history uses more browser memory. Downloads include all retained entries, including those hidden by the filter.",
"filter": "Filter",
"search": {
"title": "Search",
"previous": "Previous",
"next": "Next"
},
"level": {
"note": "The level applies to device logging (Serial and WebSerial). The filter below only changes the display.",
"loadError": "Could not load the log level. Retry the request.",
"saveError": "Could not save the log level. Check the connection and try again."
},
"clear": "Clear",
"download": "Download logs"
},
"upgrade": {
"title": "Upgrade - OpenTherm Gateway",
"name": "Upgrade",
"section": {
"backupAndRestore": {
"title": "Backup & restore",
"desc": "In this section you can save and restore a backup of ALL settings."
},
"upgrade": {
"title": "Upgrade",
"desc": "In this section you can upgrade the firmware and filesystem of your device.<br />Latest releases can be downloaded from the <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases page</a> of the project repository."
}
"backupAndRestore": "Backup & restore",
"backupAndRestore.desc": "In this section you can save and restore a backup of ALL settings.",
"upgrade": "Upgrade",
"upgrade.desc": "In this section you can upgrade the firmware and filesystem of your device.<br />Latest releases can be downloaded from the <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases page</a> of the project repository."
},
"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 15 seconds."
},
"settingsFile": "Settings file",
"fw": "Firmware",
"fs": "Filesystem"
+76 -110
View File
@@ -8,21 +8,15 @@
"issues": "Problemi e domande",
"releases": "Versione"
},
"units": {
"dbm": "dBm",
"kw": "kW",
"time": {
"days": "d.",
"hours": "h.",
"min": "min.",
"sec": "sec.",
"dbm": "dBm",
"mhz": "MHz",
"kw": "kW",
"rpm": "RPM",
"ppm": "ppm",
"byte": "byte",
"mbyte": "MB",
"liter": "litro",
"gallon": "gal"
"sec": "sec."
},
"button": {
"upgrade": "Aggiorna",
"restart": "Riavvia",
@@ -37,25 +31,26 @@
"success": "Riuscito",
"error": "Errore"
},
"index": {
"title": "OpenTherm Gateway",
"section": {
"network": "Rete",
"system": "Sistema"
},
"system": {
"build": {
"title": "Build",
"version": "Versione",
"commit": "Commit",
"date": "Data",
"core": "Core",
"sdk": "SDK"
},
"uptime": "Tempo di attività",
"memory": {
"heap": "Memoria (heap)",
"psram": "Memoria (PSRAM)",
"title": "Memoria libera",
"maxFreeBlock": "max free block",
"min": "min"
},
@@ -72,22 +67,26 @@
"lastResetReason": "Motivo ultimo Reset"
}
},
"dashboard": {
"name": "Pannello",
"title": "Pannello - OpenTherm Gateway",
"section": {
"control": "Controlli",
"states": "Stato",
"sensors": "Sensori",
"diag": "Diagnostica OpenTherm"
},
"thermostat": {
"heating": "Riscaldamento",
"dhw": "ACS",
"currentTemp": "Attuale",
"temp.current": "Attuale",
"enable": "Attiva",
"turbo": "Turbo"
},
"notify": {
"fault": {
"title": "Rilevamento guasti caldiaia attivo!",
@@ -99,6 +98,7 @@
},
"reset": "Prova a resettare"
},
"states": {
"mNetworkConnected": "Connessione Rete",
"mMqttConnected": "Connessione MQTT",
@@ -106,18 +106,18 @@
"mExtPumpState": "Pompa esterna",
"mCascadeControlInput": "Controllo a cascata (input)",
"mCascadeControlOutput": "Controllo a cascata (output)",
"sConnected": "Connessione OpenTherm",
"sFlame": "Fiamma",
"sCoolingActive": "Raffrescamento",
"sCoolingSetpoint": "Raffrescamento setpoint",
"sCooling": "Raffrescamento",
"sFaultActive": "Anomalia",
"sFaultCode": "Codice anomalia",
"sDiagActive": "Diagnostica",
"sDiagCode": "Codice Diagnostica",
"mHeatEnabled": "Riscaldamento attivato",
"mHeatBlocking": "Riscaldamento bloccato",
"mHeatOverheat": "Riscaldamento surriscaldamento",
"mHeatFreezing": "Protezione antigelo",
"sHeatActive": "Riscaldamento attivo",
"mHeatSetpointTemp": "Temp riscaldamento impostato",
"mHeatTargetTemp": "Target Temp caldaia",
@@ -125,6 +125,7 @@
"mHeatRetTemp": "Temp ritorno riscaldamento",
"mHeatIndoorTemp": "Riscaldamento, temp interna",
"mHeatOutdoorTemp": "Riscaldamento, temp esterna",
"mDhwEnabled": "ACS attivata",
"mDhwOverheat": "ACS surriscaldamento",
"sDhwActive": "ACS attiva",
@@ -132,6 +133,7 @@
"mDhwCurrTemp": "ACS temp attuale",
"mDhwRetTemp": "ACS temp ricircolo"
},
"sensors": {
"values": {
"temp": "Temperatura",
@@ -141,19 +143,23 @@
}
}
},
"network": {
"title": "Rete - OpenTherm Gateway",
"name": "Impostazioni rete",
"section": {
"static": "Impostazioni statico",
"availableNetworks": "Reti disponibili",
"staSettings": "Impostazioni WiFi",
"apSettings": "Impostazioni AP"
},
"scan": {
"pos": "#",
"info": "Info"
},
"wifi": {
"ssid": "SSID",
"password": "Password",
@@ -161,6 +167,7 @@
"signal": "Segnale",
"connected": "Connesso"
},
"params": {
"hostname": "Hostname",
"dhcp": "Usa DHCP",
@@ -170,15 +177,16 @@
"gateway": "Gateway",
"dns": "DNS"
},
"sta": {
"channel": {
"note": "Metti 0 per auto selezione"
}
"channel.note": "Metti 0 per auto selezione"
}
},
"sensors": {
"title": "Impostazione sensori - OpenTherm Gateway",
"name": "Impostazione sensori",
"enabled": "Attivato",
"sensorName": {
"title": "Nome sensore",
@@ -234,12 +242,10 @@
"otDhwBurnerHours": "OpenTherm, numero di ore di funzionamento del bruciatore (ACS)",
"otHeatingPumpHours": "OpenTherm, numero di ore di funzionamento della pompa (riscaldamento)",
"otDhwPumpHours": "OpenTherm, numero di ore di funzionamento della pompa (ACS)",
"otCoolingHours": "OpenTherm, numero di ore di funzionamento della cooling",
"ntcTemp": "Sensore NTC",
"dallasTemp": "Sensore DALLAS",
"bluetooth": "Sensore BLE",
"dht11": "Sensore DHT11",
"dht22": "Sensore DHT22",
"heatSetpointTemp": "Riscaldamento, temp impostata",
"manual": "Manuale via MQTT/API",
"notConfigured": "Non configurato"
@@ -266,9 +272,11 @@
}
}
},
"settings": {
"title": "Impostazioni - OpenTherm Gateway",
"name": "Impostazioni",
"section": {
"portal": "Impostazioni Accesso",
"system": "Impostazioni sistema",
@@ -283,21 +291,18 @@
"extPump": "Impostazioni pompa esterna",
"cascadeControl": "Impostazioni controllo a cascata"
},
"enable": "Attiva",
"note": {
"restart": "Dopo aver cambiato queste impostazioni, il sistema sarà riavviato perchè i cambiamenti abbiano effetto.",
"blankNotUse": "vuoto - non usare",
"bleDevice": "Dispositivi BLE possono essere usati <u>solo</u> con alcune schede ESP32 che supportano il bluetooth!"
},
"temp": {
"min": "Temperatura minima",
"max": "Temperatura massima"
},
"avgType": {
"mean": "Temperatura media",
"min": "Temperatura minima",
"max": "Temperatura massima"
},
"maxModulation": "Max livello modulazione",
"ohProtection": {
"title": "Protezione contro il surriscaldamento",
@@ -313,22 +318,18 @@
},
"freezeProtection": {
"title": "Protezione antigelo",
"desc": "Il riscaldamento verrà forzatamente attivato se la temperatura del vettore termico o la temperatura interna scende al di sotto della <b>Soglia di temperatura bassa</b>.",
"highTemp": {
"title": "Soglia di temperatura alta",
"note": "Soglia quando il sistema ritorna alla modalità normale dopo l'attivazione della protezione antigelo"
},
"lowTemp": {
"title": "Soglia di temperatura bassa",
"note": "Soglia quando il riscaldamento viene forzatamente attivato"
}
"desc": "Il riscaldamento verrà attivato forzatamente se la temperatura del vettore di calore o interna scende al di sotto della <b>temperatura minima</b> durante il <b>tempo di attesa</b>.",
"lowTemp": "Soglia di temperatura minima",
"thresholdTime": "Tempo di attesa <small>(sec)</small>"
},
"portal": {
"login": "Login",
"password": "Password",
"auth": "Richiede autenticazione",
"mdns": "Usa mDNS"
},
"system": {
"unit": "Unità di misura",
"metric": "Metrico <small>(celsius, litri, bar)</small>",
@@ -339,8 +340,12 @@
"enable": "Porta seriale attivata",
"baud": "Porta seriale baud rate"
},
"webSerial": {
"enable": "WebSerial attivato"
"telnet": {
"enable": "Telnet attivato",
"port": {
"title": "Porta Telnet",
"note": "Default: 23"
}
},
"ntp": {
"server": "NTP server",
@@ -348,53 +353,46 @@
"timezonePresets": "Seleziona preimpostato..."
}
},
"heating": {
"hyst": {
"title": "Isteresi",
"desc": "L'isteresi è utile per mantenere una temperatura interna impostata (quando si utilizza «Equitherm» e/o «PID»). Forza la disabilitazione del riscaldamento quando <code>current indoor > target + value</code> e abilita il riscaldamento quando <code>current indoor < (target - value)</code>.",
"value": "Valore <small>(in gradi)</small>"
},
"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»)."
}
"hyst": "Isteresi <small>(in gradi)</small>",
"turboFactor": "Turbo mode coeff."
},
"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.",
"target": {
"title": "Temperatura impostata",
"note": "<b>Importante:</b> <u>Temperatura interna impostata</u> se l'opzione OT <i>«Controllo riscaldamento interno»</i> è attivato.<br />In tutti gli altri casi, la <u>target heat carrier temperature</u>."
},
"treshold": "Tempo di soglia <small>(sec)</small>"
},
"equitherm": {
"slope": {
"title": "Pendenza",
"note": "Compensazione della perdita di calore. Parametro di regolazione principale."
"note": "Compensazione delle perdite di calore. Principale parametro di regolazione."
},
"exponent": {
"title": "Esponente",
"note": "Efficienza del radiatore. Valori tipici: <code>1.1</code> - Riscaldamento a pavimento, <code>1.2</code> - Ghisa, <code>1.3</code> - Radiatori a pannello, <code>1.4</code> - Convettori."
"note": "Efficienza del radiatore. Valori tipici: 1.1 - Riscaldamento a pavimento, 1.2 - Radiatori in ghisa, 1.3 - Radiatori a pannelli, 1.4 - Convetttori."
},
"shift": {
"title": "Spostamento",
"note": "Compensa perdite di calore aggiuntive (ad es., nelle tubature) o fonti di calore extra."
"title": "Spostare",
"note": "Compensa le perdite di calore aggiuntive (ad esempio, nelle tubature) o fonti di calore extra."
},
"targetDiffFactor": {
"title": "Fattore T",
"note": "Non utilizzato se PID è abilitato. Aggiunge al setpoint la differenza tra la temperatura target e quella interna attuale: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
"note": "Non usato se PID è attivato"
},
"chart": {
"targetTemp": "Temperatura interna target",
"setpointTemp": "Temperatura del vettore termico",
"setpointTemp": "Temperatura del portatore di calore",
"outdoorTemp": "Temperatura esterna"
}
},
"pid": {
"p": "Fattore P",
"i": "Fattore I",
@@ -414,17 +412,8 @@
"thresholdLow": "Soglia inferiore"
}
},
"ot": {
"disablingAction": {
"title": "Azione di disattivazione dei circuiti",
"desc": "L'azione selezionata si applica a ogni disattivazione del circuito, anche in caso di surriscaldamento.<br />Alcune caldaie arrestano la pompa di circolazione quando il circuito viene disattivato; in questo caso si può provare a impostare la temperatura desiderata a <b>0</b>, affinché la post-circolazione della pompa funzioni correttamente.",
"heating": "Circuito di riscaldamento",
"dhw": "Circuito ACS",
"ch2": "Circuito 2",
"off": "Disattiva solo il circuito",
"zeroSetpoint": "Imposta la temperatura desiderata a <b>0</b>",
"offAndZeroSetpoint": "Disattiva il circuito e imposta la temperatura desiderata a <b>0</b>"
},
"advanced": "Impostazioni avanzate",
"inGpio": "In GPIO",
"outGpio": "Out GPIO",
@@ -439,6 +428,7 @@
"title": "Potenza massima caldaia <small>(kW)</small>",
"note": "<b>0</b> - prova a rilevarla automaticamente. Di solito si trova nelle specifiche delle caldaia come \"potenza massima disponibile\"."
},
"options": {
"title": "Opzioni (impostazioni aggiuntive)",
"desc": "Le opzioni possono modificare la logica della caldaia. Non tutte le opzioni sono documentate nel protocollo, quindi la stessa opzione può avere effetti diversi su caldaie diverse.<br /><b>Nota:</b> Non è necessario modificare nulla se tutto funziona correttamente.",
@@ -457,14 +447,15 @@
"autoFaultReset": "Ripristino automatico degli errori <small>(sconsigliato!)</small>",
"autoDiagReset": "Ripristino diagnostico automatica <small>(sconsigliato!)</small>",
"setDateAndTime": "Imposta data e ora sulla caldaia",
"immergasFix": "Fix per caldiaie Immergas",
"alwaysSendIndoorTemp": "Invia la temp attuale interna alla caldaia"
"immergasFix": "Fix per caldiaie Immergas"
},
"nativeOTC": {
"title": "Modalità nativa di calcolo della temperatura del vettore termico",
"note": "Funziona <u>SOLO</u> se la caldaia è in modalità OTC: richiede e accetta la temperatura interna target e regola autonomamente la temperatura del vettore termico basata sulla modalità curve integrata. Incompatibile con PID e Equitherm."
"nativeHeating": {
"title": "Controllo del riscaldamento nativo (caldaia)",
"note": "Lavora <u>SOLO</u> se la caldaia richiede la temperatura ambiente desiderata e regola autonomamente la temperatura del fluido. Non compatiblile con regolazioni PID e Equitherm del sistema."
}
},
"mqtt": {
"homeAssistantDiscovery": "Home Assistant Discovery",
"server": "Server",
@@ -474,14 +465,15 @@
"prefix": "Prefisso",
"interval": "Intervallo invio <small>(sec)</small>"
},
"extPump": {
"use": "Usa pompa/circolatore esterno",
"gpio": "GPIO relè",
"invertState": "Inverti stato GPIO",
"postCirculationTime": "Tempo di post circolazione <small>(min)</small>",
"antiStuckInterval": "Intervallo antiblocco <small>(days)</small>",
"antiStuckTime": "Tempo antiblocco <small>(min)</small>"
},
"cascadeControl": {
"input": {
"desc": "Può essere attivata la caldaia se un'altra ha fallito. Il controllo dell'altra caldaia cambia lo stato dell'ingresso del GPIO in caso di errore.",
@@ -505,49 +497,23 @@
}
}
},
"logs": {
"title": "Registro - OpenTherm Gateway",
"name": "Registro",
"desc": "Registro del dispositivo in tempo reale.",
"status": {
"connecting": "Connessione in corso...",
"connected": "Connesso",
"disconnected": "Disconnesso"
},
"autoscroll": "Scorrimento automatico",
"limitHistory": "Conserva al massimo 1000 voci",
"historyNote": "La cronologia illimitata usa più memoria del browser. Il download include tutte le voci conservate, anche quelle nascoste dal filtro.",
"filter": "Filtro",
"search": {
"title": "Cerca",
"previous": "Precedente",
"next": "Successivo"
},
"level": {
"note": "Il livello si applica al registro del dispositivo (Serial e WebSerial). Il filtro sottostante cambia solo la visualizzazione.",
"loadError": "Impossibile caricare il livello del registro. Riprovare la richiesta.",
"saveError": "Impossibile salvare il livello del registro. Controllare la connessione e riprovare."
},
"clear": "Cancella",
"download": "Scarica il registro"
},
"upgrade": {
"title": "Aggiornamenti - OpenTherm Gateway",
"name": "Aggiornamenti",
"section": {
"backupAndRestore": {
"title": "Backup & restore",
"desc": "In questa sezione puoi salvare e recuperare un backup di tutte le impostazioni."
},
"upgrade": {
"title": "Aggiorna",
"desc": "In questa sezione puoi aggiornare il firmware il filesystem del tuo dispositivo.<br />L'ultimo aggiornamento può essere scaricato da <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases page</a> del progetto."
}
"backupAndRestore": "Backup & restore",
"backupAndRestore.desc": "In questa sezione puoi salvare e recuperare un backup di tutte le impostazioni.",
"upgrade": "Aggiorna",
"upgrade.desc": "In questa sezione puoi aggiornare il firmware il filesystem del tuo dispositivo.<br />L'ultimo aggiornamento può essere scaricato da <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases page</a> del progetto."
},
"note": {
"disclaimer1": "Dopo un aggiornamento riuscito del filesystem, tutte le impostazioni sono impostate di default! Salva un backup prima di aggiornare.",
"disclaimer2": "Dopo un aggiornamento riuscito, il sistema viene automaticamente riavviato dopo 15 secondi."
},
"settingsFile": "Settings file",
"fw": "Firmware",
"fs": "Filesystem"
-556
View File
@@ -1,556 +0,0 @@
{
"values": {
"logo": "OpenTherm Gateway",
"nav": {
"license": "Licentie",
"source": "Broncode",
"help": "Help",
"issues": "Problemen & vragen",
"releases": "Releases"
},
"units": {
"days": "d.",
"hours": "u.",
"min": "min.",
"sec": "sec.",
"dbm": "dBm",
"mhz": "MHz",
"kw": "kW",
"rpm": "RPM",
"ppm": "ppm",
"byte": "byte",
"mbyte": "MB",
"liter": "L",
"gallon": "gal"
},
"button": {
"upgrade": "Upgraden",
"restart": "Herstarten",
"save": "Opslaan",
"saved": "Opgeslagen",
"refresh": "Vernieuwen",
"restore": "Herstellen",
"restored": "Hersteld",
"backup": "Back-up",
"wait": "Even wachten...",
"uploading": "Uploaden...",
"success": "Succes",
"error": "Fout"
},
"index": {
"title": "OpenTherm Gateway",
"section": {
"network": "Netwerk",
"system": "Systeem"
},
"system": {
"build": {
"title": "Build",
"version": "Versie",
"commit": "Commit",
"date": "Datum",
"core": "Core",
"sdk": "SDK"
},
"uptime": "Uptime",
"memory": {
"heap": "Geheugen (heap)",
"psram": "Geheugen (PSRAM)",
"maxFreeBlock": "max. vrij blok",
"min": "min"
},
"board": "Board",
"chip": {
"model": "Chip",
"cores": "Kernen",
"freq": "frequentie"
},
"flash": {
"size": "Flash-grootte",
"realSize": "werkelijke grootte"
},
"lastResetReason": "Reden laatste herstart"
}
},
"dashboard": {
"name": "Dashboard",
"title": "Dashboard - OpenTherm Gateway",
"section": {
"control": "Bediening",
"states": "Statussen",
"sensors": "Sensoren",
"diag": "OpenTherm diagnose"
},
"thermostat": {
"heating": "Verwarming",
"dhw": "Warm water",
"currentTemp": "Huidig",
"enable": "Inschakelen",
"turbo": "Turbomodus"
},
"notify": {
"fault": {
"title": "Ketelstoring is actief!",
"note": "Het wordt aanbevolen de ketel te inspecteren en de documentatie te raadplegen om de storingscode te interpreteren:"
},
"diag": {
"title": "Keteldiagnose is actief!",
"note": "Heeft uw ketel misschien een inspectie nodig? Het wordt aanbevolen de documentatie te raadplegen om de diagnosecode te interpreteren:"
},
"reset": "Probeer te resetten"
},
"states": {
"mNetworkConnected": "Netwerkverbinding",
"mMqttConnected": "MQTT-verbinding",
"mEmergencyState": "Noodmodus",
"mExtPumpState": "Externe pomp",
"mCascadeControlInput": "Cascaderegeling (ingang)",
"mCascadeControlOutput": "Cascaderegeling (uitgang)",
"sConnected": "OpenTherm-verbinding",
"sFlame": "Vlam",
"sCoolingActive": "Koeling",
"sCoolingSetpoint": "Koelinstelpunt",
"sFaultActive": "Storing",
"sFaultCode": "Storingscode",
"sDiagActive": "Diagnose",
"sDiagCode": "Diagnosecode",
"mHeatEnabled": "Verwarming ingeschakeld",
"mHeatBlocking": "Verwarming geblokkeerd",
"mHeatOverheat": "Verwarming oververhit",
"mHeatFreezing": "Verwarming vorstbescherming",
"sHeatActive": "Verwarming actief",
"mHeatSetpointTemp": "Insteltemperatuur verwarming",
"mHeatTargetTemp": "Doeltemperatuur verwarming",
"mHeatCurrTemp": "Huidige temperatuur verwarming",
"mHeatRetTemp": "Retourtemperatuur verwarming",
"mHeatIndoorTemp": "Verwarming, binnentemperatuur",
"mHeatOutdoorTemp": "Verwarming, buitentemperatuur",
"mDhwEnabled": "Warm water ingeschakeld",
"mDhwOverheat": "Warm water oververhit",
"sDhwActive": "Warm water actief",
"mDhwTargetTemp": "Doeltemperatuur warm water",
"mDhwCurrTemp": "Huidige temperatuur warm water",
"mDhwRetTemp": "Retourtemperatuur warm water"
},
"sensors": {
"values": {
"temp": "Temperatuur",
"humidity": "Luchtvochtigheid",
"battery": "Batterij",
"rssi": "RSSI"
}
}
},
"network": {
"title": "Netwerk - OpenTherm Gateway",
"name": "Netwerkinstellingen",
"section": {
"static": "Statische instellingen",
"availableNetworks": "Beschikbare netwerken",
"staSettings": "WiFi-instellingen",
"apSettings": "AP-instellingen"
},
"scan": {
"pos": "#",
"info": "Info"
},
"wifi": {
"ssid": "SSID",
"password": "Wachtwoord",
"channel": "Kanaal",
"signal": "Signaal",
"connected": "Verbonden"
},
"params": {
"hostname": "Hostnaam",
"dhcp": "Gebruik DHCP",
"mac": "MAC",
"ip": "IP",
"subnet": "Subnet",
"gateway": "Gateway",
"dns": "DNS"
},
"sta": {
"channel": {
"note": "zet op 0 voor automatische selectie"
}
}
},
"sensors": {
"title": "Sensorinstellingen - OpenTherm Gateway",
"name": "Sensorinstellingen",
"enabled": "Ingeschakeld",
"sensorName": {
"title": "Sensornaam",
"note": "Mag alleen bevatten: a-z, A-Z, 0-9, _ en spatie"
},
"purpose": "Doel",
"purposes": {
"outdoorTemp": "Buitentemperatuur",
"indoorTemp": "Binnentemperatuur",
"heatTemp": "Verwarming, temperatuur",
"heatRetTemp": "Verwarming, retourtemperatuur",
"dhwTemp": "Warm water, temperatuur",
"dhwRetTemp": "Warm water, retourtemperatuur",
"dhwFlowRate": "Warm water, doorstroomsnelheid",
"exhaustTemp": "Rookgastemperatuur",
"modLevel": "Modulatieniveau (in procenten)",
"number": "Getal (ruw)",
"powerFactor": "Vermogen (in procent)",
"power": "Vermogen (in kWt)",
"fanSpeed": "Ventilatorsnelheid",
"co2": "CO2",
"pressure": "Druk",
"humidity": "Luchtvochtigheid",
"temperature": "Temperatuur",
"notConfigured": "Niet geconfigureerd"
},
"type": "Type/bron",
"types": {
"otOutdoorTemp": "OpenTherm, buitentemp.",
"otHeatTemp": "OpenTherm, verwarming, temp.",
"otHeatRetTemp": "OpenTherm, verwarming, retourtemp.",
"otDhwTemp": "OpenTherm, warm water, temperatuur",
"otDhwTemp2": "OpenTherm, warm water, temperatuur 2",
"otDhwFlowRate": "OpenTherm, warm water, doorstroomsnelheid",
"otCh2Temp": "OpenTherm, kanaal 2, temp.",
"otExhaustTemp": "OpenTherm, rookgastemp.",
"otHeatExchangerTemp": "OpenTherm, warmtewisselaar temp.",
"otPressure": "OpenTherm, druk",
"otModLevel": "OpenTherm, modulatieniveau",
"otCurrentPower": "OpenTherm, huidig vermogen",
"otExhaustCo2": "OpenTherm, rookgas CO2",
"otExhaustFanSpeed": "OpenTherm, rookgasventilator snelheid",
"otSupplyFanSpeed": "OpenTherm, toevoerventilator snelheid",
"otSolarStorageTemp": "OpenTherm, zonneboiler opslagtemp.",
"otSolarCollectorTemp": "OpenTherm, zonnecollector temp.",
"otFanSpeedSetpoint": "OpenTherm, instelpunt ventilatorsnelheid",
"otFanSpeedCurrent": "OpenTherm, huidige ventilatorsnelheid",
"otBurnerStarts": "OpenTherm, aantal branderstarts",
"otDhwBurnerStarts": "OpenTherm, aantal branderstarts (warm water)",
"otHeatingPumpStarts": "OpenTherm, aantal pompstarts (verwarming)",
"otDhwPumpStarts": "OpenTherm, aantal pompstarts (warm water)",
"otBurnerHours": "OpenTherm, aantal branderuren",
"otDhwBurnerHours": "OpenTherm, aantal branderuren (warm water)",
"otHeatingPumpHours": "OpenTherm, aantal pompuren (verwarming)",
"otDhwPumpHours": "OpenTherm, aantal pompuren (warm water)",
"otCoolingHours": "OpenTherm, aantal cooling",
"ntcTemp": "NTC-sensor",
"dallasTemp": "DALLAS-sensor",
"bluetooth": "BLE-sensor",
"dht11": "DHT11-sensor",
"dht22": "DHT22-sensor",
"heatSetpointTemp": "Verwarming, insteltemperatuur",
"manual": "Handmatig via MQTT/API",
"notConfigured": "Niet geconfigureerd"
},
"gpio": "GPIO",
"address": {
"title": "Sensoradres",
"note": "Laat leeg voor automatische detectie van DALLAS-sensoren. Voor BLE-apparaten is een MAC-adres vereist."
},
"correction": {
"desc": "Correctie van waarden",
"offset": "Compensatie (offset)",
"factor": "Vermenigvuldiger"
},
"filtering": {
"desc": "Filteren van waarden",
"enabled": {
"title": "Filteren ingeschakeld",
"note": "Kan handig zijn bij veel scherpe ruis in de grafieken. Het gebruikte filter is \"Voortschrijdend gemiddelde\"."
},
"factor": {
"title": "Filterfactor",
"note": "Hoe lager de waarde, hoe vloeiender en <u>langer</u> de verandering in numerieke waarden."
}
}
},
"settings": {
"title": "Instellingen - OpenTherm Gateway",
"name": "Instellingen",
"section": {
"portal": "Portaalinstellingen",
"system": "Systeeminstellingen",
"diag": "Diagnose",
"heating": "Verwarmingsinstellingen",
"dhw": "Warmwaterinstellingen",
"emergency": "Instellingen noodmodus",
"equitherm": "Equitherm-instellingen",
"pid": "PID-instellingen",
"ot": "OpenTherm-instellingen",
"mqtt": "MQTT-instellingen",
"extPump": "Instellingen externe pomp",
"cascadeControl": "Instellingen cascaderegeling"
},
"enable": "Inschakelen",
"note": {
"restart": "Na het wijzigen van deze instellingen moet het apparaat opnieuw worden opgestart om de wijzigingen door te voeren.",
"blankNotUse": "leeg - niet gebruiken",
"bleDevice": "BLE-apparaat kan <u>alleen</u> worden gebruikt met sommige ESP32-boards met BLE-ondersteuning!"
},
"temp": {
"min": "Minimumtemperatuur",
"max": "Maximumtemperatuur"
},
"avgType": {
"mean": "Gemiddelde temperatuur",
"min": "Minimum temperatuur",
"max": "Maximum temperatuur"
},
"maxModulation": "Max. modulatieniveau",
"ohProtection": {
"title": "Oververhittingsbeveiliging",
"desc": "<b>Let op:</b> Deze functie kan handig zijn als de ingebouwde oververhittingsbeveiliging van de ketel niet of niet correct werkt en de warmtedrager kookt. Om uit te schakelen, stel 0 in als <b>hoge</b> en <b>lage</b> temperatuur.",
"highTemp": {
"title": "Drempelwaarde hoge temperatuur",
"note": "Drempelwaarde waarbij de brander geforceerd wordt uitgeschakeld"
},
"lowTemp": {
"title": "Drempelwaarde lage temperatuur",
"note": "Drempelwaarde waarbij de brander weer ingeschakeld kan worden"
}
},
"freezeProtection": {
"title": "Vorbeveiliging",
"desc": "Verwarming zal geforceerd worden ingeschakeld als de temperatuur van de warmtedrager of de binnentemperatuur daalt onder de <b>Lage temperatuurdrempel</b>.",
"highTemp": {
"title": "Hoge temperatuurdrempel",
"note": "Drempel waarna het systeem terugkeert naar de normale modus na activering van de vorbeveiliging"
},
"lowTemp": {
"title": "Lage temperatuurdrempel",
"note": "Drempel wanneer de verwarming geforceerd wordt ingeschakeld"
}
},
"portal": {
"login": "Gebruikersnaam",
"password": "Wachtwoord",
"auth": "Authenticatie vereisen",
"mdns": "Gebruik mDNS"
},
"system": {
"unit": "Eenheidssysteem",
"metric": "Metrisch <small>(celsius, liters, bar)</small>",
"imperial": "Imperiaal <small>(fahrenheit, gallons, psi)</small>",
"statusLedGpio": "Status LED GPIO",
"logLevel": "Logniveau",
"serial": {
"enable": "Seriële poort ingeschakeld",
"baud": "Baudrate seriële poort"
},
"webSerial": {
"enable": "WebSerial ingeschakeld"
},
"ntp": {
"server": "NTP-server",
"timezone": "Tijdzone",
"timezonePresets": "Selecteer voorinstelling..."
}
},
"heating": {
"hyst": {
"title": "Hysterese",
"desc": "Hysterese is nuttig voor het handhaven van een ingestelde binnentemperatuur (bij gebruik van «Equitherm» en/of «PID»). Forceert uitschakelen van verwarming wanneer <code>current indoor > target + value</code> en inschakelen van verwarming wanneer <code>current indoor < (target - value)</code>.",
"value": "Waarde <small>(in graden)</small>"
},
"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": {
"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.",
"target": {
"title": "Doeltemperatuur",
"note": "<b>Belangrijk:</b> <u>Doel binnentemperatuur</u> als OT-optie <i>«Natuurlijke verwarmingsregeling»</i> is ingeschakeld.<br />In alle andere gevallen, de <u>doel warmtedragertemperatuur</u>."
},
"treshold": "Drempeltijd <small>(sec)</small>"
},
"equitherm": {
"slope": {
"title": "Helling",
"note": "Compensatie voor warmteverlies. Hoofdafstelparameter."
},
"exponent": {
"title": "Exponent",
"note": "Radiator efficiëntie. Typische waarden: <code>1.1</code> - Vloerverwarming, <code>1.2</code> - Gietijzer, <code>1.3</code> - Paneelradiatoren, <code>1.4</code> - Convectors."
},
"shift": {
"title": "Verschuiving",
"note": "Compenseert voor extra warmteverliezen (bijv. in leidingen) of extra warmtebronnen."
},
"targetDiffFactor": {
"title": "T factor",
"note": "Niet gebruikt als PID is ingeschakeld. Voegt aan de setpoint de verschil tussen de target en huidige binnentemperatuur toe: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
},
"chart": {
"targetTemp": "Doel binnentemperatuur",
"setpointTemp": "Warmtedrager temperatuur",
"outdoorTemp": "Buitentemperatuur"
}
},
"pid": {
"p": "P-factor",
"i": "I-factor",
"d": "D-factor",
"dt": "DT <small>in seconden</small>",
"limits": {
"title": "Limieten",
"note": "<b>Belangrijk:</b> Bij gelijktijdig gebruik van «Equitherm» en «PID» beperken de min- en maxtemperaturen de invloed op de resulterende «Equitherm»-temperatuur.<br />Dus, als de min-temperatuur is ingesteld op -15 en de max-temperatuur op 15, zal het uiteindelijke instelpunt van de warmtedrager liggen tussen <code>equitherm_resultaat - 15</code> en <code>equitherm_resultaat + 15</code>."
},
"deadband": {
"title": "Deadband",
"note": "Deadband is een bereik rond de doeltemperatuur waarbinnen de PID-regeling minder actief wordt. Binnen dit bereik kan het algoritme de intensiteit verminderen of aanpassingen pauzeren om overreactie op kleine schommelingen te voorkomen.<br /><br />Bijvoorbeeld, met een doeltemperatuur van 22°, een onderdrempel van 1.0 en een bovendrempel van 0.5, werkt de deadband tussen 21° en 22.5°. Als de I-coëfficiënt 0.0005 is en de I-vermenigvuldiger 0.05, dan wordt de I-coëfficiënt binnen de deadband: <code>0.0005 * 0.05 = 0.000025</code>",
"p_multiplier": "Vermenigvuldiger voor P-factor",
"i_multiplier": "Vermenigvuldiger voor I-factor",
"d_multiplier": "Vermenigvuldiger voor D-factor",
"thresholdHigh": "Bovendrempel",
"thresholdLow": "Onderdrempel"
}
},
"ot": {
"disablingAction": {
"title": "Actie bij uitschakelen van circuits",
"desc": "De gekozen actie geldt bij elke uitschakeling van het circuit, ook bij oververhitting.<br />Sommige ketels schakelen de circulatiepomp uit wanneer het circuit wordt uitgeschakeld. In dat geval kunt u proberen de doeltemperatuur op <b>0</b> in te stellen, zodat de pompnaloop correct werkt.",
"heating": "Verwarmingscircuit",
"dhw": "Warmtapwatercircuit",
"ch2": "Circuit 2",
"off": "Alleen het circuit uitschakelen",
"zeroSetpoint": "Doeltemperatuur op <b>0</b> instellen",
"offAndZeroSetpoint": "Circuit uitschakelen en doeltemperatuur op <b>0</b> instellen"
},
"advanced": "Geavanceerde instellingen",
"inGpio": "In GPIO",
"outGpio": "Uit GPIO",
"ledGpio": "RX LED GPIO",
"memberId": "Master member ID",
"flags": "Master flags",
"minPower": {
"title": "Min. ketelvermogen <small>(kW)</small>",
"note": "Deze waarde is bij 0-1% modulatieniveau van de ketel. Typisch te vinden in de ketelspecificatie als \"minimum nuttig warmtevermogen\"."
},
"maxPower": {
"title": "Max. ketelvermogen <small>(kW)</small>",
"note": "<b>0</b> - probeer automatisch te detecteren. Typisch te vinden in de ketelspecificatie als \"maximum nuttig warmtevermogen\"."
},
"options": {
"title": "Opties (aanvullende instellingen)",
"desc": "Opties kunnen de logica van de ketel veranderen. Niet alle opties zijn gedocumenteerd in het protocol, dus dezelfde optie kan verschillende effecten hebben op verschillende ketels.<br /><b>Let op:</b> Het is niet nodig om iets te veranderen als alles goed werkt.",
"dhwSupport": "Warm water ondersteuning",
"coolingSupport": "Koeling ondersteuning",
"summerWinterMode": "Zomer/wintermodus",
"heatingStateToSummerWinterMode": "Verwarmingsstatus als zomer/wintermodus",
"ch2AlwaysEnabled": "CH2 altijd ingeschakeld",
"heatingToCh2": "Dupliceer verwarming naar CH2",
"dhwToCh2": "Dupliceer warm water naar CH2",
"dhwBlocking": "Blokkering warm water",
"dhwStateAsDhwBlocking": "Status warm water als blokkering warm water",
"maxTempSyncWithTargetTemp": "Synchroniseer max. verwarmingstemp. met doeltemp.",
"getMinMaxTemp": "Haal min/max temp. op van ketel",
"ignoreDiagState": "Negeer diagnosestatus",
"autoFaultReset": "Automatische storingsreset <small>(niet aanbevolen!)</small>",
"autoDiagReset": "Automatische diagnosereset <small>(niet aanbevolen!)</small>",
"setDateAndTime": "Stel datum & tijd in op ketel",
"immergasFix": "Fix voor Immergas-ketels",
"alwaysSendIndoorTemp": "Stuur huidige binnentemp naar ketel"
},
"nativeOTC": {
"title": "Native warmtedrager temperatuur berekeningsmodus",
"note": "Werkt <u>ALLEEN</u> als de ketel in OTC-modus is: vereist en accepteert de doel binnentemperatuur en regelt zelf de warmtedrager temperatuur op basis van de ingebouwde curves modus. Incompatibel met PID en Equitherm."
}
},
"mqtt": {
"homeAssistantDiscovery": "Home Assistant Discovery",
"server": "Server",
"port": "Poort",
"user": "Gebruiker",
"password": "Wachtwoord",
"prefix": "Prefix",
"interval": "Publicatie-interval <small>(sec)</small>"
},
"extPump": {
"use": "Gebruik externe pomp",
"gpio": "Relais GPIO",
"invertState": "Inverteer GPIO-status",
"postCirculationTime": "Nacirculatietijd <small>(min)</small>",
"antiStuckInterval": "Anti-vastloopinterval <small>(dagen)</small>",
"antiStuckTime": "Anti-vastlooptijd <small>(min)</small>"
},
"cascadeControl": {
"input": {
"desc": "Kan worden gebruikt om de verwarming alleen in te schakelen als een andere ketel defect is. De besturing van de andere ketel moet de status van de GPIO-ingang wijzigen in geval van een storing.",
"enable": "Ingang ingeschakeld",
"gpio": "GPIO",
"invertState": "Inverteer GPIO-status",
"thresholdTime": "Drempeltijd statuswijziging <small>(sec)</small>"
},
"output": {
"desc": "Kan worden gebruikt om een andere ketel in te schakelen <u>via een relais</u>.",
"enable": "Uitgang ingeschakeld",
"gpio": "GPIO",
"invertState": "Inverteer GPIO-status",
"thresholdTime": "Drempeltijd statuswijziging <small>(sec)</small>",
"events": {
"desc": "Gebeurtenissen",
"onFault": "Als de storingsstatus actief is",
"onLossConnection": "Als de verbinding via Opentherm is verbroken",
"onEnabledHeating": "Als de verwarming is ingeschakeld"
}
}
}
},
"logs": {
"title": "Logboek - OpenTherm Gateway",
"name": "Logboek",
"desc": "Apparaatlogboek in realtime.",
"status": {
"connecting": "Verbinden...",
"connected": "Verbonden",
"disconnected": "Verbinding verbroken"
},
"autoscroll": "Automatisch scrollen",
"limitHistory": "Bewaar maximaal 1000 regels",
"historyNote": "Onbeperkte geschiedenis gebruikt meer browsergeheugen. Downloads bevatten alle bewaarde regels, ook de regels die door het filter zijn verborgen.",
"filter": "Filter",
"search": {
"title": "Zoeken",
"previous": "Vorige",
"next": "Volgende"
},
"level": {
"note": "Het niveau geldt voor het apparaatlogboek (Serial en WebSerial). Het onderstaande filter wijzigt alleen de weergave.",
"loadError": "Het logniveau kon niet worden geladen. Probeer opnieuw.",
"saveError": "Het logniveau kon niet worden opgeslagen. Controleer de verbinding en probeer opnieuw."
},
"clear": "Wissen",
"download": "Logboek downloaden"
},
"upgrade": {
"title": "Upgrade - OpenTherm Gateway",
"name": "Upgrade",
"section": {
"backupAndRestore": {
"title": "Back-up & herstel",
"desc": "In deze sectie kunt u een back-up van ALLE instellingen opslaan en herstellen."
},
"upgrade": {
"title": "Upgrade",
"desc": "In deze sectie kunt u de firmware en het bestandssysteem van uw apparaat upgraden.<br />De nieuwste releases kunnen worden gedownload van de <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Releases-pagina</a> van de projectrepository."
}
},
"note": {
"disclaimer1": "Na een succesvolle upgrade van het bestandssysteem worden ALLE instellingen teruggezet naar de standaardwaarden! Sla een back-up op voordat u gaat upgraden.",
"disclaimer2": "Na een succesvolle upgrade zal het apparaat automatisch herstarten na 15 seconden."
},
"settingsFile": "Instellingenbestand",
"fw": "Firmware",
"fs": "Bestandssysteem"
}
}
}
+72 -106
View File
@@ -8,21 +8,15 @@
"issues": "Проблемы и вопросы",
"releases": "Релизы"
},
"units": {
"dbm": "дБм",
"kw": "кВт",
"time": {
"days": "д.",
"hours": "ч.",
"min": "мин.",
"sec": "сек.",
"dbm": "дБм",
"mhz": "МГц",
"kw": "кВт",
"rpm": "RPM",
"ppm": "ppm",
"byte": "байт",
"mbyte": "мбайт",
"liter": "Л.",
"gallon": "гал."
"sec": "сек."
},
"button": {
"upgrade": "Обновить",
"restart": "Перезагрузка",
@@ -37,25 +31,26 @@
"success": "Успешно",
"error": "Ошибка"
},
"index": {
"title": "OpenTherm Gateway",
"section": {
"network": "Сеть",
"system": "Система"
},
"system": {
"build": {
"title": "Билд",
"version": "Версия",
"commit": "Коммит",
"date": "Дата",
"core": "Ядро",
"sdk": "SDK"
},
"uptime": "Аптайм",
"memory": {
"heap": "Память (heap)",
"psram": "Память (PSRAM)",
"title": "ОЗУ",
"maxFreeBlock": "макс. блок",
"min": "мин."
},
@@ -72,22 +67,26 @@
"lastResetReason": "Причина перезагрузки"
}
},
"dashboard": {
"name": "Дашборд",
"title": "Дашборд - OpenTherm Gateway",
"section": {
"control": "Управление",
"states": "Состояние",
"sensors": "Сенсоры",
"diag": "Диагностика OpenTherm"
},
"thermostat": {
"heating": "Отопление",
"dhw": "ГВС",
"currentTemp": "Текущая",
"temp.current": "Текущая",
"enable": "Вкл",
"turbo": "Турбо"
},
"notify": {
"fault": {
"title": "Состояние неисправности котла активно!",
@@ -99,6 +98,7 @@
},
"reset": "Сбросить"
},
"states": {
"mNetworkConnected": "Подключение к сети",
"mMqttConnected": "Подключение к MQTT",
@@ -106,18 +106,18 @@
"mExtPumpState": "Внешний насос",
"mCascadeControlInput": "Каскадное управление (вход)",
"mCascadeControlOutput": "Каскадное управление (выход)",
"sConnected": "Подключение к OpenTherm",
"sFlame": "Пламя",
"sCoolingActive": "Охлаждение",
"sCoolingSetpoint": "Охлаждение, уставка",
"sCooling": "Охлаждение",
"sFaultActive": "Ошибка",
"sFaultCode": "Код ошибки",
"sDiagActive": "Диагностика",
"sDiagCode": "Диагностический код",
"mHeatEnabled": "Отопление",
"mHeatBlocking": "Блокировка отопления",
"mHeatOverheat": "Отопление, перегрев",
"mHeatFreezing": "Отопление, защита от замерзания",
"sHeatActive": "Активность отопления",
"mHeatSetpointTemp": "Отопление, уставка",
"mHeatTargetTemp": "Отопление, целевая температура",
@@ -125,6 +125,7 @@
"mHeatRetTemp": "Отопление, температура обратки",
"mHeatIndoorTemp": "Отопление, внутренняя темп.",
"mHeatOutdoorTemp": "Отопление, наружная темп.",
"mDhwEnabled": "ГВС",
"mDhwOverheat": "ГВС, перегрев",
"sDhwActive": "Активность ГВС",
@@ -132,6 +133,7 @@
"mDhwCurrTemp": "ГВС, текущая температура",
"mDhwRetTemp": "ГВС, температура обратки"
},
"sensors": {
"values": {
"temp": "Температура",
@@ -141,19 +143,23 @@
}
}
},
"network": {
"title": "Сеть - OpenTherm Gateway",
"name": "Настройки сети",
"section": {
"static": "Статические параметры",
"availableNetworks": "Доступные сети",
"staSettings": "Настройки подключения",
"apSettings": "Настройки точки доступа"
},
"scan": {
"pos": "#",
"info": "Инфо"
},
"wifi": {
"ssid": "Имя сети",
"password": "Пароль",
@@ -161,6 +167,7 @@
"signal": "Сигнал",
"connected": "Подключено"
},
"params": {
"hostname": "Имя хоста",
"dhcp": "Использовать DHCP",
@@ -170,15 +177,16 @@
"gateway": "Адрес шлюза",
"dns": "DNS адрес"
},
"sta": {
"channel": {
"note": "установите 0 для автоматического выбора"
}
"channel.note": "установите 0 для автоматического выбора"
}
},
"sensors": {
"title": "Настройки сенсоров - OpenTherm Gateway",
"name": "Настройки сенсоров",
"enabled": "Включить и использовать",
"sensorName": {
"title": "Имя сенсора",
@@ -234,12 +242,10 @@
"otDhwBurnerHours": "OpenTherm, кол-во часов работы горелки (ГВС)",
"otHeatingPumpHours": "OpenTherm, кол-во часов работы насоса (отопление)",
"otDhwPumpHours": "OpenTherm, кол-во часов работы насоса (ГВС)",
"otCoolingHours": "OpenTherm, кол-во часов работы охлаждения",
"ntcTemp": "NTC датчик",
"dallasTemp": "DALLAS датчик",
"bluetooth": "BLE датчик",
"dht11": "DHT11 датчик",
"dht22": "DHT22 датчик",
"heatSetpointTemp": "Отопление, температура уставки",
"manual": "Вручную через MQTT/API",
"notConfigured": "Не сконфигурировано"
@@ -266,9 +272,11 @@
}
}
},
"settings": {
"title": "Настройки - OpenTherm Gateway",
"name": "Настройки",
"section": {
"portal": "Настройки портала",
"system": "Системные настройки",
@@ -283,21 +291,18 @@
"extPump": "Настройки дополнительного насоса",
"cascadeControl": "Настройки каскадного управления"
},
"enable": "Вкл",
"note": {
"restart": "После изменения этих настроек устройство необходимо перезагрузить, чтобы изменения вступили в силу.",
"blankNotUse": "пусто - не использовать",
"bleDevice": "BLE устройство можно использовать <u>только</u> с некоторыми платами ESP32, которые поддерживают BLE!"
},
"temp": {
"min": "Мин. температура",
"max": "Макс. температура"
},
"avgType": {
"mean": "Средняя температура",
"min": "Минимальная температура",
"max": "Максимальная температура"
},
"maxModulation": "Макс. уровень модуляции",
"ohProtection": {
"title": "Защита от перегрева",
@@ -313,22 +318,18 @@
},
"freezeProtection": {
"title": "Защита от замерзания",
"desc": "Отопление будет принудительно включено, если темп. теплоносителя или внутренняя темп. опустится ниже <b>нижнего порога</b>.",
"highTemp": {
"title": "Верхний порог температуры",
"note": "Порог, при котором система вернется в нормальное состояние после активации защиты от замерзания"
},
"lowTemp": {
"title": "Нижний порог температуры",
"note": "Порог, при котором отопление будет принудительно включено"
}
"desc": "Отопление будет принудительно включено, если темп. теплоносителя или внутренняя темп. опустится ниже <b>нижнего порога</b> в течение <b>времени ожидания</b>.",
"lowTemp": "Нижний порог температуры",
"thresholdTime": "Время ожидания <small>(сек)</small>"
},
"portal": {
"login": "Логин",
"password": "Пароль",
"auth": "Требовать аутентификацию",
"mdns": "Использовать mDNS"
},
"system": {
"unit": "Система единиц",
"metric": "Метрическая <small>(цельсии, литры, бары)</small>",
@@ -339,8 +340,12 @@
"enable": "Вкл. Serial порт",
"baud": "Скорость Serial порта"
},
"webSerial": {
"enable": "Вкл. WebSerial"
"telnet": {
"enable": "Вкл. Telnet",
"port": {
"title": "Telnet порт",
"note": "По умолчанию: 23"
}
},
"ntp": {
"server": "NTP сервер",
@@ -348,30 +353,22 @@
"timezonePresets": "Выберите пресет..."
}
},
"heating": {
"hyst": {
"title": "Гистерезис",
"desc": "Гистерезис полезен для поддержания заданной внутр. темп. (при использовании «ПЗА» и/или «ПИД»). Принудительно откл. отопление, когда <code>current indoor > target + value</code>, и вкл. отопление, когда <code>current indoor < (target - value)</code>.",
"value": "Значение <small>(в градусах)</small>"
},
"turboFactor": "Коэфф. турбо режима",
"indoorTempAvgType": {
"title": "Тип усреднения внутренней темп.",
"desc": "Полезно при использовании двух и более датчиков внутренней температуры (при использовании «Equitherm» и/или «PID»)."
},
"outdoorTempAvgType": {
"title": "Тип усреднения наружнной темп.",
"desc": "Полезно при использовании двух и более датчиков наружной температуры (при использовании «Equitherm»)."
}
"hyst": "Гистерезис <small>(в градусах)</small>",
"turboFactor": "Коэфф. турбо режима"
},
"emergency": {
"desc": "Аварийный режим активируется автоматически, если «ПИД» или «ПЗА» не могут рассчитать уставку теплоносителя:<br />- если «ПЗА» включен и датчик наружной температуры отключен;<br />- если включен «ПИД» или OT опция <i>«Передать управление отоплением котлу»</i> и датчик внутренней температуры отключен.<br /><b>Примечание:</b> При сбое сети или MQTT датчики с типом <i>«Вручную через MQTT/API»</i> будут находиться в состоянии ОТКЛЮЧЕН.",
"target": {
"title": "Целевая температура",
"note": "<b>Важно:</b> <u>Целевая температура в помещении</u>, если включена ОТ опция <i>«Передать управление отоплением котлу»</i>.<br />Во всех остальных случаях <u>целевая температура теплоносителя</u>."
},
"treshold": "Пороговое время включения <small>(сек)</small>"
},
"equitherm": {
"slope": {
"title": "Наклон",
@@ -379,7 +376,7 @@
},
"exponent": {
"title": "Экспонента",
"note": "Эффективность радиатора. Типичные значения: <code>1.1</code> - Тёплый пол, <code>1.2</code> - Чугунные радиаторы, <code>1.3</code> - Панельные радиаторы, <code>1.4</code> - Конвекторы."
"note": "Эффективность радиатора. Типичные значения: 1.1 - Тёплый пол, 1.2 - Чугунные радиаторы, 1.3 - Панельные радиаторы, 1.4 - Конвекторы."
},
"shift": {
"title": "Смещение",
@@ -387,7 +384,7 @@
},
"targetDiffFactor": {
"title": "Коэффициент T",
"note": "Не используется, если ПИД включен. Добавляет разницу между целевой и текущей температурой в помещении: <code>setpoint = setpoint + ((target - indoor) * T)</code>."
"note": "Не используется, если ПИД включен"
},
"chart": {
"targetTemp": "Целевая внутренняя температура",
@@ -395,6 +392,7 @@
"outdoorTemp": "Наружная температура"
}
},
"pid": {
"p": "Коэффициент P",
"i": "Коэффициент I",
@@ -414,17 +412,8 @@
"thresholdLow": "Нижний порог"
}
},
"ot": {
"disablingAction": {
"title": "Действие отключения контуров",
"desc": "Выбранное действие применяется при любой причине отключения, включая перегрев.<br />Некоторые котлы отключают циркуляционный насос при отключении контура, в этом случае можно попробовать установить <b>0</b> в качестве целевой температуры, чтобы выбег насоса работал корректно.",
"heating": "Контур отопления",
"dhw": "Контур ГВС",
"ch2": "Контур 2",
"off": "Только отключить контур",
"zeroSetpoint": "Установить <b>0</b> в качестве целевой температуры",
"offAndZeroSetpoint": "Отключить контур и установить <b>0</b> в качестве целевой температуры"
},
"advanced": "Дополнительные настройки",
"inGpio": "Вход GPIO",
"outGpio": "Выход GPIO",
@@ -439,6 +428,7 @@
"title": "Макс. мощность котла <small>(кВт)</small>",
"note": "<b>0</b> - попробовать определить автоматически. Обычно можно найти в спецификации котла как \"максимальная полезная тепловая мощность\"."
},
"options": {
"title": "Опции (дополнительные настройки)",
"desc": "Опции могут менять логику работы котла. Не все опции задокументированы в протоколе, поэтому одна и та же опция может иметь разный эффект на разных котлах.<br /><b>Примечание:</b> Нет необходимости что-то менять, если всё работает хорошо.",
@@ -457,14 +447,15 @@
"autoFaultReset": "Автоматический сброс ошибок <small>(не рекомендуется!)</small>",
"autoDiagReset": "Автоматический сброс диагностики <small>(не рекомендуется!)</small>",
"setDateAndTime": "Устанавливать время и дату на котле",
"immergasFix": "Фикс для котлов Immergas",
"alwaysSendIndoorTemp": "Передавать текущую темп. в помещении котлу"
"immergasFix": "Фикс для котлов Immergas"
},
"nativeOTC": {
"title": "Нативный режим OTC (расчёт температуры теплоносителя)",
"note": "Работает <u>ТОЛЬКО</u> если котел в режиме OTC: требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА."
"nativeHeating": {
"title": "Передать управление отоплением котлу",
"note": "Работает <u>ТОЛЬКО</u> если котел требует и принимает целевую температуру в помещении и сам регулирует температуру теплоносителя на основе встроенного режима кривых. Несовместимо с ПИД и ПЗА."
}
},
"mqtt": {
"homeAssistantDiscovery": "Home Assistant Discovery",
"server": "Адрес сервера",
@@ -474,14 +465,15 @@
"prefix": "Префикс",
"interval": "Интервал публикации <small>(сек)</small>"
},
"extPump": {
"use": "Использовать доп. насос",
"gpio": "GPIO реле",
"invertState": "Инвертировать состояние GPIO",
"postCirculationTime": "Время постциркуляции <small>(в минутах)</small>",
"antiStuckInterval": "Интервал защиты от блокировки <small>(в днях)</small>",
"antiStuckTime": "Время работы насоса <small>(в минутах)</small>"
},
"cascadeControl": {
"input": {
"desc": "Может использоваться для включения отопления только при неисправности другого котла. Контроллер другого котла должен изменить состояние входа GPIO в случае неисправности.",
@@ -505,49 +497,23 @@
}
}
},
"logs": {
"title": "Журнал - OpenTherm Gateway",
"name": "Журнал",
"desc": "Журнал устройства в реальном времени.",
"status": {
"connecting": "Подключение...",
"connected": "Подключено",
"disconnected": "Нет подключения"
},
"autoscroll": "Автопрокрутка",
"limitHistory": "Хранить не более 1000 записей",
"historyNote": "Без ограничения журнал использует больше памяти браузера. Скачиваются все сохранённые записи, включая скрытые фильтром.",
"filter": "Фильтр",
"search": {
"title": "Поиск",
"previous": "Назад",
"next": "Вперёд"
},
"level": {
"note": "Уровень применяется к журналу устройства (Serial и WebSerial). Фильтр ниже меняет только отображение.",
"loadError": "Не удалось загрузить уровень журнала. Повторите запрос.",
"saveError": "Не удалось сохранить уровень журнала. Проверьте соединение и повторите попытку."
},
"clear": "Очистить",
"download": "Скачать журнал"
},
"upgrade": {
"title": "Обновление - OpenTherm Gateway",
"name": "Обновление",
"section": {
"backupAndRestore": {
"title": "Резервное копирование и восстановление",
"desc": "В этом разделе вы можете сохранить и восстановить резервную копию ВСЕХ настроек."
},
"upgrade": {
"title": "Обновление",
"desc": "В этом разделе вы можете обновить прошивку и файловую систему вашего устройства.<br />Последнюю версию можно загрузить со страницы <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Релизы</a> в репозитории проекта."
}
"backupAndRestore": "Резервное копирование и восстановление",
"backupAndRestore.desc": "В этом разделе вы можете сохранить и восстановить резервную копию ВСЕХ настроек.",
"upgrade": "Обновление",
"upgrade.desc": "В этом разделе вы можете обновить прошивку и файловую систему вашего устройства.<br />Последнюю версию можно загрузить со страницы <a href=\"https://github.com/Laxilef/OTGateway/releases\" target=\"_blank\">Релизы</a> в репозитории проекта."
},
"note": {
"disclaimer1": "После успешного обновления файловой системы ВСЕ настройки будут сброшены на стандартные! Создайте резервную копию ПЕРЕД обновлением.",
"disclaimer2": "После успешного обновления устройство автоматически перезагрузится через 15 секунд."
},
"settingsFile": "Файл настроек",
"fw": "Прошивка",
"fs": "Файловая система"
+14 -63
View File
@@ -21,9 +21,7 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -47,7 +45,7 @@
<div class="thermostat-header" data-i18n>dashboard.thermostat.heating</div>
<div class="thermostat-temp">
<div class="thermostat-temp-target"><span class="targetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.currentTemp</span>: <span id="tHeatCurrentTemp"></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 class="tAction outline" data-action="decrement"><i class="icons-down"></i></button>
@@ -68,7 +66,7 @@
<div class="thermostat-header" data-i18n>dashboard.thermostat.dhw</div>
<div class="thermostat-temp">
<div class="thermostat-temp-target"><span class="targetTemp"></span> <span class="tempUnit"></span></div>
<div class="thermostat-temp-current"><span data-i18n>dashboard.thermostat.currentTemp</span>: <span id="tDhwCurrentTemp"></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="tAction outline" data-action="decrement"><i class="icons-down"></i></button>
@@ -154,14 +152,9 @@
<th scope="row" data-i18n>dashboard.states.sFlame</th>
<td><i class="sFlame"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sCoolingActive</th>
<td><i class="sCoolingActive"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sCoolingSetpoint</th>
<td><b class="sCoolingSetpoint"></b> %</td>
<th scope="row" data-i18n>dashboard.states.sCooling</th>
<td><i class="sCooling"></i></td>
</tr>
@@ -195,10 +188,6 @@
<th scope="row" data-i18n>dashboard.states.mHeatOverheat</th>
<td><i class="mHeatOverheat"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.mHeatFreezing</th>
<td><i class="mHeatFreezing"></i></td>
</tr>
<tr>
<th scope="row" data-i18n>dashboard.states.sHeatActive</th>
<td><i class="sHeatActive"></i></td>
@@ -324,7 +313,7 @@
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));
await lang.build();
lang.build();
let actionTimer = null;
let actionLongPress = false;
@@ -351,14 +340,9 @@
value = -(step);
}
const newValue = parseFloat(constrain(newSettings[purpose].target + value, minTemp, maxTemp).toFixed(2));
if (newSettings[purpose].target != newValue) {
newSettings[purpose].target = newValue;
modifiedTime = Date.now();
setValue('.targetTemp', newValue, tContainer);
tContainer.querySelector('.thermostat-header').setAttribute("aria-busy", "true");
}
newSettings[purpose].target = parseFloat(constrain(newSettings[purpose].target + value, minTemp, maxTemp).toFixed(2));
modifiedTime = Date.now();
setValue('.targetTemp', newSettings[purpose].target, tContainer);
}
});
}
@@ -388,46 +372,27 @@
value = -(bigStep);
}
const newValue = parseFloat(constrain(newSettings[purpose].target + value, minTemp, maxTemp).toFixed(2));
if (newSettings[purpose].target != newValue) {
newSettings[purpose].target = newValue;
modifiedTime = Date.now();
newSettings[purpose].target = parseFloat(constrain(newSettings[purpose].target + value, minTemp, maxTemp).toFixed(2));
modifiedTime = Date.now();
setValue('.targetTemp', newSettings[purpose].target, tContainer);
tContainer.querySelector('.thermostat-header').setAttribute("aria-busy", "true");
}
setValue('.targetTemp', newSettings[purpose].target, tContainer);
}, 500);
});
});
document.querySelector('#tHeatEnabled').addEventListener('change', (event) => {
if (newSettings.heating.enabled == event.currentTarget.checked) {
return;
}
modifiedTime = Date.now();
newSettings.heating.enabled = event.currentTarget.checked;
document.querySelector('.tHeat .thermostat-header').setAttribute("aria-busy", "true");
});
document.querySelector('#tHeatTurbo').addEventListener('change', (event) => {
if (newSettings.heating.turbo == event.currentTarget.checked) {
return;
}
modifiedTime = Date.now();
newSettings.heating.turbo = event.currentTarget.checked;
document.querySelector('.tHeat .thermostat-header').setAttribute("aria-busy", "true");
});
document.querySelector('#tDhwEnabled').addEventListener('change', (event) => {
if (newSettings.dhw.enabled == event.currentTarget.checked) {
return;
}
modifiedTime = Date.now();
newSettings.dhw.enabled = event.currentTarget.checked;
document.querySelector('.tDhw .thermostat-header').setAttribute("aria-busy", "true");
});
document.querySelector('.notify-fault .reset').addEventListener('click', async (event) => {
@@ -519,9 +484,6 @@
if (modified) {
parameters.method = "POST";
parameters.body = JSON.stringify(newSettings);
parameters.headers = {
"Content-Type": "application/json"
};
}
const response = await fetch("/api/settings", parameters);
@@ -555,10 +517,6 @@
setValue('.pressureUnit', pressureUnit(unitSystem));
setValue('.volumeUnit', volumeUnit(unitSystem));
document.querySelectorAll('.thermostat-header').forEach((item) => {
item.setAttribute("aria-busy", "false");
});
} catch (error) {
console.log(error);
}
@@ -598,9 +556,7 @@
result.slave.connected ? "green" : "red"
);
setState('.sFlame', result.slave.flame);
setState('.sCoolingActive', result.slave.cooling.active);
setValue('.sCoolingSetpoint', result.slave.cooling.setpoint);
setState('.sCooling', result.slave.cooling);
setValue('.sModMin', result.slave.modulation.min);
setValue('.sModMax', result.slave.modulation.max);
@@ -668,11 +624,6 @@
result.master.heating.overheat ? "success" : "error",
result.master.heating.overheat ? "red" : "green"
);
setStatus(
'.mHeatFreezing',
result.master.heating.freezing ? "success" : "error",
result.master.heating.freezing ? "red" : "green"
);
setValue('.mHeatSetpointTemp', result.master.heating.setpointTemp);
setValue('.mHeatTargetTemp', result.master.heating.targetTemp);
setValue('.mHeatCurrTemp', result.master.heating.currentTemp);
@@ -770,7 +721,7 @@
sData.connected ? "green" : "red",
sensorNode
);
setTextValue(".sName", sData.name, sensorNode);
setValue(".sName", sData.name, sensorNode);
setValue(".sValue", "", sensorNode);
const statusNode = sensorNode.querySelector(`.sStatusContainer`);
@@ -797,7 +748,7 @@
}
if (sData.rssi !== undefined) {
appendValue(".sValue", `${i18n('dashboard.sensors.values.rssi')}: <b>${sData.rssi.toFixed(0)}</b> ${i18n('units.dbm')}`, `<br />`, sensorNode);
appendValue(".sValue", `${i18n('dashboard.sensors.values.rssi')}: <b>${sData.rssi.toFixed(0)}</b> ${i18n('dbm')}`, `<br />`, sensorNode);
}
}
+24 -41
View File
@@ -21,9 +21,7 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -104,7 +102,6 @@
<th scope="row" data-i18n>index.system.build.title</th>
<td>
Env: <b id="build-env"></b><br />
<span data-i18n>index.system.build.commit</span>: <b id="build-commit"></b><br />
<span data-i18n>index.system.build.date</span>: <b id="build-date"></b><br />
<span data-i18n>index.system.build.core</span>: <b id="build-core"></b><br />
<span data-i18n>index.system.build.sdk</span>: <b id="build-sdk"></b>
@@ -113,32 +110,25 @@
<tr>
<th scope="row" data-i18n>index.system.uptime</th>
<td>
<b id="uptime-days"></b> <span data-i18n>units.days</span>,
<b id="uptime-hours"></b> <span data-i18n>units.hours</span>,
<b id="uptime-min"></b> <span data-i18n>units.min</span>,
<b id="uptime-sec"></b> <span data-i18n>units.sec</span>
<b id="uptime-days"></b> <span data-i18n>time.days</span>,
<b id="uptime-hours"></b> <span data-i18n>time.hours</span>,
<b id="uptime-min"></b> <span data-i18n>time.min</span>,
<b id="uptime-sec"></b> <span data-i18n>time.sec</span>
</td>
</tr>
<tr>
<th scope="row" data-i18n>index.system.memory.heap</th>
<th scope="row" data-i18n>index.system.memory.title</th>
<td>
<b id="heap-free"></b> of <b id="heap-total"></b> <span data-i18n>units.byte</span> (<span data-i18n>index.system.memory.min</span>: <b id="heap-min-free"></b> <span data-i18n>units.byte</span>)<br />
<span data-i18n>index.system.memory.maxFreeBlock</span>: <b id="heap-max-free-block"></b> <span data-i18n>units.byte</span> (<span data-i18n>index.system.memory.min</span>: <b id="heap-min-max-free-block"></b> <span data-i18n>units.byte</span>)
</td>
</tr>
<tr>
<th scope="row" data-i18n>index.system.memory.psram</th>
<td>
<b id="psram-free"></b> of <b id="psram-total"></b> <span data-i18n>units.byte</span> (<span data-i18n>index.system.memory.min</span>: <b id="psram-min-free"></b> <span data-i18n>units.byte</span>)<br />
<span data-i18n>index.system.memory.maxFreeBlock</span>: <b id="psram-max-free-block"></b> <span data-i18n>units.byte</span>
<b id="heap-free"></b> of <b id="heap-total"></b> bytes (<span data-i18n>index.system.memory.min</span>: <b id="heap-min-free"></b> bytes)<br />
<span data-i18n>index.system.memory.maxFreeBlock</span>: <b id="heap-max-free-block"></b> bytes (<span data-i18n>index.system.memory.min</span>: <b id="heap-min-max-free-block"></b> bytes)
</td>
</tr>
<tr>
<th scope="row" data-i18n>index.system.board</th>
<td>
<span data-i18n>index.system.chip.model</span>: <b id="chip-model"></b> (rev. <span id="chip-rev"></span>)<br />
<span data-i18n>index.system.chip.cores</span>: <b id="chip-cores"></b>, <span data-i18n>index.system.chip.freq</span>: <b id="chip-freq"></b> <span data-i18n>units.mhz</span><br />
<span data-i18n>index.system.flash.size</span>: <b id="flash-size"></b> <span data-i18n>units.mbyte</span> (<span data-i18n>index.system.flash.realSize</span>: <b id="flash-real-size"></b> <span data-i18n>units.mbyte</span>)
<span data-i18n>index.system.chip.cores</span>: <b id="chip-cores"></b>, <span data-i18n>index.system.chip.freq</span>: <b id="chip-freq"></b> mHz<br />
<span data-i18n>index.system.flash.size</span>: <b id="flash-size"></b> MB (<span data-i18n>index.system.flash.realSize</span>: <b id="flash-real-size"></b> MB)
</td>
</tr>
<tr>
@@ -155,7 +145,6 @@
<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="/logs.html" role="button" data-i18n>logs.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>
@@ -178,7 +167,7 @@
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));
await lang.build();
lang.build();
setTimeout(async function onLoadPage() {
try {
@@ -192,34 +181,33 @@
}
const result = await response.json();
setTextValue('#reset-reason', result.system.resetReason);
setValue('#reset-reason', result.system.resetReason);
setValue('#uptime', result.system.uptime);
setValue('#uptime-days', Math.floor(result.system.uptime / 86400));
setValue('#uptime-hours', Math.floor(result.system.uptime % 86400 / 3600));
setValue('#uptime-min', Math.floor(result.system.uptime % 3600 / 60));
setValue('#uptime-sec', Math.floor(result.system.uptime % 60));
setTextValue('#network-hostname', result.network.hostname);
setTextValue('#network-mac', result.network.mac);
setValue('#network-hostname', result.network.hostname);
setValue('#network-mac', result.network.mac);
setStatus(
'#network-connected',
result.network.connected ? "success" : "error",
result.network.connected ? "green" : "red"
);
setTextValue('#network-ssid', result.network.ssid);
setValue('#network-ssid', result.network.ssid);
setValue('#network-signal', result.network.signalQuality);
setTextValue('#network-ip', result.network.ip);
setTextValue('#network-subnet', result.network.subnet);
setTextValue('#network-gateway', result.network.gateway);
setTextValue('#network-dns', result.network.dns);
setValue('#network-ip', result.network.ip);
setValue('#network-subnet', result.network.subnet);
setValue('#network-gateway', result.network.gateway);
setValue('#network-dns', result.network.dns);
setBusy('#main-busy', '#main-table', false);
setTextValue('#build-version', result.build.version);
setTextValue('#build-commit', result.build.commit);
setTextValue('#build-date', result.build.date);
setTextValue('#build-env', result.build.env);
setTextValue('#build-core', result.build.core);
setTextValue('#build-sdk', result.build.sdk);
setValue('#build-version', result.build.version);
setValue('#build-date', result.build.date);
setValue('#build-env', result.build.env);
setValue('#build-core', result.build.core);
setValue('#build-sdk', result.build.sdk);
setValue('#heap-total', result.heap.total);
setValue('#heap-free', result.heap.free);
@@ -227,12 +215,7 @@
setValue('#heap-max-free-block', result.heap.maxFreeBlock);
setValue('#heap-min-max-free-block', result.heap.minMaxFreeBlock);
setValue('#psram-total', result.psram.total);
setValue('#psram-free', result.psram.free);
setValue('#psram-min-free', result.psram.minFree);
setValue('#psram-max-free-block', result.psram.maxFreeBlock);
setTextValue('#chip-model', result.chip.model);
setValue('#chip-model', result.chip.model);
setValue('#chip-rev', result.chip.rev);
setValue('#chip-cores', result.chip.cores);
setValue('#chip-freq', result.chip.freq);
-666
View File
@@ -1,666 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title data-i18n>logs.title</title>
<link rel="stylesheet" href="/static/app.css?{BUILD_TIME}" />
<style>
#log-console {
background: #15191e;
color: #e5e7eb;
border-radius: var(--pico-border-radius);
margin-bottom: 1rem;
}
#log-console-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem;
border-bottom: 1px solid var(--pico-muted-border-color);
}
#connection-status {
display: inline-block;
width: 0.65rem;
height: 0.65rem;
border: 0;
border-radius: 50%;
background: var(--pico-form-element-invalid-border-color);
}
#connection-status[data-state="connected"] {
background: var(--pico-form-element-valid-border-color);
}
#log-output {
position: relative;
height: 55vh;
min-height: 10rem;
resize: vertical;
overflow: auto;
padding: 0.5rem;
font: 0.6rem/1.4 monospace;
white-space: pre-wrap;
overflow-wrap: anywhere;
}
#log-output > .search-match {
background: var(--pico-primary-focus);
outline: 1px solid var(--pico-primary);
outline-offset: -1px;
}
#log-output > div {
min-height: 1.4em;
}
#search-count {
white-space: nowrap;
}
#search-logs {
min-width: 0;
}
#log-controls label {
margin-bottom: 0.5rem;
}
#log-search-controls > button {
flex: 0 0 auto;
width: auto;
padding-inline: 0.75rem;
}
</style>
</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="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
</ul>
</nav>
</header>
<main class="container">
<article>
<hgroup>
<h2 data-i18n>logs.name</h2>
<p data-i18n>logs.desc</p>
</hgroup>
<form action="/api/settings" method="post" id="log-level-form">
<label for="log-level" data-i18n>settings.system.logLevel</label>
<div role="group">
<select name="system[logLevel]" id="log-level" disabled>
<option value="0">SILENT</option>
<option value="1">FATAL</option>
<option value="2">ERROR</option>
<option value="3">WARNING</option>
<option value="4">INFO</option>
<option value="5">NOTICE</option>
<option value="6">TRACE</option>
<option value="7">VERBOSE</option>
</select>
<button type="submit" disabled data-i18n>button.save</button>
</div>
<small data-i18n>logs.level.note</small>
<div id="log-level-load-error" hidden>
<small id="log-level-error" data-i18n="logs.level.loadError"></small>
<button type="button" id="retry-log-level" class="secondary" data-i18n>button.refresh</button>
</div>
</form>
<hr />
<div id="log-controls" class="grid">
<label>
<input type="checkbox" id="autoscroll" checked />
<span data-i18n>logs.autoscroll</span>
</label>
<label>
<input type="checkbox" id="limit-history" checked />
<span data-i18n>logs.limitHistory</span>
</label>
</div>
<small data-i18n>logs.historyNote</small>
<div class="grid">
<label for="filter-logs">
<span data-i18n>logs.filter</span>
<input type="search" id="filter-logs" />
</label>
<div>
<label for="search-logs" data-i18n>logs.search.title</label>
<div role="group" id="log-search-controls">
<input type="search" id="search-logs" />
<button type="button" id="search-prev" class="secondary" disabled>
<span aria-hidden="true">↑</span>
<span hidden data-i18n>logs.search.previous</span>
</button>
<button type="button" id="search-next" class="secondary" disabled>
<span aria-hidden="true">↓</span>
<span hidden data-i18n>logs.search.next</span>
</button>
</div>
</div>
</div>
<div id="log-console">
<div id="log-console-header">
<small id="search-count" aria-live="polite">0 / 0</small>
<span id="connection-status" role="img" tabindex="0" data-state="connecting" data-placement="left">
<span id="connection-text" hidden data-i18n="logs.status.connecting"></span>
</span>
</div>
<div id="log-output" role="log" aria-live="off" tabindex="0"></div>
</div>
<div role="group" id="log-actions">
<button type="button" id="clear-logs" class="secondary" data-i18n>logs.clear</button>
<button type="button" id="download-logs" data-i18n>logs.download</button>
</div>
</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'));
await lang.build();
const output = document.getElementById('log-output');
const autoscroll = document.getElementById('autoscroll');
const limitHistory = document.getElementById('limit-history');
const filterInput = document.getElementById('filter-logs');
const searchInput = document.getElementById('search-logs');
const searchPrevious = document.getElementById('search-prev');
const searchNext = document.getElementById('search-next');
const searchCount = document.getElementById('search-count');
const status = document.getElementById('connection-status');
const statusText = document.getElementById('connection-text');
const colors = [
'#000000', '#cd3131', '#0dbc79', '#e5e510',
'#2472c8', '#bc3fbc', '#11a8cd', '#e5e5e5',
'#666666', '#f14c4c', '#23d18b', '#f5f543',
'#3b8eea', '#d670d6', '#29b8db', '#ffffff'
];
let escapeState = 'text';
let escapeParams = '';
let foreground = null;
let background = null;
let bold = false;
let socket = null;
let reconnectTimer = null;
let heartbeatTimer = null;
let waitingForPong = false;
let active = true;
let viewTimer = null;
let filterText = '';
let searchText = '';
let matches = [];
let selectedIndex = -1;
let selectedRecord = null;
const updateLabels = () => {
status.setAttribute('data-tooltip', statusText.textContent);
status.setAttribute('aria-label', statusText.textContent);
for (const button of [searchPrevious, searchNext]) {
const text = button.querySelector('[data-i18n]').textContent;
button.setAttribute('data-tooltip', text);
button.setAttribute('aria-label', text);
}
};
const labelObserver = new MutationObserver(updateLabels);
for (const label of [statusText, ...document.querySelectorAll('#log-search-controls [data-i18n]')]) {
labelObserver.observe(label, {childList: true, subtree: true, characterData: true});
}
const setConnectionStatus = (state) => {
status.dataset.state = state;
statusText.setAttribute('data-i18n', `logs.status.${state}`);
lang.translateElement(statusText);
updateLabels();
};
const updateSelection = (scroll = false) => {
const nextRecord = matches[selectedIndex] || null;
if (selectedRecord !== nextRecord) {
if (selectedRecord) {
selectedRecord.classList.remove('search-match');
}
selectedRecord = nextRecord;
if (selectedRecord) {
selectedRecord.classList.add('search-match');
}
}
searchCount.textContent = `${selectedRecord ? selectedIndex + 1 : 0} / ${matches.length}`;
searchPrevious.disabled = matches.length === 0;
searchNext.disabled = matches.length === 0;
if (scroll && selectedRecord) {
autoscroll.checked = false;
output.scrollTop = selectedRecord.offsetTop - (output.clientHeight - selectedRecord.offsetHeight) / 2;
}
};
const pruneHistory = () => {
let removedMatches = 0;
while (limitHistory.checked && output.childElementCount > 1000) {
const record = output.firstElementChild;
if (matches[removedMatches] === record) {
removedMatches++;
}
record.remove();
}
if (removedMatches) {
matches.splice(0, removedMatches);
selectedIndex = matches.length ? Math.max(0, selectedIndex - removedMatches) : -1;
}
};
const applyView = (scrollToSelection = false) => {
clearTimeout(viewTimer);
viewTimer = null;
filterText = filterInput.value.toLowerCase();
searchText = searchInput.value.toLowerCase();
matches = [];
for (const record of output.children) {
const text = record.textContent.toLowerCase();
record.hidden = !text.includes(filterText);
if (!record.hidden && searchText && text.includes(searchText)) {
matches.push(record);
}
}
selectedIndex = matches.indexOf(selectedRecord);
if (selectedIndex < 0 && matches.length) {
selectedIndex = 0;
}
updateSelection(scrollToSelection);
if (autoscroll.checked) {
output.scrollTop = output.scrollHeight;
}
};
const scheduleView = (event) => {
clearTimeout(viewTimer);
const scrollToSelection = event.target === searchInput;
viewTimer = setTimeout(() => applyView(scrollToSelection), 150);
};
filterInput.addEventListener('input', scheduleView);
searchInput.addEventListener('input', scheduleView);
const moveSearch = (direction) => {
if (viewTimer !== null) {
applyView(true);
return;
}
if (!matches.length) {
return;
}
selectedIndex = (selectedIndex + direction + matches.length) % matches.length;
updateSelection(true);
};
searchPrevious.addEventListener('click', () => moveSearch(-1));
searchNext.addEventListener('click', () => moveSearch(1));
searchInput.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
moveSearch(event.shiftKey ? -1 : 1);
}
});
limitHistory.addEventListener('change', () => {
pruneHistory();
updateSelection();
});
const levelForm = document.getElementById('log-level-form');
const levelSelect = document.getElementById('log-level');
const levelSave = levelForm.querySelector('button[type="submit"]');
const levelError = document.getElementById('log-level-load-error');
const levelErrorText = document.getElementById('log-level-error');
const levelRetry = document.getElementById('retry-log-level');
let confirmedLevel = null;
let levelBusy = false;
const setSaveLabel = (key) => {
levelSave.setAttribute('data-i18n', key);
lang.translateElement(levelSave);
};
const updateLevelControls = () => {
levelSelect.disabled = levelBusy || confirmedLevel === null;
levelSave.disabled = levelSelect.disabled;
levelRetry.disabled = levelBusy;
if (levelBusy) {
levelSave.setAttribute('aria-busy', 'true');
} else {
levelSave.removeAttribute('aria-busy');
}
};
const updateLogLevel = async (save = false) => {
if (levelBusy) {
return;
}
const level = Number(levelSelect.value);
if (save && (!Number.isInteger(level) || level < 0 || level > 7)) {
return;
}
levelBusy = true;
levelError.hidden = true;
setSaveLabel(save ? 'button.wait' : 'button.save');
updateLevelControls();
try {
const options = {cache: 'no-cache', credentials: 'include'};
if (save) {
options.method = 'POST';
options.headers = {'Content-Type': 'application/json'};
options.body = JSON.stringify({system: {logLevel: level}});
}
const response = await fetch(levelForm.action, options);
if (!response.ok) {
throw new Error('Response not valid');
}
const result = await response.json();
const value = result.system && result.system.logLevel;
if (!Number.isInteger(value) || value < 0 || value > 7) {
throw new Error('Invalid log level');
}
confirmedLevel = value;
levelSelect.value = String(value);
setSaveLabel(save ? 'button.saved' : 'button.save');
} catch (err) {
if (confirmedLevel !== null) {
levelSelect.value = String(confirmedLevel);
}
levelErrorText.setAttribute('data-i18n', save ? 'logs.level.saveError' : 'logs.level.loadError');
lang.translateElement(levelErrorText);
levelError.hidden = false;
setSaveLabel('button.save');
} finally {
levelBusy = false;
updateLevelControls();
}
};
levelForm.addEventListener('submit', (event) => {
event.preventDefault();
updateLogLevel(true);
});
levelRetry.addEventListener('click', () => updateLogLevel());
levelSelect.addEventListener('change', () => setSaveLabel('button.save'));
updateLogLevel();
const resetFormatting = () => {
escapeState = 'text';
escapeParams = '';
foreground = null;
background = null;
bold = false;
};
const applyFormatting = () => {
const codes = escapeParams.split(';').map(Number);
for (let i = 0; i < codes.length; i++) {
const code = codes[i];
if (code === 0) {
foreground = null;
background = null;
bold = false;
} else if (code === 1) {
bold = true;
} else if (code === 22) {
bold = false;
} else if (code === 39) {
foreground = null;
} else if (code === 49) {
background = null;
} else if (code >= 30 && code <= 37) {
foreground = colors[code - 30];
} else if (code >= 90 && code <= 97) {
foreground = colors[code - 90 + 8];
} else if (code >= 40 && code <= 47) {
background = colors[code - 40];
} else if (code >= 100 && code <= 107) {
background = colors[code - 100 + 8];
} else if (code === 38 || code === 48) {
// Skip unsupported extended colors, including their parameters.
if (codes[i + 1] === 5) {
i += 2;
} else if (codes[i + 1] === 2) {
i += 4;
}
}
}
};
const appendLog = (message) => {
const record = document.createElement('div');
let text = '';
const flushText = () => {
if (!text) {
return;
}
if (bold || foreground || background) {
const span = document.createElement('span');
span.textContent = text;
if (bold) {
span.style.fontWeight = 'bold';
}
if (foreground) {
span.style.color = foreground;
}
if (background) {
span.style.backgroundColor = background;
}
record.appendChild(span);
} else {
record.appendChild(document.createTextNode(text));
}
text = '';
};
// Retain parser state when an escape sequence crosses frame boundaries.
for (const symbol of message) {
const code = symbol.charCodeAt(0);
if (escapeState === 'string' || escapeState === 'stringEscape') {
if (symbol === '\x07' || (escapeState === 'stringEscape' && symbol === '\\')) {
escapeState = 'text';
} else {
escapeState = symbol === '\x1b' ? 'stringEscape' : 'string';
}
continue;
}
if (symbol === '\x1b') {
flushText();
escapeState = 'escape';
continue;
}
if (escapeState === 'escape') {
if (symbol === '[') {
escapeState = 'csi';
escapeParams = '';
} else if (']P^_'.includes(symbol)) {
escapeState = 'string';
} else {
escapeState = code >= 0x20 && code <= 0x2f ? 'intermediate' : 'text';
}
continue;
}
if (escapeState === 'intermediate') {
if (code >= 0x30 && code <= 0x7e) {
escapeState = 'text';
}
continue;
}
if (escapeState === 'csi') {
if (code >= 0x40 && code <= 0x7e) {
if (symbol === 'm') {
applyFormatting();
}
escapeState = 'text';
} else if (escapeParams.length < 128) {
escapeParams += symbol;
}
continue;
}
if (symbol === '\n' || symbol === '\t' || (code >= 0x20 && (code < 0x7f || code > 0x9f))) {
text += symbol;
}
}
flushText();
const plainText = record.textContent.toLowerCase();
record.hidden = !plainText.includes(filterText);
output.appendChild(record);
if (!record.hidden && searchText && plainText.includes(searchText)) {
matches.push(record);
if (selectedIndex < 0) {
selectedIndex = 0;
}
}
pruneHistory();
updateSelection();
if (autoscroll.checked) {
output.scrollTop = output.scrollHeight;
}
};
const scheduleReconnect = () => {
setConnectionStatus('disconnected');
if (active && reconnectTimer === null) {
reconnectTimer = setTimeout(() => {
reconnectTimer = null;
connect();
}, 3000);
}
};
const connect = () => {
if (!active || socket) {
return;
}
setConnectionStatus('connecting');
resetFormatting();
try {
socket = new WebSocket(`${location.protocol === 'https:' ? 'wss:' : 'ws:'}//${location.host}/webserial`);
} catch (err) {
scheduleReconnect();
return;
}
socket.onopen = () => {
setConnectionStatus('connected');
waitingForPong = false;
heartbeatTimer = setInterval(() => {
if (socket.readyState !== WebSocket.OPEN) {
return;
}
if (waitingForPong) {
socket.close();
return;
}
waitingForPong = true;
socket.send('ping');
}, 10000);
};
socket.onmessage = (event) => {
if (event.data === 'pong') {
waitingForPong = false;
} else if (typeof event.data === 'string') {
appendLog(event.data);
}
};
socket.onerror = () => socket.close();
socket.onclose = () => {
clearInterval(heartbeatTimer);
heartbeatTimer = null;
socket = null;
scheduleReconnect();
};
};
document.getElementById('clear-logs').addEventListener('click', () => {
output.replaceChildren();
applyView();
resetFormatting();
});
document.getElementById('download-logs').addEventListener('click', () => {
const text = Array.from(output.children, (record) => record.textContent).join('\n');
const url = URL.createObjectURL(new Blob([text], {type: 'text/plain;charset=utf-8'}));
const link = document.createElement('a');
link.href = url;
link.download = `otgateway-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.txt`;
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
});
autoscroll.addEventListener('change', () => {
if (autoscroll.checked) {
output.scrollTop = output.scrollHeight;
}
});
window.addEventListener('pagehide', () => {
active = false;
clearTimeout(reconnectTimer);
clearInterval(heartbeatTimer);
clearTimeout(viewTimer);
reconnectTimer = null;
heartbeatTimer = null;
viewTimer = null;
if (socket) {
socket.onopen = null;
socket.onmessage = null;
socket.onerror = null;
socket.onclose = null;
socket.close();
socket = null;
}
});
window.addEventListener('pageshow', (event) => {
if (event.persisted) {
active = true;
applyView();
connect();
}
});
connect();
});
</script>
</body>
</html>
+1 -3
View File
@@ -21,9 +21,7 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -179,7 +177,7 @@
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));
await lang.build();
lang.build();
const fillData = (data) => {
setInputValue("[name='hostname']", data.hostname);
+6 -19
View File
@@ -21,9 +21,7 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -113,13 +111,10 @@
<option value="24" data-i18n>sensors.types.otDhwBurnerHours</option>
<option value="25" data-i18n>sensors.types.otHeatingPumpHours</option>
<option value="26" data-i18n>sensors.types.otDhwPumpHours</option>
<option value="27" data-i18n>sensors.types.otCoolingHours</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="53" data-i18n>sensors.types.dht11</option>
<option value="54" data-i18n>sensors.types.dht22</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>
@@ -203,7 +198,7 @@
<script>
document.addEventListener("DOMContentLoaded", async () => {
const lang = new Lang(document.getElementById("lang"));
await lang.build();
lang.build();
const container = document.querySelector("article");
const templateNode = container.querySelector("#template");
@@ -226,7 +221,7 @@
sensorNode.dataset.id = sensorId;
setValue(".id", sensorId, sensorNode);
setValue(".pos", parseInt(sensorId) + 1, sensorNode);
setTextValue(".name", result[sensorId], sensorNode);
setValue(".name", result[sensorId], sensorNode);
container.appendChild(sensorNode);
container.appendChild(document.createElement("hr"));
@@ -273,35 +268,27 @@
}
switch(parseInt(event.target.value)) {
// NTC10K
// ntc
case 50:
parentGpio.classList.remove("hidden");
parentAddress.classList.add("hidden");
address.removeAttribute("pattern");
break;
// OneWire
// 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;
// Bluetooth
// 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;
// DHT
case 53:
case 54:
parentGpio.classList.remove("hidden");
parentAddress.classList.add("hidden");
address.removeAttribute("pattern");
break;
// other
default:
parentGpio.classList.add("hidden");
@@ -335,7 +322,7 @@
}
});
setupForm(`.sensor[data-id='${sensorId}'] form`, fillData, ['name', 'address']);
setupForm(`.sensor[data-id='${sensorId}'] form`, fillData, ['address']);
}
} catch (error) {
+49 -177
View File
@@ -21,9 +21,7 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -106,7 +104,7 @@
<option disabled selected data-i18n>settings.system.ntp.timezonePresets</option>
</select>
</div>
</label>
</label>
</fieldset>
<fieldset>
@@ -126,8 +124,8 @@
</label>
<label>
<input type="checkbox" name="system[webSerial][enabled]" value="true">
<span data-i18n>settings.system.webSerial.enable</span>
<input type="checkbox" name="system[telnet][enabled]" value="true">
<span data-i18n>settings.system.telnet.enable</span>
</label>
<label>
@@ -156,6 +154,12 @@
<option value="115200">115200</option>
</select>
</label>
<label>
<span data-i18n>settings.system.telnet.port.title</span>
<input type="number" inputmode="numeric" name="system[telnet][port]" min="1" max="65535" step="1" required>
<small data-i18n>settings.system.telnet.port.note</small>
</label>
</div>
<mark data-i18n>settings.note.restart</mark>
@@ -187,63 +191,21 @@
</div>
<div class="grid">
<label>
<span data-i18n>settings.heating.hyst</span>
<input type="number" inputmode="decimal" name="heating[hysteresis]" min="0" max="5" step="0.05" required>
</label>
<label>
<span data-i18n>settings.heating.turboFactor</span>
<input type="number" inputmode="decimal" name="heating[turboFactor]" min="1.5" max="10" step="0.1" required>
</label>
<label>
<span data-i18n>settings.maxModulation</span>
<input type="number" inputmode="numeric" name="heating[maxModulation]" min="1" max="100" step="1" required>
</label>
</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 />
<details>
<summary><b data-i18n>settings.heating.hyst.title</b></summary>
<div>
<fieldset>
<label>
<input type="checkbox" name="heating[hysteresis][enabled]" value="true">
<span data-i18n>settings.enable</span>
</label>
</fieldset>
<div class="grid">
<label>
<span data-i18n>settings.heating.hyst.value</span>
<input type="number" inputmode="decimal" name="heating[hysteresis][value]" min="0" max="5" step="0.05" required>
</label>
</div>
</div>
<small data-i18n>settings.heating.hyst.desc</small>
</details>
<label>
<span data-i18n>settings.maxModulation</span>
<input type="number" inputmode="numeric" name="heating[maxModulation]" min="1" max="100" step="1" required>
</label>
<hr />
@@ -274,15 +236,13 @@
<div class="grid">
<label>
<span data-i18n>settings.freezeProtection.highTemp.title</span>
<input type="number" inputmode="numeric" name="heating[freezeProtection][highTemp]" min="0" max="0" step="1" required>
<small data-i18n>settings.freezeProtection.highTemp.note</small>
<span data-i18n>settings.freezeProtection.lowTemp</span>
<input type="number" inputmode="numeric" name="heating[freezeProtection][lowTemp]" min="0" max="0" step="1" required>
</label>
<label>
<span data-i18n>settings.freezeProtection.lowTemp.title</span>
<input type="number" inputmode="numeric" name="heating[freezeProtection][lowTemp]" min="0" max="0" step="1" required>
<small data-i18n>settings.freezeProtection.lowTemp.note</small>
<span data-i18n>settings.freezeProtection.thresholdTime</span>
<input type="number" inputmode="numeric" name="heating[freezeProtection][thresholdTime]" min="30" max="1800" step="1" required>
</label>
</div>
@@ -614,81 +574,6 @@
</label>
</div>
<hr />
<details>
<summary><b data-i18n>settings.ot.disablingAction.title</b></summary>
<div>
<fieldset>
<small data-i18n>settings.ot.disablingAction.desc</small>
</fieldset>
<fieldset>
<legend><b data-i18n>settings.ot.disablingAction.heating</b></legend>
<label>
<input type="radio" name="opentherm[heatingDisablingAction]" value="0" />
<span data-i18n>settings.ot.disablingAction.off</span>
</label>
<label>
<input type="radio" name="opentherm[heatingDisablingAction]" value="1" />
<span data-i18n>settings.ot.disablingAction.zeroSetpoint</span>
</label>
<label>
<input type="radio" name="opentherm[heatingDisablingAction]" value="2" />
<span data-i18n>settings.ot.disablingAction.offAndZeroSetpoint</span>
</label>
</fieldset>
<hr />
<fieldset>
<legend><b data-i18n>settings.ot.disablingAction.dhw</b></legend>
<label>
<input type="radio" name="opentherm[dhwDisablingAction]" value="0" />
<span data-i18n>settings.ot.disablingAction.off</span>
</label>
<label>
<input type="radio" name="opentherm[dhwDisablingAction]" value="1" />
<span data-i18n>settings.ot.disablingAction.zeroSetpoint</span>
</label>
<label>
<input type="radio" name="opentherm[dhwDisablingAction]" value="2" />
<span data-i18n>settings.ot.disablingAction.offAndZeroSetpoint</span>
</label>
</fieldset>
<hr />
<fieldset>
<legend><b data-i18n>settings.ot.disablingAction.ch2</b></legend>
<label>
<input type="radio" name="opentherm[ch2DisablingAction]" value="0" />
<span data-i18n>settings.ot.disablingAction.off</span>
</label>
<label>
<input type="radio" name="opentherm[ch2DisablingAction]" value="1" />
<span data-i18n>settings.ot.disablingAction.zeroSetpoint</span>
</label>
<label>
<input type="radio" name="opentherm[ch2DisablingAction]" value="2" />
<span data-i18n>settings.ot.disablingAction.offAndZeroSetpoint</span>
</label>
</fieldset>
</div>
</details>
<hr />
<details>
<summary><b data-i18n>settings.ot.options.title</b></summary>
@@ -778,17 +663,12 @@
<span data-i18n>settings.ot.options.immergasFix</span>
</label>
<label>
<input type="checkbox" name="opentherm[options][alwaysSendIndoorTemp]" value="true">
<span data-i18n>settings.ot.options.alwaysSendIndoorTemp</span>
</label>
<hr />
<label>
<input type="checkbox" name="opentherm[options][nativeOTC]" value="true">
<span data-i18n>settings.ot.nativeOTC.title</span><br />
<small data-i18n>settings.ot.nativeOTC.note</small>
<input type="checkbox" name="opentherm[options][nativeHeatingControl]" value="true">
<span data-i18n>settings.ot.nativeHeating.title</span><br />
<small data-i18n>settings.ot.nativeHeating.note</small>
</label>
</fieldset>
</div>
@@ -872,11 +752,6 @@
<input type="checkbox" name="externalPump[use]" value="true">
<span data-i18n>settings.extPump.use</span>
</label>
<label>
<input type="checkbox" name="externalPump[invertState]" value="true">
<span data-i18n>settings.extPump.invertState</span>
</label>
</fieldset>
<div class="grid">
@@ -1011,7 +886,7 @@
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));
await lang.build();
lang.build();
let etChart = null;
let etChartConfig = {
@@ -1021,8 +896,7 @@
unitSystem: null,
targetTemp: null,
minTemp: null,
maxTemp: null,
decimated: false
maxTemp: null
};
const hasNeedDecimationChart = () => {
@@ -1040,7 +914,7 @@
datasets: [{
borderColor: (context) => {
const chart = context.chart;
const { ctx, chartArea } = chart;
const {ctx, chartArea} = chart;
if (!chartArea) {
return;
@@ -1082,6 +956,12 @@
},
legend: {
display: false
},
decimation: {
enabled: hasNeedDecimationChart(),
algorithm: 'lttb',
samples: 30,
threshold: 30
}
},
scales: {
@@ -1127,18 +1007,19 @@
return;
}
let data = [];
etChartConfig.decimated = hasNeedDecimationChart();
for (let value = 30; value >= -30; value -= etChartConfig.decimated ? 2 : 1) {
while (etChart.data.datasets[0].data.length) {
etChart.data.datasets[0].data.pop();
}
for (let value = 30; value >= -30; value--) {
const outdoorTemp = etChartConfig.unitSystem == 0 ? value : c2f(value);
data.push({
etChart.data.datasets[0].data.push({
x: parseFloat(outdoorTemp.toFixed(1)),
y: parseFloat(calculateEquithermTemp(outdoorTemp).toFixed(1))
});
}
etChart.data.datasets[0].data = data;
etChart.data.datasets[0].label = i18n("settings.equitherm.chart.setpointTemp");
etChart.options.scales.x.title.text = i18n("settings.equitherm.chart.outdoorTemp");
etChart.options.scales.y.title.text = i18n("settings.equitherm.chart.setpointTemp");
@@ -1169,7 +1050,8 @@
setSelectValue("[name='system[logLevel]']", data.system.logLevel);
setCheckboxValue("[name='system[serial][enabled]']", data.system.serial.enabled);
setSelectValue("[name='system[serial][baudrate]']", data.system.serial.baudrate);
setCheckboxValue("[name='system[webSerial][enabled]']", data.system.webSerial.enabled);
setCheckboxValue("[name='system[telnet][enabled]']", data.system.telnet.enabled);
setInputValue("[name='system[telnet][port]']", data.system.telnet.port);
setInputValue("[name='system[ntp][server]']", data.system.ntp.server);
setInputValue("[name='system[ntp][timezone]']", data.system.ntp.timezone);
setRadioValue("[name='system[unitSystem]']", data.system.unitSystem);
@@ -1192,9 +1074,6 @@
setInputValue("[name='opentherm[flags]']", data.opentherm.flags);
setInputValue("[name='opentherm[minPower]']", data.opentherm.minPower);
setInputValue("[name='opentherm[maxPower]']", data.opentherm.maxPower);
setRadioValue("[name='opentherm[heatingDisablingAction]']", data.opentherm.heatingDisablingAction);
setRadioValue("[name='opentherm[dhwDisablingAction]']", data.opentherm.dhwDisablingAction);
setRadioValue("[name='opentherm[ch2DisablingAction]']", data.opentherm.ch2DisablingAction);
setCheckboxValue("[name='opentherm[options][dhwSupport]']", data.opentherm.options.dhwSupport);
setCheckboxValue("[name='opentherm[options][coolingSupport]']", data.opentherm.options.coolingSupport);
setCheckboxValue("[name='opentherm[options][summerWinterMode]']", data.opentherm.options.summerWinterMode);
@@ -1210,9 +1089,8 @@
setCheckboxValue("[name='opentherm[options][autoFaultReset]']", data.opentherm.options.autoFaultReset);
setCheckboxValue("[name='opentherm[options][autoDiagReset]']", data.opentherm.options.autoDiagReset);
setCheckboxValue("[name='opentherm[options][setDateAndTime]']", data.opentherm.options.setDateAndTime);
setCheckboxValue("[name='opentherm[options][nativeOTC]']", data.opentherm.options.nativeOTC);
setCheckboxValue("[name='opentherm[options][nativeHeatingControl]']", data.opentherm.options.nativeHeatingControl);
setCheckboxValue("[name='opentherm[options][immergasFix]']", data.opentherm.options.immergasFix);
setCheckboxValue("[name='opentherm[options][alwaysSendIndoorTemp]']", data.opentherm.options.alwaysSendIndoorTemp);
setBusy('#ot-settings-busy', '#ot-settings', false);
// MQTT
@@ -1229,7 +1107,6 @@
// Extpump
setCheckboxValue("[name='externalPump[use]']", data.externalPump.use);
setInputValue("[name='externalPump[gpio]']", data.externalPump.gpio < 255 ? data.externalPump.gpio : '');
setCheckboxValue("[name='externalPump[invertState]']", data.externalPump.invertState);
setInputValue("[name='externalPump[postCirculationTime]']", data.externalPump.postCirculationTime);
setInputValue("[name='externalPump[antiStuckInterval]']", data.externalPump.antiStuckInterval);
setInputValue("[name='externalPump[antiStuckTime]']", data.externalPump.antiStuckTime);
@@ -1259,12 +1136,9 @@
"min": data.system.unitSystem == 0 ? 1 : 33,
"max": data.system.unitSystem == 0 ? 100 : 212
});
setCheckboxValue("[name='heating[hysteresis][enabled]']", data.heating.hysteresis.enabled);
setInputValue("[name='heating[hysteresis][value]']", data.heating.hysteresis.value);
setInputValue("[name='heating[hysteresis]']", data.heating.hysteresis);
setInputValue("[name='heating[turboFactor]']", data.heating.turboFactor);
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, {
"min": 0,
"max": data.system.unitSystem == 0 ? 100 : 212
@@ -1273,14 +1147,11 @@
"min": 0,
"max": data.system.unitSystem == 0 ? 99 : 211
});
setInputValue("[name='heating[freezeProtection][highTemp]']", data.heating.freezeProtection.highTemp, {
"min": data.system.unitSystem == 0 ? 1 : 34,
"max": data.system.unitSystem == 0 ? 50 : 122
});
setInputValue("[name='heating[freezeProtection][lowTemp]']", data.heating.freezeProtection.lowTemp, {
"min": data.system.unitSystem == 0 ? 1 : 34,
"max": data.system.unitSystem == 0 ? 30 : 86
});
setInputValue("[name='heating[freezeProtection][thresholdTime]']", data.heating.freezeProtection.thresholdTime);
setBusy('#heating-settings-busy', '#heating-settings', false);
// DHW
@@ -1304,7 +1175,7 @@
setBusy('#dhw-settings-busy', '#dhw-settings', false);
// Emergency mode
if (data.opentherm.options.nativeOTC) {
if (data.opentherm.options.nativeHeatingControl) {
setInputValue("[name='emergency[target]']", data.emergency.target, {
"min": data.system.unitSystem == 0 ? 5 : 41,
"max": data.system.unitSystem == 0 ? 40 : 104
@@ -1464,8 +1335,9 @@
if (etChart) {
etChart.resize();
if (etChartConfig.decimated != hasNeedDecimationChart()) {
makeEquithermChart();
if (etChart.options.plugins.decimation.enabled != hasNeedDecimationChart()) {
etChart.options.plugins.decimation.enabled = hasNeedDecimationChart();
etChart.update();
}
}
});
+10 -128
View File
@@ -21,9 +21,7 @@
<li>
<select id="lang" aria-label="Lang">
<option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option>
<option value="nl">NL</option>
<option value="ru">RU</option>
</select>
</li>
@@ -35,7 +33,7 @@
<article>
<div>
<hgroup>
<h2 data-i18n>upgrade.section.backupAndRestore.title</h2>
<h2 data-i18n>upgrade.section.backupAndRestore</h2>
<p data-i18n>upgrade.section.backupAndRestore.desc</p>
</hgroup>
@@ -56,25 +54,25 @@
<article>
<div>
<hgroup>
<h2 data-i18n>upgrade.section.upgrade.title</h2>
<h2 data-i18n>upgrade.section.upgrade</h2>
<p data-i18n>upgrade.section.upgrade.desc</p>
</hgroup>
<form action="/api/upgrade" id="upgrade">
<fieldset class="primary">
<label>
<label for="firmware-file">
<span data-i18n>upgrade.fw</span>:
<div class="grid">
<input type="file" name="fw" accept=".bin">
<button type="button" class="fwResult hidden" disabled></button>
<input type="file" name="firmware" id="firmware-file" accept=".bin">
<button type="button" class="upgrade-firmware-result hidden" disabled></button>
</div>
</label>
<label>
<label for="filesystem-file">
<span data-i18n>upgrade.fs</span>:
<div class="grid">
<input type="file" name="fs" accept=".bin">
<button type="button" class="fsResult hidden" disabled></button>
<input type="file" name="filesystem" id="filesystem-file" accept=".bin">
<button type="button" class="upgrade-filesystem-result hidden" disabled></button>
</div>
</label>
</fieldset>
@@ -105,126 +103,10 @@
<script>
document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang'));
await lang.build();
lang.build();
setupRestoreBackupForm('#restore');
const upgradeForm = document.querySelector('#upgrade');
if (upgradeForm) {
upgradeForm.reset();
const statusToText = (status) => {
switch (status) {
case 0:
return "None";
case 1:
return "No file";
case 2:
return "Success";
case 3:
return "Prohibited";
case 4:
return "Size mismatch";
case 5:
return "Error on start";
case 6:
return "Error on write";
case 7:
return "Error on finish";
default:
return "Unknown";
}
};
upgradeForm.addEventListener('submit', async (event) => {
event.preventDefault();
hide('.fwResult');
hide('.fsResult');
let button = upgradeForm.querySelector('button[type="submit"]');
button.textContent = i18n('button.uploading');
button.setAttribute('disabled', true);
button.setAttribute('aria-busy', true);
try {
let fd = new FormData();
const fw = upgradeForm.querySelector("[name='fw']").files;
if (fw.length > 0) {
fd.append("fw_size", fw[0].size);
fd.append("fw", fw[0]);
}
const fs = upgradeForm.querySelector("[name='fs']").files;
if (fs.length > 0) {
fd.append("fs_size", fs[0].size);
fd.append("fs", fs[0]);
}
let response = await fetch(upgradeForm.action, {
method: "POST",
cache: "no-cache",
credentials: "include",
body: fd
});
if (response.status != 202 && response.status != 406) {
throw new Error('Response not valid');
}
const result = await response.json();
let resItem = upgradeForm.querySelector('.fwResult');
if (resItem && result.firmware.status > 1) {
resItem.textContent = statusToText(result.firmware.status);
resItem.classList.remove('hidden');
if (result.firmware.status == 2) {
resItem.classList.remove('failed');
resItem.classList.add('success');
} else {
resItem.classList.remove('success');
resItem.classList.add('failed');
if (result.firmware.error != "") {
resItem.textContent += `: ${result.firmware.error}`;
}
}
}
resItem = upgradeForm.querySelector('.fsResult');
if (resItem && result.filesystem.status > 1) {
resItem.textContent = statusToText(result.filesystem.status);
resItem.classList.remove('hidden');
if (result.filesystem.status == 2) {
resItem.classList.remove('failed');
resItem.classList.add('success');
} else {
resItem.classList.remove('success');
resItem.classList.add('failed');
if (result.filesystem.error != "") {
resItem.textContent += `: ${result.filesystem.error}`;
}
}
}
} catch (err) {
console.log(err);
button.textContent = i18n('button.error');
button.classList.add('failed');
} finally {
setTimeout(() => {
button.removeAttribute('aria-busy');
button.removeAttribute('disabled');
button.classList.remove('success', 'failed');
button.textContent = i18n(button.dataset.i18n);
upgradeForm.reset();
}, 10000);
}
});
}
setupUpgradeForm('#upgrade');
});
</script>
</body>
+10 -4
View File
@@ -1,14 +1,20 @@
/**
* Skipped minification because the original files appears to be already minified.
* Original file: /npm/chart.js@4.4.8/dist/chart.umd.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
/*!
* Chart.js v4.5.0
* Chart.js v4.4.8
* https://www.chartjs.org
* (c) 2025 Chart.js Contributors
* Released under the MIT License
*/
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Jo},get Decimation(){return ta},get Filler(){return ba},get Legend(){return Ma},get SubTitle(){return Pa},get Title(){return ka},get Tooltip(){return Na}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;a<r;a++)e.call(i,t[a],a);else if(o(t))for(l=Object.keys(t),r=l.length,a=0;a<r;a++)e.call(i,t[l[a]],l[a])}function f(t,e){let i,s,n,o;if(!t||!e||t.length!==e.length)return!1;for(i=0,s=t.length;i<s;++i)if(n=t[i],o=e[i],n.datasetIndex!==o.datasetIndex||n.index!==o.index)return!1;return!0}function g(t){if(n(t))return t.map(g);if(o(t)){const e=Object.create(null),i=Object.keys(t),s=i.length;let n=0;for(;n<s;++n)e[i[n]]=g(t[i[n]]);return e}return t}function p(t){return-1===["__proto__","prototype","constructor"].indexOf(t)}function m(t,e,i,s){if(!p(t))return;const n=e[t],a=i[t];o(n)&&o(a)?x(n,a,s):e[t]=g(a)}function x(t,e,i){const s=n(e)?e:[e],a=s.length;if(!o(t))return t;const r=(i=i||{}).merger||m;let l;for(let e=0;e<a;++e){if(l=s[e],!o(l))continue;const n=Object.keys(l);for(let e=0,s=n.length;e<s;++e)r(n[e],t,l,i)}return t}function b(t,e){return x(t,e,{merger:_})}function _(t,e,i){if(!p(t))return;const s=e[t],n=i[t];o(s)&&o(n)?b(s,n):Object.prototype.hasOwnProperty.call(e,t)||(e[t]=g(n))}const y={"":t=>t,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)<i}function B(t){const e=Math.round(t);t=V(t,e,t/1e3)?e:t;const i=Math.pow(10,Math.floor(z(t))),s=t/i;return(s<=1?1:s<=2?2:s<=5?5:10)*i}function W(t){const e=[],i=Math.sqrt(t);let s;for(s=1;s<i;s++)t%s==0&&(e.push(s),e.push(t/s));return i===(0|i)&&e.push(i),e.sort(((t,e)=>t-e)).pop(),e}function N(t){return!function(t){return"symbol"==typeof t||"object"==typeof t&&null!==t&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;s<n;s++)o=t[s][i],isNaN(o)||(e.min=Math.min(e.min,o),e.max=Math.max(e.max,o))}function $(t){return t*(C/180)}function Y(t){return t*(180/C)}function U(t){if(!a(t))return;let e=1,i=0;for(;Math.round(t*e)/e!==t;)e*=10,i++;return i}function X(t,e){const i=e.x-t.x,s=e.y-t.y,n=Math.sqrt(i*i+s*s);let o=Math.atan2(s,i);return o<-.5*C&&(o+=O),{angle:o,distance:n}}function q(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function K(t,e){return(t-e+A)%O-C}function G(t){return(t%O+O)%O}function J(t,e,i,s){const n=G(t),o=G(e),a=G(i),r=G(o-n),l=G(a-n),h=G(n-o),c=G(n-a);return n===o||n===a||s&&o===a||r>l&&h<c}function Z(t,e,i){return Math.max(e,Math.min(i,t))}function Q(t){return Z(t,-32768,32767)}function tt(t,e,i,s=1e-6){return t>=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]<e);let s,n=t.length-1,o=0;for(;n-o>1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return n<i||n===i&&t[s+1][e]===i}:s=>t[s][e]<i),st=(t,e,i)=>et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;s<n&&t[s]<e;)s++;for(;n>s&&t[n-1]>i;)n--;return s>0||n<t.length?t.slice(s,n):t}const ot=["push","pop","shift","splice","unshift"];function at(t,e){t._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),ot.forEach((e=>{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...eLine truncated
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";var t=Object.freeze({__proto__:null,get Colors(){return Go},get Decimation(){return Qo},get Filler(){return ma},get Legend(){return ya},get SubTitle(){return ka},get Title(){return Ma},get Tooltip(){return Ba}});function e(){}const i=(()=>{let t=0;return()=>t++})();function s(t){return null==t}function n(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.slice(0,7)&&"Array]"===e.slice(-6)}function o(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}function a(t){return("number"==typeof t||t instanceof Number)&&isFinite(+t)}function r(t,e){return a(t)?t:e}function l(t,e){return void 0===t?e:t}const h=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:+t/e,c=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function d(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function u(t,e,i,s){let a,r,l;if(n(t))if(r=t.length,s)for(a=r-1;a>=0;a--)e.call(i,t[a],a);else for(a=0;a<r;a++)e.call(i,t[a],a);else if(o(t))for(l=Object.keys(t),r=l.length,a=0;a<r;a++)e.call(i,t[l[a]],l[a])}function f(t,e){let i,s,n,o;if(!t||!e||t.length!==e.length)return!1;for(i=0,s=t.length;i<s;++i)if(n=t[i],o=e[i],n.datasetIndex!==o.datasetIndex||n.index!==o.index)return!1;return!0}function g(t){if(n(t))return t.map(g);if(o(t)){const e=Object.create(null),i=Object.keys(t),s=i.length;let n=0;for(;n<s;++n)e[i[n]]=g(t[i[n]]);return e}return t}function p(t){return-1===["__proto__","prototype","constructor"].indexOf(t)}function m(t,e,i,s){if(!p(t))return;const n=e[t],a=i[t];o(n)&&o(a)?x(n,a,s):e[t]=g(a)}function x(t,e,i){const s=n(e)?e:[e],a=s.length;if(!o(t))return t;const r=(i=i||{}).merger||m;let l;for(let e=0;e<a;++e){if(l=s[e],!o(l))continue;const n=Object.keys(l);for(let e=0,s=n.length;e<s;++e)r(n[e],t,l,i)}return t}function b(t,e){return x(t,e,{merger:_})}function _(t,e,i){if(!p(t))return;const s=e[t],n=i[t];o(s)&&o(n)?b(s,n):Object.prototype.hasOwnProperty.call(e,t)||(e[t]=g(n))}const y={"":t=>t,x:t=>t.x,y:t=>t.y};function v(t){const e=t.split("."),i=[];let s="";for(const t of e)s+=t,s.endsWith("\\")?s=s.slice(0,-1)+".":(i.push(s),s="");return i}function M(t,e){const i=y[e]||(y[e]=function(t){const e=v(t);return t=>{for(const i of e){if(""===i)break;t=t&&t[i]}return t}}(e));return i(t)}function w(t){return t.charAt(0).toUpperCase()+t.slice(1)}const k=t=>void 0!==t,S=t=>"function"==typeof t,P=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function D(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const C=Math.PI,O=2*C,A=O+C,T=Number.POSITIVE_INFINITY,L=C/180,E=C/2,R=C/4,I=2*C/3,z=Math.log10,F=Math.sign;function V(t,e,i){return Math.abs(t-e)<i}function B(t){const e=Math.round(t);t=V(t,e,t/1e3)?e:t;const i=Math.pow(10,Math.floor(z(t))),s=t/i;return(s<=1?1:s<=2?2:s<=5?5:10)*i}function W(t){const e=[],i=Math.sqrt(t);let s;for(s=1;s<i;s++)t%s==0&&(e.push(s),e.push(t/s));return i===(0|i)&&e.push(i),e.sort(((t,e)=>t-e)).pop(),e}function N(t){return!function(t){return"symbol"==typeof t||"object"==typeof t&&null!==t&&!(Symbol.toPrimitive in t||"toString"in t||"valueOf"in t)}(t)&&!isNaN(parseFloat(t))&&isFinite(t)}function H(t,e){const i=Math.round(t);return i-e<=t&&i+e>=t}function j(t,e,i){let s,n,o;for(s=0,n=t.length;s<n;s++)o=t[s][i],isNaN(o)||(e.min=Math.min(e.min,o),e.max=Math.max(e.max,o))}function $(t){return t*(C/180)}function Y(t){return t*(180/C)}function U(t){if(!a(t))return;let e=1,i=0;for(;Math.round(t*e)/e!==t;)e*=10,i++;return i}function X(t,e){const i=e.x-t.x,s=e.y-t.y,n=Math.sqrt(i*i+s*s);let o=Math.atan2(s,i);return o<-.5*C&&(o+=O),{angle:o,distance:n}}function q(t,e){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function K(t,e){return(t-e+A)%O-C}function G(t){return(t%O+O)%O}function Z(t,e,i,s){const n=G(t),o=G(e),a=G(i),r=G(o-n),l=G(a-n),h=G(n-o),c=G(n-a);return n===o||n===a||s&&o===a||r>l&&h<c}function J(t,e,i){return Math.max(e,Math.min(i,t))}function Q(t){return J(t,-32768,32767)}function tt(t,e,i,s=1e-6){return t>=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function et(t,e,i){i=i||(i=>t[i]<e);let s,n=t.length-1,o=0;for(;n-o>1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const it=(t,e,i,s)=>et(t,i,s?s=>{const n=t[s][e];return n<i||n===i&&t[s+1][e]===i}:s=>t[s][e]<i),st=(t,e,i)=>et(t,i,(s=>t[s][e]>=i));function nt(t,e,i){let s=0,n=t.length;for(;s<n&&t[s]<e;)s++;for(;n>s&&t[n-1]>i;)n--;return s>0||n<t.length?t.slice(s,n):t}const ot=["push","pop","shift","splice","unshift"];function at(t,e){t._chartjs?t._chartjs.listeners.push(e):(Object.defineProperty(t,"_chartjs",{configurable:!0,enumerable:!1,value:{listeners:[e]}}),ot.forEach((e=>{const i="_onData"+w(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...eLine truncated
/*!
* @kurkle/color v0.3.2
* https://github.com/kurkle/color#readme
* (c) 2023 Jukka Kurkela
* Released under the MIT License
*/function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e<i?6:0):e===n?(i-t)/s+2:(t-e)/s+4}(e,i,s,h,n),r=60*r+.5),[0|r,l||0,a]}function zt(t,e,i,s){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,i,s)).map(Mt)}function Ft(t,e,i){return zt(Lt,t,e,i)}function Vt(t){return(t%360+360)%360}function Bt(t){const e=Tt.exec(t);let i,s=255;if(!e)return;e[5]!==i&&(s=e[6]?vt(+e[5]):Mt(+e[5]));const n=Vt(+e[2]),o=+e[3]/100,a=+e[4]/100;return i="hwb"===e[1]?function(t,e,i){return zt(Rt,t,e,i)}(n,o,a):"hsv"===e[1]?function(t,e,i){return zt(Et,t,e,i)}(n,o,a):Ft(n,o,a),{r:i[0],g:i[1],b:i[2],a:s}}const Wt={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Nt={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};let Ht;function jt(t){Ht||(Ht=function(){const t={},e=Object.keys(Nt),i=Object.keys(Wt);let s,n,o,a,r;for(s=0;s<e.length;s++){for(a=r=e[s],n=0;n<i.length;n++)o=i[n],r=r.replace(o,Wt[o]);o=parseInt(Nt[a],16),t[r]=[o>>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),Line truncated
//# sourceMappingURL=chart.umd.min.js.map
*/function _t(t){return t+.5|0}const yt=(t,e,i)=>Math.max(Math.min(t,i),e);function vt(t){return yt(_t(2.55*t),0,255)}function Mt(t){return yt(_t(255*t),0,255)}function wt(t){return yt(_t(t/2.55)/100,0,1)}function kt(t){return yt(_t(100*t),0,100)}const St={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},Pt=[..."0123456789ABCDEF"],Dt=t=>Pt[15&t],Ct=t=>Pt[(240&t)>>4]+Pt[15&t],Ot=t=>(240&t)>>4==(15&t);function At(t){var e=(t=>Ot(t.r)&&Ot(t.g)&&Ot(t.b)&&Ot(t.a))(t)?Dt:Ct;return t?"#"+e(t.r)+e(t.g)+e(t.b)+((t,e)=>t<255?e(t):"")(t.a,e):void 0}const Tt=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function Lt(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function Et(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function Rt(t,e,i){const s=Lt(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function It(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=function(t,e,i,s,n){return t===n?(e-i)/s+(e<i?6:0):e===n?(i-t)/s+2:(t-e)/s+4}(e,i,s,h,n),r=60*r+.5),[0|r,l||0,a]}function zt(t,e,i,s){return(Array.isArray(e)?t(e[0],e[1],e[2]):t(e,i,s)).map(Mt)}function Ft(t,e,i){return zt(Lt,t,e,i)}function Vt(t){return(t%360+360)%360}function Bt(t){const e=Tt.exec(t);let i,s=255;if(!e)return;e[5]!==i&&(s=e[6]?vt(+e[5]):Mt(+e[5]));const n=Vt(+e[2]),o=+e[3]/100,a=+e[4]/100;return i="hwb"===e[1]?function(t,e,i){return zt(Rt,t,e,i)}(n,o,a):"hsv"===e[1]?function(t,e,i){return zt(Et,t,e,i)}(n,o,a):Ft(n,o,a),{r:i[0],g:i[1],b:i[2],a:s}}const Wt={x:"dark",Z:"light",Y:"re",X:"blu",W:"gr",V:"medium",U:"slate",A:"ee",T:"ol",S:"or",B:"ra",C:"lateg",D:"ights",R:"in",Q:"turquois",E:"hi",P:"ro",O:"al",N:"le",M:"de",L:"yello",F:"en",K:"ch",G:"arks",H:"ea",I:"ightg",J:"wh"},Nt={OiceXe:"f0f8ff",antiquewEte:"faebd7",aqua:"ffff",aquamarRe:"7fffd4",azuY:"f0ffff",beige:"f5f5dc",bisque:"ffe4c4",black:"0",blanKedOmond:"ffebcd",Xe:"ff",XeviTet:"8a2be2",bPwn:"a52a2a",burlywood:"deb887",caMtXe:"5f9ea0",KartYuse:"7fff00",KocTate:"d2691e",cSO:"ff7f50",cSnflowerXe:"6495ed",cSnsilk:"fff8dc",crimson:"dc143c",cyan:"ffff",xXe:"8b",xcyan:"8b8b",xgTMnPd:"b8860b",xWay:"a9a9a9",xgYF:"6400",xgYy:"a9a9a9",xkhaki:"bdb76b",xmagFta:"8b008b",xTivegYF:"556b2f",xSange:"ff8c00",xScEd:"9932cc",xYd:"8b0000",xsOmon:"e9967a",xsHgYF:"8fbc8f",xUXe:"483d8b",xUWay:"2f4f4f",xUgYy:"2f4f4f",xQe:"ced1",xviTet:"9400d3",dAppRk:"ff1493",dApskyXe:"bfff",dimWay:"696969",dimgYy:"696969",dodgerXe:"1e90ff",fiYbrick:"b22222",flSOwEte:"fffaf0",foYstWAn:"228b22",fuKsia:"ff00ff",gaRsbSo:"dcdcdc",ghostwEte:"f8f8ff",gTd:"ffd700",gTMnPd:"daa520",Way:"808080",gYF:"8000",gYFLw:"adff2f",gYy:"808080",honeyMw:"f0fff0",hotpRk:"ff69b4",RdianYd:"cd5c5c",Rdigo:"4b0082",ivSy:"fffff0",khaki:"f0e68c",lavFMr:"e6e6fa",lavFMrXsh:"fff0f5",lawngYF:"7cfc00",NmoncEffon:"fffacd",ZXe:"add8e6",ZcSO:"f08080",Zcyan:"e0ffff",ZgTMnPdLw:"fafad2",ZWay:"d3d3d3",ZgYF:"90ee90",ZgYy:"d3d3d3",ZpRk:"ffb6c1",ZsOmon:"ffa07a",ZsHgYF:"20b2aa",ZskyXe:"87cefa",ZUWay:"778899",ZUgYy:"778899",ZstAlXe:"b0c4de",ZLw:"ffffe0",lime:"ff00",limegYF:"32cd32",lRF:"faf0e6",magFta:"ff00ff",maPon:"800000",VaquamarRe:"66cdaa",VXe:"cd",VScEd:"ba55d3",VpurpN:"9370db",VsHgYF:"3cb371",VUXe:"7b68ee",VsprRggYF:"fa9a",VQe:"48d1cc",VviTetYd:"c71585",midnightXe:"191970",mRtcYam:"f5fffa",mistyPse:"ffe4e1",moccasR:"ffe4b5",navajowEte:"ffdead",navy:"80",Tdlace:"fdf5e6",Tive:"808000",TivedBb:"6b8e23",Sange:"ffa500",SangeYd:"ff4500",ScEd:"da70d6",pOegTMnPd:"eee8aa",pOegYF:"98fb98",pOeQe:"afeeee",pOeviTetYd:"db7093",papayawEp:"ffefd5",pHKpuff:"ffdab9",peru:"cd853f",pRk:"ffc0cb",plum:"dda0dd",powMrXe:"b0e0e6",purpN:"800080",YbeccapurpN:"663399",Yd:"ff0000",Psybrown:"bc8f8f",PyOXe:"4169e1",saddNbPwn:"8b4513",sOmon:"fa8072",sandybPwn:"f4a460",sHgYF:"2e8b57",sHshell:"fff5ee",siFna:"a0522d",silver:"c0c0c0",skyXe:"87ceeb",UXe:"6a5acd",UWay:"708090",UgYy:"708090",snow:"fffafa",sprRggYF:"ff7f",stAlXe:"4682b4",tan:"d2b48c",teO:"8080",tEstN:"d8bfd8",tomato:"ff6347",Qe:"40e0d0",viTet:"ee82ee",JHt:"f5deb3",wEte:"ffffff",wEtesmoke:"f5f5f5",Lw:"ffff00",LwgYF:"9acd32"};let Ht;function jt(t){Ht||(Ht=function(){const t={},e=Object.keys(Nt),i=Object.keys(Wt);let s,n,o,a,r;for(s=0;s<e.length;s++){for(a=r=e[s],n=0;n<i.length;n++)o=i[n],r=r.replace(o,Wt[o]);o=parseInt(Nt[a],16),t[r]=[o>>16&255,o>>8&255,255&o]}return t}(),Ht.transparent=[0,0,0,0]);const e=Ht[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}const $t=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const Yt=t=>t<=.0031308?12.92*t:1.055*Math.pow(t,1/2.4)-.055,Ut=t=>t<=.04045?t/12.92:Math.pow((t+.055)/1.055,2.4);function Xt(t,e,i){if(t){let s=It(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),Line truncated
//# sourceMappingURL=chart.umd.js.map
+20 -84
View File
@@ -141,18 +141,10 @@ const setupNetworkScanForm = (formSelector, tableSelector) => {
row.insertCell().textContent = `#${i + 1}`;
const nameCell = row.insertCell();
if (result[i].hidden) {
const hiddenName = document.createElement('i');
hiddenName.textContent = result[i].bssid;
nameCell.appendChild(hiddenName);
} else {
nameCell.textContent = result[i].ssid;
}
nameCell.innerHTML = result[i].hidden ? `<i>${result[i].bssid}</i>` : result[i].ssid;
nameCell.onclick = (event) => {
const input = document.querySelector("[name='sta[ssid]']");
const ssid = row.dataset.ssid;
const ssid = event.target.parentNode.dataset.ssid;
if (!input || !ssid) {
return;
}
@@ -326,60 +318,19 @@ const setupRestoreBackupForm = (formSelector) => {
console.log("Backup: ", data);
if (data.settings != undefined) {
for (const key in data.settings) {
let response = await fetch(url, {
method: "POST",
cache: "no-cache",
credentials: "include",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"settings": {
[key]: data.settings[key]
}
})
});
let response = await fetch(url, {
method: "POST",
cache: "no-cache",
credentials: "include",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({"settings": data.settings})
});
if (!response.ok) {
onFailed();
return;
}
}
let finalPayload = {};
if (data.settings.emergency?.target !== undefined) {
finalPayload.emergency ??= {};
finalPayload.emergency.target = data.settings.emergency.target;
}
if (data.settings.heating?.target !== undefined) {
finalPayload.heating ??= {};
finalPayload.heating.target = data.settings.heating.target;
}
if (data.settings.dhw?.target !== undefined) {
finalPayload.dhw ??= {};
finalPayload.dhw.target = data.settings.dhw.target;
}
if (Object.keys(finalPayload).length) {
let response = await fetch(url, {
method: "POST",
cache: "no-cache",
credentials: "include",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
"settings": finalPayload
})
});
if (!response.ok) {
onFailed();
return;
}
if (!response.ok) {
onFailed();
return;
}
}
@@ -649,21 +600,6 @@ const setValue = (selector, value, parent = undefined) => {
}
}
const setTextValue = (selector, value, parent = undefined) => {
if (parent === undefined) {
parent = document;
}
let items = parent.querySelectorAll(selector);
if (!items.length) {
return;
}
for (let item of items) {
item.textContent = value;
}
}
const appendValue = (selector, value, nl = null, parent = undefined) => {
if (parent === undefined) {
parent = document;
@@ -823,8 +759,8 @@ const pressureUnit = (unitSystem) => {
const volumeUnit = (unitSystem) => {
return unit2str(unitSystem, {
0: i18n('units.liter'),
1: i18n('units.gallon')
0: "L",
1: "gal"
});
}
@@ -838,13 +774,13 @@ const purposeUnit = (purpose, unitSystem) => {
3: tUnit,
4: tUnit,
5: tUnit,
6: `${volumeUnit(unitSystem)}/${i18n('units.min')}`,
6: `${volumeUnit(unitSystem)}/${i18n('time.min')}`,
7: tUnit,
8: "%",
248: "%",
249: i18n('units.kw'),
250: i18n('units.rpm'),
251: i18n('units.ppm'),
249: i18n('kw'),
250: "RPM",
251: "ppm",
252: pressureUnit(unitSystem),
253: "%",
254: tUnit
-16
View File
@@ -1,16 +0,0 @@
import subprocess
Import("env")
try:
commit_hash = "undefined"
result = subprocess.check_output(
["git", "rev-parse", "--short", "HEAD"]
)
commit_hash = result.decode("utf-8").strip()
env.Append(
CPPDEFINES=[
("BUILD_COMMIT", '\\"{}\\"'.format(commit_hash))
]
)
except Exception as error:
print("Failed to get commit hash: {}".format(error))
+18 -21
View File
@@ -23,15 +23,10 @@ Import("env")
platform = env.PioPlatform()
import sys
import os
import subprocess
from os.path import join
def normalize_paths(cmd):
for i, arg in enumerate(cmd):
if isinstance(arg, str) and '/' in arg:
cmd[i] = os.path.normpath(arg)
return cmd
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
def esp32_create_combined_bin(source, target, env):
print("Generating combined binary for serial flashing")
@@ -44,21 +39,26 @@ def esp32_create_combined_bin(source, target, env):
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
flash_size = env.BoardConfig().get("upload.flash_size", "4MB")
flash_mode = env["__get_board_flash_mode"](env)
flash_freq = env["__get_board_f_flash"](env)
flash_size = env.BoardConfig().get("upload.flash_size")
flash_freq = env.BoardConfig().get("build.f_flash", '40m')
flash_freq = flash_freq.replace('000000L', 'm')
flash_mode = env.BoardConfig().get("build.flash_mode", "dio")
memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi")
if flash_mode == "qio" or flash_mode == "qout":
flash_mode = "dio"
if memory_type == "opi_opi" or memory_type == "opi_qspi":
flash_mode = "dout"
cmd = [
"--chip",
chip,
"merge-bin",
"merge_bin",
"-o",
new_file_name,
"--flash-mode",
"--flash_mode",
flash_mode,
"--flash-freq",
"--flash_freq",
flash_freq,
"--flash-size",
"--flash_size",
flash_size,
]
@@ -71,12 +71,9 @@ def esp32_create_combined_bin(source, target, env):
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
# print('Using esptool.py arguments: %s' % ' '.join(cmd))
cmdline = [env.subst("$OBJCOPY")] + normalize_paths(cmd)
print('Command Line: %s' % cmdline)
result = subprocess.run(cmdline, text=True, check=False, stdout=subprocess.DEVNULL)
if result.returncode != 0:
print(f"esptool create firmware failed with exit code: {result.returncode}")
print('Using esptool.py arguments: %s' % ' '.join(cmd))
esptool.main(cmd)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)