29 Commits

Author SHA1 Message Date
Yurii
769daa0857 chore: bump version to 1.4.2 2024-06-15 03:03:50 +03:00
Yurii
76979531b8 feat: added more info about the build to the portal 2024-06-15 03:03:31 +03:00
Yurii
7779076498 refactor: optimizing work with network 2024-06-14 18:18:43 +03:00
Yurii
45e2e0334e chore: added nodemcu v3 board; changed default gpio for nodemcu 32s 2024-06-14 18:17:12 +03:00
Yurii
b631b496cb chore: bump version to 1.4.1 2024-06-13 17:15:37 +03:00
Yurii
36328a1db5 refactor: NetworkMgr code optimization 2024-06-13 17:09:41 +03:00
Yurii
a28c7f67b5 chore: added min version of espressif8266 for build #68 2024-06-13 17:07:53 +03:00
Yurii
bb9b6a5f4c fix: casting types in setupForm fixed #61 2024-06-11 20:19:54 +03:00
Yurii
ce7bd7e23b feat: migrate to arduino-esp32 core 3.0.1 2024-06-10 16:20:03 +03:00
Yurii
249d32ce35 chore: more info when scan wifi 2024-06-10 14:55:14 +03:00
Yurii
baf8adfb02 fix: validation GPIO and reset wifi for arduino-esp32 core 3.x.x fixed 2024-06-06 16:37:57 +03:00
Yurii
018a1c5188 fix: set ssid on portal fixed 2024-06-06 02:56:19 +03:00
Yurii
b600c130f0 fix: conflicts with sdk 3.x.x for esp32 fixed 2024-06-05 23:11:27 +03:00
Yurii
59eb05726a chore: bump platformio/framework-arduinoespressif32 to 2.0.17 2024-05-26 23:22:19 +03:00
Yurii
f245f37dfd chore: bump version 2024-05-26 17:41:12 +03:00
Yurii
a825412f37 feat: added fault state GPIO setting 2024-05-25 02:51:10 +03:00
Yurii
935f8bd0a8 fix: outdoor sensor GPIO validation fixed 2024-05-25 00:17:55 +03:00
Yurii
f78d2f38b8 fix: equitherm with BLE indoor sensor in emergency mode fixed 2024-04-25 13:38:03 +03:00
Yurii
ef083991e3 feat: added board info on portal 2024-04-23 10:30:42 +03:00
Yurii
3c0f846335 fix: added set target indoor temp to CH2 for native heating control #58 2024-04-23 08:13:03 +03:00
Yurii
85011ce4ea chore: bump version 2024-04-22 08:19:41 +03:00
Yurii
8687e122ca feat: added native heating control by boiler; refactoring; emergency settings removed from HA 2024-04-22 08:18:59 +03:00
Yurii
d35ea81080 fix: PID optimization, correction of default PID settings 2024-04-18 00:04:23 +03:00
Yurii
cca8ec58b4 fix: fix radio on settings page (portal) 2024-04-16 18:58:28 +03:00
Yurii
301b14bbd4 chore: bump version 2024-04-15 15:07:28 +03:00
Yurii
41ce9b268e fix: fix set heating temp on ITALTHERM TIME MAX 30F 2024-04-15 15:00:41 +03:00
Yurii
646939179e fix: serial on s2, s3 fixed 2024-04-15 05:49:46 +03:00
Yurii
73dddd18f0 fix: scan networks on s3 fixed 2024-04-15 02:47:42 +03:00
Yurii
f069de0415 chore: fix typo 2024-04-12 21:47:09 +03:00
28 changed files with 1214 additions and 908 deletions

View File

