19 Commits

Author SHA1 Message Date
github-actions[bot]
e4338b10d5 Bump NimBLE-Arduino to 2.3.6 2025-10-04 00:33:57 +00:00
Yurii
9d77256051 refactor: not initialize Bluetooth if BLE sensors are not used 2025-10-03 20:56:04 +03:00
Yurii
d3e7a13e1f fix: rollback NimBLE-Arduino from 2.3.6 to 2.3.3 2025-10-03 20:54:42 +03:00
Yurii
14826c10cd chore: `byte to uint8_t` 2025-09-23 04:03:36 +03:00
Yurii
28a5218b7c fix: various fixes
Found by PVS-Studio
2025-09-23 04:02:40 +03:00
Yurii
f3ba43adbd docs: fix typo 2025-09-19 21:14:38 +03:00
Yurii
062c6714d1 chore: bump dependencies 2025-09-19 21:09:55 +03:00
Yurii
502f73db0e chore: bump pioarduino/platform-espressif32 from 3.3.0 to 3.3.1 2025-09-19 21:06:17 +03:00
zedward
c2ee30d1ab fix: added `nodemcu_32_160mhz` env, deadband usage in PID fixed (#176)
* Small fixes: CPU limit for odemcu_32,  deadbamd usage in PID

* PR review fixes

* Fix: remove spaces

---------

Co-authored-by: Edward Zhuravlov <Edward.Zhuravlev@topsoft.by>
2025-09-19 21:00:11 +03:00
dependabot[bot]
9217bb7448 chore: bump actions/stale from 9 to 10 (#172)
Bumps [actions/stale](https://github.com/actions/stale) from 9 to 10.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/v9...v10)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: '10'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 06:33:43 +03:00
Yurii
d694ae18c0 chore: bump version to 1.5.6 2025-08-21 19:34:07 +03:00
dependabot[bot]
0f4fa2e406 chore(deps): bump actions/checkout from 4 to 5 (#169)
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 13:24:55 +03:00
Yurii
df2af928a5 chore: bump pioarduino/platform-espressif32 from 3.2.1 to 3.3.0 2025-08-04 07:08:37 +03:00
Yurii
3e6a2d93d4 chore: updated esp32.py
Source: https://raw.githubusercontent.com/letscontrolit/ESPEasy/mega/tools/pio/post_esp32.py
2025-08-04 07:07:31 +03:00
Yurii
062468c6b1 chore: updated esp32.py to be compatible with new versions of esptool
Source: https://raw.githubusercontent.com/letscontrolit/ESPEasy/mega/tools/pio/post_esp32.py
2025-07-04 04:12:06 +03:00
Yurii
530892dc09 chore: bump pioarduino/platform-espressif32 from 3.2.0 to 3.2.1 2025-07-04 04:10:06 +03:00
Yurii
c73f74ad45 chore: upd platformio.ini 2025-07-04 03:08:51 +03:00
Yurii
a67ff046b0 refactor: refactoring after #163 2025-07-04 03:00:41 +03:00
menyuguren
0f496fee42 feat: added Chinese locale by @menyuguren #163
* Add files via upload

支持中文

* fix typo

---------

Co-authored-by: Yurii <34578544+Laxilef@users.noreply.github.com>
2025-07-04 02:55:09 +03:00
30 changed files with 821 additions and 578 deletions

View File

@@ -15,7 +15,7 @@ jobs:
name: run PlatformIO Dependabot name: run PlatformIO Dependabot
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: run PlatformIO Dependabot - name: run PlatformIO Dependabot
uses: peterus/platformio_dependabot@v1.2.0 uses: peterus/platformio_dependabot@v1.2.0
with: with:

View File

@@ -7,7 +7,7 @@ jobs:
stale: stale:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v10
with: with:
stale-issue-message: > 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. 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.

View File

@@ -72,7 +72,7 @@ All available information and instructions can be found in the wiki:
* [Leds on board](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#leds-on-board) * [Leds on board](https://github.com/Laxilef/OTGateway/wiki/OT-adapters#leds-on-board)
## Gratitude ## Gratitude
* 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 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), [OneWireNg](https://github.com/pstolarz/OneWireNg) & [OneWire](https://github.com/PaulStoffregen/OneWire)
* To the [PlatformIO](https://platformio.org/) Team * To the [PlatformIO](https://platformio.org/) Team
* To the team and contributors of the [pioarduino](https://github.com/pioarduino/platform-espressif32) project * 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. * To the [BrowserStack](https://www.browserstack.com/) team. This project is tested with BrowserStack.

View File

@@ -27,9 +27,6 @@ let paths = {
'src_data/scripts/i18n.min.js', 'src_data/scripts/i18n.min.js',
'src_data/scripts/lang.js', 'src_data/scripts/lang.js',
'src_data/scripts/utils.js' 'src_data/scripts/utils.js'
],
'chart.js': [
'src_data/scripts/chart.js'
] ]
} }
}, },

View File

@@ -4,8 +4,8 @@
class CustomOpenTherm : public OpenTherm { class CustomOpenTherm : public OpenTherm {
public: public:
typedef std::function<void(unsigned int)> DelayCallback; typedef std::function<void(unsigned int)> DelayCallback;
typedef std::function<void(unsigned long, byte)> BeforeSendRequestCallback; typedef std::function<void(unsigned long, uint8_t)> BeforeSendRequestCallback;
typedef std::function<void(unsigned long, unsigned long, OpenThermResponseStatus, byte)> AfterSendRequestCallback; typedef std::function<void(unsigned long, unsigned long, OpenThermResponseStatus, uint8_t)> AfterSendRequestCallback;
CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false, bool alwaysReceive = false) : OpenTherm(inPin, outPin, isSlave, alwaysReceive) {} CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false, bool alwaysReceive = false) : OpenTherm(inPin, outPin, isSlave, alwaysReceive) {}
~CustomOpenTherm() {} ~CustomOpenTherm() {}
@@ -106,15 +106,14 @@ public:
return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest); return isValidResponse(response) && isValidResponseId(response, OpenThermMessageID::RemoteRequest);
} }
static bool isCh2Active(unsigned long response) static bool isCh2Active(unsigned long response) {
{
return response & 0x20; return response & 0x20;
} }
static bool isValidResponseId(unsigned long response, OpenThermMessageID id) { static bool isValidResponseId(unsigned long response, OpenThermMessageID id) {
byte responseId = (response >> 16) & 0xFF; uint8_t responseId = (response >> 16) & 0xFF;
return (byte)id == responseId; return (uint8_t)id == responseId;
} }
static uint8_t getResponseMessageTypeId(unsigned long response) { static uint8_t getResponseMessageTypeId(unsigned long response) {

63
lib/Equitherm/Equitherm.h Normal file
View File

@@ -0,0 +1,63 @@
#include <Arduino.h>
#if defined(EQUITHERM_INTEGER)
// расчёты с целыми числами
typedef int datatype;
#else
// расчёты с float числами
typedef float datatype;
#endif
class Equitherm {
public:
datatype targetTemp = 0;
datatype indoorTemp = 0;
datatype outdoorTemp = 0;
float Kn = 0.0;
float Kk = 0.0;
float Kt = 0.0;
Equitherm() = default;
// kn, kk, kt
Equitherm(float new_kn, float new_kk, float new_kt) {
Kn = new_kn;
Kk = new_kk;
Kt = new_kt;
}
// лимит выходной величины
void setLimits(unsigned short min_output, unsigned short max_output) {
_minOut = min_output;
_maxOut = max_output;
}
// возвращает новое значение при вызове
datatype getResult() {
datatype output = getResultN() + getResultK() + getResultT();
output = constrain(output, _minOut, _maxOut); // ограничиваем выход
return output;
}
private:
unsigned short _minOut = 20, _maxOut = 90;
// температура контура отопления в зависимости от наружной температуры
datatype getResultN() {
float a = (-0.21 * Kn) - 0.06; // a = -0,21k — 0,06
float b = (6.04 * Kn) + 1.98; // b = 6,04k + 1,98
float c = (-5.06 * Kn) + 18.06; // с = -5,06k + 18,06
float x = (-0.2 * outdoorTemp) + 5; // x = -0.2*t1 + 5
return (a * x * x) + (b * x) + c; // Tn = ax2 + bx + c
}
// поправка на желаемую комнатную температуру
datatype getResultK() {
return (targetTemp - 20) * Kk;
}
// Расчет поправки (ошибки) термостата
datatype getResultT() {
return constrain((targetTemp - indoorTemp), -3, 3) * Kt;
}
};

View File

@@ -35,7 +35,7 @@ namespace NetworkUtils {
return this; return this;
} }
NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, byte channel = 0) { NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, uint8_t channel = 0) {
this->apName = ssid; this->apName = ssid;
this->apPassword = password; this->apPassword = password;
this->apChannel = channel; this->apChannel = channel;
@@ -43,7 +43,7 @@ namespace NetworkUtils {
return this; return this;
} }
NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, byte channel = 0) { NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, uint8_t channel = 0) {
this->staSsid = ssid; this->staSsid = ssid;
this->staPassword = password; this->staPassword = password;
this->staChannel = channel; this->staChannel = channel;
@@ -140,7 +140,7 @@ namespace NetworkUtils {
return this->staPassword; return this->staPassword;
} }
byte getStaChannel() { uint8_t getStaChannel() {
return this->staChannel; return this->staChannel;
} }
@@ -377,7 +377,7 @@ namespace NetworkUtils {
} }
} }
static byte rssiToSignalQuality(short int rssi) { static uint8_t rssiToSignalQuality(short int rssi) {
return constrain(map(rssi, -100, -50, 0, 100), 0, 100); return constrain(map(rssi, -100, -50, 0, 100), 0, 100);
} }
@@ -397,11 +397,11 @@ namespace NetworkUtils {
const char* hostname = "esp"; const char* hostname = "esp";
const char* apName = "ESP"; const char* apName = "ESP";
const char* apPassword = nullptr; const char* apPassword = nullptr;
byte apChannel = 1; uint8_t apChannel = 1;
const char* staSsid = nullptr; const char* staSsid = nullptr;
const char* staPassword = nullptr; const char* staPassword = nullptr;
byte staChannel = 0; uint8_t staChannel = 0;
bool useDhcp = true; bool useDhcp = true;
IPAddress staticIp; IPAddress staticIp;

View File

@@ -14,15 +14,15 @@ extra_configs = secrets.default.ini
core_dir = .pio core_dir = .pio
[env] [env]
version = 1.5.5 version = 1.5.6
framework = arduino framework = arduino
lib_deps = lib_deps =
bblanchon/ArduinoJson@^7.3.0 bblanchon/ArduinoJson@^7.4.2
;ihormelnyk/OpenTherm Library@^1.1.5 ;ihormelnyk/OpenTherm Library@^1.1.5
https://github.com/Laxilef/opentherm_library#esp32_timer https://github.com/Laxilef/opentherm_library#esp32_timer
arduino-libraries/ArduinoMqttClient@^0.1.8 arduino-libraries/ArduinoMqttClient@^0.1.8
lennarthennigs/ESP Telnet@^2.2 lennarthennigs/ESP Telnet@^2.2.3
gyverlibs/FileData@^1.0.2 gyverlibs/FileData@^1.0.3
gyverlibs/GyverPID@^3.3.2 gyverlibs/GyverPID@^3.3.2
gyverlibs/GyverBlinker@^1.1.1 gyverlibs/GyverBlinker@^1.1.1
https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg https://github.com/pstolarz/Arduino-Temperature-Control-Library.git#OneWireNg
@@ -60,10 +60,16 @@ monitor_filters =
esp8266_exception_decoder esp8266_exception_decoder
board_build.flash_mode = dio board_build.flash_mode = dio
board_build.filesystem = littlefs board_build.filesystem = littlefs
check_tool = ; pvs-studio
check_flags =
; pvs-studio:
; --analysis-mode=4
; --exclude-path=./.pio/libdeps
; Defaults ; Defaults
[esp8266_defaults] [esp8266_defaults]
platform = espressif8266@^4.2.1 platform = espressif8266@^4.2.1
platform_packages = ${env.platform_packages}
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
nrwiersma/ESP8266Scheduler@^1.2 nrwiersma/ESP8266Scheduler@^1.2
@@ -77,6 +83,8 @@ build_flags =
;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH ;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
-D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305 -D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
board_build.ldscript = eagle.flash.4m1m.ld board_build.ldscript = eagle.flash.4m1m.ld
check_tool = ${env.check_tool}
check_flags = ${env.check_flags}
[esp32_defaults] [esp32_defaults]
;platform = espressif32@^6.7 ;platform = espressif32@^6.7
@@ -84,13 +92,13 @@ board_build.ldscript = eagle.flash.4m1m.ld
;platform_packages = ;platform_packages =
; framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.5 ; 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 ; 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 = https://github.com/pioarduino/platform-espressif32/releases/download/55.03.31/platform-espressif32.zip
platform_packages = platform_packages = ${env.platform_packages}
board_build.partitions = esp32_partitions.csv board_build.partitions = esp32_partitions.csv
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
laxilef/ESP32Scheduler@^1.0.1 laxilef/ESP32Scheduler@^1.0.1
nimble_lib = h2zero/NimBLE-Arduino@^2.1.0 nimble_lib = h2zero/NimBLE-Arduino@2.3.6
lib_ignore = lib_ignore =
extra_scripts = extra_scripts =
post:tools/esp32.py post:tools/esp32.py
@@ -100,11 +108,14 @@ build_flags =
${env.build_flags} ${env.build_flags}
-D CORE_DEBUG_LEVEL=0 -D CORE_DEBUG_LEVEL=0
-Wl,--wrap=esp_panic_handler -Wl,--wrap=esp_panic_handler
check_tool = ${env.check_tool}
check_flags = ${env.check_flags}
; Boards ; Boards
[env:d1_mini] [env:d1_mini]
platform = ${esp8266_defaults.platform} platform = ${esp8266_defaults.platform}
platform_packages = ${esp8266_defaults.platform_packages}
board = d1_mini board = d1_mini
lib_deps = ${esp8266_defaults.lib_deps} lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore} lib_ignore = ${esp8266_defaults.lib_ignore}
@@ -119,9 +130,12 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=14 -D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13 -D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15 -D DEFAULT_OT_RX_LED_GPIO=15
check_tool = ${esp8266_defaults.check_tool}
check_flags = ${esp8266_defaults.check_flags}
[env:d1_mini_lite] [env:d1_mini_lite]
platform = ${esp8266_defaults.platform} platform = ${esp8266_defaults.platform}
platform_packages = ${esp8266_defaults.platform_packages}
board = d1_mini_lite board = d1_mini_lite
lib_deps = ${esp8266_defaults.lib_deps} lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore} lib_ignore = ${esp8266_defaults.lib_ignore}
@@ -136,9 +150,12 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=14 -D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13 -D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15 -D DEFAULT_OT_RX_LED_GPIO=15
check_tool = ${esp8266_defaults.check_tool}
check_flags = ${esp8266_defaults.check_flags}
[env:d1_mini_pro] [env:d1_mini_pro]
platform = ${esp8266_defaults.platform} platform = ${esp8266_defaults.platform}
platform_packages = ${esp8266_defaults.platform_packages}
board = d1_mini_pro board = d1_mini_pro
lib_deps = ${esp8266_defaults.lib_deps} lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore} lib_ignore = ${esp8266_defaults.lib_ignore}
@@ -153,9 +170,12 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=14 -D DEFAULT_SENSOR_INDOOR_GPIO=14
-D DEFAULT_STATUS_LED_GPIO=13 -D DEFAULT_STATUS_LED_GPIO=13
-D DEFAULT_OT_RX_LED_GPIO=15 -D DEFAULT_OT_RX_LED_GPIO=15
check_tool = ${esp8266_defaults.check_tool}
check_flags = ${esp8266_defaults.check_flags}
[env:nodemcu_8266] [env:nodemcu_8266]
platform = ${esp8266_defaults.platform} platform = ${esp8266_defaults.platform}
platform_packages = ${esp8266_defaults.platform_packages}
board = nodemcuv2 board = nodemcuv2
lib_deps = ${esp8266_defaults.lib_deps} lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore} lib_ignore = ${esp8266_defaults.lib_ignore}
@@ -170,6 +190,8 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=4 -D DEFAULT_SENSOR_INDOOR_GPIO=4
-D DEFAULT_STATUS_LED_GPIO=2 -D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=16 -D DEFAULT_OT_RX_LED_GPIO=16
check_tool = ${esp8266_defaults.check_tool}
check_flags = ${esp8266_defaults.check_flags}
[env:s2_mini] [env:s2_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -192,6 +214,8 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=7 -D DEFAULT_SENSOR_INDOOR_GPIO=7
-D DEFAULT_STATUS_LED_GPIO=11 -D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=12 -D DEFAULT_OT_RX_LED_GPIO=12
check_tool = ${esp32_defaults.check_tool}
check_flags = ${esp32_defaults.check_flags}
[env:s3_mini] [env:s3_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -218,6 +242,8 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=12 -D DEFAULT_SENSOR_INDOOR_GPIO=12
-D DEFAULT_STATUS_LED_GPIO=11 -D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=10 -D DEFAULT_OT_RX_LED_GPIO=10
check_tool = ${esp32_defaults.check_tool}
check_flags = ${esp32_defaults.check_flags}
[env:c3_mini] [env:c3_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -242,6 +268,8 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=1 -D DEFAULT_SENSOR_INDOOR_GPIO=1
-D DEFAULT_STATUS_LED_GPIO=4 -D DEFAULT_STATUS_LED_GPIO=4
-D DEFAULT_OT_RX_LED_GPIO=5 -D DEFAULT_OT_RX_LED_GPIO=5
check_tool = ${esp32_defaults.check_tool}
check_flags = ${esp32_defaults.check_flags}
[env:nodemcu_32] [env:nodemcu_32]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -263,6 +291,12 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=26 -D DEFAULT_SENSOR_INDOOR_GPIO=26
-D DEFAULT_STATUS_LED_GPIO=2 -D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19 -D DEFAULT_OT_RX_LED_GPIO=19
check_tool = ${esp32_defaults.check_tool}
check_flags = ${esp32_defaults.check_flags}
[env:nodemcu_32_160mhz]
extends = env:nodemcu_32
board_build.f_cpu = 160000000L ; set frequency to 160MHz
[env:d1_mini32] [env:d1_mini32]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -284,6 +318,8 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=18 -D DEFAULT_SENSOR_INDOOR_GPIO=18
-D DEFAULT_STATUS_LED_GPIO=2 -D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19 -D DEFAULT_OT_RX_LED_GPIO=19
check_tool = ${esp32_defaults.check_tool}
check_flags = ${esp32_defaults.check_flags}
[env:esp32_c6] [env:esp32_c6]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -312,6 +348,8 @@ build_flags =
-D DEFAULT_SENSOR_INDOOR_GPIO=0 -D DEFAULT_SENSOR_INDOOR_GPIO=0
-D DEFAULT_STATUS_LED_GPIO=11 -D DEFAULT_STATUS_LED_GPIO=11
-D DEFAULT_OT_RX_LED_GPIO=10 -D DEFAULT_OT_RX_LED_GPIO=10
check_tool = ${esp32_defaults.check_tool}
check_flags = ${esp32_defaults.check_flags}
[env:otthing] [env:otthing]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
@@ -337,3 +375,5 @@ build_flags =
-D DEFAULT_STATUS_LED_GPIO=8 -D DEFAULT_STATUS_LED_GPIO=8
-D DEFAULT_OT_RX_LED_GPIO=2 -D DEFAULT_OT_RX_LED_GPIO=2
-D OT_BYPASS_RELAY_GPIO=20 -D OT_BYPASS_RELAY_GPIO=20
check_tool = ${esp32_defaults.check_tool}
check_flags = ${esp32_defaults.check_flags}

View File

@@ -3,8 +3,8 @@
class HaHelper : public HomeAssistantHelper { class HaHelper : public HomeAssistantHelper {
public: public:
static const byte TEMP_SOURCE_HEATING = 0; static const uint8_t TEMP_SOURCE_HEATING = 0;
static const byte TEMP_SOURCE_INDOOR = 1; static const uint8_t TEMP_SOURCE_INDOOR = 1;
static const char AVAILABILITY_OT_CONN[]; static const char AVAILABILITY_OT_CONN[];
static const char AVAILABILITY_SENSOR_CONN[]; static const char AVAILABILITY_SENSOR_CONN[];
@@ -840,19 +840,19 @@ public:
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("equitherm")).c_str(), doc); return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_SWITCH), F("equitherm")).c_str(), doc);
} }
bool publishInputEquithermSlope(bool enabledByDefault = true) { bool publishInputEquithermFactorN(bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_slope")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_n"));
doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)];
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
doc[FPSTR(HA_NAME)] = F("Equitherm slope"); doc[FPSTR(HA_NAME)] = F("Equitherm factor N");
doc[FPSTR(HA_ICON)] = F("mdi:slope-uphill"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-n-circle-outline");
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.slope|float(0)|round(3) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|float(0)|round(3) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"slope\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0.001f; doc[FPSTR(HA_MIN)] = 0.001f;
doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_MAX)] = 10;
doc[FPSTR(HA_STEP)] = 0.001f; doc[FPSTR(HA_STEP)] = 0.001f;
@@ -860,80 +860,56 @@ public:
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
doc.shrinkToFit(); doc.shrinkToFit();
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_slope")).c_str(), doc); return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_n_factor")).c_str(), doc);
} }
bool publishInputEquithermExponent(bool enabledByDefault = true) { bool publishInputEquithermFactorK(bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_exponent")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_k"));
doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)];
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
doc[FPSTR(HA_NAME)] = F("Equitherm exponent"); doc[FPSTR(HA_NAME)] = F("Equitherm factor K");
doc[FPSTR(HA_ICON)] = F("mdi:exponent"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-k-circle-outline");
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.exponent|float(0)|round(3) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.k_factor|float(0)|round(2) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"exponent\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0.1; doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 2; doc[FPSTR(HA_MAX)] = 10;
doc[FPSTR(HA_STEP)] = 0.001f;
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
doc.shrinkToFit();
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_exponent")).c_str(), doc);
}
bool publishInputEquithermShift(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_shift"));
doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)];
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
doc[FPSTR(HA_DEVICE_CLASS)] = FPSTR(S_TEMPERATURE);
doc[FPSTR(HA_NAME)] = F("Equitherm shift");
doc[FPSTR(HA_ICON)] = F("mdi:chart-areaspline");
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.shift|float(0)|round(2) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"shift\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = -15;
doc[FPSTR(HA_MAX)] = 15;
doc[FPSTR(HA_STEP)] = 0.01f; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
doc.shrinkToFit(); doc.shrinkToFit();
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_shift")).c_str(), doc); return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_k_factor")).c_str(), doc);
} }
bool publishInputEquithermTargetDiffFactor(bool enabledByDefault = true) { bool publishInputEquithermFactorT(bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][0][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_TOPIC)] = this->settingsTopic.c_str();
doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.pid.enabled, 'offline', 'online') }}"); doc[FPSTR(HA_AVAILABILITY)][1][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.pid.enabled, 'offline', 'online') }}");
doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all"); doc[FPSTR(HA_AVAILABILITY_MODE)] = F("all");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_target_diff_factor")); doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectIdWithPrefix(F("equitherm_t"));
doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)]; doc[FPSTR(HA_OBJECT_ID)] = doc[FPSTR(HA_UNIQUE_ID)];
doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG); doc[FPSTR(HA_ENTITY_CATEGORY)] = FPSTR(HA_ENTITY_CATEGORY_CONFIG);
doc[FPSTR(HA_NAME)] = F("Equitherm target diff factor"); doc[FPSTR(HA_NAME)] = F("Equitherm factor T");
doc[FPSTR(HA_ICON)] = F("mdi:chart-timeline-variant-shimmer"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-t-circle-outline");
doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str(); doc[FPSTR(HA_STATE_TOPIC)] = this->settingsTopic.c_str();
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.targetDiffFactor|float(0)|round(3) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.t_factor|float(0)|round(2) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str(); doc[FPSTR(HA_COMMAND_TOPIC)] = this->setSettingsTopic.c_str();
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"targetDiffFactor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_MAX)] = 10;
doc[FPSTR(HA_STEP)] = 0.001f; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX); doc[FPSTR(HA_MODE)] = FPSTR(HA_MODE_BOX);
doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter; doc[FPSTR(HA_EXPIRE_AFTER)] = this->expireAfter;
doc.shrinkToFit(); doc.shrinkToFit();
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_target_diff_factor")).c_str(), doc); return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_NUMBER), F("equitherm_t_factor")).c_str(), doc);
} }
@@ -1194,7 +1170,7 @@ public:
} }
bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 20, byte maxTemp = 90, bool enabledByDefault = true) { bool publishClimateHeating(UnitSystem unit = UnitSystem::METRIC, uint8_t minTemp = 20, uint8_t maxTemp = 90, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -1246,7 +1222,7 @@ public:
return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_CLIMATE), F("heating"), '_').c_str(), doc); return this->publish(this->makeConfigTopic(FPSTR(HA_ENTITY_CLIMATE), F("heating"), '_').c_str(), doc);
} }
bool publishClimateDhw(UnitSystem unit = UnitSystem::METRIC, byte minTemp = 40, byte maxTemp = 60, bool enabledByDefault = true) { bool publishClimateDhw(UnitSystem unit = UnitSystem::METRIC, uint8_t minTemp = 40, uint8_t maxTemp = 60, bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str(); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->statusTopic.c_str();
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;

View File

@@ -502,10 +502,9 @@ protected:
// equitherm // equitherm
this->haHelper->publishSwitchEquitherm(); this->haHelper->publishSwitchEquitherm();
this->haHelper->publishInputEquithermSlope(false); this->haHelper->publishInputEquithermFactorN(false);
this->haHelper->publishInputEquithermExponent(false); this->haHelper->publishInputEquithermFactorK(false);
this->haHelper->publishInputEquithermShift(false); this->haHelper->publishInputEquithermFactorT(false);
this->haHelper->publishInputEquithermTargetDiffFactor(false);
// states // states
this->haHelper->publishStatusState(); this->haHelper->publishStatusState();
@@ -558,7 +557,7 @@ protected:
} }
bool publishNonStaticHaEntities(bool force = false) { bool publishNonStaticHaEntities(bool force = false) {
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0; static uint8_t _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
static bool _indoorTempControl, _dhwSupport = false; static bool _indoorTempControl, _dhwSupport = false;
bool published = false; bool published = false;

View File

@@ -19,8 +19,8 @@ protected:
CustomOpenTherm* instance = nullptr; CustomOpenTherm* instance = nullptr;
unsigned long instanceCreatedTime = 0; unsigned long instanceCreatedTime = 0;
byte instanceInGpio = 0; uint8_t instanceInGpio = 0;
byte instanceOutGpio = 0; uint8_t instanceOutGpio = 0;
bool initialized = false; bool initialized = false;
unsigned long connectedTime = 0; unsigned long connectedTime = 0;
unsigned long disconnectedTime = 0; unsigned long disconnectedTime = 0;
@@ -31,7 +31,7 @@ protected:
unsigned long heatingSetTempTime = 0; unsigned long heatingSetTempTime = 0;
unsigned long dhwSetTempTime = 0; unsigned long dhwSetTempTime = 0;
unsigned long ch2SetTempTime = 0; unsigned long ch2SetTempTime = 0;
byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED; uint8_t configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED;
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override { const char* getTaskName() override {
@@ -90,7 +90,7 @@ protected:
Log.sinfoln(FPSTR(L_OT), F("Started. GPIO IN: %hhu, GPIO OUT: %hhu"), settings.opentherm.inGpio, settings.opentherm.outGpio); Log.sinfoln(FPSTR(L_OT), F("Started. GPIO IN: %hhu, GPIO OUT: %hhu"), settings.opentherm.inGpio, settings.opentherm.outGpio);
this->instance->setAfterSendRequestCallback([this](unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) { this->instance->setAfterSendRequestCallback([this](unsigned long request, unsigned long response, OpenThermResponseStatus status, uint8_t attempt) {
Log.sverboseln( Log.sverboseln(
FPSTR(L_OT), FPSTR(L_OT),
F("ID: %4d Request: %8lx Response: %8lx Msg type: %s Attempt: %2d Status: %s"), F("ID: %4d Request: %8lx Response: %8lx Msg type: %s Attempt: %2d Status: %s"),

View File

@@ -1,5 +1,7 @@
#include <Equitherm.h>
#include <GyverPID.h> #include <GyverPID.h>
Equitherm etRegulator;
GyverPID pidRegulator(0, 0, 0); GyverPID pidRegulator(0, 0, 0);
@@ -144,32 +146,39 @@ protected:
// if use equitherm // if use equitherm
if (settings.equitherm.enabled) { if (settings.equitherm.enabled) {
float tempDelta = settings.heating.target - vars.master.heating.outdoorTemp; unsigned short minTemp = settings.heating.minTemp;
float maxPoint = settings.heating.target - ( unsigned short maxTemp = settings.heating.maxTemp;
settings.heating.maxTemp - settings.heating.target float targetTemp = settings.heating.target;
) / settings.equitherm.slope; float indoorTemp = vars.master.heating.indoorTemp;
float outdoorTemp = vars.master.heating.outdoorTemp;
float sf = (settings.heating.maxTemp - settings.heating.target) / pow( if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
settings.heating.target - maxPoint, minTemp = f2c(minTemp);
1.0f / settings.equitherm.exponent maxTemp = f2c(maxTemp);
); targetTemp = f2c(targetTemp);
float etResult = settings.heating.target + settings.equitherm.shift + sf * ( indoorTemp = f2c(indoorTemp);
tempDelta >= 0 outdoorTemp = f2c(outdoorTemp);
? pow(tempDelta, 1.0f / settings.equitherm.exponent)
: -(pow(-(tempDelta), 1.0f / settings.equitherm.exponent))
);
// add diff
if (this->indoorSensorsConnected && !settings.pid.enabled && !settings.heating.turbo) {
etResult += constrain(
settings.heating.target - vars.master.heating.indoorTemp,
-3.0f,
3.0f
) * settings.equitherm.targetDiffFactor;
} }
// limit if (!this->indoorSensorsConnected || settings.pid.enabled) {
etResult = constrain(etResult, settings.heating.minTemp, settings.heating.maxTemp); etRegulator.Kt = 0.0f;
etRegulator.indoorTemp = 0.0f;
} else {
etRegulator.Kt = settings.heating.turbo ? 0.0f : settings.equitherm.t_factor;
etRegulator.indoorTemp = indoorTemp;
}
etRegulator.setLimits(minTemp, maxTemp);
etRegulator.Kn = settings.equitherm.n_factor;
etRegulator.Kk = settings.equitherm.k_factor;
etRegulator.targetTemp = targetTemp;
etRegulator.outdoorTemp = outdoorTemp;
float etResult = etRegulator.getResult();
if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
etResult = c2f(etResult);
}
if (fabsf(prevEtResult - etResult) > 0.09f) { if (fabsf(prevEtResult - etResult) > 0.09f) {
prevEtResult = etResult; prevEtResult = etResult;
@@ -204,7 +213,8 @@ protected:
}*/ }*/
float error = pidRegulator.setpoint - pidRegulator.input; float error = pidRegulator.setpoint - pidRegulator.input;
bool hasDeadband = (error > -(settings.pid.deadband.thresholdHigh)) bool hasDeadband = settings.pid.deadband.enabled
&& (error > -(settings.pid.deadband.thresholdHigh))
&& (error < settings.pid.deadband.thresholdLow); && (error < settings.pid.deadband.thresholdLow);
if (hasDeadband) { if (hasDeadband) {

View File

@@ -334,7 +334,7 @@ public:
uint8_t valueId = (uint8_t) valueType; uint8_t valueId = (uint8_t) valueType;
if (!isValidValueId(valueId)) { if (!isValidValueId(valueId)) {
return false; return 0;
} }
float value = 0.0f; float value = 0.0f;

View File

@@ -385,7 +385,7 @@ protected:
continue; continue;
} }
const float sensorResistance = value > 0.001f const float sensorResistance = value > 1
? DEFAULT_NTC_REF_RESISTANCE / (DEFAULT_NTC_VREF / (float) value - 1.0f) ? DEFAULT_NTC_REF_RESISTANCE / (DEFAULT_NTC_VREF / (float) value - 1.0f)
: 0.0f; : 0.0f;
const float rawTemp = 1.0f / ( const float rawTemp = 1.0f / (
@@ -405,7 +405,6 @@ protected:
#if USE_BLE #if USE_BLE
void cleanBleInstances() { void cleanBleInstances() {
#if USE_BLE
if (!NimBLEDevice::isInitialized()) { if (!NimBLEDevice::isInitialized()) {
return; return;
} }
@@ -442,10 +441,13 @@ protected:
NimBLEDevice::deleteClient(client); NimBLEDevice::deleteClient(client);
} }
} }
#endif
} }
void pollingBleSensors() { void pollingBleSensors() {
if (!Sensors::getAmountByType(Sensors::Type::BLUETOOTH, true)) {
return;
}
if (!NimBLEDevice::isInitialized() && millis() > 5000) { if (!NimBLEDevice::isInitialized() && millis() > 5000) {
Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Initialized")); Log.sinfoln(FPSTR(L_SENSORS_BLE), F("Initialized"));
BLEDevice::init(""); BLEDevice::init("");
@@ -577,7 +579,7 @@ protected:
bool subscribeToBleDevice(const uint8_t sensorId, NimBLEClient* pClient) { bool subscribeToBleDevice(const uint8_t sensorId, NimBLEClient* pClient) {
auto& sSensor = Sensors::settings[sensorId]; auto& sSensor = Sensors::settings[sensorId];
auto pAddress = pClient->getPeerAddress().toString().c_str(); auto pAddress = pClient->getPeerAddress().toString();
NimBLERemoteService* pService = nullptr; NimBLERemoteService* pService = nullptr;
NimBLERemoteCharacteristic* pChar = nullptr; NimBLERemoteCharacteristic* pChar = nullptr;
@@ -588,13 +590,13 @@ protected:
if (!pService) { if (!pService) {
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find env service (%s) on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find env service (%s) on device %s"),
sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress.c_str()
); );
} else { } else {
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found env service (%s) on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found env service (%s) on device %s"),
sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress.c_str()
); );
// 0x2A6E - Notify temperature x0.01C (pvvx) // 0x2A6E - Notify temperature x0.01C (pvvx)
@@ -606,7 +608,7 @@ protected:
if (pChar && (pChar->canNotify() || pChar->canIndicate())) { if (pChar && (pChar->canNotify() || pChar->canIndicate())) {
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"),
sensorId, sSensor.name, charUuid.toString().c_str(), pAddress sensorId, sSensor.name, charUuid.toString().c_str(), pAddress.c_str()
); );
pChar->unsubscribe(); pChar->unsubscribe();
@@ -661,14 +663,14 @@ protected:
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"),
sensorId, sSensor.name, sensorId, sSensor.name,
charUuid.toString().c_str(), pAddress charUuid.toString().c_str(), pAddress.c_str()
); );
} else { } else {
Log.swarningln( Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"),
sensorId, sSensor.name, sensorId, sSensor.name,
charUuid.toString().c_str(), pAddress charUuid.toString().c_str(), pAddress.c_str()
); );
} }
} }
@@ -683,7 +685,7 @@ protected:
if (pChar && (pChar->canNotify() || pChar->canIndicate())) { if (pChar && (pChar->canNotify() || pChar->canIndicate())) {
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found temp char (%s) in env service on device %s"),
sensorId, sSensor.name, charUuid.toString().c_str(), pAddress sensorId, sSensor.name, charUuid.toString().c_str(), pAddress.c_str()
); );
pChar->unsubscribe(); pChar->unsubscribe();
@@ -738,14 +740,14 @@ protected:
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to temp char (%s) in env service on device %s"),
sensorId, sSensor.name, sensorId, sSensor.name,
charUuid.toString().c_str(), pAddress charUuid.toString().c_str(), pAddress.c_str()
); );
} else { } else {
Log.swarningln( Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to temp char (%s) in env service on device %s"),
sensorId, sSensor.name, sensorId, sSensor.name,
charUuid.toString().c_str(), pAddress charUuid.toString().c_str(), pAddress.c_str()
); );
} }
} }
@@ -754,7 +756,7 @@ protected:
if (!tempNotifyCreated) { if (!tempNotifyCreated) {
Log.swarningln( Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported temp chars in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported temp chars in env service on device %s"),
sensorId, sSensor.name, pAddress sensorId, sSensor.name, pAddress.c_str()
); );
pClient->disconnect(); pClient->disconnect();
@@ -772,7 +774,7 @@ protected:
if (pChar && (pChar->canNotify() || pChar->canIndicate())) { if (pChar && (pChar->canNotify() || pChar->canIndicate())) {
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found humidity char (%s) in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found humidity char (%s) in env service on device %s"),
sensorId, sSensor.name, charUuid.toString().c_str(), pAddress sensorId, sSensor.name, charUuid.toString().c_str(), pAddress.c_str()
); );
pChar->unsubscribe(); pChar->unsubscribe();
@@ -827,14 +829,14 @@ protected:
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to humidity char (%s) in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to humidity char (%s) in env service on device %s"),
sensorId, sSensor.name, sensorId, sSensor.name,
charUuid.toString().c_str(), pAddress charUuid.toString().c_str(), pAddress.c_str()
); );
} else { } else {
Log.swarningln( Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to humidity char (%s) in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to humidity char (%s) in env service on device %s"),
sensorId, sSensor.name, sensorId, sSensor.name,
charUuid.toString().c_str(), pAddress charUuid.toString().c_str(), pAddress.c_str()
); );
} }
} }
@@ -843,7 +845,7 @@ protected:
if (!humidityNotifyCreated) { if (!humidityNotifyCreated) {
Log.swarningln( Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported humidity chars in env service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported humidity chars in env service on device %s"),
sensorId, sSensor.name, pAddress sensorId, sSensor.name, pAddress.c_str()
); );
} }
} }
@@ -857,13 +859,13 @@ protected:
if (!pService) { if (!pService) {
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find battery service (%s) on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to find battery service (%s) on device %s"),
sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress.c_str()
); );
} else { } else {
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery service (%s) on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery service (%s) on device %s"),
sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress sensorId, sSensor.name, serviceUuid.toString().c_str(), pAddress.c_str()
); );
// 0x2A19 - Notify the battery charge level 0..99% (pvvx) // 0x2A19 - Notify the battery charge level 0..99% (pvvx)
@@ -875,7 +877,7 @@ protected:
if (pChar && (pChar->canNotify() || pChar->canIndicate())) { if (pChar && (pChar->canNotify() || pChar->canIndicate())) {
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery char (%s) in battery service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': found battery char (%s) in battery service on device %s"),
sensorId, sSensor.name, charUuid.toString().c_str(), pAddress sensorId, sSensor.name, charUuid.toString().c_str(), pAddress.c_str()
); );
pChar->unsubscribe(); pChar->unsubscribe();
@@ -930,14 +932,14 @@ protected:
Log.straceln( Log.straceln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to battery char (%s) in battery service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': subscribed to battery char (%s) in battery service on device %s"),
sensorId, sSensor.name, sensorId, sSensor.name,
charUuid.toString().c_str(), pAddress charUuid.toString().c_str(), pAddress.c_str()
); );
} else { } else {
Log.swarningln( Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to battery char (%s) in battery service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': failed to subscribe to battery char (%s) in battery service on device %s"),
sensorId, sSensor.name, sensorId, sSensor.name,
charUuid.toString().c_str(), pAddress charUuid.toString().c_str(), pAddress.c_str()
); );
} }
} }
@@ -946,7 +948,7 @@ protected:
if (!batteryNotifyCreated) { if (!batteryNotifyCreated) {
Log.swarningln( Log.swarningln(
FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported battery chars in battery service on device %s"), FPSTR(L_SENSORS_BLE), F("Sensor #%hhu '%s': not found supported battery chars in battery service on device %s"),
sensorId, sSensor.name, pAddress sensorId, sSensor.name, pAddress.c_str()
); );
} }
} }