@@ -31,7 +31,6 @@
- The current temperature of the heat carrier (usually the return heat carrier) - The current temperature of the heat carrier (usually the return heat carrier)
- Set heat carrier temperature (depending on the selected mode) - Set heat carrier temperature (depending on the selected mode)
- Current hot water temperature - Current hot water temperature
- Auto tuning of PID and Equitherm parameters *(in development)*
- [Home Assistant](https://www.home-assistant.io/) integration via MQTT. The ability to create any automation for the boiler! - [Home Assistant](https://www.home-assistant.io/) integration via MQTT. The ability to create any automation for the boiler!
![logo](/assets/ha.png) ![logo](/assets/ha.png)

View File

@@ -126,6 +126,36 @@ public:
return isValidResponse(response); return isValidResponse(response);
} }
bool setRoomSetpoint(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TrSet,
temperatureToData(temperature)
));
return isValidResponse(response);
}
bool setRoomSetpointCh2(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::TrSetCH2,
temperatureToData(temperature)
));
return isValidResponse(response);
}
bool setRoomTemp(float temperature) {
unsigned long response = this->sendRequest(buildRequest(
OpenThermMessageType::WRITE_DATA,
OpenThermMessageID::Tr,
temperatureToData(temperature)
));
return isValidResponse(response);
}
bool sendBoilerReset() { bool sendBoilerReset() {
unsigned int data = 1; unsigned int data = 1;
data <<= 8; data <<= 8;

View File

@@ -1,54 +1,58 @@
#include "NetworkConnection.h" #include "NetworkConnection.h"
using namespace Network; using namespace NetworkUtils;
void Connection::setup(bool useDhcp) { void NetworkConnection::setup(bool useDhcp) {
setUseDhcp(useDhcp); setUseDhcp(useDhcp);
#if defined(ARDUINO_ARCH_ESP8266) #if defined(ARDUINO_ARCH_ESP8266)
wifi_set_event_handler_cb(Connection::onEvent); wifi_set_event_handler_cb(NetworkConnection::onEvent);
#elif defined(ARDUINO_ARCH_ESP32) #elif defined(ARDUINO_ARCH_ESP32)
WiFi.onEvent(Connection::onEvent); WiFi.onEvent(NetworkConnection::onEvent);
#endif #endif
} }
void Connection::reset() { void NetworkConnection::reset() {
status = Status::NONE; status = Status::NONE;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::NONE; disconnectReason = DisconnectReason::NONE;
} }
void Connection::setUseDhcp(bool value) { void NetworkConnection::setUseDhcp(bool value) {
useDhcp = value; useDhcp = value;
} }
Connection::Status Connection::getStatus() { NetworkConnection::Status NetworkConnection::getStatus() {
return status; return status;
} }
Connection::DisconnectReason Connection::getDisconnectReason() { NetworkConnection::DisconnectReason NetworkConnection::getDisconnectReason() {
return disconnectReason; return disconnectReason;
} }
#if defined(ARDUINO_ARCH_ESP8266) #if defined(ARDUINO_ARCH_ESP8266)
void Connection::onEvent(System_Event_t *event) { void NetworkConnection::onEvent(System_Event_t *event) {
switch (event->event) { switch (event->event) {
case EVENT_STAMODE_CONNECTED: case EVENT_STAMODE_CONNECTED:
status = useDhcp ? Status::CONNECTING : Status::CONNECTED; status = useDhcp ? Status::CONNECTING : Status::CONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::NONE; disconnectReason = DisconnectReason::NONE;
break; break;
case EVENT_STAMODE_GOT_IP: case EVENT_STAMODE_GOT_IP:
status = Status::CONNECTED; status = Status::CONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::NONE; disconnectReason = DisconnectReason::NONE;
break; break;
case EVENT_STAMODE_DHCP_TIMEOUT: case EVENT_STAMODE_DHCP_TIMEOUT:
status = Status::DISCONNECTED; status = Status::DISCONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::DHCP_TIMEOUT; disconnectReason = DisconnectReason::DHCP_TIMEOUT;
break; break;
case EVENT_STAMODE_DISCONNECTED: case EVENT_STAMODE_DISCONNECTED:
status = Status::DISCONNECTED; status = Status::DISCONNECTED;
rawDisconnectReason = event->event_info.disconnected.reason;
disconnectReason = convertDisconnectReason(event->event_info.disconnected.reason); disconnectReason = convertDisconnectReason(event->event_info.disconnected.reason);
// https://github.com/esp8266/Arduino/blob/d5eb265f78bff9deb7063d10030a02d021c8c66c/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp#L231 // https://github.com/esp8266/Arduino/blob/d5eb265f78bff9deb7063d10030a02d021c8c66c/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp#L231
@@ -63,6 +67,7 @@ void Connection::onEvent(System_Event_t *event) {
auto& src = event->event_info.auth_change; auto& src = event->event_info.auth_change;
if ((src.old_mode != AUTH_OPEN) && (src.new_mode == AUTH_OPEN)) { if ((src.old_mode != AUTH_OPEN) && (src.new_mode == AUTH_OPEN)) {
status = Status::DISCONNECTED; status = Status::DISCONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::OTHER; disconnectReason = DisconnectReason::OTHER;
wifi_station_disconnect(); wifi_station_disconnect();
@@ -75,29 +80,31 @@ void Connection::onEvent(System_Event_t *event) {
} }
} }
#elif defined(ARDUINO_ARCH_ESP32) #elif defined(ARDUINO_ARCH_ESP32)
void Connection::onEvent(WiFiEvent_t event, WiFiEventInfo_t info) { void NetworkConnection::onEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
switch (event) { switch (event) {
case ARDUINO_EVENT_WIFI_STA_CONNECTED: case ARDUINO_EVENT_WIFI_STA_CONNECTED:
status = useDhcp ? Status::CONNECTING : Status::CONNECTED; status = useDhcp ? Status::CONNECTING : Status::CONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::NONE; disconnectReason = DisconnectReason::NONE;
break; break;
case ARDUINO_EVENT_WIFI_STA_GOT_IP: case ARDUINO_EVENT_WIFI_STA_GOT_IP:
case ARDUINO_EVENT_WIFI_STA_GOT_IP6: case ARDUINO_EVENT_WIFI_STA_GOT_IP6:
status = Status::CONNECTED; status = Status::CONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::NONE; disconnectReason = DisconnectReason::NONE;
break; break;
case ARDUINO_EVENT_WIFI_STA_LOST_IP: case ARDUINO_EVENT_WIFI_STA_LOST_IP:
status = Status::DISCONNECTED; status = Status::DISCONNECTED;
rawDisconnectReason = 0;
disconnectReason = DisconnectReason::DHCP_TIMEOUT; disconnectReason = DisconnectReason::DHCP_TIMEOUT;
break; break;
case ARDUINO_EVENT_WIFI_STA_DISCONNECTED: case ARDUINO_EVENT_WIFI_STA_DISCONNECTED:
status = Status::DISCONNECTED; status = Status::DISCONNECTED;
rawDisconnectReason = info.wifi_sta_disconnected.reason;
disconnectReason = convertDisconnectReason(info.wifi_sta_disconnected.reason); disconnectReason = convertDisconnectReason(info.wifi_sta_disconnected.reason);
break; break;
default: default:
@@ -106,7 +113,7 @@ void Connection::onEvent(WiFiEvent_t event, WiFiEventInfo_t info) {
} }
#endif #endif
Connection::DisconnectReason Connection::convertDisconnectReason(uint8_t reason) { NetworkConnection::DisconnectReason NetworkConnection::convertDisconnectReason(uint8_t reason) {
switch (reason) { switch (reason) {
#if defined(ARDUINO_ARCH_ESP8266) #if defined(ARDUINO_ARCH_ESP8266)
case REASON_BEACON_TIMEOUT: case REASON_BEACON_TIMEOUT:
@@ -145,6 +152,7 @@ Connection::DisconnectReason Connection::convertDisconnectReason(uint8_t reason)
} }
} }
bool Connection::useDhcp = false; bool NetworkConnection::useDhcp = false;
Connection::Status Connection::status = Status::NONE; NetworkConnection::Status NetworkConnection::status = Status::NONE;
Connection::DisconnectReason Connection::disconnectReason = DisconnectReason::NONE; NetworkConnection::DisconnectReason NetworkConnection::disconnectReason = DisconnectReason::NONE;
uint8_t NetworkConnection::rawDisconnectReason = 0;

View File

@@ -5,8 +5,8 @@
#include <WiFi.h> #include <WiFi.h>
#endif #endif
namespace Network { namespace NetworkUtils {
struct Connection { struct NetworkConnection {
enum class Status { enum class Status {
CONNECTED, CONNECTED,
CONNECTING, CONNECTING,
@@ -27,6 +27,7 @@ namespace Network {
static Status status; static Status status;
static DisconnectReason disconnectReason; static DisconnectReason disconnectReason;
static uint8_t rawDisconnectReason;
static void setup(bool useDhcp); static void setup(bool useDhcp);
static void setUseDhcp(bool value); static void setUseDhcp(bool value);

View File

@@ -7,35 +7,35 @@
#endif #endif
#include <NetworkConnection.h> #include <NetworkConnection.h>
namespace Network { namespace NetworkUtils {
class Manager { class NetworkMgr {
public: public:
typedef std::function<void()> YieldCallback; typedef std::function<void()> YieldCallback;
typedef std::function<void(unsigned int)> DelayCallback; typedef std::function<void(unsigned int)> DelayCallback;
Manager() { NetworkMgr() {
Connection::setup(this->useDhcp); NetworkConnection::setup(this->useDhcp);
this->resetWifi(); this->resetWifi();
} }
Manager* setYieldCallback(YieldCallback callback = nullptr) { NetworkMgr* setYieldCallback(YieldCallback callback = nullptr) {
this->yieldCallback = callback; this->yieldCallback = callback;
return this; return this;
} }
Manager* setDelayCallback(DelayCallback callback = nullptr) { NetworkMgr* setDelayCallback(DelayCallback callback = nullptr) {
this->delayCallback = callback; this->delayCallback = callback;
return this; return this;
} }
Manager* setHostname(const char* value) { NetworkMgr* setHostname(const char* value) {
this->hostname = value; this->hostname = value;
return this; return this;
} }
Manager* setApCredentials(const char* ssid, const char* password = nullptr, byte channel = 0) { NetworkMgr* setApCredentials(const char* ssid, const char* password = nullptr, byte 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 Network {
return this; return this;
} }
Manager* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, byte channel = 0) { NetworkMgr* setStaCredentials(const char* ssid = nullptr, const char* password = nullptr, byte channel = 0) {
this->staSsid = ssid; this->staSsid = ssid;
this->staPassword = password; this->staPassword = password;
this->staChannel = channel; this->staChannel = channel;
@@ -51,14 +51,14 @@ namespace Network {
return this; return this;
} }
Manager* setUseDhcp(bool value) { NetworkMgr* setUseDhcp(bool value) {
this->useDhcp = value; this->useDhcp = value;
Connection::setup(this->useDhcp); NetworkConnection::setup(this->useDhcp);
return this; return this;
} }
Manager* setStaticConfig(const char* ip, const char* gateway, const char* subnet, const char* dns) { NetworkMgr* setStaticConfig(const char* ip, const char* gateway, const char* subnet, const char* dns) {
this->staticIp.fromString(ip); this->staticIp.fromString(ip);
this->staticGateway.fromString(gateway); this->staticGateway.fromString(gateway);
this->staticSubnet.fromString(subnet); this->staticSubnet.fromString(subnet);
@@ -67,7 +67,7 @@ namespace Network {
return this; return this;
} }
Manager* setStaticConfig(IPAddress& ip, IPAddress& gateway, IPAddress& subnet, IPAddress& dns) { NetworkMgr* setStaticConfig(IPAddress& ip, IPAddress& gateway, IPAddress& subnet, IPAddress& dns) {
this->staticIp = ip; this->staticIp = ip;
this->staticGateway = gateway; this->staticGateway = gateway;
this->staticSubnet = subnet; this->staticSubnet = subnet;
@@ -81,11 +81,11 @@ namespace Network {
} }
bool isConnected() { bool isConnected() {
return this->isStaEnabled() && Connection::getStatus() == Connection::Status::CONNECTED; return this->isStaEnabled() && NetworkConnection::getStatus() == NetworkConnection::Status::CONNECTED;
} }
bool isConnecting() { bool isConnecting() {
return this->isStaEnabled() && Connection::getStatus() == Connection::Status::CONNECTING; return this->isStaEnabled() && NetworkConnection::getStatus() == NetworkConnection::Status::CONNECTING;
} }
bool isStaEnabled() { bool isStaEnabled() {
@@ -147,16 +147,19 @@ namespace Network {
bool resetWifi() { bool resetWifi() {
// set policy manual for work 13 ch // set policy manual for work 13 ch
{ {
wifi_country_t country = {"CN", 1, 13, WIFI_COUNTRY_POLICY_MANUAL};
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
wifi_country_t country = {"CN", 1, 13, WIFI_COUNTRY_POLICY_AUTO};
wifi_set_country(&country); wifi_set_country(&country);
#elif defined(ARDUINO_ARCH_ESP32) #elif defined(ARDUINO_ARCH_ESP32)
const wifi_country_t country = {"CN", 1, 13, CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER, WIFI_COUNTRY_POLICY_AUTO};
esp_wifi_set_country(&country); esp_wifi_set_country(&country);
#endif #endif
} }
WiFi.persistent(false); WiFi.persistent(false);
#if !defined(ESP_ARDUINO_VERSION_MAJOR) || ESP_ARDUINO_VERSION_MAJOR < 3
WiFi.setAutoConnect(false); WiFi.setAutoConnect(false);
#endif
WiFi.setAutoReconnect(false); WiFi.setAutoReconnect(false);
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
@@ -180,9 +183,9 @@ namespace Network {
wifi_station_dhcpc_set_maxtry(5); wifi_station_dhcpc_set_maxtry(5);
#endif #endif
#ifdef ARDUINO_ARCH_ESP32 #if defined(ARDUINO_ARCH_ESP32) && ESP_ARDUINO_VERSION_MAJOR < 3
// Nothing. Because memory leaks when turn off WiFi on ESP32, bug? // Nothing. Because memory leaks when turn off WiFi on ESP32 SDK < 3.0.0
return true; return true;
#else #else
return WiFi.mode(WIFI_OFF); return WiFi.mode(WIFI_OFF);
@@ -200,6 +203,7 @@ namespace Network {
if (force && !this->isApEnabled()) { if (force && !this->isApEnabled()) {
this->resetWifi(); this->resetWifi();
NetworkConnection::reset();
} else { } else {
/*#ifdef ARDUINO_ARCH_ESP8266 /*#ifdef ARDUINO_ARCH_ESP8266
@@ -208,14 +212,14 @@ namespace Network {
} }
#endif*/ #endif*/
WiFi.disconnect(false, true); this->disconnect();
} }
if (!this->hasStaCredentials()) { if (!this->hasStaCredentials()) {
return false; return false;
} }
this->delayCallback(200); this->delayCallback(250);
#ifdef ARDUINO_ARCH_ESP32 #ifdef ARDUINO_ARCH_ESP32
if (this->setWifiHostname(this->hostname)) { if (this->setWifiHostname(this->hostname)) {
@@ -230,7 +234,7 @@ namespace Network {
return false; return false;
} }
this->delayCallback(200); this->delayCallback(250);
#ifdef ARDUINO_ARCH_ESP8266 #ifdef ARDUINO_ARCH_ESP8266
if (this->setWifiHostname(this->hostname)) { if (this->setWifiHostname(this->hostname)) {
@@ -240,7 +244,7 @@ namespace Network {
Log.serrorln(FPSTR(L_NETWORK), F("Set hostname '%s': fail"), this->hostname); Log.serrorln(FPSTR(L_NETWORK), F("Set hostname '%s': fail"), this->hostname);
} }
this->delayCallback(200); this->delayCallback(250);
#endif #endif
if (!this->useDhcp) { if (!this->useDhcp) {
@@ -253,23 +257,36 @@ namespace Network {
while (millis() - beginConnectionTime < timeout) { while (millis() - beginConnectionTime < timeout) {
this->delayCallback(100); this->delayCallback(100);
Connection::Status status = Connection::getStatus(); NetworkConnection::Status status = NetworkConnection::getStatus();
if (status != Connection::Status::CONNECTING && status != Connection::Status::NONE) { if (status != NetworkConnection::Status::CONNECTING && status != NetworkConnection::Status::NONE) {
return status == Connection::Status::CONNECTED; return status == NetworkConnection::Status::CONNECTED;
} }
} }
return false; return false;
} }
void disconnect() {
WiFi.disconnect(false, true);
}
void loop() { void loop() {
if (this->isConnected() && !this->hasStaCredentials()) { if (this->reconnectFlag) {
this->delayCallback(5000);
Log.sinfoln(FPSTR(L_NETWORK), F("Reconnecting..."));
this->reconnectFlag = false;
this->disconnect();
NetworkConnection::reset();
this->delayCallback(1000);
} else if (this->isConnected() && !this->hasStaCredentials()) {
Log.sinfoln(FPSTR(L_NETWORK), F("Reset")); Log.sinfoln(FPSTR(L_NETWORK), F("Reset"));
this->resetWifi(); this->resetWifi();
Connection::reset(); NetworkConnection::reset();
this->delayCallback(200); this->delayCallback(1000);
} else if (this->isConnected() && !this->reconnectFlag) { } else if (this->isConnected()) {
if (!this->connected) { if (!this->connected) {
this->connectedTime = millis(); this->connectedTime = millis();
this->connected = true; this->connected = true;
@@ -284,7 +301,7 @@ namespace Network {
} }
if (this->isApEnabled() && millis() - this->connectedTime > this->reconnectInterval && !this->hasApClients()) { if (this->isApEnabled() && millis() - this->connectedTime > this->reconnectInterval && !this->hasApClients()) {
Log.sinfoln(FPSTR(L_NETWORK), F("Stop AP because connected, start only STA")); Log.sinfoln(FPSTR(L_NETWORK), F("Stop AP because STA connected"));
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
return; return;
@@ -305,7 +322,7 @@ namespace Network {
Log.sinfoln( Log.sinfoln(
FPSTR(L_NETWORK), FPSTR(L_NETWORK),
F("Disconnected, reason: %d, uptime: %lu s."), F("Disconnected, reason: %d, uptime: %lu s."),
Connection::getDisconnectReason(), NetworkConnection::getDisconnectReason(),
(millis() - this->connectedTime) / 1000 (millis() - this->connectedTime) / 1000
); );
} }
@@ -327,16 +344,15 @@ namespace Network {
} else if (this->isConnecting() && millis() - this->prevReconnectingTime > this->resetConnectionTimeout) { } else if (this->isConnecting() && millis() - this->prevReconnectingTime > this->resetConnectionTimeout) {
Log.swarningln(FPSTR(L_NETWORK), F("Connection timeout, reset wifi...")); Log.swarningln(FPSTR(L_NETWORK), F("Connection timeout, reset wifi..."));
this->resetWifi(); this->resetWifi();
Connection::reset(); NetworkConnection::reset();
this->delayCallback(200); this->delayCallback(250);
} else if (!this->isConnecting() && this->hasStaCredentials() && (!this->prevReconnectingTime || millis() - this->prevReconnectingTime > this->reconnectInterval)) { } else if (!this->isConnecting() && this->hasStaCredentials() && (!this->prevReconnectingTime || millis() - this->prevReconnectingTime > this->reconnectInterval)) {
Log.sinfoln(FPSTR(L_NETWORK), F("Try connect...")); Log.sinfoln(FPSTR(L_NETWORK), F("Try connect..."));
this->reconnectFlag = false; NetworkConnection::reset();
Connection::reset();
if (!this->connect(true, this->connectionTimeout)) { if (!this->connect(true, this->connectionTimeout)) {
Log.straceln(FPSTR(L_NETWORK), F("Connection failed. Status: %d, reason: %d"), Connection::getStatus(), Connection::getDisconnectReason()); Log.straceln(FPSTR(L_NETWORK), F("Connection failed. Status: %d, reason: %d, raw reason: %d"), NetworkConnection::getStatus(), NetworkConnection::getDisconnectReason(), NetworkConnection::rawDisconnectReason);
} }
this->prevReconnectingTime = millis(); this->prevReconnectingTime = millis();

View File

@@ -13,24 +13,29 @@
extra_configs = secrets.default.ini extra_configs = secrets.default.ini
[env] [env]
version = 1.4.2
framework = arduino framework = arduino
lib_deps = lib_deps =
bblanchon/ArduinoJson@^7.0.4 bblanchon/ArduinoJson@^7.0.4
;ihormelnyk/OpenTherm Library@^1.1.5 ;ihormelnyk/OpenTherm Library@^1.1.5
https://github.com/Laxilef/opentherm_library/archive/refs/heads/fix_lambda.zip https://github.com/ihormelnyk/opentherm_library#master
arduino-libraries/ArduinoMqttClient@^0.1.8 arduino-libraries/ArduinoMqttClient@^0.1.8
lennarthennigs/ESP Telnet@^2.2 lennarthennigs/ESP Telnet@^2.2
gyverlibs/FileData@^1.0.2 gyverlibs/FileData@^1.0.2
gyverlibs/GyverPID@^3.3.2 gyverlibs/GyverPID@^3.3.2
gyverlibs/GyverBlinker@^1.0 gyverlibs/GyverBlinker@^1.0
https://github.com/PaulStoffregen/OneWire#master
milesburton/DallasTemperature@^3.11.0 milesburton/DallasTemperature@^3.11.0
laxilef/TinyLogger@^1.1.0 laxilef/TinyLogger@^1.1.0
build_flags = build_flags =
-D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY -D PIO_FRAMEWORK_ARDUINO_LWIP2_LOW_MEMORY
;-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH_LOW_FLASH
-D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305 -D PIO_FRAMEWORK_ARDUINO_ESPRESSIF_SDK305
-mtext-section-literals -mtext-section-literals
-D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1 -D MQTT_CLIENT_STD_FUNCTION_CALLBACK=1
;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial ;-D DEBUG_ESP_CORE -D DEBUG_ESP_WIFI -D DEBUG_ESP_HTTP_SERVER -D DEBUG_ESP_PORT=Serial
-D BUILD_VERSION='"${this.version}"'
-D BUILD_ENV='"$PIOENV"'
-D USE_SERIAL=${secrets.use_serial} -D USE_SERIAL=${secrets.use_serial}
-D USE_TELNET=${secrets.use_telnet} -D USE_TELNET=${secrets.use_telnet}
-D DEBUG_BY_DEFAULT=${secrets.debug} -D DEBUG_BY_DEFAULT=${secrets.debug}
@@ -51,11 +56,10 @@ monitor_speed = 115200
monitor_filters = direct monitor_filters = direct
board_build.flash_mode = dio board_build.flash_mode = dio
board_build.filesystem = littlefs board_build.filesystem = littlefs
version = 1.4.0-rc.22
; Defaults ; Defaults
[esp8266_defaults] [esp8266_defaults]
platform = espressif8266 platform = espressif8266@^4.2.1
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
nrwiersma/ESP8266Scheduler@^1.2 nrwiersma/ESP8266Scheduler@^1.2
@@ -66,7 +70,12 @@ build_flags = ${env.build_flags}
board_build.ldscript = eagle.flash.4m1m.ld board_build.ldscript = eagle.flash.4m1m.ld
[esp32_defaults] [esp32_defaults]
platform = espressif32@^6.6 ;platform = espressif32@^6.7
platform = https://github.com/platformio/platform-espressif32.git
platform_packages =
;platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32/archive/refs/tags/2.0.17.zip
framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.1
framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.1/esp32-arduino-libs-3.0.1.zip
board_build.partitions = esp32_partitions.csv board_build.partitions = esp32_partitions.csv
lib_deps = lib_deps =
${env.lib_deps} ${env.lib_deps}
@@ -129,15 +138,36 @@ build_flags =
-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
[env:nodemcu_8266]
platform = ${esp8266_defaults.platform}
board = nodemcuv2
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
board_build.ldscript = ${esp8266_defaults.board_build.ldscript}
build_flags =
${esp8266_defaults.build_flags}
-D DEFAULT_OT_IN_GPIO=13
-D DEFAULT_OT_OUT_GPIO=15
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12
-D DEFAULT_SENSOR_INDOOR_GPIO=4
-D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=16
[env:s2_mini] [env:s2_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_s2_mini board = lolin_s2_mini
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = ${esp32_defaults.lib_deps} lib_deps = ${esp32_defaults.lib_deps}
lib_ignore = ${esp32_defaults.lib_ignore} lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts} extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-DARDUINO_USB_MODE=1
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D DEFAULT_OT_IN_GPIO=33 -D DEFAULT_OT_IN_GPIO=33
-D DEFAULT_OT_OUT_GPIO=35 -D DEFAULT_OT_OUT_GPIO=35
-D DEFAULT_SENSOR_OUTDOOR_GPIO=9 -D DEFAULT_SENSOR_OUTDOOR_GPIO=9
@@ -147,6 +177,7 @@ build_flags =
[env:s3_mini] [env:s3_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_s3_mini board = lolin_s3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
@@ -154,8 +185,12 @@ lib_deps =
h2zero/NimBLE-Arduino@^1.4.1 h2zero/NimBLE-Arduino@^1.4.1
lib_ignore = ${esp32_defaults.lib_ignore} lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts} extra_scripts = ${esp32_defaults.extra_scripts}
build_unflags =
-DARDUINO_USB_MODE=1
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D ARDUINO_USB_MODE=0
-D ARDUINO_USB_CDC_ON_BOOT=1
-D USE_BLE=1 -D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=35 -D DEFAULT_OT_IN_GPIO=35
-D DEFAULT_OT_OUT_GPIO=36 -D DEFAULT_OT_OUT_GPIO=36
@@ -166,6 +201,7 @@ build_flags =
[env:c3_mini] [env:c3_mini]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = lolin_c3_mini board = lolin_c3_mini
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
@@ -185,8 +221,9 @@ build_flags =
-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
[env:nodemcu_32s] [env:nodemcu_32]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = nodemcu-32s board = nodemcu-32s
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =
@@ -197,16 +234,17 @@ extra_scripts = ${esp32_defaults.extra_scripts}
build_flags = build_flags =
${esp32_defaults.build_flags} ${esp32_defaults.build_flags}
-D USE_BLE=1 -D USE_BLE=1
-D DEFAULT_OT_IN_GPIO=21 -D DEFAULT_OT_IN_GPIO=16
-D DEFAULT_OT_OUT_GPIO=22 -D DEFAULT_OT_OUT_GPIO=4
-D DEFAULT_SENSOR_OUTDOOR_GPIO=12 -D DEFAULT_SENSOR_OUTDOOR_GPIO=15
-D DEFAULT_SENSOR_INDOOR_GPIO=13 -D DEFAULT_SENSOR_INDOOR_GPIO=26
-D DEFAULT_STATUS_LED_GPIO=2 ; 18 -D DEFAULT_STATUS_LED_GPIO=2
-D DEFAULT_OT_RX_LED_GPIO=19 -D DEFAULT_OT_RX_LED_GPIO=19
;-D WOKWI=1 ;-D WOKWI=1
[env:d1_mini32] [env:d1_mini32]
platform = ${esp32_defaults.platform} platform = ${esp32_defaults.platform}
platform_packages = ${esp32_defaults.platform_packages}
board = wemos_d1_mini32 board = wemos_d1_mini32
board_build.partitions = ${esp32_defaults.board_build.partitions} board_build.partitions = ${esp32_defaults.board_build.partitions}
lib_deps = lib_deps =

View File

@@ -6,107 +6,6 @@ public:
static const byte TEMP_SOURCE_HEATING = 0; static const byte TEMP_SOURCE_HEATING = 0;
static const byte TEMP_SOURCE_INDOOR = 1; static const byte TEMP_SOURCE_INDOOR = 1;
bool publishSwitchEmergency(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Use emergency");
doc[FPSTR(HA_ICON)] = F("mdi:sun-snowflake-variant");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_STATE_ON)] = true;
doc[FPSTR(HA_STATE_OFF)] = false;
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.enable }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"enable\" : true}}");
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"enable\" : false}}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency")).c_str(), doc);
}
bool publishNumberEmergencyTarget(UnitSystem unit = UnitSystem::METRIC, bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_target"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_target"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_DEVICE_CLASS)] = F("temperature");
if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_C);
doc[FPSTR(HA_MIN)] = 5;
doc[FPSTR(HA_MAX)] = 50;
} else if (unit == UnitSystem::IMPERIAL) {
doc[FPSTR(HA_UNIT_OF_MEASUREMENT)] = FPSTR(HA_UNIT_OF_MEASUREMENT_F);
doc[FPSTR(HA_MIN)] = 41;
doc[FPSTR(HA_MAX)] = 122;
}
doc[FPSTR(HA_NAME)] = F("Emergency target temp");
doc[FPSTR(HA_ICON)] = F("mdi:thermometer-alert");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.target|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"emergency\": {\"target\" : {{ value }}}}");
doc[FPSTR(HA_STEP)] = 0.5;
doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_NUMBER), F("emergency_target")).c_str(), doc);
}
bool publishSwitchEmergencyUseEquitherm(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.outdoor.type != 1, 'online', 'offline') }}");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_use_equitherm"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_use_equitherm"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Use equitherm in emergency");
doc[FPSTR(HA_ICON)] = F("mdi:snowflake-alert");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_STATE_ON)] = true;
doc[FPSTR(HA_STATE_OFF)] = false;
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.useEquitherm }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"useEquitherm\" : true}}");
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"useEquitherm\" : false}}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency_use_equitherm")).c_str(), doc);
}
bool publishSwitchEmergencyUsePid(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_VALUE_TEMPLATE)] = F("{{ iif(value_json.sensors.indoor.type != 1, 'online', 'offline') }}");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("emergency_use_pid"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("emergency_use_pid"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Use PID in emergency");
doc[FPSTR(HA_ICON)] = F("mdi:snowflake-alert");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_STATE_ON)] = true;
doc[FPSTR(HA_STATE_OFF)] = false;
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.emergency.usePid }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"emergency\": {\"usePid\" : true}}");
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"emergency\": {\"usePid\" : false}}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("emergency_use_pid")).c_str(), doc);
}
bool publishSwitchHeating(bool enabledByDefault = true) { bool publishSwitchHeating(bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status")); doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
@@ -175,7 +74,7 @@ public:
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"target\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = minTemp; doc[FPSTR(HA_MIN)] = minTemp;
doc[FPSTR(HA_MAX)] = maxTemp; doc[FPSTR(HA_MAX)] = maxTemp;
doc[FPSTR(HA_STEP)] = 0.5; doc[FPSTR(HA_STEP)] = 0.5f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -206,7 +105,7 @@ public:
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"heating\": {\"hysteresis\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 5; doc[FPSTR(HA_MAX)] = 5;
doc[FPSTR(HA_STEP)] = 0.1; doc[FPSTR(HA_STEP)] = 0.1f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -234,7 +133,7 @@ public:
doc[FPSTR(HA_NAME)] = F("Heating setpoint"); doc[FPSTR(HA_NAME)] = F("Heating setpoint");
doc[FPSTR(HA_ICON)] = F("mdi:coolant-temperature"); doc[FPSTR(HA_ICON)] = F("mdi:coolant-temperature");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingSetpoint|int(0) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.parameters.heatingSetpoint|float(0)|round(1) }}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -433,7 +332,7 @@ public:
doc[FPSTR(HA_NAME)] = F("DHW target"); doc[FPSTR(HA_NAME)] = F("DHW target");
doc[FPSTR(HA_ICON)] = F("mdi:water-pump"); doc[FPSTR(HA_ICON)] = F("mdi:water-pump");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.target|int(0) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.dhw.target|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}");
doc[FPSTR(HA_MIN)] = minTemp; doc[FPSTR(HA_MIN)] = minTemp;
@@ -605,9 +504,9 @@ public:
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.p_factor|float(0)|round(3) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.p_factor|float(0)|round(3) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"p_factor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"p_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0.1; doc[FPSTR(HA_MIN)] = 0.1f;
doc[FPSTR(HA_MAX)] = 1000; doc[FPSTR(HA_MAX)] = 1000;
doc[FPSTR(HA_STEP)] = 0.1; doc[FPSTR(HA_STEP)] = 0.1f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -623,12 +522,12 @@ public:
doc[FPSTR(HA_NAME)] = F("PID factor I"); doc[FPSTR(HA_NAME)] = F("PID factor I");
doc[FPSTR(HA_ICON)] = F("mdi:alpha-i-circle-outline"); doc[FPSTR(HA_ICON)] = F("mdi:alpha-i-circle-outline");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.i_factor|float(0)|round(3) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.pid.i_factor|float(0)|round(4) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"i_factor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"pid\": {\"i_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0; doc[FPSTR(HA_MIN)] = 0;
doc[FPSTR(HA_MAX)] = 100; doc[FPSTR(HA_MAX)] = 100;
doc[FPSTR(HA_STEP)] = 0.001; doc[FPSTR(HA_STEP)] = 0.001f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -779,9 +678,9 @@ public:
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.equitherm.n_factor|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->getDeviceTopic(F("settings/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("settings/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"n_factor\" : {{ value }}}}");
doc[FPSTR(HA_MIN)] = 0.001; doc[FPSTR(HA_MIN)] = 0.001f;
doc[FPSTR(HA_MAX)] = 10; doc[FPSTR(HA_MAX)] = 10;
doc[FPSTR(HA_STEP)] = 0.001; doc[FPSTR(HA_STEP)] = 0.001f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -802,7 +701,7 @@ public:
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_factor\" : {{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"k_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.01; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -825,7 +724,7 @@ public:
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"equitherm\": {\"t_factor\" : {{ 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.01; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -834,48 +733,6 @@ public:
} }
bool publishSwitchTuning(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("tuning"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("tuning"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Tuning");
doc[FPSTR(HA_ICON)] = F("mdi:tune-vertical");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_STATE_ON)] = true;
doc[FPSTR(HA_STATE_OFF)] = false;
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.tuning.enable }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
doc[FPSTR(HA_PAYLOAD_ON)] = F("{\"tuning\": {\"enable\" : true}}");
doc[FPSTR(HA_PAYLOAD_OFF)] = F("{\"tuning\": {\"enable\" : false}}");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SWITCH), F("tuning")).c_str(), doc);
}
bool publishSelectTuningRegulator(bool enabledByDefault = true) {
JsonDocument doc;
doc[FPSTR(HA_AVAILABILITY)][FPSTR(HA_TOPIC)] = this->getDeviceTopic(F("status"));
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"tuning\": {\"regulator\": {% if value == 'Equitherm' %}0{% elif value == 'PID' %}1{% endif %}}}");
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
doc[FPSTR(HA_UNIQUE_ID)] = this->getObjectId(F("tuning_regulator"));
doc[FPSTR(HA_OBJECT_ID)] = this->getObjectId(F("tuning_regulator"));
doc[FPSTR(HA_ENTITY_CATEGORY)] = F("config");
doc[FPSTR(HA_NAME)] = F("Tuning regulator");
doc[FPSTR(HA_STATE_TOPIC)] = this->getDeviceTopic(F("state"));
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{% if value_json.tuning.regulator == 0 %}Equitherm{% elif value_json.tuning.regulator == 1 %}PID{% endif %}");
doc[FPSTR(HA_OPTIONS)][0] = F("Equitherm");
doc[FPSTR(HA_OPTIONS)][1] = F("PID");
doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit();
return this->publish(this->getTopic(FPSTR(HA_ENTITY_SELECT), F("tuning_regulator")).c_str(), doc);
}
bool publishBinSensorStatus(bool enabledByDefault = true) { bool publishBinSensorStatus(bool enabledByDefault = true) {
JsonDocument doc; JsonDocument doc;
doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault; doc[FPSTR(HA_ENABLED_BY_DEFAULT)] = enabledByDefault;
@@ -1184,7 +1041,7 @@ public:
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.indoor|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"indoor\":{{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"indoor\":{{ value }}}}");
doc[FPSTR(HA_STEP)] = 0.01; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -1243,7 +1100,7 @@ public:
doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}"); doc[FPSTR(HA_VALUE_TEMPLATE)] = F("{{ value_json.temperatures.outdoor|float(0)|round(1) }}");
doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set")); doc[FPSTR(HA_COMMAND_TOPIC)] = this->getDeviceTopic(F("state/set"));
doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}"); doc[FPSTR(HA_COMMAND_TEMPLATE)] = F("{\"temperatures\": {\"outdoor\":{{ value }}}}");
doc[FPSTR(HA_STEP)] = 0.01; doc[FPSTR(HA_STEP)] = 0.01f;
doc[FPSTR(HA_MODE)] = "box"; doc[FPSTR(HA_MODE)] = "box";
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -1452,7 +1309,7 @@ public:
doc[FPSTR(HA_MIN_TEMP)] = minTemp; doc[FPSTR(HA_MIN_TEMP)] = minTemp;
doc[FPSTR(HA_MAX_TEMP)] = maxTemp; doc[FPSTR(HA_MAX_TEMP)] = maxTemp;
doc[FPSTR(HA_TEMP_STEP)] = 0.5; doc[FPSTR(HA_TEMP_STEP)] = 0.5f;
doc[FPSTR(HA_EXPIRE_AFTER)] = 120; doc[FPSTR(HA_EXPIRE_AFTER)] = 120;
doc.shrinkToFit(); doc.shrinkToFit();
@@ -1475,7 +1332,7 @@ public:
doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}"); doc[FPSTR(HA_TEMPERATURE_COMMAND_TEMPLATE)] = F("{\"dhw\": {\"target\" : {{ value|int(0) }}}}");
doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings")); doc[FPSTR(HA_TEMPERATURE_STATE_TOPIC)] = this->getDeviceTopic(F("settings"));
doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|int(0) }}"); doc[FPSTR(HA_TEMPERATURE_STATE_TEMPLATE)] = F("{{ value_json.dhw.target|float(0)|round(1) }}");
if (unit == UnitSystem::METRIC) { if (unit == UnitSystem::METRIC) {
doc[FPSTR(HA_TEMPERATURE_UNIT)] = "C"; doc[FPSTR(HA_TEMPERATURE_UNIT)] = "C";

View File

@@ -1,6 +1,8 @@
#include <Blinker.h> #include <Blinker.h>
extern Network::Manager* network; using namespace NetworkUtils;
extern NetworkMgr* network;
extern MqttTask* tMqtt; extern MqttTask* tMqtt;
extern OpenThermTask* tOt; extern OpenThermTask* tOt;
extern FileData fsSettings, fsNetworkSettings; extern FileData fsSettings, fsNetworkSettings;
@@ -38,17 +40,19 @@ protected:
unsigned long externalPumpStartTime = 0; unsigned long externalPumpStartTime = 0;
bool telnetStarted = false; bool telnetStarted = false;
const char* getTaskName() { #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Main"; return "Main";
} }
/*int getTaskCore() { /*BaseType_t getTaskCore() override {
return 1; return 1;
}*/ }*/
int getTaskPriority() { int getTaskPriority() override {
return 3; return 3;
} }
#endif
void setup() {} void setup() {}

View File

@@ -53,16 +53,25 @@ public:
Log.sinfoln(FPSTR(L_MQTT), F("Enabled")); Log.sinfoln(FPSTR(L_MQTT), F("Enabled"));
} }
bool isConnected() { inline bool isConnected() {
return this->connected; return this->connected;
} }
inline void resetPublishedSettingsTime() {
this->prevPubSettingsTime = 0;
}
inline void resetPublishedVarsTime() {
this->prevPubVarsTime = 0;
}
protected: protected:
MqttWiFiClient* wifiClient = nullptr; MqttWiFiClient* wifiClient = nullptr;
MqttClient* client = nullptr; MqttClient* client = nullptr;
HaHelper* haHelper = nullptr; HaHelper* haHelper = nullptr;
MqttWriter* writer = nullptr; MqttWriter* writer = nullptr;
UnitSystem currentUnitSystem = UnitSystem::METRIC; UnitSystem currentUnitSystem = UnitSystem::METRIC;
bool currentHomeAssistantDiscovery = false;
unsigned short readyForSendTime = 15000; unsigned short readyForSendTime = 15000;
unsigned long lastReconnectTime = 0; unsigned long lastReconnectTime = 0;
unsigned long connectedTime = 0; unsigned long connectedTime = 0;
@@ -72,19 +81,21 @@ protected:
bool connected = false; bool connected = false;
bool newConnection = false; bool newConnection = false;
const char* getTaskName() { #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Mqtt"; return "Mqtt";
} }
/*int getTaskCore() { /*BaseType_t getTaskCore() override {
return 1; return 1;
}*/ }*/
int getTaskPriority() { int getTaskPriority() override {
return 2; return 2;
} }
#endif
bool isReadyForSend() { inline bool isReadyForSend() {
return millis() - this->connectedTime > this->readyForSendTime; return millis() - this->connectedTime > this->readyForSendTime;
} }
@@ -152,7 +163,7 @@ protected:
// ha helper settings // ha helper settings
this->haHelper->setDevicePrefix(settings.mqtt.prefix); this->haHelper->setDevicePrefix(settings.mqtt.prefix);
this->haHelper->setDeviceVersion(PROJECT_VERSION); this->haHelper->setDeviceVersion(BUILD_VERSION);
this->haHelper->setDeviceModel(PROJECT_NAME); this->haHelper->setDeviceModel(PROJECT_NAME);
this->haHelper->setDeviceName(PROJECT_NAME); this->haHelper->setDeviceName(PROJECT_NAME);
this->haHelper->setWriter(this->writer); this->haHelper->setWriter(this->writer);
@@ -227,15 +238,24 @@ protected:
} }
// publish ha entities if not published // publish ha entities if not published
if (this->newConnection || this->currentUnitSystem != settings.system.unitSystem) { if (settings.mqtt.homeAssistantDiscovery) {
this->publishHaEntities(); if (this->newConnection || !this->currentHomeAssistantDiscovery || this->currentUnitSystem != settings.system.unitSystem) {
this->publishNonStaticHaEntities(true); this->publishHaEntities();
this->newConnection = false; this->publishNonStaticHaEntities(true);
this->currentUnitSystem = settings.system.unitSystem; this->currentHomeAssistantDiscovery = true;
this->currentUnitSystem = settings.system.unitSystem;
} else { } else {
// publish non static ha entities // publish non static ha entities
this->publishNonStaticHaEntities(); this->publishNonStaticHaEntities();
}
} else if (this->currentHomeAssistantDiscovery) {
this->currentHomeAssistantDiscovery = false;
}
if (this->newConnection) {
this->newConnection = false;
} }
} }
@@ -291,52 +311,26 @@ protected:
Log.swarningln(FPSTR(L_MQTT_MSG), F("Not valid json")); Log.swarningln(FPSTR(L_MQTT_MSG), F("Not valid json"));
return; return;
} }
doc.shrinkToFit();
if (this->haHelper->getDeviceTopic("state/set").equals(topic)) { if (this->haHelper->getDeviceTopic("state/set").equals(topic)) {
this->writer->publish(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true); this->writer->publish(this->haHelper->getDeviceTopic("state/set").c_str(), nullptr, 0, true);
this->updateVariables(doc);
if (jsonToVars(doc, vars)) {
this->resetPublishedVarsTime();
}
} else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) { } else if (this->haHelper->getDeviceTopic("settings/set").equals(topic)) {
this->writer->publish(this->haHelper->getDeviceTopic("settings/set").c_str(), nullptr, 0, true); this->writer->publish(this->haHelper->getDeviceTopic("settings/set").c_str(), nullptr, 0, true);
this->updateSettings(doc);
if (safeJsonToSettings(doc, settings)) {
this->resetPublishedSettingsTime();
fsSettings.update();
}
} }
} }
bool updateSettings(JsonDocument& doc) {
bool changed = safeJsonToSettings(doc, settings);
doc.clear();
doc.shrinkToFit();
if (changed) {
this->prevPubSettingsTime = 0;
fsSettings.update();
return true;
}
return false;
}
bool updateVariables(JsonDocument& doc) {
bool changed = jsonToVars(doc, vars);
doc.clear();
doc.shrinkToFit();
if (changed) {
this->prevPubVarsTime = 0;
return true;
}
return false;
}
void publishHaEntities() { void publishHaEntities() {
// emergency
this->haHelper->publishSwitchEmergency();
this->haHelper->publishNumberEmergencyTarget(settings.system.unitSystem);
this->haHelper->publishSwitchEmergencyUseEquitherm();
this->haHelper->publishSwitchEmergencyUsePid();
// heating // heating
this->haHelper->publishSwitchHeating(false); this->haHelper->publishSwitchHeating(false);
this->haHelper->publishSwitchHeatingTurbo(); this->haHelper->publishSwitchHeatingTurbo();
@@ -363,10 +357,6 @@ protected:
this->haHelper->publishNumberEquithermFactorK(); this->haHelper->publishNumberEquithermFactorK();
this->haHelper->publishNumberEquithermFactorT(); this->haHelper->publishNumberEquithermFactorT();
// tuning
this->haHelper->publishSwitchTuning();
this->haHelper->publishSelectTuningRegulator();
// states // states
this->haHelper->publishBinSensorStatus(); this->haHelper->publishBinSensorStatus();
this->haHelper->publishBinSensorOtStatus(); this->haHelper->publishBinSensorOtStatus();
@@ -396,26 +386,22 @@ protected:
bool publishNonStaticHaEntities(bool force = false) { bool publishNonStaticHaEntities(bool force = false) {
static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0; static byte _heatingMinTemp, _heatingMaxTemp, _dhwMinTemp, _dhwMaxTemp = 0;
static bool _isStupidMode, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false; static bool _noRegulators, _editableOutdoorTemp, _editableIndoorTemp, _dhwPresent = false;
bool published = false; bool published = false;
bool isStupidMode = !settings.pid.enable && !settings.equitherm.enable; bool noRegulators = !settings.opentherm.nativeHeatingControl && !settings.pid.enable && !settings.equitherm.enable;
byte heatingMinTemp = 0; byte heatingMinTemp = 0;
byte heatingMaxTemp = 0; byte heatingMaxTemp = 0;
bool editableOutdoorTemp = settings.sensors.outdoor.type == SensorType::MANUAL; bool editableOutdoorTemp = settings.sensors.outdoor.type == SensorType::MANUAL;
bool editableIndoorTemp = settings.sensors.indoor.type == SensorType::MANUAL; bool editableIndoorTemp = settings.sensors.indoor.type == SensorType::MANUAL;
if (isStupidMode) { if (noRegulators) {
heatingMinTemp = settings.heating.minTemp; heatingMinTemp = settings.heating.minTemp;
heatingMaxTemp = settings.heating.maxTemp; heatingMaxTemp = settings.heating.maxTemp;
} else if (settings.system.unitSystem == UnitSystem::METRIC) { } else {
heatingMinTemp = 5; heatingMinTemp = convertTemp(THERMOSTAT_INDOOR_MIN_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
heatingMaxTemp = 30; heatingMaxTemp = convertTemp(THERMOSTAT_INDOOR_MAX_TEMP, UnitSystem::METRIC, settings.system.unitSystem);
} else if (settings.system.unitSystem == UnitSystem::IMPERIAL) {
heatingMinTemp = 41;
heatingMaxTemp = 86;
} }
if (force || _dhwPresent != settings.opentherm.dhwPresent) { if (force || _dhwPresent != settings.opentherm.dhwPresent) {
@@ -447,32 +433,17 @@ protected:
published = true; published = true;
} }
if (force || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) { if (force || _noRegulators != noRegulators || _heatingMinTemp != heatingMinTemp || _heatingMaxTemp != heatingMaxTemp) {
if (settings.heating.target < heatingMinTemp || settings.heating.target > heatingMaxTemp) {
settings.heating.target = constrain(settings.heating.target, heatingMinTemp, heatingMaxTemp);
}
_heatingMinTemp = heatingMinTemp; _heatingMinTemp = heatingMinTemp;
_heatingMaxTemp = heatingMaxTemp; _heatingMaxTemp = heatingMaxTemp;
_isStupidMode = isStupidMode; _noRegulators = noRegulators;
this->haHelper->publishNumberHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false); this->haHelper->publishNumberHeatingTarget(settings.system.unitSystem, heatingMinTemp, heatingMaxTemp, false);
this->haHelper->publishClimateHeating( this->haHelper->publishClimateHeating(
settings.system.unitSystem, settings.system.unitSystem,
heatingMinTemp, heatingMinTemp,
heatingMaxTemp, heatingMaxTemp,
isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR noRegulators ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
);
published = true;
} else if (_isStupidMode != isStupidMode) {
_isStupidMode = isStupidMode;
this->haHelper->publishClimateHeating(
settings.system.unitSystem,
heatingMinTemp,
heatingMaxTemp,
isStupidMode ? HaHelper::TEMP_SOURCE_HEATING : HaHelper::TEMP_SOURCE_INDOOR
); );
published = true; published = true;

View File

@@ -30,19 +30,22 @@ protected:
unsigned long dhwSetTempTime = 0; unsigned long dhwSetTempTime = 0;
unsigned long heatingSetTempTime = 0; unsigned long heatingSetTempTime = 0;
byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED; byte configuredRxLedGpio = GPIO_IS_NOT_CONFIGURED;
byte configuredFaultStateGpio = GPIO_IS_NOT_CONFIGURED;
bool faultState = false;
#if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() { const char* getTaskName() override {
return "OpenTherm"; return "OpenTherm";
} }
int getTaskCore() { BaseType_t getTaskCore() override {
return 1; return 1;
} }
int getTaskPriority() { int getTaskPriority() override {
return 5; return 5;
} }
#endif
void setup() { void setup() {
if (settings.system.unitSystem != UnitSystem::METRIC) { if (settings.system.unitSystem != UnitSystem::METRIC) {
@@ -101,7 +104,8 @@ protected:
} }
void loop() { void loop() {
static byte currentHeatingTemp, currentDhwTemp = 0; static float currentHeatingTemp = 0.0f;
static float currentDhwTemp = 0.0f;
if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) { if (this->instanceInGpio != settings.opentherm.inGpio || this->instanceOutGpio != settings.opentherm.outGpio) {
this->setup(); this->setup();
@@ -115,6 +119,7 @@ protected:
return; return;
} }
// RX LED GPIO setup
if (settings.opentherm.rxLedGpio != this->configuredRxLedGpio) { if (settings.opentherm.rxLedGpio != this->configuredRxLedGpio) {
if (this->configuredRxLedGpio != GPIO_IS_NOT_CONFIGURED) { if (this->configuredRxLedGpio != GPIO_IS_NOT_CONFIGURED) {
digitalWrite(this->configuredRxLedGpio, LOW); digitalWrite(this->configuredRxLedGpio, LOW);
@@ -130,6 +135,27 @@ protected:
} }
} }
// Fault state setup
if (settings.opentherm.faultStateGpio != this->configuredFaultStateGpio) {
if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
digitalWrite(this->configuredFaultStateGpio, LOW);
}
if (GPIO_IS_VALID(settings.opentherm.faultStateGpio)) {
this->configuredFaultStateGpio = settings.opentherm.faultStateGpio;
this->faultState = false ^ settings.opentherm.invertFaultState ? HIGH : LOW;
pinMode(this->configuredFaultStateGpio, OUTPUT);
digitalWrite(
this->configuredFaultStateGpio,
this->faultState
);
} else if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
this->configuredFaultStateGpio = GPIO_IS_NOT_CONFIGURED;
}
}
bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && this->isReady(); bool heatingEnabled = (vars.states.emergency || settings.heating.enable) && this->pump && this->isReady();
bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled; bool heatingCh2Enabled = settings.opentherm.heatingCh2Enabled;
if (settings.opentherm.heatingCh1ToCh2) { if (settings.opentherm.heatingCh1ToCh2) {
@@ -143,7 +169,7 @@ protected:
heatingEnabled, heatingEnabled,
settings.opentherm.dhwPresent && settings.dhw.enable, settings.opentherm.dhwPresent && settings.dhw.enable,
false, false,
false, settings.opentherm.nativeHeatingControl,
heatingCh2Enabled, heatingCh2Enabled,
settings.opentherm.summerWinterMode, settings.opentherm.summerWinterMode,
settings.opentherm.dhwBlocking settings.opentherm.dhwBlocking
@@ -173,6 +199,16 @@ protected:
vars.states.fault = false; vars.states.fault = false;
vars.states.diagnostic = false; vars.states.diagnostic = false;
// Force fault state = on
if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
bool fState = true ^ settings.opentherm.invertFaultState ? HIGH : LOW;
if (fState != this->faultState) {
this->faultState = fState;
digitalWrite(this->configuredFaultStateGpio, this->faultState);
}
}
return; return;
} }
@@ -198,6 +234,16 @@ protected:
vars.states.fault = CustomOpenTherm::isFault(response); vars.states.fault = CustomOpenTherm::isFault(response);
vars.states.diagnostic = CustomOpenTherm::isDiagnostic(response); vars.states.diagnostic = CustomOpenTherm::isDiagnostic(response);
// Fault state
if (this->configuredFaultStateGpio != GPIO_IS_NOT_CONFIGURED) {
bool fState = vars.states.fault ^ settings.opentherm.invertFaultState ? HIGH : LOW;
if (fState != this->faultState) {
this->faultState = fState;
digitalWrite(this->configuredFaultStateGpio, this->faultState);
}
}
// These parameters will be updated every minute // These parameters will be updated every minute
if (millis() - this->prevUpdateNonEssentialVars > 60000) { if (millis() - this->prevUpdateNonEssentialVars > 60000) {
if (!heatingEnabled && settings.opentherm.modulationSyncWithHeating) { if (!heatingEnabled && settings.opentherm.modulationSyncWithHeating) {
@@ -285,6 +331,9 @@ protected:
// Get fault code (if necessary) // Get fault code (if necessary)
if (vars.states.fault) { if (vars.states.fault) {
updateFaultCode(); updateFaultCode();
} else if (vars.sensors.faultCode != 0) {
vars.sensors.faultCode = 0;
} }
updatePressure(); updatePressure();
@@ -351,18 +400,13 @@ protected:
// Update DHW temp // Update DHW temp
byte newDhwTemp = settings.dhw.target; if (settings.opentherm.dhwPresent && settings.dhw.enable && (this->needSetDhwTemp() || fabs(settings.dhw.target - currentDhwTemp) > 0.0001f)) {
if (settings.opentherm.dhwPresent && settings.dhw.enable && (this->needSetDhwTemp() || newDhwTemp != currentDhwTemp)) { float convertedTemp = convertTemp(settings.dhw.target, settings.system.unitSystem, settings.opentherm.unitSystem);
if (newDhwTemp < settings.dhw.minTemp || newDhwTemp > settings.dhw.maxTemp) { Log.sinfoln(FPSTR(L_OT_DHW), F("Set temp: %.2f (converted: %.2f)"), settings.dhw.target, convertedTemp);
newDhwTemp = constrain(newDhwTemp, settings.dhw.minTemp, settings.dhw.maxTemp);
}
float convertedTemp = convertTemp(newDhwTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
Log.sinfoln(FPSTR(L_OT_DHW), F("Set temp: %u (converted: %.2f)"), newDhwTemp, convertedTemp);
// Set DHW temp // Set DHW temp
if (this->instance->setDhwTemp(convertedTemp)) { if (this->instance->setDhwTemp(convertedTemp)) {
currentDhwTemp = newDhwTemp; currentDhwTemp = settings.dhw.target;
this->dhwSetTempTime = millis(); this->dhwSetTempTime = millis();
} else { } else {
@@ -372,48 +416,101 @@ protected:
// Set DHW temp to CH2 // Set DHW temp to CH2
if (settings.opentherm.dhwToCh2) { if (settings.opentherm.dhwToCh2) {
if (!this->instance->setHeatingCh2Temp(convertedTemp)) { if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
Log.swarningln(FPSTR(L_OT_DHW), F("Failed set ch2 temp")); Log.swarningln(FPSTR(L_OT_DHW), F("Failed set CH2 temp"));
} }
} }
} }
// Update heating temp // Native heating control
if (heatingEnabled && (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001)) { if (settings.opentherm.nativeHeatingControl) {
float convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem); // Set current indoor temp
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set temp: %u (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp); float indoorTemp = 0.0f;
float convertedTemp = 0.0f;
// Set heating temp if (!vars.states.emergency || settings.sensors.indoor.type != SensorType::MANUAL) {
if (this->instance->setHeatingCh1Temp(convertedTemp) || this->setMaxHeatingTemp(convertedTemp)) { indoorTemp = vars.temperatures.indoor;
currentHeatingTemp = vars.parameters.heatingSetpoint; convertedTemp = convertTemp(indoorTemp, settings.system.unitSystem, settings.opentherm.unitSystem);
this->heatingSetTempTime = millis();
} else {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set temp"));
} }
// Set heating temp to CH2 Log.sinfoln(FPSTR(L_OT_HEATING), F("Set current indoor temp: %.2f (converted: %.2f)"), indoorTemp, convertedTemp);
if (settings.opentherm.heatingCh1ToCh2) { if (!this->instance->setRoomTemp(convertedTemp)) {
if (!this->instance->setHeatingCh2Temp(convertedTemp)) { Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set current indoor temp"));
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set ch2 temp")); }
// Set target indoor temp
if (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001f) {
convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem);
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set target indoor temp: %.2f (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp);
if (this->instance->setRoomSetpoint(convertedTemp)) {
currentHeatingTemp = vars.parameters.heatingSetpoint;
this->heatingSetTempTime = millis();
} else {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set target indoor temp"));
}
// Set target temp to CH2
if (settings.opentherm.heatingCh1ToCh2) {
if (!this->instance->setRoomSetpointCh2(convertedTemp)) {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set target indoor temp to CH2"));
}
} }
} }
}
// force enable pump
// Hysteresis if (!this->pump) {
// Only if enabled PID or/and Equitherm
if (settings.heating.hysteresis > 0 && (!vars.states.emergency || settings.emergency.usePid) && (settings.equitherm.enable || settings.pid.enable)) {
float halfHyst = settings.heating.hysteresis / 2;
if (this->pump && vars.temperatures.indoor - settings.heating.target + 0.0001 >= halfHyst) {
this->pump = false;
} else if (!this->pump && vars.temperatures.indoor - settings.heating.target - 0.0001 <= -(halfHyst)) {
this->pump = true; this->pump = true;
} }
} else if (!this->pump) { } else {
this->pump = true; // Update heating temp
if (heatingEnabled && (this->needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001f)) {
float convertedTemp = convertTemp(vars.parameters.heatingSetpoint, settings.system.unitSystem, settings.opentherm.unitSystem);
Log.sinfoln(FPSTR(L_OT_HEATING), F("Set temp: %.2f (converted: %.2f)"), vars.parameters.heatingSetpoint, convertedTemp);
// Set max heating temp
if (this->setMaxHeatingTemp(convertedTemp)) {
currentHeatingTemp = vars.parameters.heatingSetpoint;
this->heatingSetTempTime = millis();
} else {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set max heating temp"));
}
// Set heating temp
if (this->instance->setHeatingCh1Temp(convertedTemp)) {
currentHeatingTemp = vars.parameters.heatingSetpoint;
this->heatingSetTempTime = millis();
} else {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH1 temp"));
}
// Set heating temp to CH2
if (settings.opentherm.heatingCh1ToCh2) {
if (!this->instance->setHeatingCh2Temp(convertedTemp)) {
Log.swarningln(FPSTR(L_OT_HEATING), F("Failed set CH2 temp"));
}
}
}
// Hysteresis
// Only if enabled PID or/and Equitherm
if (settings.heating.hysteresis > 0 && (!vars.states.emergency || settings.emergency.usePid) && (settings.equitherm.enable || settings.pid.enable)) {
float halfHyst = settings.heating.hysteresis / 2;
if (this->pump && vars.temperatures.indoor - settings.heating.target + 0.0001f >= halfHyst) {
this->pump = false;
} else if (!this->pump && vars.temperatures.indoor - settings.heating.target - 0.0001f <= -(halfHyst)) {
this->pump = true;
}
} else if (!this->pump) {
this->pump = true;
}
} }
} }

View File

@@ -14,7 +14,9 @@ using WebServer = ESP8266WebServer;
#include <UpgradeHandler.h> #include <UpgradeHandler.h>
#include <DNSServer.h> #include <DNSServer.h>
extern Network::Manager* network; using namespace NetworkUtils;
extern NetworkMgr* network;
extern FileData fsSettings, fsNetworkSettings; extern FileData fsSettings, fsNetworkSettings;
extern MqttTask* tMqtt; extern MqttTask* tMqtt;
@@ -53,17 +55,19 @@ protected:
unsigned long webServerChangeState = 0; unsigned long webServerChangeState = 0;
unsigned long dnsServerChangeState = 0; unsigned long dnsServerChangeState = 0;
const char* getTaskName() { #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Portal"; return "Portal";
} }
/*int getTaskCore() { /*BaseType_t getTaskCore() override {
return 1; return 1;
}*/ }*/
int getTaskPriority() { int getTaskPriority() override {
return 1; return 1;
} }
#endif
void setup() { void setup() {
this->dnsServer->setTTL(0); this->dnsServer->setTTL(0);
@@ -78,7 +82,7 @@ protected:
String result; String result;
if (strcmp(var, "ver") == 0) { if (strcmp(var, "ver") == 0) {
result = PROJECT_VERSION; result = BUILD_VERSION;
} }
return result; return result;
@@ -344,7 +348,11 @@ protected:
auto apCount = WiFi.scanComplete(); auto apCount = WiFi.scanComplete();
if (apCount <= 0) { if (apCount <= 0) {
if (apCount != WIFI_SCAN_RUNNING) { if (apCount != WIFI_SCAN_RUNNING) {
#ifdef ARDUINO_ARCH_ESP8266
WiFi.scanNetworks(true, true); WiFi.scanNetworks(true, true);
#else
WiFi.scanNetworks(true, true, true);
#endif
} }
this->webServer->send(404); this->webServer->send(404);
@@ -355,10 +363,16 @@ protected:
for (short int i = 0; i < apCount; i++) { for (short int i = 0; i < apCount; i++) {
String ssid = WiFi.SSID(i); String ssid = WiFi.SSID(i);
doc[i]["ssid"] = ssid; doc[i]["ssid"] = ssid;
doc[i]["signalQuality"] = Network::Manager::rssiToSignalQuality(WiFi.RSSI(i)); doc[i]["bssid"] = WiFi.BSSIDstr(i);
doc[i]["signalQuality"] = NetworkMgr::rssiToSignalQuality(WiFi.RSSI(i));
doc[i]["channel"] = WiFi.channel(i); doc[i]["channel"] = WiFi.channel(i);
doc[i]["hidden"] = !ssid.length(); doc[i]["hidden"] = !ssid.length();
doc[i]["encryptionType"] = WiFi.encryptionType(i); #ifdef ARDUINO_ARCH_ESP8266
const bss_info* info = WiFi.getScanInfoByIndex(i);
doc[i]["auth"] = info->authmode;
#else
doc[i]["auth"] = WiFi.encryptionType(i);
#endif
} }
doc.shrinkToFit(); doc.shrinkToFit();
@@ -423,7 +437,9 @@ protected:
if (changed) { if (changed) {
doc.clear(); doc.clear();
doc.shrinkToFit(); doc.shrinkToFit();
fsSettings.update(); fsSettings.update();
tMqtt->resetPublishedSettingsTime();
} }
}); });
@@ -473,6 +489,13 @@ protected:
doc.shrinkToFit(); doc.shrinkToFit();
this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc); this->bufferedWebServer->send(changed ? 201 : 200, "application/json", doc);
if (changed) {
doc.clear();
doc.shrinkToFit();
tMqtt->resetPublishedVarsTime();
}
}); });
this->webServer->on("/api/info", HTTP_GET, [this]() { this->webServer->on("/api/info", HTTP_GET, [this]() {
@@ -483,15 +506,16 @@ protected:
doc["network"]["mac"] = network->getStaMac(); doc["network"]["mac"] = network->getStaMac();
doc["network"]["connected"] = isConnected; doc["network"]["connected"] = isConnected;
doc["network"]["ssid"] = network->getStaSsid(); doc["network"]["ssid"] = network->getStaSsid();
doc["network"]["signalQuality"] = isConnected ? Network::Manager::rssiToSignalQuality(network->getRssi()) : 0; doc["network"]["signalQuality"] = isConnected ? NetworkMgr::rssiToSignalQuality(network->getRssi()) : 0;
doc["network"]["channel"] = isConnected ? network->getStaChannel() : 0; doc["network"]["channel"] = isConnected ? network->getStaChannel() : 0;
doc["network"]["ip"] = isConnected ? network->getStaIp().toString() : ""; doc["network"]["ip"] = isConnected ? network->getStaIp().toString() : "";
doc["network"]["subnet"] = isConnected ? network->getStaSubnet().toString() : ""; doc["network"]["subnet"] = isConnected ? network->getStaSubnet().toString() : "";
doc["network"]["gateway"] = isConnected ? network->getStaGateway().toString() : ""; doc["network"]["gateway"] = isConnected ? network->getStaGateway().toString() : "";
doc["network"]["dns"] = isConnected ? network->getStaDns().toString() : ""; doc["network"]["dns"] = isConnected ? network->getStaDns().toString() : "";
doc["system"]["version"] = PROJECT_VERSION; doc["system"]["buildVersion"] = BUILD_VERSION;
doc["system"]["buildDate"] = __DATE__ " " __TIME__; doc["system"]["buildDate"] = __DATE__ " " __TIME__;
doc["system"]["buildEnv"] = BUILD_ENV;
doc["system"]["uptime"] = millis() / 1000ul; doc["system"]["uptime"] = millis() / 1000ul;
doc["system"]["totalHeap"] = getTotalHeap(); doc["system"]["totalHeap"] = getTotalHeap();
doc["system"]["freeHeap"] = getFreeHeap(); doc["system"]["freeHeap"] = getFreeHeap();
@@ -499,6 +523,34 @@ protected:
doc["system"]["maxFreeBlockHeap"] = getMaxFreeBlockHeap(); doc["system"]["maxFreeBlockHeap"] = getMaxFreeBlockHeap();
doc["system"]["minMaxFreeBlockHeap"] = getMaxFreeBlockHeap(true); doc["system"]["minMaxFreeBlockHeap"] = getMaxFreeBlockHeap(true);
doc["system"]["resetReason"] = getResetReason(); doc["system"]["resetReason"] = getResetReason();
#ifdef ARDUINO_ARCH_ESP8266
doc["system"]["chipModel"] = esp_is_8285() ? "ESP8285" : "ESP8266";
doc["system"]["chipRevision"] = 0;
doc["system"]["chipCores"] = 1;
doc["system"]["cpuFreq"] = ESP.getCpuFreqMHz();
doc["system"]["coreVersion"] = ESP.getCoreVersion();
doc["system"]["flashSize"] = ESP.getFlashChipSize();
doc["system"]["flashRealSize"] = ESP.getFlashChipRealSize();
#elif ARDUINO_ARCH_ESP32
doc["system"]["chipModel"] = ESP.getChipModel();
doc["system"]["chipRevision"] = ESP.getChipRevision();
doc["system"]["chipCores"] = ESP.getChipCores();
doc["system"]["cpuFreq"] = ESP.getCpuFreqMHz();
doc["system"]["coreVersion"] = ESP.getSdkVersion();
doc["system"]["flashSize"] = ESP.getFlashChipSize();
doc["system"]["flashRealSize"] = doc["system"]["flashSize"];
#else
doc["system"]["chipModel"] = 0;
doc["system"]["chipRevision"] = 0;
doc["system"]["chipCores"] = 0;
doc["system"]["cpuFreq"] = 0;
doc["system"]["coreVersion"] = 0;
doc["system"]["flashSize"] = 0;
doc["system"]["flashRealSize"] = 0;
#endif
doc.shrinkToFit(); doc.shrinkToFit();
this->bufferedWebServer->send(200, "application/json", doc); this->bufferedWebServer->send(200, "application/json", doc);
@@ -572,6 +624,10 @@ protected:
if (this->stateWebServer()) { if (this->stateWebServer()) {
this->webServer->handleClient(); this->webServer->handleClient();
} }
if (!this->stateDnsServer() && !this->stateWebServer()) {
this->delay(250);
}
} }
bool isAuthRequired() { bool isAuthRequired() {

View File

@@ -1,10 +1,8 @@
#include <Equitherm.h> #include <Equitherm.h>
#include <GyverPID.h> #include <GyverPID.h>
#include <PIDtuner.h>
Equitherm etRegulator; Equitherm etRegulator;
GyverPID pidRegulator(0, 0, 0); GyverPID pidRegulator(0, 0, 0);
PIDtuner pidTuner;
class RegulatorTask : public LeanTask { class RegulatorTask : public LeanTask {
@@ -12,27 +10,26 @@ public:
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {} RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
protected: protected:
bool tunerInit = false;
byte tunerState = 0;
byte tunerRegulator = 0;
float prevHeatingTarget = 0; float prevHeatingTarget = 0;
float prevEtResult = 0; float prevEtResult = 0;
float prevPidResult = 0; float prevPidResult = 0;
const char* getTaskName() { #if defined(ARDUINO_ARCH_ESP32)
const char* getTaskName() override {
return "Regulator"; return "Regulator";
} }
/*int getTaskCore() { /*BaseType_t getTaskCore() override {
return 1; return 1;
}*/ }*/
int getTaskPriority() { int getTaskPriority() override {
return 4; return 4;
} }
#endif
void loop() { void loop() {
byte newTemp = vars.parameters.heatingSetpoint; float newTemp = vars.parameters.heatingSetpoint;
if (vars.states.emergency) { if (vars.states.emergency) {
if (settings.heating.turbo) { if (settings.heating.turbo) {
@@ -41,74 +38,60 @@ protected:
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled")); Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
} }
newTemp = getEmergencyModeTemp(); newTemp = this->getEmergencyModeTemp();
} else { } else {
if (vars.tuning.enable || tunerInit) { if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || !settings.heating.enable || (settings.equitherm.enable && settings.pid.enable))) {
if (settings.heating.turbo) { settings.heating.turbo = false;
settings.heating.turbo = false;
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled")); Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
newTemp = getTuningModeTemp();
if (newTemp == 0) {
vars.tuning.enable = false;
}
} }
if (!vars.tuning.enable) { newTemp = this->getNormalModeTemp();
if (settings.heating.turbo && (fabs(settings.heating.target - vars.temperatures.indoor) < 1 || !settings.heating.enable || (settings.equitherm.enable && settings.pid.enable))) {
settings.heating.turbo = false;
Log.sinfoln(FPSTR(L_REGULATOR), F("Turbo mode auto disabled"));
}
newTemp = getNormalModeTemp();
}
} }
// Limits // Limits
if (newTemp < settings.heating.minTemp || newTemp > settings.heating.maxTemp) { newTemp = constrain(
newTemp = constrain(newTemp, settings.heating.minTemp, settings.heating.maxTemp); newTemp,
} !settings.opentherm.nativeHeatingControl ? settings.heating.minTemp : THERMOSTAT_INDOOR_MIN_TEMP,
!settings.opentherm.nativeHeatingControl ? settings.heating.maxTemp : THERMOSTAT_INDOOR_MAX_TEMP
);
if (abs(vars.parameters.heatingSetpoint - newTemp) + 0.0001 >= 1) { if (fabs(vars.parameters.heatingSetpoint - newTemp) > 0.4999f) {
vars.parameters.heatingSetpoint = newTemp; vars.parameters.heatingSetpoint = newTemp;
} }
} }
byte getEmergencyModeTemp() { float getEmergencyModeTemp() {
float newTemp = 0; float newTemp = 0;
// if use equitherm // if use equitherm
if (settings.emergency.useEquitherm && settings.sensors.outdoor.type != SensorType::MANUAL) { if (settings.emergency.useEquitherm) {
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp); float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { if (fabs(prevEtResult - etResult) > 0.4999f) {
prevEtResult = etResult; prevEtResult = etResult;
newTemp += etResult; newTemp += etResult;
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %hhu (%.2f)"), (uint8_t) round(etResult), etResult); Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New emergency result: %.2f"), etResult);
} else { } else {
newTemp += prevEtResult; newTemp += prevEtResult;
} }
} else if(settings.emergency.usePid && settings.sensors.indoor.type != SensorType::MANUAL) { } else if(settings.emergency.usePid) {
if (vars.parameters.heatingEnabled) { if (vars.parameters.heatingEnabled) {
float pidResult = getPidTemp( float pidResult = getPidTemp(
settings.heating.minTemp, settings.heating.minTemp,
settings.heating.maxTemp settings.heating.maxTemp
); );
if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { if (fabs(prevPidResult - pidResult) > 0.4999f) {
prevPidResult = pidResult; prevPidResult = pidResult;
newTemp += pidResult; newTemp += pidResult;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %hhu (%.2f)"), (uint8_t) round(pidResult), pidResult); Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New emergency result: %.2f"), pidResult);
} else { } else {
newTemp += prevPidResult; newTemp += prevPidResult;
@@ -123,17 +106,17 @@ protected:
newTemp = settings.emergency.target; newTemp = settings.emergency.target;
} }
return round(newTemp); return newTemp;
} }
byte getNormalModeTemp() { float getNormalModeTemp() {
float newTemp = 0; float newTemp = 0;
if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001) { if (fabs(prevHeatingTarget - settings.heating.target) > 0.0001f) {
prevHeatingTarget = settings.heating.target; prevHeatingTarget = settings.heating.target;
Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target); Log.sinfoln(FPSTR(L_REGULATOR), F("New target: %.2f"), settings.heating.target);
if (settings.equitherm.enable && settings.pid.enable) { if (/*settings.equitherm.enable && */settings.pid.enable) {
pidRegulator.integral = 0; pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset")); Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
} }
@@ -143,11 +126,11 @@ protected:
if (settings.equitherm.enable) { if (settings.equitherm.enable) {
float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp); float etResult = getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp);
if (fabs(prevEtResult - etResult) + 0.0001 >= 0.5) { if (fabs(prevEtResult - etResult) > 0.4999f) {
prevEtResult = etResult; prevEtResult = etResult;
newTemp += etResult; newTemp += etResult;
Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New result: %hhu (%.2f)"), (uint8_t) round(etResult), etResult); Log.sinfoln(FPSTR(L_REGULATOR_EQUITHERM), F("New result: %.2f"), etResult);
} else { } else {
newTemp += prevEtResult; newTemp += prevEtResult;
@@ -162,11 +145,12 @@ protected:
settings.pid.maxTemp settings.pid.maxTemp
); );
if (fabs(prevPidResult - pidResult) + 0.0001 >= 0.5) { if (fabs(prevPidResult - pidResult) > 0.4999f) {
prevPidResult = pidResult; prevPidResult = pidResult;
newTemp += pidResult; newTemp += pidResult;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New result: %hhd (%.2f)"), (int8_t) round(pidResult), pidResult); Log.sinfoln(FPSTR(L_REGULATOR_PID), F("New result: %.2f"), pidResult);
Log.straceln(FPSTR(L_REGULATOR_PID), F("Integral: %.2f"), pidRegulator.integral);
} else { } else {
newTemp += prevPidResult; newTemp += prevPidResult;
@@ -174,6 +158,10 @@ protected:
} else { } else {
newTemp += prevPidResult; newTemp += prevPidResult;
} }
} else if (fabs(pidRegulator.integral) > 0.0001f) {
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
} }
// default temp, manual mode // default temp, manual mode
@@ -181,97 +169,9 @@ protected:
newTemp = settings.heating.target; newTemp = settings.heating.target;
} }
newTemp = round(newTemp);
return newTemp; return newTemp;
} }
byte getTuningModeTemp() {
if (tunerInit && (!vars.tuning.enable || vars.tuning.regulator != tunerRegulator)) {
if (tunerRegulator == 0) {
pidTuner.reset();
}
tunerInit = false;
tunerRegulator = 0;
tunerState = 0;
Log.sinfoln("REGULATOR.TUNING", F("Stopped"));
}
if (!vars.tuning.enable) {
return 0;
}
if (vars.tuning.regulator == 0) {
// @TODO дописать
Log.sinfoln("REGULATOR.TUNING.EQUITHERM", F("Not implemented"));
return 0;
} else if (vars.tuning.regulator == 1) {
// PID tuner
float defaultTemp = settings.equitherm.enable
? getEquithermTemp(settings.heating.minTemp, settings.heating.maxTemp)
: settings.heating.target;
if (tunerInit && pidTuner.getState() == 3) {
Log.sinfoln("REGULATOR.TUNING.PID", F("Finished"));
for (Stream* stream : Log.getStreams()) {
pidTuner.debugText(stream);
}
pidTuner.reset();
tunerInit = false;
tunerRegulator = 0;
tunerState = 0;
if (pidTuner.getAccuracy() < 90) {
Log.swarningln("REGULATOR.TUNING.PID", F("Bad result, try again..."));
} else {
settings.pid.p_factor = pidTuner.getPID_p();
settings.pid.i_factor = pidTuner.getPID_i();
settings.pid.d_factor = pidTuner.getPID_d();
return 0;
}
}
if (!tunerInit) {
Log.sinfoln("REGULATOR.TUNING.PID", F("Start..."));
float step;
if (vars.temperatures.indoor - vars.temperatures.outdoor > 10) {
step = ceil(vars.parameters.heatingSetpoint / vars.temperatures.indoor * 2);
} else {
step = 5.0f;
}
float startTemp = step;
Log.sinfoln("REGULATOR.TUNING.PID", F("Started. Start value: %f, step: %f"), startTemp, step);
pidTuner.setParameters(NORMAL, startTemp, step, 20 * 60 * 1000, 0.15, 60 * 1000, 10000);
tunerInit = true;
tunerRegulator = 1;
}
pidTuner.setInput(vars.temperatures.indoor);
pidTuner.compute();
if (tunerState > 0 && pidTuner.getState() != tunerState) {
Log.sinfoln("REGULATOR.TUNING.PID", F("Log:"));
for (Stream* stream : Log.getStreams()) {
pidTuner.debugText(stream);
}
tunerState = pidTuner.getState();
}
return round(defaultTemp + pidTuner.getOutput());
} else {
return 0;
}
}
/** /**
* @brief Get the Equitherm Temp * @brief Get the Equitherm Temp
* Calculations in degrees C, conversion occurs when using F * Calculations in degrees C, conversion occurs when using F
@@ -294,8 +194,15 @@ protected:
} }
if (vars.states.emergency) { if (vars.states.emergency) {
etRegulator.Kt = 0; if (settings.sensors.indoor.type == SensorType::MANUAL) {
etRegulator.indoorTemp = 0; etRegulator.Kt = 0;
etRegulator.indoorTemp = 0;
} else {
etRegulator.Kt = settings.equitherm.t_factor;
etRegulator.indoorTemp = indoorTemp;
}
etRegulator.outdoorTemp = outdoorTemp; etRegulator.outdoorTemp = outdoorTemp;
} else if (settings.pid.enable) { } else if (settings.pid.enable) {
@@ -327,9 +234,23 @@ protected:
} }
float getPidTemp(int minTemp, int maxTemp) { float getPidTemp(int minTemp, int maxTemp) {
pidRegulator.Kp = settings.pid.p_factor; if (fabs(pidRegulator.Kp - settings.pid.p_factor) >= 0.0001f) {
pidRegulator.Ki = settings.pid.i_factor; pidRegulator.Kp = settings.pid.p_factor;
pidRegulator.Kd = settings.pid.d_factor; pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
if (fabs(pidRegulator.Ki - settings.pid.i_factor) >= 0.0001f) {
pidRegulator.Ki = settings.pid.i_factor;
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
if (fabs(pidRegulator.Kd - settings.pid.d_factor) >= 0.0001f) {
pidRegulator.Kd = settings.pid.d_factor;
pidRegulator.integral = 0;
Log.sinfoln(FPSTR(L_REGULATOR_PID), F("Integral sum has been reset"));
}
pidRegulator.setLimits(minTemp, maxTemp); pidRegulator.setLimits(minTemp, maxTemp);
pidRegulator.setDt(settings.pid.dt * 1000u); pidRegulator.setDt(settings.pid.dt * 1000u);
@@ -338,32 +259,4 @@ protected:
return pidRegulator.getResultTimer(); return pidRegulator.getResultTimer();
} }
float tuneEquithermN(float ratio, float currentTemp, float setTemp, unsigned int dirtyInterval = 60, unsigned int accurateInterval = 1800, float accurateStep = 0.01, float accurateStepAfter = 1) {
static uint32_t _prevIteration = millis();
if (abs(currentTemp - setTemp) < accurateStepAfter) {
if (millis() - _prevIteration < (accurateInterval * 1000)) {
return ratio;
}
if (currentTemp - setTemp > 0.1f) {
ratio -= accurateStep;
} else if (currentTemp - setTemp < -0.1f) {
ratio += accurateStep;
}
} else {
if (millis() - _prevIteration < (dirtyInterval * 1000)) {
return ratio;
}
ratio = ratio * (setTemp / currentTemp);
}
_prevIteration = millis();
return ratio;
}
}; };

View File

@@ -43,29 +43,36 @@ protected:
float filteredIndoorTemp = 0; float filteredIndoorTemp = 0;
bool emptyIndoorTemp = true; bool emptyIndoorTemp = true;
#if USE_BLE #if defined(ARDUINO_ARCH_ESP32)
#if USE_BLE
BLEClient* pBleClient = nullptr; BLEClient* pBleClient = nullptr;
bool initBleSensor = false; bool initBleSensor = false;
bool initBleNotify = false; bool initBleNotify = false;
#endif #endif
const char* getTaskName() { const char* getTaskName() override {
return "Sensors"; return "Sensors";
} }
/*int getTaskCore() { BaseType_t getTaskCore() override {
return 1; // https://github.com/h2zero/NimBLE-Arduino/issues/676
}*/ #if USE_BLE && defined(CONFIG_BT_NIMBLE_PINNED_TO_CORE)
return CONFIG_BT_NIMBLE_PINNED_TO_CORE;
#else
return tskNO_AFFINITY;
#endif
}
int getTaskPriority() { int getTaskPriority() override {
return 4; return 4;
} }
#endif
void loop() { void loop() {
bool indoorTempUpdated = false; bool indoorTempUpdated = false;
bool outdoorTempUpdated = false; bool outdoorTempUpdated = false;
if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.indoor.gpio)) { if (settings.sensors.outdoor.type == SensorType::DS18B20 && GPIO_IS_VALID(settings.sensors.outdoor.gpio)) {
outdoorTemperatureSensor(); outdoorTemperatureSensor();
outdoorTempUpdated = true; outdoorTempUpdated = true;
} }
@@ -90,7 +97,7 @@ protected:
newTemp += c2f(this->filteredOutdoorTemp); newTemp += c2f(this->filteredOutdoorTemp);
} }
if (fabs(vars.temperatures.outdoor - newTemp) > 0.099) { if (fabs(vars.temperatures.outdoor - newTemp) > 0.099f) {
vars.temperatures.outdoor = newTemp; vars.temperatures.outdoor = newTemp;
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor); Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("New temp: %f"), vars.temperatures.outdoor);
} }
@@ -105,7 +112,7 @@ protected:
newTemp += c2f(this->filteredIndoorTemp); newTemp += c2f(this->filteredIndoorTemp);
} }
if (fabs(vars.temperatures.indoor - newTemp) > 0.099) { if (fabs(vars.temperatures.indoor - newTemp) > 0.099f) {
vars.temperatures.indoor = newTemp; vars.temperatures.indoor = newTemp;
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor); Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("New temp: %f"), vars.temperatures.indoor);
} }
@@ -163,7 +170,7 @@ protected:
return; return;
} }
float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01); float rawTemp = ((pData[0] | (pData[1] << 8)) * 0.01f);
Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp); Log.straceln(FPSTR(L_SENSORS_INDOOR), F("Raw temp: %f"), rawTemp);
if (this->emptyIndoorTemp) { if (this->emptyIndoorTemp) {
@@ -235,7 +242,7 @@ protected:
return; return;
} }
Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on gpio %hhu..."), settings.sensors.outdoor.gpio); Log.sinfoln(FPSTR(L_SENSORS_OUTDOOR), F("Starting on GPIO %hhu..."), settings.sensors.outdoor.gpio);
this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio); this->oneWireOutdoorSensor->begin(settings.sensors.outdoor.gpio);
this->oneWireOutdoorSensor->reset(); this->oneWireOutdoorSensor->reset();
@@ -307,7 +314,7 @@ protected:
return; return;
} }
Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on gpio %hhu..."), settings.sensors.indoor.gpio); Log.sinfoln(FPSTR(L_SENSORS_INDOOR), F("Starting on GPIO %hhu..."), settings.sensors.indoor.gpio);
this->oneWireIndoorSensor->begin(settings.sensors.indoor.gpio); this->oneWireIndoorSensor->begin(settings.sensors.indoor.gpio);
this->oneWireIndoorSensor->reset(); this->oneWireIndoorSensor->reset();