View File

@@ -12,13 +12,13 @@ struct NetworkSettings {
struct { struct {
char ssid[33] = DEFAULT_AP_SSID; char ssid[33] = DEFAULT_AP_SSID;
char password[65] = DEFAULT_AP_PASSWORD; char password[65] = DEFAULT_AP_PASSWORD;
byte channel = 6; uint8_t channel = 6;
} ap; } ap;
struct { struct {
char ssid[33] = DEFAULT_STA_SSID; char ssid[33] = DEFAULT_STA_SSID;
char password[65] = DEFAULT_STA_PASSWORD; char password[65] = DEFAULT_STA_PASSWORD;
byte channel = 0; uint8_t channel = 0;
} sta; } sta;
} networkSettings; } networkSettings;
@@ -42,7 +42,7 @@ struct Settings {
} ntp; } ntp;
UnitSystem unitSystem = UnitSystem::METRIC; UnitSystem unitSystem = UnitSystem::METRIC;
byte statusLedGpio = DEFAULT_STATUS_LED_GPIO; uint8_t statusLedGpio = DEFAULT_STATUS_LED_GPIO;
} system; } system;
struct { struct {
@@ -54,9 +54,9 @@ struct Settings {
struct { struct {
UnitSystem unitSystem = UnitSystem::METRIC; UnitSystem unitSystem = UnitSystem::METRIC;
byte inGpio = DEFAULT_OT_IN_GPIO; uint8_t inGpio = DEFAULT_OT_IN_GPIO;
byte outGpio = DEFAULT_OT_OUT_GPIO; uint8_t outGpio = DEFAULT_OT_OUT_GPIO;
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO; uint8_t rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
uint8_t memberId = 0; uint8_t memberId = 0;
uint8_t flags = 0; uint8_t flags = 0;
float minPower = 0.0f; float minPower = 0.0f;
@@ -105,8 +105,8 @@ struct Settings {
float target = DEFAULT_HEATING_TARGET_TEMP; float target = DEFAULT_HEATING_TARGET_TEMP;
float hysteresis = 0.5f; float hysteresis = 0.5f;
float turboFactor = 7.5f; float turboFactor = 7.5f;
byte minTemp = DEFAULT_HEATING_MIN_TEMP; uint8_t minTemp = DEFAULT_HEATING_MIN_TEMP;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP; uint8_t maxTemp = DEFAULT_HEATING_MAX_TEMP;
uint8_t maxModulation = 100; uint8_t maxModulation = 100;
struct { struct {
@@ -123,8 +123,8 @@ struct Settings {
struct { struct {
bool enabled = true; bool enabled = true;
float target = DEFAULT_DHW_TARGET_TEMP; float target = DEFAULT_DHW_TARGET_TEMP;
byte minTemp = DEFAULT_DHW_MIN_TEMP; uint8_t minTemp = DEFAULT_DHW_MIN_TEMP;
byte maxTemp = DEFAULT_DHW_MAX_TEMP; uint8_t maxTemp = DEFAULT_DHW_MAX_TEMP;
uint8_t maxModulation = 100; uint8_t maxModulation = 100;
struct { struct {
@@ -154,15 +154,14 @@ struct Settings {
struct { struct {
bool enabled = false; bool enabled = false;
float slope = 0.7f; float n_factor = 0.7f;
float exponent = 1.3f; float k_factor = 3.0f;
float shift = 0.0f; float t_factor = 2.0f;
float targetDiffFactor = 2.0f;
} equitherm; } equitherm;
struct { struct {
bool use = false; bool use = false;
byte gpio = DEFAULT_EXT_PUMP_GPIO; uint8_t gpio = DEFAULT_EXT_PUMP_GPIO;
unsigned short postCirculationTime = 600; unsigned short postCirculationTime = 600;
unsigned int antiStuckInterval = 2592000; unsigned int antiStuckInterval = 2592000;
unsigned short antiStuckTime = 300; unsigned short antiStuckTime = 300;
@@ -171,15 +170,15 @@ struct Settings {
struct { struct {
struct { struct {
bool enabled = false; bool enabled = false;
byte gpio = GPIO_IS_NOT_CONFIGURED; uint8_t gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false; bool invertState = false;
unsigned short thresholdTime = 60; unsigned short thresholdTime = 60;
} input; } input;
struct { struct {
bool enabled = false; bool enabled = false;
byte gpio = GPIO_IS_NOT_CONFIGURED; uint8_t gpio = GPIO_IS_NOT_CONFIGURED;
byte invertState = false; bool invertState = false;
unsigned short thresholdTime = 60; unsigned short thresholdTime = 60;
bool onFault = true; bool onFault = true;
bool onLossConnection = true; bool onLossConnection = true;

View File

@@ -81,7 +81,6 @@ const char S_ENABLED[] PROGMEM = "enabled";
const char S_ENV[] PROGMEM = "env"; const char S_ENV[] PROGMEM = "env";
const char S_EPC[] PROGMEM = "epc"; const char S_EPC[] PROGMEM = "epc";
const char S_EQUITHERM[] PROGMEM = "equitherm"; const char S_EQUITHERM[] PROGMEM = "equitherm";
const char S_EXPONENT[] PROGMEM = "exponent";
const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump"; const char S_EXTERNAL_PUMP[] PROGMEM = "externalPump";
const char S_FACTOR[] PROGMEM = "factor"; const char S_FACTOR[] PROGMEM = "factor";
const char S_FAULT[] PROGMEM = "fault"; const char S_FAULT[] PROGMEM = "fault";
@@ -118,6 +117,7 @@ const char S_INVERT_STATE[] PROGMEM = "invertState";
const char S_IP[] PROGMEM = "ip"; const char S_IP[] PROGMEM = "ip";
const char S_I_FACTOR[] PROGMEM = "i_factor"; const char S_I_FACTOR[] PROGMEM = "i_factor";
const char S_I_MULTIPLIER[] PROGMEM = "i_multiplier"; const char S_I_MULTIPLIER[] PROGMEM = "i_multiplier";
const char S_K_FACTOR[] PROGMEM = "k_factor";
const char S_LOGIN[] PROGMEM = "login"; const char S_LOGIN[] PROGMEM = "login";
const char S_LOG_LEVEL[] PROGMEM = "logLevel"; const char S_LOG_LEVEL[] PROGMEM = "logLevel";
const char S_LOW_TEMP[] PROGMEM = "lowTemp"; const char S_LOW_TEMP[] PROGMEM = "lowTemp";
@@ -143,6 +143,7 @@ const char S_NAME[] PROGMEM = "name";
const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl"; const char S_NATIVE_HEATING_CONTROL[] PROGMEM = "nativeHeatingControl";
const char S_NETWORK[] PROGMEM = "network"; const char S_NETWORK[] PROGMEM = "network";
const char S_NTP[] PROGMEM = "ntp"; const char S_NTP[] PROGMEM = "ntp";
const char S_N_FACTOR[] PROGMEM = "n_factor";
const char S_OFFSET[] PROGMEM = "offset"; const char S_OFFSET[] PROGMEM = "offset";
const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating"; const char S_ON_ENABLED_HEATING[] PROGMEM = "onEnabledHeating";
const char S_ON_FAULT[] PROGMEM = "onFault"; const char S_ON_FAULT[] PROGMEM = "onFault";
@@ -181,11 +182,9 @@ const char S_SERIAL[] PROGMEM = "serial";
const char S_SERVER[] PROGMEM = "server"; const char S_SERVER[] PROGMEM = "server";
const char S_SETTINGS[] PROGMEM = "settings"; const char S_SETTINGS[] PROGMEM = "settings";
const char S_SET_DATE_AND_TIME[] PROGMEM = "setDateAndTime"; const char S_SET_DATE_AND_TIME[] PROGMEM = "setDateAndTime";
const char S_SHIFT[] PROGMEM = "shift";
const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality"; const char S_SIGNAL_QUALITY[] PROGMEM = "signalQuality";
const char S_SIZE[] PROGMEM = "size"; const char S_SIZE[] PROGMEM = "size";
const char S_SLAVE[] PROGMEM = "slave"; const char S_SLAVE[] PROGMEM = "slave";
const char S_SLOPE[] PROGMEM = "slope";
const char S_SSID[] PROGMEM = "ssid"; const char S_SSID[] PROGMEM = "ssid";
const char S_STA[] PROGMEM = "sta"; const char S_STA[] PROGMEM = "sta";
const char S_STATE[] PROGMEM = "state"; const char S_STATE[] PROGMEM = "state";
@@ -196,7 +195,6 @@ const char S_SUBNET[] PROGMEM = "subnet";
const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode"; const char S_SUMMER_WINTER_MODE[] PROGMEM = "summerWinterMode";
const char S_SYSTEM[] PROGMEM = "system"; const char S_SYSTEM[] PROGMEM = "system";
const char S_TARGET[] PROGMEM = "target"; const char S_TARGET[] PROGMEM = "target";
const char S_TARGET_DIFF_FACTOR[] PROGMEM = "targetDiffFactor";
const char S_TARGET_TEMP[] PROGMEM = "targetTemp"; const char S_TARGET_TEMP[] PROGMEM = "targetTemp";
const char S_TELNET[] PROGMEM = "telnet"; const char S_TELNET[] PROGMEM = "telnet";
const char S_TEMPERATURE[] PROGMEM = "temperature"; const char S_TEMPERATURE[] PROGMEM = "temperature";
@@ -209,6 +207,7 @@ const char S_TRESHOLD_TIME[] PROGMEM = "tresholdTime";
const char S_TURBO[] PROGMEM = "turbo"; const char S_TURBO[] PROGMEM = "turbo";
const char S_TURBO_FACTOR[] PROGMEM = "turboFactor"; const char S_TURBO_FACTOR[] PROGMEM = "turboFactor";
const char S_TYPE[] PROGMEM = "type"; const char S_TYPE[] PROGMEM = "type";
const char S_T_FACTOR[] PROGMEM = "t_factor";
const char S_UNIT_SYSTEM[] PROGMEM = "unitSystem"; const char S_UNIT_SYSTEM[] PROGMEM = "unitSystem";
const char S_UPTIME[] PROGMEM = "uptime"; const char S_UPTIME[] PROGMEM = "uptime";
const char S_USE[] PROGMEM = "use"; const char S_USE[] PROGMEM = "use";

View File

@@ -72,7 +72,7 @@ time_t mkgmtime(const struct tm *ptm) {
inline bool isDigit(const char* ptr) { inline bool isDigit(const char* ptr) {
char* endPtr; char* endPtr;
strtol(ptr, &endPtr, 10); auto tmp = strtol(ptr, &endPtr, 10);
return *endPtr == 0; return *endPtr == 0;
} }
@@ -517,10 +517,9 @@ void settingsToJson(const Settings& src, JsonVariant dst, bool safe = false) {
auto equitherm = dst[FPSTR(S_EQUITHERM)].to<JsonObject>(); auto equitherm = dst[FPSTR(S_EQUITHERM)].to<JsonObject>();
equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled; equitherm[FPSTR(S_ENABLED)] = src.equitherm.enabled;
equitherm[FPSTR(S_SLOPE)] = roundf(src.equitherm.slope, 3); equitherm[FPSTR(S_N_FACTOR)] = roundf(src.equitherm.n_factor, 3);
equitherm[FPSTR(S_EXPONENT)] = roundf(src.equitherm.exponent, 3); equitherm[FPSTR(S_K_FACTOR)] = roundf(src.equitherm.k_factor, 3);
equitherm[FPSTR(S_SHIFT)] = roundf(src.equitherm.shift, 2); equitherm[FPSTR(S_T_FACTOR)] = roundf(src.equitherm.t_factor, 3);
equitherm[FPSTR(S_TARGET_DIFF_FACTOR)] = roundf(src.equitherm.targetDiffFactor, 3);
auto pid = dst[FPSTR(S_PID)].to<JsonObject>(); auto pid = dst[FPSTR(S_PID)].to<JsonObject>();
pid[FPSTR(S_ENABLED)] = src.pid.enabled; pid[FPSTR(S_ENABLED)] = src.pid.enabled;
@@ -1127,38 +1126,29 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
} }
} }
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_SLOPE)].isNull()) { if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_N_FACTOR)].isNull()) {
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_SLOPE)].as<float>(); float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_N_FACTOR)].as<float>();
if (value > 0.0f && value <= 10.0f && fabsf(value - dst.equitherm.slope) > 0.0001f) { if (value > 0 && value <= 10 && fabsf(value - dst.equitherm.n_factor) > 0.0001f) {
dst.equitherm.slope = roundf(value, 3); dst.equitherm.n_factor = roundf(value, 3);
changed = true; changed = true;
} }
} }
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_EXPONENT)].isNull()) { if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_K_FACTOR)].isNull()) {
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_EXPONENT)].as<float>(); float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_K_FACTOR)].as<float>();
if (value > 0.0f && value <= 2.0f && fabsf(value - dst.equitherm.exponent) > 0.0001f) { if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.k_factor) > 0.0001f) {
dst.equitherm.exponent = roundf(value, 3); dst.equitherm.k_factor = roundf(value, 3);
changed = true; changed = true;
} }
} }
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_SHIFT)].isNull()) { if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_T_FACTOR)].isNull()) {
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_SHIFT)].as<float>(); float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_T_FACTOR)].as<float>();
if (value >= -15.0f && value <= 15.0f && fabsf(value - dst.equitherm.shift) > 0.0001f) { if (value >= 0 && value <= 10 && fabsf(value - dst.equitherm.t_factor) > 0.0001f) {
dst.equitherm.shift = roundf(value, 2); dst.equitherm.t_factor = roundf(value, 3);
changed = true;
}
}
if (!src[FPSTR(S_EQUITHERM)][FPSTR(S_TARGET_DIFF_FACTOR)].isNull()) {
float value = src[FPSTR(S_EQUITHERM)][FPSTR(S_TARGET_DIFF_FACTOR)].as<float>();
if (value >= 0.0f && value <= 10.0f && fabsf(value - dst.equitherm.targetDiffFactor) > 0.0001f) {
dst.equitherm.targetDiffFactor = roundf(value, 3);
changed = true; changed = true;
} }
} }
@@ -1334,7 +1324,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (!src[FPSTR(S_HEATING)][FPSTR(S_MIN_TEMP)].isNull()) { if (!src[FPSTR(S_HEATING)][FPSTR(S_MIN_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MIN_TEMP)].as<unsigned char>(); unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MIN_TEMP)].as<unsigned char>();
if (value != dst.heating.minTemp && value >= vars.slave.heating.minTemp && value < vars.slave.heating.maxTemp && value != dst.heating.minTemp) { if (value != dst.heating.minTemp && value >= vars.slave.heating.minTemp && value < vars.slave.heating.maxTemp && value != dst.heating.maxTemp) {
dst.heating.minTemp = value; dst.heating.minTemp = value;
changed = true; changed = true;
} }
@@ -1343,7 +1333,7 @@ bool jsonToSettings(const JsonVariantConst src, Settings& dst, bool safe = false
if (!src[FPSTR(S_HEATING)][FPSTR(S_MAX_TEMP)].isNull()) { if (!src[FPSTR(S_HEATING)][FPSTR(S_MAX_TEMP)].isNull()) {
unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MAX_TEMP)].as<unsigned char>(); unsigned char value = src[FPSTR(S_HEATING)][FPSTR(S_MAX_TEMP)].as<unsigned char>();
if (value != dst.heating.maxTemp && value > vars.slave.heating.minTemp && value <= vars.slave.heating.maxTemp && value != dst.heating.maxTemp) { if (value != dst.heating.maxTemp && value > vars.slave.heating.minTemp && value <= vars.slave.heating.maxTemp && value != dst.heating.minTemp) {
dst.heating.maxTemp = value; dst.heating.maxTemp = value;
changed = true; changed = true;
} }
@@ -1904,7 +1894,7 @@ bool jsonToSensorSettings(const uint8_t sensorId, const JsonVariantConst src, Se
// gpio // gpio
if (!src[FPSTR(S_GPIO)].isNull()) { if (!src[FPSTR(S_GPIO)].isNull()) {
if (dst.type != Sensors::Type::DALLAS_TEMP && dst.type == Sensors::Type::BLUETOOTH && dst.type == Sensors::Type::NTC_10K_TEMP) { if (dst.type != Sensors::Type::DALLAS_TEMP && dst.type != Sensors::Type::NTC_10K_TEMP) {
if (dst.gpio != GPIO_IS_NOT_CONFIGURED) { if (dst.gpio != GPIO_IS_NOT_CONFIGURED) {
dst.gpio = GPIO_IS_NOT_CONFIGURED; dst.gpio = GPIO_IS_NOT_CONFIGURED;
changed = true; changed = true;

507
src_data/locales/cn.json Normal file
View File

@@ -0,0 +1,507 @@
{
"values": {
"logo": "OpenTherm Gateway",
"nav": {
"license": "授权许可",
"source": "源代码",
"help": "帮助",
"issues": "问题与反馈",
"releases": "发行版"
},
"dbm": "dBm",
"kw": "kW",
"time": {
"days": "天",
"hours": "小时",
"min": "分",
"sec": "秒"
},
"button": {
"upgrade": "固件升级",
"restart": "重启",
"save": "保存",
"saved": "已保存",
"refresh": "刷新",
"restore": "恢复",
"restored": "已恢复",
"backup": "备份",
"wait": "请等待...",
"uploading": "上传中...",
"success": "成功",
"error": "错误"
},
"index": {
"title": "OpenTherm Gateway",
"section": {
"network": "网络",
"system": "系统"
},
"system": {
"build": {
"title": "Build",
"version": "固件版本",
"date": "日期",
"core": "内核版本",
"sdk": "SDK"
},
"uptime": "运行时间",
"memory": {
"title": "可用内存",
"maxFreeBlock": "max free block",
"min": "min"
},
"board": "开发板",
"chip": {
"model": "芯片",
"cores": "核心数",
"freq": "频率"
},
"flash": {
"size": "闪存容量",
"realSize": "实际容量"
},
"lastResetReason": "上次重置原因"
}
},
"dashboard": {
"name": "仪表盘",
"title": "仪表盘 - OpenTherm Gateway",
"section": {
"control": "调节",
"states": "状态",
"sensors": "传感器",
"diag": "OpenTherm 诊断"
},
"thermostat": {
"heating": "供暖",
"dhw": "生活热水",
"temp.current": "当前温度",
"enable": "启用",
"turbo": "Turbo 模式"
},
"notify": {
"fault": {
"title": "锅炉报警状态已激活!",
"note": "建议检查锅炉并查看说明书对应的报警代码:"
},
"diag": {
"title": "锅炉诊断状态已激活!",
"note": "锅炉可能需要检查,建议查看说明书对应的诊断代码:"
},
"reset": "点击复位"
},
"states": {
"mNetworkConnected": "网络连接状态",
"mMqttConnected": "MQTT服务器连接状态",
"mEmergencyState": "应急模式",
"mExtPumpState": "外置循环泵",
"mCascadeControlInput": "Cascade 控制 (input)",
"mCascadeControlOutput": "Cascade 控制 (output)",
"sConnected": "OpenTherm 通讯状态",
"sFlame": "火焰",
"sCooling": "制冷",
"sFaultActive": "报警状态",
"sFaultCode": "报警代码",
"sDiagActive": "诊断状态",
"sDiagCode": "诊断代码",
"mHeatEnabled": "供暖功能已启用",
"mHeatBlocking": "供暖",
"mHeatOverheat": "供暖超热保护",
"sHeatActive": "供暖激活状态",
"mHeatSetpointTemp": "供暖供水设定温度",
"mHeatTargetTemp": "供暖供水目标温度",
"mHeatCurrTemp": "供暖当前供水温度",
"mHeatRetTemp": "供暖回水温度",
"mHeatIndoorTemp": "供暖,室内温度",
"mHeatOutdoorTemp": "供暖,室外温度",
"mDhwEnabled": "生活热水功能已启用",
"mDhwOverheat": "生活热水超热保护",
"sDhwActive": "生活热水激活",
"mDhwTargetTemp": "生活热水目标温度",
"mDhwCurrTemp": "生活热水当前出水温度",
"mDhwRetTemp": "生活热水回水温度"
},
"sensors": {
"values": {
"temp": "温度",
"humidity": "湿度",
"battery": "电量",
"rssi": "RSSI"
}
}
},
"network": {
"title": "网络 - OpenTherm Gateway",
"name": "网络设置",
"section": {
"static": "静态设置",
"availableNetworks": "可用网络",
"staSettings": "WiFi 设置",
"apSettings": "AP 设置"
},
"scan": {
"pos": "#",
"info": "Info"
},
"wifi": {
"ssid": "SSID",
"password": "密码",
"channel": "频道",
"signal": "信号强度",
"connected": "已连接"
},
"params": {
"hostname": "Hostname",
"dhcp": "自动 (DHCP)",
"mac": "物理地址 (MAC)",
"ip": "IP",
"subnet": "子网掩码",
"gateway": "网关",
"dns": "DNS 服务器"
},
"sta": {
"channel.note": "自动选择设置为0"
}
},
"sensors": {
"title": "传感器设置 - OpenTherm Gateway",
"name": "传感器设置",
"enabled": "启用",
"sensorName": {
"title": "传感器名称",
"note": "只能包含a-z、A-Z、0-9、下划线和空格"
},
"purpose": "用途",
"purposes": {
"outdoorTemp": "室外温度",
"indoorTemp": "室内温度",
"heatTemp": "供暖,温度",
"heatRetTemp": "供暖回水温度",
"dhwTemp": "生活热水温度",
"dhwRetTemp": "生活热水回水温度",
"dhwFlowRate": "生活热水水流量",
"exhaustTemp": "烟气温度",
"modLevel": "Modulation level (%)",
"number": "Number (raw)",
"powerFactor": "功率 (%)",
"power": "功率(kW)",
"fanSpeed": "风机转速",
"co2": "CO2",
"pressure": "压力",
"humidity": "湿度",
"temperature": "温度",
"notConfigured": "未配置"
},
"type": "类型/来源",
"types": {
"otOutdoorTemp": "OpenTherm, outdoor temp",
"otHeatTemp": "OpenTherm, heating, temp",
"otHeatRetTemp": "OpenTherm, heating, return temp",
"otDhwTemp": "OpenTherm, DHW, temperature",
"otDhwTemp2": "OpenTherm, DHW, temperature 2",
"otDhwFlowRate": "OpenTherm, DHW, flow rate",
"otCh2Temp": "OpenTherm, channel 2, temp",
"otExhaustTemp": "OpenTherm, exhaust temp",
"otHeatExchangerTemp": "OpenTherm, heat exchanger temp",
"otPressure": "OpenTherm, pressure",
"otModLevel": "OpenTherm, modulation level",
"otCurrentPower": "OpenTherm, current power",
"otExhaustCo2": "OpenTherm, exhaust CO2",
"otExhaustFanSpeed": "OpenTherm, exhaust fan speed",
"otSupplyFanSpeed": "OpenTherm, supply fan speed",
"otSolarStorageTemp": "OpenTherm, solar storage temp",
"otSolarCollectorTemp": "OpenTherm, solar collector temp",
"otFanSpeedSetpoint": "OpenTherm, setpoint fan speed",
"otFanSpeedCurrent": "OpenTherm, current fan speed",
"otBurnerStarts": "OpenTherm, number of burner starts",
"otDhwBurnerStarts": "OpenTherm, number of burner starts (DHW)",
"otHeatingPumpStarts": "OpenTherm, number of pump starts (heating)",
"otDhwPumpStarts": "OpenTherm, number of pump starts (DHW)",
"otBurnerHours": "OpenTherm, number of burner operating hours",
"otDhwBurnerHours": "OpenTherm, number of burner operating hours (DHW)",
"otHeatingPumpHours": "OpenTherm, number of pump operating hours (heating)",
"otDhwPumpHours": "OpenTherm, number of pump operating hours (DHW)",
"ntcTemp": "NTC 传感器",
"dallasTemp": "DALLAS 传感器",
"bluetooth": "BLE 传感器",
"heatSetpointTemp": "Heating, setpoint temp",
"manual": "通过 MQTT/API 手动配置",
"notConfigured": "未配置"
},
"gpio": "GPIO",
"address": {
"title": "传感器地址",
"note": "如需自动检测DALLAS传感器请保持默认设置如需连接BLE设备则需提供MAC地址"
},
"correction": {
"desc": "数值校正",
"offset": "补偿值(偏移量)",
"factor": "Multiplier"
},
"filtering": {
"desc": "数值滤波",
"enabled": {
"title": "启用滤波",
"note": "如果图表中有大量尖锐的噪声,此功能会很有用。使用的滤波器是 \"滑动平均滤波器\"."
},
"factor": {
"title": "滤波系数",
"note": "数值越低,数值变化越平滑且响应越滞后"
}
}
},
"settings": {
"title": "设置 - OpenTherm Gateway",
"name": "设置",
"section": {
"portal": "Portal 设置",
"system": "系统设置",
"diag": "诊断",
"heating": "供热设置",
"dhw": "生活热水设置",
"emergency": "应急模式设置",
"equitherm": "气候补偿设置",
"pid": "PID 参数设置",
"ot": "OpenTherm协议设置",
"mqtt": "MQTT 服务器设置",
"extPump": "外置循环泵设置",
"cascadeControl": "Cascade 级联控制设置"
},
"enable": "启用",
"note": {
"restart": "更改这些设置后,必须重启设备以使变更生效",
"blankNotUse": "空白 - 未使用",
"bleDevice": "BLE设备仅支持搭载BLE功能的特定ESP32开发板使用!"
},
"temp": {
"min": "最低温度",
"max": "最高温度"
},
"maxModulation": "最大调制范围",
"ohProtection": {
"title": "超温保护",
"desc": "<b>注意:</b> 当锅炉内置过热保护失效或工作异常导致系统超温时,此功能可提供额外保护。如需禁用,请将<b>最高温度</b>和<b>最低温度</b>均设为0。",
"highTemp": {
"title": "高温阈值",
"note": "触发燃烧器强制关闭的阈值温度"
},
"lowTemp": {
"title": "低温阈值",
"note": "燃烧器重新启动的阈值温度"
}
},
"freezeProtection": {
"title": "防冻保护",
"desc": "当热媒或室内温度在<b>等待时间</b> 内降至<b>低温阈值</b>以下时,系统将强制启动加热功能。",
"lowTemp": "低温阈值",
"thresholdTime": "等待时间<small>(秒)</small>"
},
"portal": {
"login": "登录",
"password": "密码",
"auth": "需身份验证",
"mdns": "使用 mDNS"
},
"system": {
"unit": "单位",
"metric": "公制 <small>(摄氏度、升、巴)</small>",
"imperial": "英制 <small>(华氏度、加仑、psi)</small>",
"statusLedGpio": "状态指示灯GPIO引脚",
"logLevel": "日志级别",
"serial": {
"enable": "启用串口",
"baud": "串口波特率"
},
"telnet": {
"enable": "启用 Telnet",
"port": {
"title": "Telnet 端口",
"note": "默认值23"
}
},
"ntp": {
"server": "NTP服务器",
"timezone": "时区",
"timezonePresets": "选择预设配置..."
}
},
"heating": {
"hyst": "滞后值<small>(单位:度)</small>",
"turboFactor": "Turbo 模式系数"
},
"emergency": {
"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": {
"n": "N 系数",
"k": "K 系数",
"t": {
"title": "T 系数",
"note": "启用PID时此参数无效"
}
},
"pid": {
"p": "P 系数",
"i": "I 系数",
"d": "D 系数",
"dt": "DT <small>以秒为单位</small>",
"limits": {
"title": "Limits",
"note": "<b>重要提示:</b> When using «Equitherm» and «PID» at the same time, the min and max temperatures limit the influence on the «Equitherm» result temperature.<br />Thus, if the min temperature is set to -15 and the max temperature is set to 15, then the final heat carrier setpoint will be from <code>equitherm_result - 15</code> to <code>equitherm_result + 15</code>."
},
"deadband": {
"title": "Deadband",
"note": "Deadband is a range around the target temperature where PID regulation becomes less active. Within this range, the algorithm can reduce intensity or pause adjustments to avoid overreacting to small fluctuations.<br /><br />For instance, with a target temperature of 22°, a lower threshold of 1.0, and an upper threshold of 0.5, the deadband operates between 21° and 22.5°. If the I coefficient is 0.0005 and the I multiplier is 0.05, then within the deadband, the I coefficient becomes: <code>0.0005 * 0.05 = 0.000025</code>",
"p_multiplier": "Multiplier for P factor",
"i_multiplier": "Multiplier for I factor",
"d_multiplier": "Multiplier for D factor",
"thresholdHigh": "Threshold high",
"thresholdLow": "Threshold low"
}
},
"ot": {
"advanced": "高级设置",
"inGpio": "In GPIO",
"outGpio": "Out GPIO",
"ledGpio": "RX LED GPIO",
"memberId": "主设备成员 ID",
"flags": "主设备标志",
"minPower": {
"title": "最小锅炉功率 <small>(kW)</small>",
"note": "该值对应锅炉0-1%的调制水平,通常在锅炉参数设置中的\"最小有效热输出\"。"
},
"maxPower": {
"title": "最大锅炉功率 <small>(kW)</small>",
"note": "<b>0</b> - 自动检测,通常在锅炉参数设置中的\"最大有效热输出\"。 "
},
"options": {
"title": "选项(附加设置)",
"desc": "附加设置选项可调整锅炉的运行逻辑。由于协议未完整记录所有选项,同一选项在不同锅炉上可能产生不同效果。<br /><b>注意:</b>若系统运行正常,无需修改设置。",
"dhwSupport": "生活热水支持",
"coolingSupport": "制冷支持",
"summerWinterMode": "夏季/冬季模式",
"heatingStateToSummerWinterMode": "以供暖状态作为夏季/冬季模式",
"ch2AlwaysEnabled": "CH2 始终启用",
"heatingToCh2": "将供暖同步至 CH2",
"dhwToCh2": "将生活热水同步至 CH2",
"dhwBlocking": "生活热水锁定",
"dhwStateAsDhwBlocking": "以生活热水状态作为生活热水锁定依据",
"maxTempSyncWithTargetTemp": "将最高供暖温度与目标温度同步",
"getMinMaxTemp": "从锅炉获取最小/最大温度参数",
"ignoreDiagState": "忽略诊断状态",
"autoFaultReset": "自动报警复位 <small>(不推荐!)</small>",
"autoDiagReset": "自动诊断复位 <small>(不推荐!)</small>",
"setDateAndTime": "同步设置锅炉日期与时间",
"immergasFix": "针对Immergas锅炉的兼容性修复"
},
"nativeHeating": {
"title": "原生锅炉供暖控制",
"note": "<u>注意:</u> 仅适用于锅炉需接收目标室温并自主调节载热介质温度的场景与固件中的PID及Equithermq气候补偿功能不兼容。"
}
},
"mqtt": {
"homeAssistantDiscovery": "Home Assistant 自动发现",
"server": "服务器地址",
"port": "端口",
"user": "用户名",
"password": "密码",
"prefix": "Prefix 前缀",
"interval": "发布间隔 <small>(秒)</small>"
},
"extPump": {
"use": "使用外置循环泵",
"gpio": "继电器 GPIO引脚",
"postCirculationTime": "后循环时间 <small>(分钟)</small>",
"antiStuckInterval": "防卡死间隔时间<small>(天)</small>",
"antiStuckTime": "防卡死运行时长<small>(分钟)</small>"
},
"cascadeControl": {
"input": {
"desc": "仅当另一台锅炉发生故障时启用本锅炉加热。另一台锅炉的控制器需在故障发生时切换GPIO输入状态以触发本功能。",
"enable": "启用 input",
"gpio": "GPIO",
"invertState": "切换 GPIO 状态",
"thresholdTime": "状态变化阈值时间<small>(秒)</small>"
},
"output": {
"desc": "可通过<u>继电器</u>控制另一台锅炉的启停。",
"enable": "启用 output",
"gpio": "GPIO",
"invertState": "切换 GPIO 状态",
"thresholdTime": "状态变化阈值时间 <small>(秒)</small>",
"events": {
"desc": "事件",
"onFault": "当故障状态激活时",
"onLossConnection": "当OpenTherm连接中断时",
"onEnabledHeating": "当供暖功能启用时"
}
}
}
},
"upgrade": {
"title": "固件升级 - OpenTherm Gateway",
"name": "固件升级",
"section": {
"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"
}
}
}

View File

@@ -370,26 +370,11 @@
}, },
"equitherm": { "equitherm": {
"slope": { "n": "N factor",
"title": "Slope", "k": "K factor",
"note": "Heat loss compensation. Main tuning parameter." "t": {
},
"exponent": {
"title": "Exponent",
"note": "Radiator efficiency. Typical values: 1.1 - Floor heating, 1.2 - Cast iron, 1.3 - Panel radiators, 1.4 - Convectors."
},
"shift": {
"title": "Shift",
"note": "Compensates for additional heat losses (e.g., in pipes) or extra heat sources."
},
"targetDiffFactor": {
"title": "T factor", "title": "T factor",
"note": "Not used if PID is enabled" "note": "Not used if PID is enabled"
},
"chart": {
"targetTemp": "Target indoor temperature",
"setpointTemp": "Heat carrier temperature",
"outdoorTemp": "Outdoor temperature"
} }
}, },

View File

@@ -370,26 +370,11 @@
}, },
"equitherm": { "equitherm": {
"slope": { "n": "Fattore N",
"title": "Pendenza", "k": "Fattore K",
"note": "Compensazione delle perdite di calore. Principale parametro di regolazione." "t": {
},
"exponent": {
"title": "Esponente",
"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": "Spostare",
"note": "Compensa le perdite di calore aggiuntive (ad esempio, nelle tubature) o fonti di calore extra."
},
"targetDiffFactor": {
"title": "Fattore T", "title": "Fattore T",
"note": "Non usato se PID è attivato" "note": "Non usato se PID è attivato"
},
"chart": {
"targetTemp": "Temperatura interna target",
"setpointTemp": "Temperatura del portatore di calore",
"outdoorTemp": "Temperatura esterna"
} }
}, },

View File

@@ -370,26 +370,11 @@
}, },
"equitherm": { "equitherm": {
"slope": { "n": "Коэффициент N",
"title": "Наклон", "k": "Коэффициент K",
"note": "Компенсация теплопотерь. Основной параметр настройки." "t": {
},
"exponent": {
"title": "Экспонента",
"note": "Эффективность радиатора. Типичные значения: 1.1 - Тёплый пол, 1.2 - Чугунные радиаторы, 1.3 - Панельные радиаторы, 1.4 - Конвекторы."
},
"shift": {
"title": "Смещение",
"note": "Компенсирует дополнительные теплопотери (например, в трубах) или дополнительные источники тепла."
},
"targetDiffFactor": {
"title": "Коэффициент T", "title": "Коэффициент T",
"note": "Не используется, если ПИД включен" "note": "Не используется, если ПИД включен"
},
"chart": {
"targetTemp": "Целевая внутренняя температура",
"setpointTemp": "Температура теплоносителя",
"outdoorTemp": "Наружная температура"
} }
}, },

View File

@@ -21,6 +21,7 @@
<li> <li>
<select id="lang" aria-label="Lang"> <select id="lang" aria-label="Lang">
<option value="en" selected>EN</option> <option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option> <option value="it">IT</option>
<option value="ru">RU</option> <option value="ru">RU</option>
</select> </select>

View File

@@ -21,6 +21,7 @@
<li> <li>
<select id="lang" aria-label="Lang"> <select id="lang" aria-label="Lang">
<option value="en" selected>EN</option> <option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option> <option value="it">IT</option>
<option value="ru">RU</option> <option value="ru">RU</option>
</select> </select>

View File

@@ -21,6 +21,7 @@
<li> <li>
<select id="lang" aria-label="Lang"> <select id="lang" aria-label="Lang">
<option value="en" selected>EN</option> <option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option> <option value="it">IT</option>
<option value="ru">RU</option> <option value="ru">RU</option>
</select> </select>

View File

@@ -21,6 +21,7 @@
<li> <li>
<select id="lang" aria-label="Lang"> <select id="lang" aria-label="Lang">
<option value="en" selected>EN</option> <option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option> <option value="it">IT</option>
<option value="ru">RU</option> <option value="ru">RU</option>
</select> </select>
@@ -239,9 +240,7 @@
setCheckboxValue("[name='filtering']", data.filtering, sensorForm); setCheckboxValue("[name='filtering']", data.filtering, sensorForm);
setInputValue("[name='filteringFactor']", data.filteringFactor, {}, sensorForm); setInputValue("[name='filteringFactor']", data.filteringFactor, {}, sensorForm);
setTimeout(() => { sensorForm.querySelector("[name='type']").dispatchEvent(new Event("change"));
sensorForm.querySelector("[name='type']").dispatchEvent(new Event("change"));
}, 10);
setBusy(".form-busy", "form", false, sensorNode); setBusy(".form-busy", "form", false, sensorNode);
}; };

View File

@@ -21,6 +21,7 @@
<li> <li>
<select id="lang" aria-label="Lang"> <select id="lang" aria-label="Lang">
<option value="en" selected>EN</option> <option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option> <option value="it">IT</option>
<option value="ru">RU</option> <option value="ru">RU</option>
</select> </select>
@@ -339,7 +340,7 @@
</details> </details>
<hr /> <hr />
<details> <details>
<summary><b data-i18n>settings.section.equitherm</b></summary> <summary><b data-i18n>settings.section.equitherm</b></summary>
<div> <div>
@@ -352,44 +353,21 @@
</label> </label>
</fieldset> </fieldset>
<div>
<div>
<canvas id="etChart"></canvas>
</div>
<label>
<div>
<span data-i18n>settings.equitherm.chart.targetTemp</span>: <b class="etChartTargetTempValue"></b>°
</div>
<input class="etChartTargetTemp" type="range" value="0" min="0" max="0" step="0.5">
</label>
</div>
<div class="grid"> <div class="grid">
<label> <label>
<span data-i18n>settings.equitherm.slope.title</span> <span data-i18n>settings.equitherm.n</span>
<input type="number" inputmode="decimal" name="equitherm[slope]" min="0.001" max="10" step="0.001" required> <input type="number" inputmode="decimal" name="equitherm[n_factor]" min="0.001" max="10" step="0.001" required>
<small data-i18n>settings.equitherm.slope.note</small>
</label> </label>
<label> <label>
<span data-i18n>settings.equitherm.exponent.title</span> <span data-i18n>settings.equitherm.k</span>
<input type="number" inputmode="decimal" name="equitherm[exponent]" min="0.1" max="2" step="0.001" required> <input type="number" inputmode="decimal" name="equitherm[k_factor]" min="0" max="10" step="0.01" required>
<small data-i18n>settings.equitherm.exponent.note</small>
</label>
</div>
<div class="grid">
<label>
<span data-i18n>settings.equitherm.shift.title</span>
<input type="number" inputmode="decimal" name="equitherm[shift]" min="-15" max="15" step="0.01" required>
<small data-i18n>settings.equitherm.shift.note</small>
</label> </label>
<label> <label>
<span data-i18n>settings.equitherm.targetDiffFactor.title</span> <span data-i18n>settings.equitherm.t.title</span>
<input type="number" inputmode="decimal" name="equitherm[targetDiffFactor]" min="0" max="10" step="0.01" required> <input type="number" inputmode="decimal" name="equitherm[t_factor]" min="0" max="10" step="0.01" required>
<small data-i18n>settings.equitherm.targetDiffFactor.note</small> <small data-i18n>settings.equitherm.t.note</small>
</label> </label>
</div> </div>
@@ -399,7 +377,7 @@
</details> </details>
<hr /> <hr />
<details> <details>
<summary><b data-i18n>settings.section.pid</b></summary> <summary><b data-i18n>settings.section.pid</b></summary>
<div> <div>
@@ -882,169 +860,11 @@
</footer> </footer>
<script src="/static/app.js?{BUILD_TIME}"></script> <script src="/static/app.js?{BUILD_TIME}"></script>
<script src="/static/chart.js?{BUILD_TIME}"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', async () => { document.addEventListener('DOMContentLoaded', async () => {
const lang = new Lang(document.getElementById('lang')); const lang = new Lang(document.getElementById('lang'));
lang.build(); lang.build();
let etChart = null;
let etChartConfig = {
slope: null,
exponent: null,
shift: null,
unitSystem: null,
targetTemp: null,
minTemp: null,
maxTemp: null
};
const hasNeedDecimationChart = () => {
return window.innerWidth <= 800;
}
const makeEquithermChart = () => {
if (etChart == null) {
const ctx = document.getElementById('etChart').getContext('2d');
try {
etChart = new Chart(ctx, {
type: 'line',
data: {
datasets: [{
borderColor: (context) => {
const chart = context.chart;
const {ctx, chartArea} = chart;
if (!chartArea) {
return;
}
const gradient = ctx.createLinearGradient(0, chartArea.bottom, 0, chartArea.top);
gradient.addColorStop(0, 'rgba(1, 114, 173, 1)');
gradient.addColorStop(0.5, 'rgba(255, 99, 132, 1)');
return gradient;
},
borderWidth: 3,
fill: false,
tension: 0.1,
pointRadius: 2,
pointHoverRadius: 4,
indexAxis: "x",
data: []
}]
},
options: {
responsive: true,
resizeDelay: 500,
parsing: false,
interaction: {
mode: 'nearest',
intersect: false
},
plugins: {
tooltip: {
enabled: true,
position: 'nearest',
displayColors: false,
callbacks: {
title: (items) => {
return `${i18n("settings.equitherm.chart.outdoorTemp")}: ${items[0].label}`;
}
}
},
legend: {
display: false
},
decimation: {
enabled: hasNeedDecimationChart(),
algorithm: 'lttb',
samples: 30,
threshold: 30
}
},
scales: {
x: {
display: true,
type: "linear",
reverse: true,
title: {
display: true
},
ticks: {
stepSize: 1,
format: {
style: "unit",
unit: "degree",
unitDisplay: "narrow"
}
}
},
y: {
display: true,
title: {
display: true
},
ticks: {
format: {
style: "unit",
unit: "degree",
unitDisplay: "narrow"
}
}
}
}
}
});
} catch (error) {
console.log(error);
}
}
if (!etChart) {
return;
}
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);
etChart.data.datasets[0].data.push({
x: parseFloat(outdoorTemp.toFixed(1)),
y: parseFloat(calculateEquithermTemp(outdoorTemp).toFixed(1))
});
}
etChart.data.datasets[0].label = i18n("settings.equitherm.chart.setpointTemp");
etChart.options.scales.x.title.text = i18n("settings.equitherm.chart.outdoorTemp");
etChart.options.scales.y.title.text = i18n("settings.equitherm.chart.setpointTemp");
etChart.update();
}
const calculateEquithermTemp = (outdoorTemp) => {
const tempDelta = etChartConfig.targetTemp - outdoorTemp;
const maxPoint = etChartConfig.targetTemp - (
etChartConfig.maxTemp - etChartConfig.targetTemp
) / etChartConfig.slope;
const sf = (etChartConfig.maxTemp - etChartConfig.targetTemp) / Math.pow(
etChartConfig.targetTemp - maxPoint,
1 / etChartConfig.exponent
);
const result = etChartConfig.targetTemp + etChartConfig.shift + sf * (
tempDelta >= 0
? Math.pow(tempDelta, 1 / etChartConfig.exponent)
: -(Math.pow(-(tempDelta), 1 / etChartConfig.exponent))
);
return Math.max(Math.min(result, etChartConfig.maxTemp), etChartConfig.minTemp);
}
const fillData = (data) => { const fillData = (data) => {
// System // System
setSelectValue("[name='system[logLevel]']", data.system.logLevel); setSelectValue("[name='system[logLevel]']", data.system.logLevel);
@@ -1192,10 +1012,9 @@
// Equitherm // Equitherm
setCheckboxValue("[name='equitherm[enabled]']", data.equitherm.enabled); setCheckboxValue("[name='equitherm[enabled]']", data.equitherm.enabled);
setInputValue("[name='equitherm[slope]']", data.equitherm.slope); setInputValue("[name='equitherm[n_factor]']", data.equitherm.n_factor);
setInputValue("[name='equitherm[exponent]']", data.equitherm.exponent); setInputValue("[name='equitherm[k_factor]']", data.equitherm.k_factor);
setInputValue("[name='equitherm[shift]']", data.equitherm.shift); setInputValue("[name='equitherm[t_factor]']", data.equitherm.t_factor);
setInputValue("[name='equitherm[targetDiffFactor]']", data.equitherm.targetDiffFactor);
setBusy('#equitherm-settings-busy', '#equitherm-settings', false); setBusy('#equitherm-settings-busy', '#equitherm-settings', false);
// PID // PID
@@ -1219,24 +1038,6 @@
setInputValue("[name='pid[deadband][thresholdHigh]']", data.pid.deadband.thresholdHigh); setInputValue("[name='pid[deadband][thresholdHigh]']", data.pid.deadband.thresholdHigh);
setInputValue("[name='pid[deadband][thresholdLow]']", data.pid.deadband.thresholdLow); setInputValue("[name='pid[deadband][thresholdLow]']", data.pid.deadband.thresholdLow);
setBusy('#pid-settings-busy', '#pid-settings', false); setBusy('#pid-settings-busy', '#pid-settings', false);
const etMinTemp = parseInt(data.system.unitSystem == 0 ? 5 : 41);
const etMaxTemp = parseInt(data.system.unitSystem == 0 ? 30 : 86);
const etTargetTemp = constrain(parseFloat(data.heating.target), etMinTemp, etMaxTemp);
setInputValue(".etChartTargetTemp", etTargetTemp.toFixed(1), {
"min": etMinTemp,
"max": etMaxTemp
});
etChartConfig.slope = data.equitherm.slope;
etChartConfig.exponent = data.equitherm.exponent;
etChartConfig.shift = data.equitherm.shift;
etChartConfig.unitSystem = data.system.unitSystem;
etChartConfig.minTemp = data.heating.minTemp;
etChartConfig.maxTemp = data.heating.maxTemp;
makeEquithermChart();
}; };
try { try {
@@ -1289,58 +1090,6 @@
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
document.querySelector(".etChartTargetTemp").addEventListener("input", async (event) => {
setValue('.etChartTargetTempValue', parseFloat(event.target.value).toFixed(1));
});
document.querySelector(".etChartTargetTemp").addEventListener("change", async (event) => {
if (!event.target.checkValidity()) {
return;
}
etChartConfig.targetTemp = parseFloat(event.target.value);
setValue('.etChartTargetTempValue', etChartConfig.targetTemp.toFixed(1));
makeEquithermChart();
});
document.querySelector("[name='equitherm[slope]']").addEventListener("change", async (event) => {
if (!event.target.checkValidity()) {
return;
}
etChartConfig.slope = parseFloat(event.target.value);
makeEquithermChart();
});
document.querySelector("[name='equitherm[exponent]']").addEventListener("change", async (event) => {
if (!event.target.checkValidity()) {
return;
}
etChartConfig.exponent = parseFloat(event.target.value);
makeEquithermChart();
});
document.querySelector("[name='equitherm[shift]']").addEventListener("change", async (event) => {
if (!event.target.checkValidity()) {
return;
}
etChartConfig.shift = parseFloat(event.target.value);
makeEquithermChart();
});
window.addEventListener('resize', async (event) => {
if (etChart) {
etChart.resize();
if (etChart.options.plugins.decimation.enabled != hasNeedDecimationChart()) {
etChart.options.plugins.decimation.enabled = hasNeedDecimationChart();
etChart.update();
}
}
});
}); });
</script> </script>
</body> </body>