View File

@@ -51,6 +51,8 @@ struct Settings {
byte inGpio = DEFAULT_OT_IN_GPIO; byte inGpio = DEFAULT_OT_IN_GPIO;
byte outGpio = DEFAULT_OT_OUT_GPIO; byte outGpio = DEFAULT_OT_OUT_GPIO;
byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO; byte rxLedGpio = DEFAULT_OT_RX_LED_GPIO;
byte faultStateGpio = DEFAULT_OT_FAULT_STATE_GPIO;
byte invertFaultState = false;
unsigned int memberIdCode = 0; unsigned int memberIdCode = 0;
bool dhwPresent = true; bool dhwPresent = true;
bool summerWinterMode = false; bool summerWinterMode = false;
@@ -60,6 +62,7 @@ struct Settings {
bool dhwBlocking = false; bool dhwBlocking = false;
bool modulationSyncWithHeating = false; bool modulationSyncWithHeating = false;
bool getMinMaxTemp = true; bool getMinMaxTemp = true;
bool nativeHeatingControl = false;
} opentherm; } opentherm;
struct { struct {
@@ -70,11 +73,12 @@ struct Settings {
char password[33] = DEFAULT_MQTT_PASSWORD; char password[33] = DEFAULT_MQTT_PASSWORD;
char prefix[33] = DEFAULT_MQTT_PREFIX; char prefix[33] = DEFAULT_MQTT_PREFIX;
unsigned short interval = 5; unsigned short interval = 5;
bool homeAssistantDiscovery = true;
} mqtt; } mqtt;
struct { struct {
bool enable = true; bool enable = true;
float target = 40.0f; float target = DEFAULT_HEATING_TARGET_TEMP;
unsigned short tresholdTime = 120; unsigned short tresholdTime = 120;
bool useEquitherm = false; bool useEquitherm = false;
bool usePid = false; bool usePid = false;
@@ -85,7 +89,7 @@ struct Settings {
struct { struct {
bool enable = true; bool enable = true;
bool turbo = false; bool turbo = false;
float target = 40.0f; float target = DEFAULT_HEATING_TARGET_TEMP;
float hysteresis = 0.5f; float hysteresis = 0.5f;
byte minTemp = DEFAULT_HEATING_MIN_TEMP; byte minTemp = DEFAULT_HEATING_MIN_TEMP;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP; byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
@@ -94,16 +98,16 @@ struct Settings {
struct { struct {
bool enable = true; bool enable = true;
byte target = 40; float target = DEFAULT_DHW_TARGET_TEMP;
byte minTemp = DEFAULT_DHW_MIN_TEMP; byte minTemp = DEFAULT_DHW_MIN_TEMP;
byte maxTemp = DEFAULT_DHW_MAX_TEMP; byte maxTemp = DEFAULT_DHW_MAX_TEMP;
} dhw; } dhw;
struct { struct {
bool enable = false; bool enable = false;
float p_factor = 50; float p_factor = 2;
float i_factor = 0.006f; float i_factor = 0.0055f;
float d_factor = 10000; float d_factor = 0;
unsigned short dt = 180; unsigned short dt = 180;
byte minTemp = 0; byte minTemp = 0;
byte maxTemp = DEFAULT_HEATING_MAX_TEMP; byte maxTemp = DEFAULT_HEATING_MAX_TEMP;
@@ -143,11 +147,6 @@ struct Settings {
} settings; } settings;
struct Variables { struct Variables {
struct {
bool enable = false;
byte regulator = 0;
} tuning;
struct { struct {
bool otStatus = false; bool otStatus = false;
bool emergency = false; bool emergency = false;
@@ -181,7 +180,7 @@ struct Variables {
bool heatingEnabled = false; bool heatingEnabled = false;
byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP; byte heatingMinTemp = DEFAULT_HEATING_MIN_TEMP;
byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP; byte heatingMaxTemp = DEFAULT_HEATING_MAX_TEMP;
byte heatingSetpoint = 0; float heatingSetpoint = 0;
unsigned long extPumpLastEnableTime = 0; unsigned long extPumpLastEnableTime = 0;
byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP; byte dhwMinTemp = DEFAULT_DHW_MIN_TEMP;
byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP; byte dhwMaxTemp = DEFAULT_DHW_MAX_TEMP;

View File

@@ -1,20 +1,34 @@
#define PROJECT_NAME "OpenTherm Gateway" #define PROJECT_NAME "OpenTherm Gateway"
#define PROJECT_VERSION "1.4.0-rc.22" #define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
#define PROJECT_REPO "https://github.com/Laxilef/OTGateway"
#define MQTT_RECONNECT_INTERVAL 15000 #define MQTT_RECONNECT_INTERVAL 15000
#define EXT_SENSORS_INTERVAL 5000 #define EXT_SENSORS_INTERVAL 5000
#define EXT_SENSORS_FILTER_K 0.15 #define EXT_SENSORS_FILTER_K 0.15
#define CONFIG_URL "http://%s/" #define CONFIG_URL "http://%s/"
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars! #define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
#define GPIO_IS_NOT_CONFIGURED 0xff
#define GPIO_IS_NOT_CONFIGURED 0xff #define DEFAULT_HEATING_TARGET_TEMP 40
#define DEFAULT_HEATING_MIN_TEMP 20 #define DEFAULT_HEATING_MIN_TEMP 20
#define DEFAULT_HEATING_MAX_TEMP 90 #define DEFAULT_HEATING_MAX_TEMP 90
#define DEFAULT_DHW_MIN_TEMP 30
#define DEFAULT_DHW_MAX_TEMP 60 #define DEFAULT_DHW_TARGET_TEMP 40
#define DEFAULT_DHW_MIN_TEMP 30
#define DEFAULT_DHW_MAX_TEMP 60
#define THERMOSTAT_INDOOR_DEFAULT_TEMP 20
#define THERMOSTAT_INDOOR_MIN_TEMP 5
#define THERMOSTAT_INDOOR_MAX_TEMP 30
#ifndef BUILD_VERSION
#define BUILD_VERSION "0.0.0"
#endif
#ifndef BUILD_ENV
#define BUILD_ENV "undefined"
#endif
#ifndef USE_SERIAL #ifndef USE_SERIAL
#define USE_SERIAL true #define USE_SERIAL true
@@ -96,6 +110,10 @@
#define DEFAULT_OT_RX_LED_GPIO GPIO_IS_NOT_CONFIGURED #define DEFAULT_OT_RX_LED_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
#ifndef DEFAULT_OT_FAULT_STATE_GPIO
#define DEFAULT_OT_FAULT_STATE_GPIO GPIO_IS_NOT_CONFIGURED
#endif
#ifndef DEFAULT_SENSOR_OUTDOOR_GPIO #ifndef DEFAULT_SENSOR_OUTDOOR_GPIO
#define DEFAULT_SENSOR_OUTDOOR_GPIO GPIO_IS_NOT_CONFIGURED #define DEFAULT_SENSOR_OUTDOOR_GPIO GPIO_IS_NOT_CONFIGURED
#endif #endif
@@ -112,7 +130,9 @@
#define PROGMEM #define PROGMEM
#endif #endif
#ifndef GPIO_IS_VALID_GPIO #ifdef ARDUINO_ARCH_ESP32
#include <driver/gpio.h>
#elif !defined(GPIO_IS_VALID_GPIO)
#define GPIO_IS_VALID_GPIO(gpioNum) (gpioNum >= 0 && gpioNum <= 16) #define GPIO_IS_VALID_GPIO(gpioNum) (gpioNum >= 0 && gpioNum <= 16)
#endif #endif

View File

@@ -4,11 +4,11 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <FileData.h> #include <FileData.h>
#include <LittleFS.h> #include <LittleFS.h>
#include "ESPTelnetStream.h" #include <ESPTelnetStream.h>
#include <TinyLogger.h> #include <TinyLogger.h>
#include <NetworkManager.h> #include <NetworkMgr.h>
#include "Settings.h" #include "Settings.h"
#include <utils.h> #include "utils.h"
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
#include <ESP32Scheduler.h> #include <ESP32Scheduler.h>
@@ -27,11 +27,13 @@
#include "PortalTask.h" #include "PortalTask.h"
#include "MainTask.h" #include "MainTask.h"
using namespace NetworkUtils;
// Vars // Vars
FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000); FileData fsNetworkSettings(&LittleFS, "/network.conf", 'n', &networkSettings, sizeof(networkSettings), 1000);
FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000); FileData fsSettings(&LittleFS, "/settings.conf", 's', &settings, sizeof(settings), 60000);
ESPTelnetStream* telnetStream = nullptr; ESPTelnetStream* telnetStream = nullptr;
Network::Manager* network = nullptr; NetworkMgr* network = nullptr;
// Tasks // Tasks
MqttTask* tMqtt; MqttTask* tMqtt;
@@ -59,7 +61,7 @@ void setup() {
return tm{sec, min, hour}; return tm{sec, min, hour};
}); });
Serial.begin(settings.system.serial.baudrate); Serial.begin(115200);
Log.addStream(&Serial); Log.addStream(&Serial);
Log.print("\n\n\r"); Log.print("\n\n\r");
@@ -110,8 +112,15 @@ void setup() {
// logs // logs
if (!settings.system.serial.enable) { if (!settings.system.serial.enable) {
Log.clearStreams();
Serial.end(); Serial.end();
Log.clearStreams();
} else if (settings.system.serial.baudrate != 115200) {
Serial.end();
Log.clearStreams();
Serial.begin(settings.system.serial.baudrate);
Log.addStream(&Serial);
} }
if (settings.system.telnet.enable) { if (settings.system.telnet.enable) {
@@ -123,7 +132,7 @@ void setup() {
Log.setLevel(settings.system.debug ? TinyLogger::Level::VERBOSE : TinyLogger::Level::INFO); Log.setLevel(settings.system.debug ? TinyLogger::Level::VERBOSE : TinyLogger::Level::INFO);
// network // network
network = (new Network::Manager) network = (new NetworkMgr)
->setHostname(networkSettings.hostname) ->setHostname(networkSettings.hostname)
->setStaCredentials( ->setStaCredentials(
#ifdef WOKWI #ifdef WOKWI

File diff suppressed because it is too large Load Diff

View File

@@ -38,8 +38,8 @@
<div class="thermostat-temp-target"><span id="thermostat-heating-target"></span> <span class="temp-unit"></span></div> <div class="thermostat-temp-target"><span id="thermostat-heating-target"></span> <span class="temp-unit"></span></div>
<div class="thermostat-temp-current">Current: <span id="thermostat-heating-current"></span> <span class="temp-unit"></span></div> <div class="thermostat-temp-current">Current: <span id="thermostat-heating-current"></span> <span class="temp-unit"></span></div>
</div> </div>
<div class="thermostat-minus"><button id="thermostat-heating-minus" class="outline"><b>-</b></button></div> <div class="thermostat-minus"><button id="thermostat-heating-minus" class="outline"><i class="icons-down"></i></button></div>
<div class="thermostat-plus"><button id="thermostat-heating-plus" class="outline"><b>+</b></button></div> <div class="thermostat-plus"><button id="thermostat-heating-plus" class="outline"><i class="icons-up"></i></button></div>
<div class="thermostat-control"> <div class="thermostat-control">
<input type="checkbox" role="switch" id="thermostat-heating-enabled" value="true"> <input type="checkbox" role="switch" id="thermostat-heating-enabled" value="true">
<label htmlFor="thermostat-heating-enabled">Enable</label> <label htmlFor="thermostat-heating-enabled">Enable</label>
@@ -55,8 +55,8 @@
<div class="thermostat-temp-target"><span id="thermostat-dhw-target"></span> <span class="temp-unit"></span></div> <div class="thermostat-temp-target"><span id="thermostat-dhw-target"></span> <span class="temp-unit"></span></div>
<div class="thermostat-temp-current">Current: <span id="thermostat-dhw-current"></span> <span class="temp-unit"></span></div> <div class="thermostat-temp-current">Current: <span id="thermostat-dhw-current"></span> <span class="temp-unit"></span></div>
</div> </div>
<div class="thermostat-minus"><button class="outline" id="thermostat-dhw-minus"><b>-</b></button></div> <div class="thermostat-minus"><button class="outline" id="thermostat-dhw-minus"><i class="icons-down"></i></button></div>
<div class="thermostat-plus"><button class="outline" id="thermostat-dhw-plus"><b>+</b></button></div> <div class="thermostat-plus"><button class="outline" id="thermostat-dhw-plus"><i class="icons-up"></i></button></div>
<div class="thermostat-control"> <div class="thermostat-control">
<input type="checkbox" role="switch" id="thermostat-dhw-enabled" value="true"> <input type="checkbox" role="switch" id="thermostat-dhw-enabled" value="true">
<label htmlFor="thermostat-dhw-enabled">Enable</label> <label htmlFor="thermostat-dhw-enabled">Enable</label>
@@ -250,7 +250,7 @@
return; return;
} }
newSettings.dhw.target -= 1; newSettings.dhw.target -= 1.0;
modifiedTime = Date.now(); modifiedTime = Date.now();
if (newSettings.dhw.target < prevSettings.dhw.minTemp) { if (newSettings.dhw.target < prevSettings.dhw.minTemp) {
@@ -265,7 +265,7 @@
return; return;
} }
newSettings.dhw.target += 1; newSettings.dhw.target += 1.0;
modifiedTime = Date.now(); modifiedTime = Date.now();
if (newSettings.dhw.target > prevSettings.dhw.maxTemp) { if (newSettings.dhw.target > prevSettings.dhw.maxTemp) {
@@ -287,7 +287,7 @@
document.querySelector('#thermostat-dhw-enabled').addEventListener('change', (event) => { document.querySelector('#thermostat-dhw-enabled').addEventListener('change', (event) => {
modifiedTime = Date.now(); modifiedTime = Date.now();
newSettings.heating.dhw = event.currentTarget.checked; newSettings.dhw.enable = event.currentTarget.checked;
}); });
setTimeout(async function onLoadPage() { setTimeout(async function onLoadPage() {
@@ -326,7 +326,7 @@
} }
const result = await response.json(); const result = await response.json();
noRegulators = !result.equitherm.enable && !result.pid.enable; noRegulators = !result.opentherm.nativeHeatingControl && !result.equitherm.enable && !result.pid.enable;
prevSettings = result; prevSettings = result;
newSettings.heating.enable = result.heating.enable; newSettings.heating.enable = result.heating.enable;
newSettings.heating.turbo = result.heating.turbo; newSettings.heating.turbo = result.heating.turbo;

View File

@@ -87,11 +87,11 @@
<tbody> <tbody>
<tr> <tr>
<th scope="row">Version:</th> <th scope="row">Version:</th>
<td><b id="version"></b></td> <td><b id="build-version"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Build date:</th> <th scope="row">Build:</th>
<td><b id="build-date"></b></td> <td>Env: <b id="build-env"></b><br />Date: <b id="build-date"></b><br />Core/SDK: <b id="core-version"></b></td>
</tr> </tr>
<tr> <tr>
<th scope="row">Uptime:</th> <th scope="row">Uptime:</th>
@@ -101,6 +101,10 @@
<th scope="row">Free memory:</th> <th scope="row">Free memory:</th>
<td><b id="free-heap"></b> of <b id="total-heap"></b> bytes (min: <b id="min-free-heap"></b> bytes)<br />max free block: <b id="max-free-block-heap"></b> bytes (min: <b id="min-max-free-block-heap"></b> bytes)</td> <td><b id="free-heap"></b> of <b id="total-heap"></b> bytes (min: <b id="min-free-heap"></b> bytes)<br />max free block: <b id="max-free-block-heap"></b> bytes (min: <b id="min-max-free-block-heap"></b> bytes)</td>
</tr> </tr>
<tr>
<th scope="row">Board:</th>
<td>Chip <b id="chip-model"></b> (rev. <span id="chip-revision"></span>)<br />Cores: <b id="chip-cores"></b>, frequency: <b id="cpu-freq"></b> mHz<br />Flash size: <b id="flash-size"></b> MB (real: <b id="flash-real-size"></b> MB)</td>
</tr>
<tr> <tr>
<th scope="row">Last reset reason:</th> <th scope="row">Last reset reason:</th>
<td><b id="reset-reason"></b></td> <td><b id="reset-reason"></b></td>
@@ -151,8 +155,9 @@
setValue('#network-dns', result.network.dns); setValue('#network-dns', result.network.dns);
setBusy('#main-busy', '#main-table', false); setBusy('#main-busy', '#main-table', false);
setValue('#version', result.system.version); setValue('#build-version', result.system.buildVersion);
setValue('#build-date', result.system.buildDate); setValue('#build-date', result.system.buildDate);
setValue('#build-env', result.system.buildEnv);
setValue('#uptime', result.system.uptime); setValue('#uptime', result.system.uptime);
setValue('#uptime-days', Math.floor(result.system.uptime / 86400)); setValue('#uptime-days', Math.floor(result.system.uptime / 86400));
setValue('#uptime-hours', Math.floor(result.system.uptime % 86400 / 3600)); setValue('#uptime-hours', Math.floor(result.system.uptime % 86400 / 3600));
@@ -164,6 +169,15 @@
setValue('#max-free-block-heap', result.system.maxFreeBlockHeap); setValue('#max-free-block-heap', result.system.maxFreeBlockHeap);
setValue('#min-max-free-block-heap', result.system.minMaxFreeBlockHeap); setValue('#min-max-free-block-heap', result.system.minMaxFreeBlockHeap);
setValue('#reset-reason', result.system.resetReason); setValue('#reset-reason', result.system.resetReason);
setValue('#chip-model', result.system.chipModel);
setValue('#chip-revision', result.system.chipRevision);
setValue('#chip-cores', result.system.chipCores);
setValue('#cpu-freq', result.system.cpuFreq);
setValue('#core-version', result.system.coreVersion);
setValue('#flash-size', result.system.flashSize / 1024 / 1024);
setValue('#flash-real-size', result.system.flashRealSize / 1024 / 1024);
setBusy('#system-busy', '#system-table', false); setBusy('#system-busy', '#system-table', false);
} catch (error) { } catch (error) {

View File

@@ -76,18 +76,18 @@
</hgroup> </hgroup>
<form action="/api/network/scan" id="network-scan"> <form action="/api/network/scan" id="network-scan">
<figure style="max-height: 25em;"> <div style="max-height: 25rem;" class="overflow-auto">
<table id="networks" role="grid"> <table id="networks" role="grid">
<thead> <thead>
<tr> <tr>
<th scope="col">#</th> <th scope="col">#</th>
<th scope="col">SSID</th> <th scope="col">SSID</th>
<th scope="col">Signal</th> <th scope="col">Info</th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>
</table> </table>
</figure> </div>
<button type="submit">Refresh</button> <button type="submit">Refresh</button>
</form> </form>
@@ -167,6 +167,26 @@
<script src="/static/app.js"></script> <script src="/static/app.js"></script>
<script> <script>
window.onload = async function () { window.onload = async function () {
const fillData = (data) => {
setInputValue('#network-hostname', data.hostname);
setCheckboxValue('#network-use-dhcp', data.useDhcp);
setInputValue('#network-static-ip', data.staticConfig.ip);
setInputValue('#network-static-gateway', data.staticConfig.gateway);
setInputValue('#network-static-subnet', data.staticConfig.subnet);
setInputValue('#network-static-dns', data.staticConfig.dns);
setBusy('#network-settings-busy', '#network-settings', false);
setInputValue('#sta-ssid', data.sta.ssid);
setInputValue('#sta-password', data.sta.password);
setInputValue('#sta-channel', data.sta.channel);
setBusy('#sta-settings-busy', '#sta-settings', false);
setInputValue('#ap-ssid', data.ap.ssid);
setInputValue('#ap-password', data.ap.password);
setInputValue('#ap-channel', data.ap.channel);
setBusy('#ap-settings-busy', '#ap-settings', false);
};
try { try {
const response = await fetch('/api/network/settings', { cache: 'no-cache' }); const response = await fetch('/api/network/settings', { cache: 'no-cache' });
if (!response.ok) { if (!response.ok) {
@@ -174,32 +194,16 @@
} }
const result = await response.json(); const result = await response.json();
setInputValue('#network-hostname', result.hostname); fillData(result);
setCheckboxValue('#network-use-dhcp', result.useDhcp);
setInputValue('#network-static-ip', result.staticConfig.ip);
setInputValue('#network-static-gateway', result.staticConfig.gateway);
setInputValue('#network-static-subnet', result.staticConfig.subnet);
setInputValue('#network-static-dns', result.staticConfig.dns);
setBusy('#network-settings-busy', '#network-settings', false);
setInputValue('#sta-ssid', result.sta.ssid); setupForm('#network-settings', fillData, ['hostname']);
setInputValue('#sta-password', result.sta.password); setupNetworkScanForm('#network-scan', '#networks');
setInputValue('#sta-channel', result.sta.channel); setupForm('#sta-settings', fillData, ['sta.ssid', 'sta.password']);
setBusy('#sta-settings-busy', '#sta-settings', false); setupForm('#ap-settings', fillData, ['ap.ssid', 'ap.password']);
setInputValue('#ap-ssid', result.ap.ssid);
setInputValue('#ap-password', result.ap.password);
setInputValue('#ap-channel', result.ap.channel);
setBusy('#ap-settings-busy', '#ap-settings', false);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
setupForm('#network-settings');
setupNetworkScanForm('#network-scan', '#networks');
setupForm('#sta-settings');
setupForm('#ap-settings');
}; };
</script> </script>
</body> </body>

View File

@@ -67,12 +67,12 @@
<legend>Unit system</legend> <legend>Unit system</legend>
<label> <label>
<input type="radio" id="system-unit-system" name="system[unitSystem]" value="0" /> <input type="radio" class="system-unit-system" name="system[unitSystem]" value="0" />
Metric (celsius, liters, bar) Metric (celsius, liters, bar)
</label> </label>
<label> <label>
<input type="radio" id="system-unit-system" name="system[unitSystem]" value="1" /> <input type="radio" class="system-unit-system" name="system[unitSystem]" value="1" />
Imperial (fahrenheit, gallons, psi) Imperial (fahrenheit, gallons, psi)
</label> </label>
</fieldset> </fieldset>
@@ -116,9 +116,7 @@
<small>Default: 23</small> <small>Default: 23</small>
</label> </label>
</div> </div>
</fieldset>
<fieldset>
<mark>After changing this settings, the ESP must be restarted for the changes to take effect.</mark> <mark>After changing this settings, the ESP must be restarted for the changes to take effect.</mark>
</fieldset> </fieldset>
@@ -317,7 +315,7 @@
<label for="pid-i-factor"> <label for="pid-i-factor">
I factor I factor
<input type="number" inputmode="numeric" id="pid-i-factor" name="pid[i_factor]" min="0" max="100" step="0.001" required> <input type="number" inputmode="numeric" id="pid-i-factor" name="pid[i_factor]" min="0" max="100" step="0.0001" required>
</label> </label>
<label for="pid-d-factor"> <label for="pid-d-factor">
@@ -361,12 +359,12 @@
<legend>Unit system</legend> <legend>Unit system</legend>
<label> <label>
<input type="radio" id="opentherm-unit-system" name="opentherm[unitSystem]" value="0" /> <input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="0" />
Metric (celsius) Metric (celsius)
</label> </label>
<label> <label>
<input type="radio" id="opentherm-unit-system" name="opentherm[unitSystem]" value="1" /> <input type="radio" class="opentherm-unit-system" name="opentherm[unitSystem]" value="1" />
Imperial (fahrenheit) Imperial (fahrenheit)
</label> </label>
</fieldset> </fieldset>
@@ -437,6 +435,27 @@
<input type="checkbox" id="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true"> <input type="checkbox" id="opentherm-get-min-max-temp" name="opentherm[getMinMaxTemp]" value="true">
Get min/max temp from boiler Get min/max temp from boiler
</label> </label>
<hr />
<fieldset>
<label for="opentherm-fault-state-gpio">
Fault state GPIO
<input type="number" inputmode="numeric" id="opentherm-fault-state-gpio" name="opentherm[faultStateGpio]" min="0" max="254" step="1">
<small>Can be useful to switch on another boiler <u>via relay</u>. Blank - not use.</small>
</label>
<label for="opentherm-invert-fault-state">
<input type="checkbox" id="opentherm-invert-fault-state" name="opentherm[invertFaultState]" value="true">
Invert fault state
</label>
</fieldset>
<hr />
<label for="opentherm-native-heating-control">
<input type="checkbox" id="opentherm-native-heating-control" name="opentherm[nativeHeatingControl]" value="true">
Native heating control (boiler)<br />
<small>Works <u>ONLY</u> if the boiler requires the desired room temperature and regulates the temperature of the coolant itself. Not compatible with PID and Equitherm regulators and hysteresis in firmware.</small>
</label>
</fieldset> </fieldset>
<button type="submit">Save</button> <button type="submit">Save</button>
@@ -456,6 +475,11 @@
<input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true"> <input type="checkbox" id="mqtt-enable" name="mqtt[enable]" value="true">
Enable Enable
</label> </label>
<label for="mqtt-ha-discovery">
<input type="checkbox" id="mqtt-ha-discovery" name="mqtt[homeAssistantDiscovery]" value="true">
Home Assistant Discovery
</label>
</fieldset> </fieldset>
<div class="grid"> <div class="grid">
@@ -510,17 +534,17 @@
<legend>Source type</legend> <legend>Source type</legend>
<label> <label>
<input type="radio" id="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" /> <input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="0" />
From boiler via OpenTherm From boiler via OpenTherm
</label> </label>
<label> <label>
<input type="radio" id="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" /> <input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="1" />
Manual via MQTT/API Manual via MQTT/API
</label> </label>
<label> <label>
<input type="radio" id="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" /> <input type="radio" class="outdoor-sensor-type" name="sensors[outdoor][type]" value="2" />
External (DS18B20) External (DS18B20)
</label> </label>
</fieldset> </fieldset>
@@ -551,17 +575,17 @@
<legend>Source type</legend> <legend>Source type</legend>
<label> <label>
<input type="radio" id="indoor-sensor-type" name="sensors[indoor][type]" value="1" /> <input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="1" />
Manual via MQTT/API Manual via MQTT/API
</label> </label>
<label> <label>
<input type="radio" id="indoor-sensor-type" name="sensors[indoor][type]" value="2" /> <input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="2" />
External (DS18B20) External (DS18B20)
</label> </label>
<label> <label>
<input type="radio" id="indoor-sensor-type" name="sensors[indoor][type]" value="3" /> <input type="radio" class="indoor-sensor-type" name="sensors[indoor][type]" value="3" />
BLE device <i>(ONLY for some ESP32 which support BLE)</i> BLE device <i>(ONLY for some ESP32 which support BLE)</i>
</label> </label>
</fieldset> </fieldset>
@@ -655,7 +679,7 @@
setInputValue('#system-serial-baudrate', data.system.serial.baudrate); setInputValue('#system-serial-baudrate', data.system.serial.baudrate);
setCheckboxValue('#system-telnet-enable', data.system.telnet.enable); setCheckboxValue('#system-telnet-enable', data.system.telnet.enable);
setInputValue('#system-telnet-port', data.system.telnet.port); setInputValue('#system-telnet-port', data.system.telnet.port);
setRadioValue('#system-unit-system', data.system.unitSystem); setRadioValue('.system-unit-system', data.system.unitSystem);
setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : ''); setInputValue('#system-status-led-gpio', data.system.statusLedGpio < 255 ? data.system.statusLedGpio : '');
setBusy('#system-settings-busy', '#system-settings', false); setBusy('#system-settings-busy', '#system-settings', false);
@@ -666,10 +690,12 @@
setBusy('#portal-settings-busy', '#portal-settings', false); setBusy('#portal-settings-busy', '#portal-settings', false);
// Opentherm // Opentherm
setRadioValue('#opentherm-unit-system', data.opentherm.unitSystem); setRadioValue('.opentherm-unit-system', data.opentherm.unitSystem);
setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : ''); setInputValue('#opentherm-in-gpio', data.opentherm.inGpio < 255 ? data.opentherm.inGpio : '');
setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : ''); setInputValue('#opentherm-out-gpio', data.opentherm.outGpio < 255 ? data.opentherm.outGpio : '');
setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : ''); setInputValue('#opentherm-rx-led-gpio', data.opentherm.rxLedGpio < 255 ? data.opentherm.rxLedGpio : '');
setInputValue('#opentherm-fault-state-gpio', data.opentherm.faultStateGpio < 255 ? data.opentherm.faultStateGpio : '');
setCheckboxValue('#opentherm-invert-fault-state', data.opentherm.invertFaultState);
setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode); setInputValue('#opentherm-member-id-code', data.opentherm.memberIdCode);
setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent); setCheckboxValue('#opentherm-dhw-present', data.opentherm.dhwPresent);
setCheckboxValue('#opentherm-sw-mode', data.opentherm.summerWinterMode); setCheckboxValue('#opentherm-sw-mode', data.opentherm.summerWinterMode);
@@ -679,10 +705,12 @@
setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking); setCheckboxValue('#opentherm-dhw-blocking', data.opentherm.dhwBlocking);
setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating); setCheckboxValue('#opentherm-sync-modulation-with-heating', data.opentherm.modulationSyncWithHeating);
setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp); setCheckboxValue('#opentherm-get-min-max-temp', data.opentherm.getMinMaxTemp);
setCheckboxValue('#opentherm-native-heating-control', data.opentherm.nativeHeatingControl);
setBusy('#opentherm-settings-busy', '#opentherm-settings', false); setBusy('#opentherm-settings-busy', '#opentherm-settings', false);
// MQTT // MQTT
setCheckboxValue('#mqtt-enable', data.mqtt.enable); setCheckboxValue('#mqtt-enable', data.mqtt.enable);
setCheckboxValue('#mqtt-ha-discovery', data.mqtt.homeAssistantDiscovery);
setInputValue('#mqtt-server', data.mqtt.server); setInputValue('#mqtt-server', data.mqtt.server);
setInputValue('#mqtt-port', data.mqtt.port); setInputValue('#mqtt-port', data.mqtt.port);
setInputValue('#mqtt-user', data.mqtt.user); setInputValue('#mqtt-user', data.mqtt.user);
@@ -692,13 +720,13 @@
setBusy('#mqtt-settings-busy', '#mqtt-settings', false); setBusy('#mqtt-settings-busy', '#mqtt-settings', false);
// Outdoor sensor // Outdoor sensor
setRadioValue('#outdoor-sensor-type', data.sensors.outdoor.type); setRadioValue('.outdoor-sensor-type', data.sensors.outdoor.type);
setInputValue('#outdoor-sensor-gpio', data.sensors.outdoor.gpio < 255 ? data.sensors.outdoor.gpio : ''); setInputValue('#outdoor-sensor-gpio', data.sensors.outdoor.gpio < 255 ? data.sensors.outdoor.gpio : '');
setInputValue('#outdoor-sensor-offset', data.sensors.outdoor.offset); setInputValue('#outdoor-sensor-offset', data.sensors.outdoor.offset);
setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false); setBusy('#outdoor-sensor-settings-busy', '#outdoor-sensor-settings', false);
// Indoor sensor // Indoor sensor
setRadioValue('#indoor-sensor-type', data.sensors.indoor.type); setRadioValue('.indoor-sensor-type', data.sensors.indoor.type);
setInputValue('#indoor-sensor-gpio', data.sensors.indoor.gpio < 255 ? data.sensors.indoor.gpio : ''); setInputValue('#indoor-sensor-gpio', data.sensors.indoor.gpio < 255 ? data.sensors.indoor.gpio : '');
setInputValue('#indoor-sensor-offset', data.sensors.indoor.offset); setInputValue('#indoor-sensor-offset', data.sensors.indoor.offset);
setInputValue('#indoor-sensor-ble-addresss', data.sensors.indoor.bleAddresss); setInputValue('#indoor-sensor-ble-addresss', data.sensors.indoor.bleAddresss);
@@ -781,7 +809,8 @@
const result = await response.json(); const result = await response.json();
fillData(result); fillData(result);
setupForm('#portal-settings', fillData);
setupForm('#portal-settings', fillData, ['portal.login', 'portal.password']);
setupForm('#system-settings', fillData); setupForm('#system-settings', fillData);
setupForm('#heating-settings', fillData); setupForm('#heating-settings', fillData);
setupForm('#dhw-settings', fillData); setupForm('#dhw-settings', fillData);
@@ -789,9 +818,9 @@
setupForm('#equitherm-settings', fillData); setupForm('#equitherm-settings', fillData);
setupForm('#pid-settings', fillData); setupForm('#pid-settings', fillData);
setupForm('#opentherm-settings', fillData); setupForm('#opentherm-settings', fillData);
setupForm('#mqtt-settings', fillData); setupForm('#mqtt-settings', fillData, ['mqtt.user', 'mqtt.password', 'mqtt.prefix']);
setupForm('#outdoor-sensor-settings', fillData); setupForm('#outdoor-sensor-settings', fillData);
setupForm('#indoor-sensor-settings', fillData); setupForm('#indoor-sensor-settings', fillData, ['sensors.indoor.bleAddresss']);
setupForm('#extpump-settings', fillData); setupForm('#extpump-settings', fillData);
} catch (error) { } catch (error) {

View File

@@ -86,18 +86,6 @@ tr.network:hover {
cursor: pointer; cursor: pointer;
} }
.greatSignal {
background-color: var(--pico-form-element-valid-border-color);
}
.normalSignal {
background-color: #e48500;
}
.badSignal {
background-color: var(--pico-form-element-invalid-border-color);
}
.primary { .primary {
border: 0.25rem solid var(--pico-form-element-invalid-border-color); border: 0.25rem solid var(--pico-form-element-invalid-border-color);
padding: 1rem; padding: 1rem;
@@ -194,3 +182,13 @@ tr.network:hover {
margin: 1.25rem 0; margin: 1.25rem 0;
} }
[class*=" icons-"],[class=icons],[class^=icons-] {font-size: 1.35rem; }
*:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]):has(+ * > [class*=" icons-"], + * > [class=icons], + * > [class^=icons-]) { margin: 0 0.5rem 0 0; }
[data-tooltip]:has(> [class*=" icons-"], > [class=icons], > [class^=icons-]) { border: 0!important; }
/*!
* Icons icon font. Generated by Iconly: https://iconly.io/
*/
@font-face{font-display:auto;font-family:"Icons";font-style:normal;font-weight:400;src:url(./fonts/iconly.eot?1717885802370);src:url(./fonts/iconly.eot?#iefix) format("embedded-opentype"),url(./fonts/iconly.woff2?1717885802370) format("woff2"),url(./fonts/iconly.woff?1717885802370) format("woff"),url(./fonts/iconly.ttf?1717885802370) format("truetype")}[class*=" icons-"],[class=icons],[class^=icons-]{display:inline-block;font-family:"Icons"!important;font-weight:400;font-style:normal;font-variant:normal;text-rendering:auto;line-height:1;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}.icons-plus:before{content:"\e000"}.icons-minus:before{content:"\e001"}.icons-unlocked:before{content:"\e002"}.icons-locked:before{content:"\e003"}.icons-wifi-strength-1:before{content:"\e004"}.icons-wifi-strength-0:before{content:"\e005"}.icons-wifi-strength-2:before{content:"\e006"}.icons-wifi-strength-3:before{content:"\e008"}.icons-down:before{content:"\e009"}.icons-wifi-strength-4:before{content:"\e00a"}.icons-up:before{content:"\e00c"}

View File

@@ -1,4 +1,4 @@
function setupForm(formSelector, onResultCallback = null) { function setupForm(formSelector, onResultCallback = null, noCastItems = []) {
const form = document.querySelector(formSelector); const form = document.querySelector(formSelector);
if (!form) { if (!form) {
return; return;
@@ -68,7 +68,7 @@ function setupForm(formSelector, onResultCallback = null) {
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: form2json(fd) body: form2json(fd, noCastItems)
}); });
if (!response.ok) { if (!response.ok) {
@@ -139,7 +139,7 @@ function setupNetworkScanForm(formSelector, tableSelector) {
row.classList.add("network"); row.classList.add("network");
row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid); row.setAttribute('data-ssid', result[i].hidden ? '' : result[i].ssid);
row.onclick = function () { row.onclick = function () {
const input = document.querySelector('input.sta-ssid'); const input = document.querySelector('input#sta-ssid');
const ssid = this.getAttribute('data-ssid'); const ssid = this.getAttribute('data-ssid');
if (!input || !ssid) { if (!input || !ssid) {
return; return;
@@ -150,19 +150,56 @@ function setupNetworkScanForm(formSelector, tableSelector) {
}; };
row.insertCell().textContent = "#" + (i + 1); row.insertCell().textContent = "#" + (i + 1);
row.insertCell().innerHTML = result[i].hidden ? '<i>Hidden</i>' : result[i].ssid; row.insertCell().innerHTML = result[i].hidden ? ("<i>" + result[i].bssid + "</i>") : result[i].ssid;
const signalCell = row.insertCell(); // info cell
const signalElement = document.createElement("kbd"); let infoCell = row.insertCell();
signalElement.textContent = result[i].signalQuality + "%";
if (result[i].signalQuality > 60) { // signal quality
signalElement.classList.add('greatSignal'); let signalQualityIcon = document.createElement("i");
if (result[i].signalQuality > 80) {
signalQualityIcon.classList.add('icons-wifi-strength-4');
} else if (result[i].signalQuality > 60) {
signalQualityIcon.classList.add('icons-wifi-strength-3');
} else if (result[i].signalQuality > 40) { } else if (result[i].signalQuality > 40) {
signalElement.classList.add('normalSignal'); signalQualityIcon.classList.add('icons-wifi-strength-2');
} else if (result[i].signalQuality > 20) {
signalQualityIcon.classList.add('icons-wifi-strength-1');
} else { } else {
signalElement.classList.add('badSignal'); signalQualityIcon.classList.add('icons-wifi-strength-0');
} }
signalCell.appendChild(signalElement);
let signalQualityContainer = document.createElement("span");
signalQualityContainer.setAttribute('data-tooltip', result[i].signalQuality + "%");
signalQualityContainer.appendChild(signalQualityIcon);
infoCell.appendChild(signalQualityContainer);
// auth
const authList = {
0: "Open",
1: "WEP",
2: "WPA",
3: "WPA2",
4: "WPA/WPA2",
5: "WPA/WPA2 Enterprise",
6: "WPA3",
7: "WPA2/WPA3",
8: "WAPI",
9: "OWE",
10: "WPA3 Enterprise"
};
let authIcon = document.createElement("i");
if (result[i].auth == 0) {
authIcon.classList.add('icons-unlocked');
} else {
authIcon.classList.add('icons-locked');
}
let authContainer = document.createElement("span");
authContainer.setAttribute('data-tooltip', (result[i].auth in authList) ? authList[result[i].auth] : "unknown");
authContainer.appendChild(authIcon);
infoCell.appendChild(authContainer);
} }
if (button) { if (button) {
@@ -598,15 +635,19 @@ function memberIdToVendor(memberId) {
: "unknown vendor"; : "unknown vendor";
} }
function form2json(data) { function form2json(data, noCastItems = []) {
let method = function (object, pair) { let method = function (object, pair) {
let keys = pair[0].replace(/\]/g, '').split('['); let keys = pair[0].replace(/\]/g, '').split('[');
let key = keys[0]; let key = keys[0];
let value = pair[1]; let value = pair[1];
if (value === 'true' || value === 'false') {
value = value === 'true'; if (!noCastItems.includes(keys.join('.'))) {
} else if (typeof (value) === 'string' && value.trim() !== '' && !isNaN(value)) { if (value === 'true' || value === 'false') {
value = parseFloat(value); value = value === 'true';
} else if (typeof (value) === 'string' && value.trim() !== '' && !isNaN(value)) {
value = parseFloat(value);
}
} }
if (keys.length > 1) { if (keys.length > 1) {

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -25,6 +25,9 @@ def before_buildfs(source, target, env):
dst_name = name + ".gz" dst_name = name + ".gz"
dst_path = os.path.join(dst, os.path.relpath(root, src), dst_name) dst_path = os.path.join(dst, os.path.relpath(root, src), dst_name)
if os.path.exists(os.path.join(dst, os.path.relpath(root, src))) == False:
os.mkdir(os.path.join(dst, os.path.relpath(root, src)))
with gzip.open(dst_path, 'wb', 9) as f_out: with gzip.open(dst_path, 'wb', 9) as f_out:
shutil.copyfileobj(f_in, f_out) shutil.copyfileobj(f_in, f_out)