View File

@@ -21,6 +21,7 @@
<li> <li>
<select id="lang" aria-label="Lang"> <select id="lang" aria-label="Lang">
<option value="en" selected>EN</option> <option value="en" selected>EN</option>
<option value="cn">CN</option>
<option value="it">IT</option> <option value="it">IT</option>
<option value="ru">RU</option> <option value="ru">RU</option>
</select> </select>

File diff suppressed because one or more lines are too long

View File

@@ -5,13 +5,8 @@ const setupForm = (formSelector, onResultCallback = null, noCastItems = []) => {
} }
form.querySelectorAll('input').forEach(item => { form.querySelectorAll('input').forEach(item => {
item.addEventListener('change', (event) => { item.addEventListener('change', (e) => {
if (!event.target.checkValidity()) { e.target.setAttribute('aria-invalid', !e.target.checkValidity());
event.target.setAttribute('aria-invalid', true);
} else if (event.target.hasAttribute('aria-invalid')) {
event.target.removeAttribute('aria-invalid');
}
}) })
}); });
@@ -635,10 +630,6 @@ const setCheckboxValue = (selector, value, parent = undefined) => {
} }
item.checked = value; item.checked = value;
setTimeout(() => {
item.dispatchEvent(new Event("change"));
}, 10);
} }
const setRadioValue = (selector, value, parent = undefined) => { const setRadioValue = (selector, value, parent = undefined) => {
@@ -652,14 +643,7 @@ const setRadioValue = (selector, value, parent = undefined) => {
} }
for (let item of items) { for (let item of items) {
const checked = item.value == value; item.checked = item.value == value;
if (item.checked != checked) {
item.checked = checked;
setTimeout(() => {
item.dispatchEvent(new Event("change"));
}, 10);
}
} }
} }
@@ -674,17 +658,13 @@ const setInputValue = (selector, value, attrs = {}, parent = undefined) => {
} }
for (let item of items) { for (let item of items) {
item.value = value;
if (attrs instanceof Object) { if (attrs instanceof Object) {
for (let attrKey of Object.keys(attrs)) { for (let attrKey of Object.keys(attrs)) {
item.setAttribute(attrKey, attrs[attrKey]); item.setAttribute(attrKey, attrs[attrKey]);
} }
} }
item.value = value;
setTimeout(() => {
item.dispatchEvent(new Event("change"));
}, 10);
} }
} }
@@ -873,12 +853,4 @@ function dec2hex(i) {
function constrain(amt, low, high) { function constrain(amt, low, high) {
return ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt))); return ((amt) < (low) ? (low) : ((amt) > (high) ? (high) : (amt)));
}
function c2f(value) {
return (9 / 5) * value + 32;
}
function f2c(value) {
return (value - 32) * (5 / 9);
} }

View File

@@ -23,10 +23,15 @@ Import("env")
platform = env.PioPlatform() platform = env.PioPlatform()
import sys import sys
import os
import subprocess
from os.path import join from os.path import join
sys.path.append(join(platform.get_package_dir("tool-esptoolpy"))) def normalize_paths(cmd):
import esptool for i, arg in enumerate(cmd):
if isinstance(arg, str) and '/' in arg:
cmd[i] = os.path.normpath(arg)
return cmd
def esp32_create_combined_bin(source, target, env): def esp32_create_combined_bin(source, target, env):
print("Generating combined binary for serial flashing") print("Generating combined binary for serial flashing")
@@ -39,26 +44,21 @@ def esp32_create_combined_bin(source, target, env):
sections = env.subst(env.get("FLASH_EXTRA_IMAGES")) sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin") firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU") chip = env.get("BOARD_MCU")
flash_size = env.BoardConfig().get("upload.flash_size") flash_size = env.BoardConfig().get("upload.flash_size", "4MB")
flash_freq = env.BoardConfig().get("build.f_flash", '40m') flash_mode = env["__get_board_flash_mode"](env)
flash_freq = flash_freq.replace('000000L', 'm') flash_freq = env["__get_board_f_flash"](env)
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 = [ cmd = [
"--chip", "--chip",
chip, chip,
"merge_bin", "merge-bin",
"-o", "-o",
new_file_name, new_file_name,
"--flash_mode", "--flash-mode",
flash_mode, flash_mode,
"--flash_freq", "--flash-freq",
flash_freq, flash_freq,
"--flash_size", "--flash-size",
flash_size, flash_size,
] ]
@@ -71,9 +71,12 @@ def esp32_create_combined_bin(source, target, env):
print(f" - {hex(app_offset)} | {firmware_name}") print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name] cmd += [hex(app_offset), firmware_name]
print('Using esptool.py arguments: %s' % ' '.join(cmd)) # print('Using esptool.py arguments: %s' % ' '.join(cmd))
cmdline = [env.subst("$OBJCOPY")] + normalize_paths(cmd)
esptool.main(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}")
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin) env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)