30 Commits
1.3.1 ... 1.3.3

Author SHA1 Message Date
Yurii
4b9ebeaa40 fix types 2023-11-16 10:34:25 +03:00
Yurii
76eaec10ea wdt fix for esp8266 2023-11-16 10:33:04 +03:00
Yurii
21de692888 small fix 2023-11-16 08:57:14 +03:00
Yurii
ce318b8cde add header to config portal 2023-11-16 08:06:50 +03:00
Yurii
77b0859cc8 fix config portal 2023-11-16 03:30:24 +03:00
Yurii
3fcb17f2c3 upd readme 2023-11-16 03:29:13 +03:00
Yurii
6204b46c17 Update the code for the status led 2023-11-16 03:29:06 +03:00
Yurii
b5760eb314 fixed heap size
fixed core numbers for esp32 tasks
compatible with lolin_c3_mini
delete task main loop() for esp32
2023-11-12 20:54:02 +03:00
Yurii
eedbd7b80a auto restart after changing some settings 2023-11-11 21:40:22 +03:00
Yurii
e9bf4c4bd5 fix default pins 2023-11-11 21:39:15 +03:00
Yurii
a255dda8dd Compatible with ESP32 2023-11-11 05:01:36 +03:00
Yurii
6de6f7d138 Added callback for yield 2023-11-11 05:00:55 +03:00
Yurii
5e8916b254 Refactoring HomeAssistantHelper 2023-11-11 04:59:39 +03:00
Yurii
7db49350a2 Optimization HomeAssistantHelper 2023-11-06 18:38:57 +03:00
Yurii
8e2a70ec04 upd build script 2023-11-06 16:44:00 +03:00
Yurii
b2ff71c59a upd readme 2023-11-06 16:43:47 +03:00
Yurii
3660b89f7c upd readme 2023-11-06 16:42:03 +03:00
Yurii
5c0dfc544e update to 1.3.2 2023-10-21 03:02:38 +03:00
Yurii
5fba94312b added build functions 2023-10-21 03:01:48 +03:00
Yurii
62bea87f8a upd env 2023-10-21 01:48:44 +03:00
Yurii
f52aa8e889 removed unused code 2023-10-20 21:07:12 +03:00
Yurii
df8354866f fix entity climate.heating 2023-10-20 21:06:44 +03:00
Yurii
6242db7a29 removed unused code 2023-10-19 02:20:37 +03:00
Yurii
dc00fdcdb6 Fix Error ''max' must be > 'min'' when processing MQTT 2023-10-19 02:18:39 +03:00
Yurii
2615e9106e upd readme 2023-10-19 00:45:06 +03:00
Yurii
0f60a07a71 upd readme 2023-10-19 00:43:17 +03:00
Yurii
f8750373d4 format code 2023-10-19 00:40:07 +03:00
Yurii
d5a92c47c7 Fixed get current dhw temp 2023-10-19 00:34:11 +03:00
Yurii
bc91168bbf added interval for forced set temperatures 2023-10-17 14:50:43 +03:00
Yurii
96c1a187cd upd readme, get modulation fix 2023-10-17 14:14:24 +03:00
20 changed files with 2026 additions and 2085 deletions

3
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.pio
.vscode
.vscode
build/*

View File

@@ -11,16 +11,16 @@
- Emergency mode. If the Wi-Fi connection is lost or the gateway cannot connect to the MQTT server, the mode will turn on. This mode will automatically maintain the set temperature and prevent your home from freezing. In this mode it is also possible to use equithermal curves (weather-compensated control).
- Automatic error reset (not with all boilers)
- Diagnostics:
- The process of heating the coolant for heating: works/does not work
- The process of heating: works/does not work
- The process of heating water for hot water: working/not working
- Display of boiler errors
- Burner status: on/off
- Burner status (flame): on/off
- Burner modulation level in percent
- Pressure in the heating system
- Gateway status (depending on errors and connection status)
- Boiler connection status via OpenTherm interface
- The current temperature of the heat carrier (usually the return heat carrier)
- Set coolant temperature (depending on the selected mode)
- Set heat carrier temperature (depending on the selected mode)
- 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!
@@ -33,6 +33,12 @@
| BAXI ECO Nova | default | Pressure sensor not supported, modulation level not stable |
| BAXI Ampera | 1028 | Pressure sensor not supported, only heating (DHW not tested) |
| [Remeha Calenta Ace 40C](https://github.com/Laxilef/OTGateway/issues/1#issuecomment-1726081554) | default | - |
| [Baxi Nuvola DUO-TEC HT 16](https://github.com/Laxilef/OTGateway/issues/3#issuecomment-1751061488) | default | - |
| [AEG GBA124](https://github.com/Laxilef/OTGateway/issues/3#issuecomment-1765857609) | default | Pressure sensor not supported |
| [Ferroli DOMIcompact C 24](https://github.com/Laxilef/OTGateway/issues/3#issuecomment-1765310058)<br><sub>Board: MF08FA</sub> | 211 | Pressure sensor not supported |
| [Thermet Ecocondens Silver 35kW](https://github.com/Laxilef/OTGateway/issues/3#issuecomment-1767026384) | default | Pressure sensor not supported |
| [BAXI LUNA-3](https://github.com/Laxilef/OTGateway/issues/3#issuecomment-1794187178) | default | - |
## PCB
<img src="/assets/pcb.svg" width="27%" /> <img src="/assets/pcb_3d.png" width="30%" /> <img src="/assets/after_assembly.png" width="40%" />
@@ -40,11 +46,21 @@
Housing for installation on DIN rail - D2MG. Occupies only 2 DIN modules.<br>
The 220V > 5V power supply is already on the board, so additional power supplies are not needed.<br>
To save money, 2 levels are ordered as one board. After manufacturing, the boards need to be divided into 2 parts - upper and lower. The boards are inexpensively (5pcs for $2) manufactured at JLCPCB (Remove Order Number = Specify a location).<br><br>
Some components can be replaced with similar ones (for example use a fuse and led with legs). Some SMD components (for example optocouplers) can be replaced with similar SOT components.<br>Most of the components can be purchased inexpensively on Aliexpress, the rest in your local stores.<br><br>
The outdoor temperature sensor must be connected to the **TEMP1** connector, the indoor temperature sensor must be connected to the **TEMP2** connector. The power supply for the sensors must be connected to the **3.3V** connector, GND to **GND**.<br>
Some components can be replaced with similar ones (for example use a fuse and led with legs). Some SMD components (for example optocouplers) can be replaced with similar SOT components.<br>Most of the components can be purchased inexpensively on Aliexpress, the rest in your local stores.
#### Connection
The outdoor temperature sensor must be connected to the **TEMP1** connector, the indoor temperature sensor must be connected to the **TEMP2** connector. The power supply for the sensors must be connected to the **3.3V** connector **(NOT 5V!)**, GND to **GND**.<br>
**The opentherm connection polarity does not matter.**
<!-- **Important!** On this board opentherm IN pin = 5, OUT pin = 4 -->
#### Leds
| LED | States |
| --- | --- |
| OT RX | Flashes when a response to the request is received from the boiler |
| Status | Controller status.<br>On, not blinking - no errors;<br>2 flashes - no connection to Wifi;<br>3 flashes - no connection to boiler;<br>4 flashes - boiler is fault;<br>5 flashes - emergency mode (no connection to Wifi or to the MQTT server)<br>10 fast flashes - end of the list of errors |
| Power | Always on when power is on |
#### Files for production
- [Schematic](/assets/Schematic.pdf)
- [BOM](/assets/BOM.xlsx)
- [Gerber](/assets/gerber.zip)
@@ -155,7 +171,7 @@ Weather-compensated temperature control maintains a comfortable set temperature
#### Ratios:
***N*** - heating curve coefficient. The coefficient is selected individually, depending on the insulation of the room, the heated area, etc.<br>
Range: 0.3...10, default: 0.7, step 0.01
Range: 0.001...10, default: 0.7, step 0.001
***K*** - сorrection for desired room temperature.<br>
@@ -187,11 +203,12 @@ See [Wikipedia](https://en.wikipedia.org/wiki/PID_controller).
In Google you can find instructions for tuning the PID controller.
### Use Equitherm mode + PID mode
@todo
<!--### Use Equitherm mode + PID mode
@todo-->
## Dependencies
- [ESP8266Scheduler](https://github.com/nrwiersma/ESP8266Scheduler)
- [ESP8266Scheduler](https://github.com/nrwiersma/ESP8266Scheduler) (for ESP8266)
- [ESP32Scheduler](https://github.com/laxilef/ESP32Scheduler) (for ESP32)
- [NTPClient](https://github.com/arduino-libraries/NTPClient)
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
- [OpenTherm Library](https://github.com/ihormelnyk/opentherm_library)
@@ -199,6 +216,7 @@ In Google you can find instructions for tuning the PID controller.
- [TelnetStream](https://github.com/jandrassy/TelnetStream)
- [EEManager](https://github.com/GyverLibs/EEManager)
- [GyverPID](https://github.com/GyverLibs/GyverPID)
- [GyverBlinker](https://github.com/GyverLibs/GyverBlinker)
- [DallasTemperature](https://github.com/milesburton/Arduino-Temperature-Control-Library)
- [WiFiManager](https://github.com/tzapu/WiFiManager)

View File

@@ -1,12 +1,12 @@
#include <Arduino.h>
#include <OpenTherm.h>
extern SchedulerClass Scheduler;
class CustomOpenTherm : public OpenTherm {
private:
unsigned long send_ts = millis();
void(*handleSendRequestCallback)(unsigned long, unsigned long, OpenThermResponseStatus status, byte attempt);
void(*yieldCallback)(void*);
void* yieldArg;
public:
CustomOpenTherm(int inPin = 4, int outPin = 5, bool isSlave = false) : OpenTherm(inPin, outPin, isSlave) {}
@@ -14,10 +14,25 @@ public:
this->handleSendRequestCallback = handleSendRequestCallback;
}
void setYieldCallback(void(*yieldCallback)(void*)) {
this->yieldCallback = yieldCallback;
this->yieldArg = nullptr;
}
void setYieldCallback(void(*yieldCallback)(void*), void* arg) {
this->yieldCallback = yieldCallback;
this->yieldArg = arg;
}
unsigned long sendRequest(unsigned long request, byte attempts = 5, byte _attempt = 0) {
_attempt++;
while (send_ts > 0 && millis() - send_ts < 200) {
Scheduler.yield();
if (yieldCallback != NULL) {
yieldCallback(yieldArg);
} else {
::yield();
}
}
unsigned long _response;
@@ -25,19 +40,26 @@ public:
_response = 0;
} else {
while (!isReady()) {
Scheduler.yield();
if (yieldCallback != NULL) {
yieldCallback(yieldArg);
} else {
::yield();
}
process();
}
_response = getLastResponse();
}
OpenThermResponseStatus _responseStatus = getLastResponseStatus();
if (handleSendRequestCallback != NULL) {
handleSendRequestCallback(request, _response, getLastResponseStatus(), _attempt);
handleSendRequestCallback(request, _response, _responseStatus, _attempt);
}
send_ts = millis();
if (getLastResponseStatus() == OpenThermResponseStatus::SUCCESS || _attempt >= attempts) {
if (_responseStatus == OpenThermResponseStatus::SUCCESS || _responseStatus == OpenThermResponseStatus::INVALID || _attempt >= attempts) {
return _response;
} else {
return sendRequest(request, attempts, _attempt);
@@ -92,7 +114,7 @@ public:
const byte valueLB = response & 0xFF;
const byte valueHB = (response >> 8) & 0xFF;
float value = (int8_t) valueHB;
float value = (int8_t)valueHB;
return value + (float)valueLB / 256.0;
}

View File

@@ -0,0 +1,89 @@
#pragma once
#include <Arduino.h>
class HomeAssistantHelper {
public:
HomeAssistantHelper(PubSubClient& client) :
client(&client)
{
}
void setDevicePrefix(String value) {
devicePrefix = value;
}
void setDeviceVersion(String value) {
deviceVersion = value;
}
void setDeviceManufacturer(String value) {
deviceManufacturer = value;
}
void setDeviceModel(String value) {
deviceModel = value;
}
void setDeviceName(String value) {
deviceName = value;
}
void setDeviceConfigUrl(String value) {
deviceConfigUrl = value;
}
bool publish(const char* topic, JsonDocument& doc) {
doc[FPSTR(HA_DEVICE)][FPSTR(HA_IDENTIFIERS)][0] = devicePrefix;
doc[FPSTR(HA_DEVICE)][FPSTR(HA_SW_VERSION)] = deviceVersion;
if (deviceManufacturer) {
doc[FPSTR(HA_DEVICE)][FPSTR(HA_MANUFACTURER)] = deviceManufacturer;
}
if (deviceModel) {
doc[FPSTR(HA_DEVICE)][FPSTR(HA_MODEL)] = deviceModel;
}
if (deviceName) {
doc[FPSTR(HA_DEVICE)][FPSTR(HA_NAME)] = deviceName;
}
if (deviceConfigUrl) {
doc[FPSTR(HA_DEVICE)][FPSTR(HA_CONF_URL)] = deviceConfigUrl;
}
// Feeding the watchdog
yield();
client->beginPublish(topic, measureJson(doc), true);
serializeJson(doc, *client);
return client->endPublish();
}
bool publish(const char* topic) {
return client->publish(topic, NULL, true);
}
String getTopic(const char* category, const char* name, const char* nameSeparator = "/") {
String topic = "";
topic.concat(prefix);
topic.concat("/");
topic.concat(category);
topic.concat("/");
topic.concat(devicePrefix);
topic.concat(nameSeparator);
topic.concat(name);
topic.concat("/config");
return topic;
}
protected:
PubSubClient* client;
String prefix = "homeassistant";
String devicePrefix = "";
String deviceVersion = "1.0";
String deviceManufacturer = "Community";
String deviceModel = "";
String deviceName = "";
String deviceConfigUrl = "";
};

View File

@@ -0,0 +1,37 @@
#pragma once
#ifndef PROGMEM
#define PROGMEM
#endif
const char HA_DEVICE[] PROGMEM = "device";
const char HA_IDENTIFIERS[] PROGMEM = "identifiers";
const char HA_SW_VERSION[] PROGMEM = "sw_version";
const char HA_MANUFACTURER[] PROGMEM = "manufacturer";
const char HA_MODEL[] PROGMEM = "model";
const char HA_NAME[] PROGMEM = "name";
const char HA_CONF_URL[] PROGMEM = "configuration_url";
const char HA_COMMAND_TOPIC[] PROGMEM = "command_topic";
const char HA_COMMAND_TEMPLATE[] PROGMEM = "command_template";
const char HA_ENABLED_BY_DEFAULT[] PROGMEM = "enabled_by_default";
const char HA_UNIQUE_ID[] PROGMEM = "unique_id";
const char HA_OBJECT_ID[] PROGMEM = "object_id";
const char HA_ENTITY_CATEGORY[] PROGMEM = "entity_category";
const char HA_STATE_TOPIC[] PROGMEM = "state_topic";
const char HA_VALUE_TEMPLATE[] PROGMEM = "value_template";
const char HA_OPTIONS[] PROGMEM = "options";
const char HA_AVAILABILITY[] PROGMEM = "availability";
const char HA_AVAILABILITY_MODE[] PROGMEM = "availability_mode";
const char HA_TOPIC[] PROGMEM = "topic";
const char HA_DEVICE_CLASS[] PROGMEM = "device_class";
const char HA_UNIT_OF_MEASUREMENT[] PROGMEM = "unit_of_measurement";
const char HA_ICON[] PROGMEM = "icon";
const char HA_MIN[] PROGMEM = "min";
const char HA_MAX[] PROGMEM = "max";
const char HA_STEP[] PROGMEM = "step";
const char HA_MODE[] PROGMEM = "mode";
const char HA_STATE_ON[] PROGMEM = "state_on";
const char HA_STATE_OFF[] PROGMEM = "state_off";
const char HA_PAYLOAD_ON[] PROGMEM = "payload_on";
const char HA_PAYLOAD_OFF[] PROGMEM = "payload_off";
const char HA_STATE_CLASS[] PROGMEM = "state_class";
const char HA_EXPIRE_AFTER[] PROGMEM = "expire_after";

View File

@@ -9,13 +9,24 @@ public:
}
};
class UnsignedIntParameter : public WiFiManagerParameter {
public:
UnsignedIntParameter(const char* id, const char* label, unsigned int value, const uint8_t length = 10) : WiFiManagerParameter("") {
init(id, label, String(value).c_str(), length, "", WFM_LABEL_DEFAULT);
}
unsigned int getValue() {
return (unsigned int) atoi(WiFiManagerParameter::getValue());
}
};
class CheckboxParameter : public WiFiManagerParameter {
public:
const char* checked = "type=\"checkbox\" checked";
const char* noChecked = "type=\"checkbox\"";
const char* trueVal = "T";
CheckboxParameter(const char* id, const char* label, bool value): WiFiManagerParameter("") {
CheckboxParameter(const char* id, const char* label, bool value) : WiFiManagerParameter("") {
init(id, label, value ? trueVal : "0", 1, "", WFM_LABEL_AFTER);
}
@@ -34,5 +45,5 @@ public:
class SeparatorParameter : public WiFiManagerParameter {
public:
SeparatorParameter(): WiFiManagerParameter("<hr>") {}
SeparatorParameter() : WiFiManagerParameter("<hr>") {}
};

View File

@@ -8,12 +8,9 @@
; Please visit documentation for the other options and examples
; https://docs.platformio.org/page/projectconf.html
[env:d1_mini_pro]
platform = espressif8266
board = d1_mini_pro
[env]
framework = arduino
lib_deps =
nrwiersma/ESP8266Scheduler@^1.0
arduino-libraries/NTPClient@^3.2.1
bblanchon/ArduinoJson@^6.20.0
ihormelnyk/OpenTherm Library@^1.1.4
@@ -21,9 +18,140 @@ lib_deps =
jandrassy/TelnetStream@^1.2.4
gyverlibs/EEManager@^2.0
gyverlibs/GyverPID@^3.3
gyverlibs/GyverBlinker@^1.0
milesburton/DallasTemperature@^3.11.0
https://github.com/Laxilef/WiFiManager/archive/refs/heads/patch-1.zip
; https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2
build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH
;https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2
build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -mtext-section-literals
upload_speed = 921600
monitor_speed = 115200
version = 1.3.3
; Defaults
[esp8266_defaults]
platform = espressif8266
lib_deps =
${env.lib_deps}
nrwiersma/ESP8266Scheduler@^1.0
lib_ignore =
extra_scripts =
post:tools/build.py
build_flags = ${env.build_flags}
[esp32_defaults]
platform = espressif32
lib_deps =
${env.lib_deps}
laxilef/ESP32Scheduler@^1.0.0
lib_ignore =
extra_scripts =
post:tools/esp32.py
post:tools/build.py
build_flags = ${env.build_flags}
; Boards
[env:d1_mini]
platform = ${esp8266_defaults.platform}
board = d1_mini
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
build_flags =
${esp8266_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=4
-D OT_OUT_PIN_DEFAULT=5
-D SENSOR_OUTDOOR_PIN_DEFAULT=12
-D SENSOR_INDOOR_PIN_DEFAULT=14
-D LED_STATUS_PIN=13
-D LED_OT_RX_PIN=15
[env:d1_mini_lite]
platform = ${esp8266_defaults.platform}
board = d1_mini_lite
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
build_flags =
${esp8266_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=4
-D OT_OUT_PIN_DEFAULT=5
-D SENSOR_OUTDOOR_PIN_DEFAULT=12
-D SENSOR_INDOOR_PIN_DEFAULT=14
-D LED_STATUS_PIN=13
-D LED_OT_RX_PIN=15
[env:d1_mini_pro]
platform = ${esp8266_defaults.platform}
board = d1_mini_pro
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
build_flags =
${esp8266_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=4
-D OT_OUT_PIN_DEFAULT=5
-D SENSOR_OUTDOOR_PIN_DEFAULT=12
-D SENSOR_INDOOR_PIN_DEFAULT=14
-D LED_STATUS_PIN=13
-D LED_OT_RX_PIN=15
[env:s2_mini]
platform = ${esp32_defaults.platform}
board = lolin_s2_mini
lib_deps = ${esp32_defaults.lib_deps}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_flags =
${esp32_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=33
-D OT_OUT_PIN_DEFAULT=35
-D SENSOR_OUTDOOR_PIN_DEFAULT=9
-D SENSOR_INDOOR_PIN_DEFAULT=7
-D LED_STATUS_PIN=11
-D LED_OT_RX_PIN=12
[env:s3_mini]
platform = ${esp32_defaults.platform}
board = lolin_s3_mini
lib_deps = ${esp32_defaults.lib_deps}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_flags =
${esp32_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=35
-D OT_OUT_PIN_DEFAULT=36
-D SENSOR_OUTDOOR_PIN_DEFAULT=13
-D SENSOR_INDOOR_PIN_DEFAULT=12
-D LED_STATUS_PIN=11
-D LED_OT_RX_PIN=10
[env:c3_mini]
platform = ${esp32_defaults.platform}
board = lolin_c3_mini
lib_deps = ${esp32_defaults.lib_deps}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_flags =
-D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH
-D OT_IN_PIN_DEFAULT=8
-D OT_OUT_PIN_DEFAULT=10
-D SENSOR_OUTDOOR_PIN_DEFAULT=0
-D SENSOR_INDOOR_PIN_DEFAULT=1
-D LED_STATUS_PIN=4
-D LED_OT_RX_PIN=5
[env:nodemcu_32s]
platform = ${esp32_defaults.platform}
board = nodemcu-32s
lib_deps = ${esp32_defaults.lib_deps}
lib_ignore = ${esp32_defaults.lib_ignore}
extra_scripts = ${esp32_defaults.extra_scripts}
build_flags =
${esp32_defaults.build_flags}
-D OT_IN_PIN_DEFAULT=21
-D OT_OUT_PIN_DEFAULT=22
-D SENSOR_OUTDOOR_PIN_DEFAULT=12
-D SENSOR_INDOOR_PIN_DEFAULT=13
-D LED_STATUS_PIN=2 ; 18
-D LED_OT_RX_PIN=19

1154
src/HaHelper.h Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,42 @@
#include <Blinker.h>
extern MqttTask* tMqtt;
extern SensorsTask* tSensors;
extern OpenThermTask* tOt;
class MainTask: public Task {
class MainTask : public Task {
public:
MainTask(bool _enabled = false, unsigned long _interval = 0): Task(_enabled, _interval) {}
MainTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
protected:
Blinker* blinker = nullptr;
unsigned long lastHeapInfo = 0;
unsigned long firstFailConnect = 0;
unsigned short minFreeHeapSize = 65535;
unsigned int heapSize = 0;
unsigned int minFreeHeapSize = 0;
const char* getTaskName() {
return "Main";
}
int getTaskCore() {
return 1;
}
void setup() {
#ifdef LED_STATUS_PIN
pinMode(LED_STATUS_PIN, OUTPUT);
digitalWrite(LED_STATUS_PIN, false);
#endif
#if defined(ESP32)
heapSize = ESP.getHeapSize();
#elif defined(ESP8266)
heapSize = 81920;
#elif
heapSize = 99999;
#endif
minFreeHeapSize = heapSize;
}
void loop() {
@@ -20,12 +44,23 @@ protected:
INFO("Settings updated (EEPROM)");
}
if (vars.parameters.restartAfterTime > 0 && millis() - vars.parameters.restartSignalTime > vars.parameters.restartAfterTime) {
vars.parameters.restartAfterTime = 0;
INFO("Received restart message...");
eeSettings.updateNow();
INFO("Restart...");
delay(1000);
ESP.restart();
}
if (WiFi.status() == WL_CONNECTED) {
if (!tMqtt->isEnabled()) {
if (!tMqtt->isEnabled() && strlen(settings.mqtt.server) > 0) {
tMqtt->enable();
}
if ( firstFailConnect != 0 ) {
if (firstFailConnect != 0) {
firstFailConnect = 0;
}
@@ -40,7 +75,7 @@ protected:
if (firstFailConnect == 0) {
firstFailConnect = millis();
}
if (millis() - firstFailConnect > EMERGENCY_TIME_TRESHOLD) {
vars.states.emergency = true;
INFO("Emergency mode enabled");
@@ -52,7 +87,9 @@ protected:
tOt->enable();
}
ledStatus();
#ifdef LED_STATUS_PIN
ledStatus(LED_STATUS_PIN);
#endif
#ifdef USE_TELNET
yield();
@@ -65,70 +102,80 @@ protected:
#endif
if (settings.debug) {
unsigned short freeHeapSize = ESP.getFreeHeap();
unsigned short minFreeHeapSizeDiff = 0;
unsigned int freeHeapSize = ESP.getFreeHeap();
unsigned int minFreeHeapSizeDiff = 0;
if (freeHeapSize < minFreeHeapSize) {
minFreeHeapSizeDiff = minFreeHeapSize - freeHeapSize;
minFreeHeapSize = freeHeapSize;
}
if (millis() - lastHeapInfo > 10000 || minFreeHeapSizeDiff > 0) {
DEBUG_F("Free heap size: %hu bytes, min: %hu bytes (diff: %hu bytes)\n", freeHeapSize, minFreeHeapSize, minFreeHeapSizeDiff);
DEBUG_F("Free heap size: %u of %u bytes, min: %u bytes (diff: %u bytes)\n", freeHeapSize, heapSize, minFreeHeapSize, minFreeHeapSizeDiff);
lastHeapInfo = millis();
}
}
}
void ledStatus() {
static byte blinkLeft = 0;
void ledStatus(uint8_t ledPin) {
uint8_t errors[4];
uint8_t errCount = 0;
static uint8_t errPos = 0;
static unsigned long endBlinkTime = 0;
static bool ledOn = false;
static unsigned long changeTime = 0;
byte errNo = 0;
if (!vars.states.otStatus) {
errNo = 1;
} else if (vars.states.fault) {
errNo = 2;
} else if (vars.states.emergency) {
errNo = 3;
if (this->blinker == nullptr) {
this->blinker = new Blinker(ledPin);
}
if (errNo == 0) {
if (!ledOn) {
digitalWrite(LED_STATUS_PIN, true);
ledOn = true;
}
if (WiFi.status() != WL_CONNECTED) {
errors[errCount++] = 2;
}
if (blinkLeft > 0) {
blinkLeft = 0;
}
if (!vars.states.otStatus) {
errors[errCount++] = 3;
}
} else {
if (blinkLeft == 0) {
if (ledOn) {
digitalWrite(LED_STATUS_PIN, false);
ledOn = false;
changeTime = millis();
}
if (vars.states.fault) {
errors[errCount++] = 4;
}
if (millis() - changeTime >= 3000) {
blinkLeft = errNo;
}
}
if (vars.states.emergency) {
errors[errCount++] = 5;
}
if (blinkLeft > 0 && millis() - changeTime >= 500) {
if (ledOn) {
digitalWrite(LED_STATUS_PIN, false);
ledOn = false;
blinkLeft--;
} else {
digitalWrite(LED_STATUS_PIN, true);
if (this->blinker->ready()) {
endBlinkTime = millis();
}
if (!this->blinker->running() && millis() - endBlinkTime >= 5000) {
if (errCount == 0) {
if (!ledOn) {
digitalWrite(ledPin, true);
ledOn = true;
}
changeTime = millis();
return;
} else if (ledOn) {
digitalWrite(ledPin, false);
ledOn = false;
endBlinkTime = millis();
return;
}
if (errPos >= errCount) {
errPos = 0;
// end of error list
this->blinker->blink(10, 50, 50);
} else {
this->blinker->blink(errors[errPos++], 300, 300);
}
}
this->blinker->tick();
}
};

View File

@@ -1,27 +1,36 @@
#include <WiFiClient.h>
#include <PubSubClient.h>
#include <netif/etharp.h>
#include "HomeAssistantHelper.h"
#include "HaHelper.h"
WiFiClient espClient;
PubSubClient client(espClient);
HomeAssistantHelper haHelper;
HaHelper haHelper(client);
class MqttTask: public Task {
class MqttTask : public Task {
public:
MqttTask(bool _enabled = false, unsigned long _interval = 0): Task(_enabled, _interval) {}
MqttTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
protected:
unsigned long lastReconnectAttempt = 0;
unsigned long firstFailConnect = 0;
const char* getTaskName() {
return "Mqtt";
}
int getTaskCore() {
return 0;
}
void setup() {
DEBUG("[MQTT] Started");
client.setCallback(__callback);
haHelper.setPrefix(settings.mqtt.prefix);
haHelper.setDevicePrefix(settings.mqtt.prefix);
haHelper.setDeviceVersion(OT_GATEWAY_VERSION);
haHelper.setDeviceModel("Opentherm Gateway");
haHelper.setDeviceName("Opentherm Gateway");
sprintf(buffer, CONFIG_URL, WiFi.localIP().toString().c_str());
haHelper.setDeviceConfigUrl(buffer);
@@ -50,14 +59,13 @@ protected:
if (firstFailConnect == 0) {
firstFailConnect = millis();
}
if (millis() - firstFailConnect > EMERGENCY_TIME_TRESHOLD) {
vars.states.emergency = true;
INFO("Emergency mode enabled");
}
}
forceARP();
lastReconnectAttempt = millis();
}
}
@@ -77,14 +85,6 @@ protected:
}
static void forceARP() {
struct netif* netif = netif_list;
while (netif) {
etharp_gratuitous(netif);
netif = netif->next;
}
}
static bool updateSettings(JsonDocument& doc) {
bool flag = false;
@@ -101,7 +101,7 @@ protected:
}
if (!doc["emergency"]["target"].isNull() && doc["emergency"]["target"].is<float>()) {
if ( doc["emergency"]["target"].as<float>() > 0 && doc["emergency"]["target"].as<float>() < 100 ) {
if (doc["emergency"]["target"].as<float>() > 0 && doc["emergency"]["target"].as<float>() < 100) {
settings.emergency.target = round(doc["emergency"]["target"].as<float>() * 10) / 10;
flag = true;
}
@@ -125,21 +125,21 @@ protected:
}
if (!doc["heating"]["target"].isNull() && doc["heating"]["target"].is<float>()) {
if ( doc["heating"]["target"].as<float>() > 0 && doc["heating"]["target"].as<float>() < 100 ) {
if (doc["heating"]["target"].as<float>() > 0 && doc["heating"]["target"].as<float>() < 100) {
settings.heating.target = round(doc["heating"]["target"].as<float>() * 10) / 10;
flag = true;
}
}
if (!doc["heating"]["hysteresis"].isNull() && doc["heating"]["hysteresis"].is<float>()) {
if ( doc["heating"]["hysteresis"].as<float>() >= 0 && doc["heating"]["hysteresis"].as<float>() <= 5 ) {
if (doc["heating"]["hysteresis"].as<float>() >= 0 && doc["heating"]["hysteresis"].as<float>() <= 5) {
settings.heating.hysteresis = round(doc["heating"]["hysteresis"].as<float>() * 10) / 10;
flag = true;
}
}
if (!doc["heating"]["maxTemp"].isNull() && doc["heating"]["maxTemp"].is<unsigned char>()) {
if ( doc["heating"]["maxTemp"].as<unsigned char>() > 0 && doc["heating"]["maxTemp"].as<unsigned char>() <= 100 && doc["heating"]["maxTemp"].as<unsigned char>() > settings.heating.minTemp ) {
if (doc["heating"]["maxTemp"].as<unsigned char>() > 0 && doc["heating"]["maxTemp"].as<unsigned char>() <= 100 && doc["heating"]["maxTemp"].as<unsigned char>() > settings.heating.minTemp) {
settings.heating.maxTemp = doc["heating"]["maxTemp"].as<unsigned char>();
vars.parameters.heatingMaxTemp = settings.heating.maxTemp;
flag = true;
@@ -147,7 +147,7 @@ protected:
}
if (!doc["heating"]["minTemp"].isNull() && doc["heating"]["minTemp"].is<unsigned char>()) {
if ( doc["heating"]["minTemp"].as<unsigned char>() >= 0 && doc["heating"]["minTemp"].as<unsigned char>() < 100 && doc["heating"]["minTemp"].as<unsigned char>() < settings.heating.maxTemp ) {
if (doc["heating"]["minTemp"].as<unsigned char>() >= 0 && doc["heating"]["minTemp"].as<unsigned char>() < 100 && doc["heating"]["minTemp"].as<unsigned char>() < settings.heating.maxTemp) {
settings.heating.minTemp = doc["heating"]["minTemp"].as<unsigned char>();
vars.parameters.heatingMinTemp = settings.heating.minTemp;
flag = true;
@@ -162,14 +162,14 @@ protected:
}
if (!doc["dhw"]["target"].isNull() && doc["dhw"]["target"].is<unsigned char>()) {
if ( doc["dhw"]["target"].as<unsigned char>() >= 0 && doc["dhw"]["target"].as<unsigned char>() < 100 ) {
if (doc["dhw"]["target"].as<unsigned char>() >= 0 && doc["dhw"]["target"].as<unsigned char>() < 100) {
settings.dhw.target = doc["dhw"]["target"].as<unsigned char>();
flag = true;
}
}
if (!doc["dhw"]["maxTemp"].isNull() && doc["dhw"]["maxTemp"].is<unsigned char>()) {
if ( doc["dhw"]["maxTemp"].as<unsigned char>() > 0 && doc["dhw"]["maxTemp"].as<unsigned char>() <= 100 && doc["dhw"]["maxTemp"].as<unsigned char>() > settings.dhw.minTemp ) {
if (doc["dhw"]["maxTemp"].as<unsigned char>() > 0 && doc["dhw"]["maxTemp"].as<unsigned char>() <= 100 && doc["dhw"]["maxTemp"].as<unsigned char>() > settings.dhw.minTemp) {
settings.dhw.maxTemp = doc["dhw"]["maxTemp"].as<unsigned char>();
vars.parameters.dhwMaxTemp = settings.dhw.maxTemp;
flag = true;
@@ -177,7 +177,7 @@ protected:
}
if (!doc["dhw"]["minTemp"].isNull() && doc["dhw"]["minTemp"].is<unsigned char>()) {
if ( doc["dhw"]["minTemp"].as<unsigned char>() >= 0 && doc["dhw"]["minTemp"].as<unsigned char>() < 100 && doc["dhw"]["minTemp"].as<unsigned char>() < settings.dhw.maxTemp ) {
if (doc["dhw"]["minTemp"].as<unsigned char>() >= 0 && doc["dhw"]["minTemp"].as<unsigned char>() < 100 && doc["dhw"]["minTemp"].as<unsigned char>() < settings.dhw.maxTemp) {
settings.dhw.minTemp = doc["dhw"]["minTemp"].as<unsigned char>();
vars.parameters.dhwMinTemp = settings.dhw.minTemp;
flag = true;
@@ -192,35 +192,35 @@ protected:
}
if (!doc["pid"]["p_factor"].isNull() && doc["pid"]["p_factor"].is<float>()) {
if ( doc["pid"]["p_factor"].as<float>() >= 0 && doc["pid"]["p_factor"].as<float>() <= 20 ) {
if (doc["pid"]["p_factor"].as<float>() > 0 && doc["pid"]["p_factor"].as<float>() <= 10) {
settings.pid.p_factor = round(doc["pid"]["p_factor"].as<float>() * 1000) / 1000;
flag = true;
}
}
if (!doc["pid"]["i_factor"].isNull() && doc["pid"]["i_factor"].is<float>()) {
if ( doc["pid"]["i_factor"].as<float>() >= 0 && doc["pid"]["i_factor"].as<float>() <= 20 ) {
if (doc["pid"]["i_factor"].as<float>() >= 0 && doc["pid"]["i_factor"].as<float>() <= 10) {
settings.pid.i_factor = round(doc["pid"]["i_factor"].as<float>() * 1000) / 1000;
flag = true;
}
}
if (!doc["pid"]["d_factor"].isNull() && doc["pid"]["d_factor"].is<float>()) {
if ( doc["pid"]["d_factor"].as<float>() >= 0 && doc["pid"]["d_factor"].as<float>() <= 20 ) {
if (doc["pid"]["d_factor"].as<float>() >= 0 && doc["pid"]["d_factor"].as<float>() <= 10) {
settings.pid.d_factor = round(doc["pid"]["d_factor"].as<float>() * 1000) / 1000;
flag = true;
}
}
if (!doc["pid"]["maxTemp"].isNull() && doc["pid"]["maxTemp"].is<unsigned char>()) {
if ( doc["pid"]["maxTemp"].as<unsigned char>() > 0 && doc["pid"]["maxTemp"].as<unsigned char>() <= 100 && doc["pid"]["maxTemp"].as<unsigned char>() > settings.pid.minTemp ) {
if (doc["pid"]["maxTemp"].as<unsigned char>() > 0 && doc["pid"]["maxTemp"].as<unsigned char>() <= 100 && doc["pid"]["maxTemp"].as<unsigned char>() > settings.pid.minTemp) {
settings.pid.maxTemp = doc["pid"]["maxTemp"].as<unsigned char>();
flag = true;
}
}
if (!doc["pid"]["minTemp"].isNull() && doc["pid"]["minTemp"].is<unsigned char>()) {
if ( doc["pid"]["minTemp"].as<unsigned char>() >= 0 && doc["pid"]["minTemp"].as<unsigned char>() < 100 && doc["pid"]["minTemp"].as<unsigned char>() < settings.pid.maxTemp ) {
if (doc["pid"]["minTemp"].as<unsigned char>() >= 0 && doc["pid"]["minTemp"].as<unsigned char>() < 100 && doc["pid"]["minTemp"].as<unsigned char>() < settings.pid.maxTemp) {
settings.pid.minTemp = doc["pid"]["minTemp"].as<unsigned char>();
flag = true;
}
@@ -233,21 +233,21 @@ protected:
}
if (!doc["equitherm"]["n_factor"].isNull() && doc["equitherm"]["n_factor"].is<float>()) {
if ( doc["equitherm"]["n_factor"].as<float>() >= 0 && doc["equitherm"]["n_factor"].as<float>() <= 20 ) {
if (doc["equitherm"]["n_factor"].as<float>() > 0 && doc["equitherm"]["n_factor"].as<float>() <= 10) {
settings.equitherm.n_factor = round(doc["equitherm"]["n_factor"].as<float>() * 1000) / 1000;
flag = true;
}
}
if (!doc["equitherm"]["k_factor"].isNull() && doc["equitherm"]["k_factor"].is<float>()) {
if ( doc["equitherm"]["k_factor"].as<float>() >= 0 && doc["equitherm"]["k_factor"].as<float>() <= 20 ) {
if (doc["equitherm"]["k_factor"].as<float>() >= 0 && doc["equitherm"]["k_factor"].as<float>() <= 10) {
settings.equitherm.k_factor = round(doc["equitherm"]["k_factor"].as<float>() * 1000) / 1000;
flag = true;
}
}
if (!doc["equitherm"]["t_factor"].isNull() && doc["equitherm"]["t_factor"].is<float>()) {
if ( doc["equitherm"]["t_factor"].as<float>() >= 0 && doc["equitherm"]["t_factor"].as<float>() <= 20 ) {
if (doc["equitherm"]["t_factor"].as<float>() >= 0 && doc["equitherm"]["t_factor"].as<float>() <= 10) {
settings.equitherm.t_factor = round(doc["equitherm"]["t_factor"].as<float>() * 1000) / 1000;
flag = true;
}
@@ -256,28 +256,28 @@ protected:
// sensors
if (!doc["sensors"]["outdoor"]["type"].isNull() && doc["sensors"]["outdoor"]["type"].is<unsigned char>()) {
if ( doc["sensors"]["outdoor"]["type"].as<unsigned char>() >= 0 && doc["sensors"]["outdoor"]["type"].as<unsigned char>() <= 2 ) {
if (doc["sensors"]["outdoor"]["type"].as<unsigned char>() >= 0 && doc["sensors"]["outdoor"]["type"].as<unsigned char>() <= 2) {
settings.sensors.outdoor.type = doc["sensors"]["outdoor"]["type"].as<unsigned char>();
flag = true;
}
}
if (!doc["sensors"]["outdoor"]["offset"].isNull() && doc["sensors"]["outdoor"]["offset"].is<float>()) {
if ( doc["sensors"]["outdoor"]["offset"].as<float>() >= -10 && doc["sensors"]["outdoor"]["offset"].as<float>() <= 10 ) {
if (doc["sensors"]["outdoor"]["offset"].as<float>() >= -10 && doc["sensors"]["outdoor"]["offset"].as<float>() <= 10) {
settings.sensors.outdoor.offset = round(doc["sensors"]["outdoor"]["offset"].as<float>() * 1000) / 1000;
flag = true;
}
}
if (!doc["sensors"]["indoor"]["type"].isNull() && doc["sensors"]["indoor"]["type"].is<unsigned char>()) {
if ( doc["sensors"]["indoor"]["type"].as<unsigned char>() >= 1 && doc["sensors"]["indoor"]["type"].as<unsigned char>() <= 2 ) {
if (doc["sensors"]["indoor"]["type"].as<unsigned char>() >= 1 && doc["sensors"]["indoor"]["type"].as<unsigned char>() <= 2) {
settings.sensors.indoor.type = doc["sensors"]["indoor"]["type"].as<unsigned char>();
flag = true;
}
}
if (!doc["sensors"]["indoor"]["offset"].isNull() && doc["sensors"]["indoor"]["offset"].is<float>()) {
if ( doc["sensors"]["indoor"]["offset"].as<float>() >= -10 && doc["sensors"]["indoor"]["offset"].as<float>() <= 10 ) {
if (doc["sensors"]["indoor"]["offset"].as<float>() >= -10 && doc["sensors"]["indoor"]["offset"].as<float>() <= 10) {
settings.sensors.indoor.offset = round(doc["sensors"]["indoor"]["offset"].as<float>() * 1000) / 1000;
flag = true;
}
@@ -314,26 +314,22 @@ protected:
}
if (!doc["temperatures"]["indoor"].isNull() && doc["temperatures"]["indoor"].is<float>()) {
if ( settings.sensors.indoor.type == 1 && doc["temperatures"]["indoor"].as<float>() > -100 && doc["temperatures"]["indoor"].as<float>() < 100 ) {
if (settings.sensors.indoor.type == 1 && doc["temperatures"]["indoor"].as<float>() > -100 && doc["temperatures"]["indoor"].as<float>() < 100) {
vars.temperatures.indoor = round(doc["temperatures"]["indoor"].as<float>() * 100) / 100;
flag = true;
}
}
if (!doc["temperatures"]["outdoor"].isNull() && doc["temperatures"]["outdoor"].is<float>()) {
if ( settings.sensors.outdoor.type == 1 && doc["temperatures"]["outdoor"].as<float>() > -100 && doc["temperatures"]["outdoor"].as<float>() < 100 ) {
if (settings.sensors.outdoor.type == 1 && doc["temperatures"]["outdoor"].as<float>() > -100 && doc["temperatures"]["outdoor"].as<float>() < 100) {
vars.temperatures.outdoor = round(doc["temperatures"]["outdoor"].as<float>() * 100) / 100;
flag = true;
}
}
if (!doc["restart"].isNull() && doc["restart"].is<bool>() && doc["restart"].as<bool>()) {
DEBUG("Received restart message...");
eeSettings.updateNow();
Scheduler.delay(10000);
DEBUG("Restart...");
ESP.restart();
vars.parameters.restartAfterTime = 5000;
vars.parameters.restartSignalTime = millis();
}
if (flag) {
@@ -358,8 +354,7 @@ protected:
} else {
client.publish(getTopicPath("status").c_str(), vars.states.otStatus ? "online" : "offline");
}
forceARP();
prevPubVars = millis();
}
@@ -560,7 +555,7 @@ protected:
doc["sensors"]["outdoor"]["type"] = settings.sensors.outdoor.type;
doc["sensors"]["outdoor"]["offset"] = settings.sensors.outdoor.offset;
doc["sensors"]["indoor"]["type"] = settings.sensors.indoor.type;
doc["sensors"]["indoor"]["offset"] = settings.sensors.indoor.offset;

View File

@@ -3,11 +3,23 @@
CustomOpenTherm* ot;
class OpenThermTask: public Task {
class OpenThermTask : public Task {
public:
OpenThermTask(bool _enabled = false, unsigned long _interval = 0): Task(_enabled, _interval) {}
OpenThermTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
void static IRAM_ATTR handleInterrupt() {
ot->handleInterrupt();
}
protected:
const char* getTaskName() {
return "OpenTherm";
}
int getTaskCore() {
return 1;
}
void setup() {
vars.parameters.heatingMinTemp = settings.heating.minTemp;
vars.parameters.heatingMaxTemp = settings.heating.maxTemp;
@@ -16,8 +28,12 @@ protected:
ot = new CustomOpenTherm(settings.opentherm.inPin, settings.opentherm.outPin);
ot->begin(handleInterrupt, responseCallback);
ot->setHandleSendRequestCallback(sendRequestCallback);
ot->setHandleSendRequestCallback(this->sendRequestCallback);
ot->begin(OpenThermTask::handleInterrupt, this->responseCallback);
ot->setYieldCallback([](void* self) {
static_cast<OpenThermTask*>(self)->delay(10);
}, this);
#ifdef LED_OT_RX_PIN
pinMode(LED_OT_RX_PIN, OUTPUT);
@@ -28,7 +44,7 @@ protected:
static byte currentHeatingTemp, currentDHWTemp = 0;
unsigned long localResponse;
if ( setMasterMemberIdCode() ) {
if (setMasterMemberIdCode()) {
DEBUG_F("Slave member id code: %u\r\n", vars.parameters.slaveMemberIdCode);
DEBUG_F("Master member id code: %u\r\n", settings.opentherm.memberIdCode > 0 ? settings.opentherm.memberIdCode : vars.parameters.slaveMemberIdCode);
@@ -42,13 +58,13 @@ protected:
settings.opentherm.dhwPresent && settings.dhw.enable,
false, false, true, false, false
);
if (!ot->isValidResponse(localResponse)) {
WARN_F("Invalid response after setBoilerStatus: %s\r\n", ot->statusToString(ot->getLastResponseStatus()));
return;
}
if ( vars.parameters.heatingEnabled != heatingEnabled ) {
if (vars.parameters.heatingEnabled != heatingEnabled) {
vars.parameters.heatingEnabled = heatingEnabled;
INFO_F("Heating enabled: %s\r\n", heatingEnabled ? "on\0" : "off\0");
}
@@ -70,7 +86,7 @@ protected:
DEBUG_F("Master type: %u, version: %u\r\n", vars.parameters.masterType, vars.parameters.masterVersion);
DEBUG_F("Slave type: %u, version: %u\r\n", vars.parameters.slaveType, vars.parameters.slaveVersion);
if ( settings.opentherm.dhwPresent ) {
if (settings.opentherm.dhwPresent) {
updateMinMaxDhwTemp();
}
updateMinMaxHeatingTemp();
@@ -83,7 +99,7 @@ protected:
ot->sendBoilerReset();
}
if ( vars.states.diagnostic ) {
if (vars.states.diagnostic) {
ot->sendServiceReset();
}
@@ -92,28 +108,27 @@ protected:
}
updatePressure();
if ((settings.opentherm.dhwPresent && settings.dhw.enable) || settings.heating.enable || heatingEnabled ) {
if ((settings.opentherm.dhwPresent && settings.dhw.enable) || settings.heating.enable || heatingEnabled) {
updateModulationLevel();
} else {
vars.sensors.modulation = 0;
}
yield();
if ( settings.opentherm.dhwPresent && settings.dhw.enable ) {
if (settings.opentherm.dhwPresent) {
updateDHWTemp();
} else {
vars.temperatures.dhw = 0;
}
//if ( settings.heating.enable || heatingEnabled ) {
updateHeatingTemp();
//} else {
// vars.temperatures.heating = 0;
//}
updateHeatingTemp();
yield();
//
// Температура ГВС
byte newDHWTemp = settings.dhw.target;
if (settings.opentherm.dhwPresent && settings.dhw.enable && newDHWTemp != currentDHWTemp) {
if (settings.opentherm.dhwPresent && settings.dhw.enable && (needSetDhwTemp() || newDHWTemp != currentDHWTemp)) {
if (newDHWTemp < vars.parameters.dhwMinTemp || newDHWTemp > vars.parameters.dhwMaxTemp) {
newDHWTemp = constrain(newDHWTemp, vars.parameters.dhwMinTemp, vars.parameters.dhwMaxTemp);
}
@@ -123,6 +138,7 @@ protected:
// Записываем заданную температуру ГВС
if (ot->setDHWSetpoint(newDHWTemp)) {
currentDHWTemp = newDHWTemp;
dhwSetTempTime = millis();
} else {
WARN("Failed set DHW temp");
@@ -131,12 +147,13 @@ protected:
//
// Температура отопления
if (heatingEnabled && fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001) {
if (heatingEnabled && (needSetHeatingTemp() || fabs(vars.parameters.heatingSetpoint - currentHeatingTemp) > 0.0001)) {
INFO_F("Setting heating temp = %u \n", vars.parameters.heatingSetpoint);
// Записываем заданную температуру
if (ot->setBoilerTemperature(vars.parameters.heatingSetpoint)) {
currentHeatingTemp = vars.parameters.heatingSetpoint;
heatingSetTempTime = millis();
} else {
WARN("Failed set heating temp");
@@ -153,16 +170,12 @@ protected:
} else if (!pump && vars.temperatures.indoor - settings.heating.target - 0.0001 <= -(halfHyst)) {
pump = true;
}
} else if (!pump) {
pump = true;
}
}
void static IRAM_ATTR handleInterrupt() {
ot->handleInterrupt();
}
void static sendRequestCallback(unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) {
printRequestDetail(ot->getDataID(request), status, request, response, attempt);
}
@@ -200,13 +213,27 @@ protected:
}
protected:
unsigned short readyTime = 60000;
unsigned short dhwSetTempInterval = 60000;
unsigned short heatingSetTempInterval = 60000;
bool pump = true;
unsigned long prevUpdateNonEssentialVars = 0;
unsigned long startupTime = millis();
unsigned long dhwSetTempTime = 0;
unsigned long heatingSetTempTime = 0;
bool isReady() {
return millis() - startupTime > 60000;
return millis() - startupTime > readyTime;
}
bool needSetDhwTemp() {
return millis() - dhwSetTempTime > dhwSetTempInterval;
}
bool needSetHeatingTemp() {
return millis() - heatingSetTempTime > heatingSetTempInterval;
}
void static printRequestDetail(OpenThermMessageID id, OpenThermResponseStatus status, unsigned long request, unsigned long response, byte attempt) {
@@ -248,16 +275,16 @@ protected:
response & 0xFF
);*/
} else if ( settings.opentherm.memberIdCode <= 0 ) {
} else if (settings.opentherm.memberIdCode <= 0) {
return false;
}
response = ot->sendRequest(ot->buildRequest(
OpenThermRequestType::WRITE,
OpenThermMessageID::MConfigMMemberIDcode,
settings.opentherm.memberIdCode > 0 ? settings.opentherm.memberIdCode : vars.parameters.slaveMemberIdCode
));
return ot->isValidResponse(response);
}

View File

@@ -6,9 +6,9 @@ Equitherm etRegulator;
GyverPID pidRegulator(0, 0, 0);
PIDtuner pidTuner;
class RegulatorTask: public LeanTask {
class RegulatorTask : public LeanTask {
public:
RegulatorTask(bool _enabled = false, unsigned long _interval = 0): LeanTask(_enabled, _interval) {}
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
protected:
bool tunerInit = false;
@@ -18,8 +18,14 @@ protected:
float prevEtResult = 0;
float prevPidResult = 0;
void setup() {}
const char* getTaskName() {
return "Regulator";
}
int getTaskCore() {
return 1;
}
void loop() {
byte newTemp = vars.parameters.heatingSetpoint;
@@ -139,7 +145,7 @@ protected:
newTemp += prevPidResult;
}
} else if ( settings.pid.enable && !vars.parameters.heatingEnabled && prevPidResult != 0 ) {
} else if (settings.pid.enable && !vars.parameters.heatingEnabled && prevPidResult != 0) {
newTemp += prevPidResult;
}

View File

@@ -1,9 +1,9 @@
#include <OneWire.h>
#include <DallasTemperature.h>
class SensorsTask: public LeanTask {
class SensorsTask : public LeanTask {
public:
SensorsTask(bool _enabled = false, unsigned long _interval = 0): LeanTask(_enabled, _interval) {}
SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
protected:
OneWire* oneWireOutdoorSensor;
@@ -22,19 +22,22 @@ protected:
bool emptyIndoorTemp = true;
void setup() {}
const char* getTaskName() {
return "Sensors";
}
void loop() {
if ( settings.sensors.outdoor.type == 2 ) {
if (settings.sensors.outdoor.type == 2) {
outdoorTemperatureSensor();
}
if ( settings.sensors.indoor.type == 2 ) {
if (settings.sensors.indoor.type == 2) {
indoorTemperatureSensor();
}
}
void outdoorTemperatureSensor() {
if ( !initOutdoorSensor ) {
if (!initOutdoorSensor) {
oneWireOutdoorSensor = new OneWire(settings.sensors.outdoor.pin);
outdoorSensor = new DallasTemperature(oneWireOutdoorSensor);
outdoorSensor->begin();
@@ -46,12 +49,12 @@ protected:
}
unsigned long estimateConversionTime = millis() - startConversionTime;
if ( estimateConversionTime < outdoorSensor->millisToWaitForConversion() ) {
if (estimateConversionTime < outdoorSensor->millisToWaitForConversion()) {
return;
}
bool completed = outdoorSensor->isConversionComplete();
if ( !completed && estimateConversionTime >= 1000 ) {
if (!completed && estimateConversionTime >= 1000) {
// fail, retry
outdoorSensor->requestTemperatures();
startConversionTime = millis();
@@ -59,7 +62,7 @@ protected:
ERROR("[SENSORS][OUTDOOR] Could not read temperature data (no response)");
}
if ( !completed ) {
if (!completed) {
return;
}
@@ -91,7 +94,7 @@ protected:
}
void indoorTemperatureSensor() {
if ( !initIndoorSensor ) {
if (!initIndoorSensor) {
oneWireIndoorSensor = new OneWire(settings.sensors.indoor.pin);
indoorSensor = new DallasTemperature(oneWireIndoorSensor);
indoorSensor->begin();
@@ -103,12 +106,12 @@ protected:
}
unsigned long estimateConversionTime = millis() - startConversionTime;
if ( estimateConversionTime < indoorSensor->millisToWaitForConversion() ) {
if (estimateConversionTime < indoorSensor->millisToWaitForConversion()) {
return;
}
bool completed = indoorSensor->isConversionComplete();
if ( !completed && estimateConversionTime >= 1000 ) {
if (!completed && estimateConversionTime >= 1000) {
// fail, retry
indoorSensor->requestTemperatures();
startConversionTime = millis();
@@ -116,7 +119,7 @@ protected:
ERROR("[SENSORS][INDOOR] Could not read temperature data (no response)");
}
if ( !completed ) {
if (!completed) {
return;
}

View File

@@ -3,15 +3,15 @@ struct Settings {
char hostname[80] = "opentherm";
struct {
byte inPin = 4;
byte outPin = 5;
byte inPin = OT_IN_PIN_DEFAULT;
byte outPin = OT_OUT_PIN_DEFAULT;
unsigned int memberIdCode = 0;
bool dhwPresent = true;
} opentherm;
struct {
char server[80];
int port = 1883;
unsigned int port = 1883;
char user[32];
char password[32];
char prefix[80] = "opentherm";
@@ -60,14 +60,14 @@ struct Settings {
struct {
// 0 - boiler, 1 - manual, 2 - ds18b20
byte type = 0;
byte pin = 12;
byte pin = SENSOR_OUTDOOR_PIN_DEFAULT;
float offset = 0.0f;
} outdoor;
struct {
// 1 - manual, 2 - ds18b20
byte type = 1;
byte pin = 14;
byte pin = SENSOR_INDOOR_PIN_DEFAULT;
float offset = 0.0f;
} indoor;
} sensors;
@@ -106,12 +106,14 @@ struct Variables {
} temperatures;
struct {
unsigned long restartSignalTime = 0;
unsigned int restartAfterTime = 0;
bool heatingEnabled = false;
byte heatingMinTemp = 0;
byte heatingMaxTemp = 0;
byte heatingMinTemp = 20;
byte heatingMaxTemp = 90;
byte heatingSetpoint = 0.0f;
byte dhwMinTemp = 0;
byte dhwMaxTemp = 0;
byte dhwMinTemp = 30;
byte dhwMaxTemp = 60;
uint8_t slaveMemberIdCode;
uint8_t slaveType;
uint8_t slaveVersion;

View File

@@ -1,33 +1,59 @@
#define WM_MDNS
#include <WiFiManager.h>
#include <WiFiManagerParameters.h>
#include <netif/etharp.h>
// Wifimanager
WiFiManager wm;
WiFiManagerParameter* wmHostname;
WiFiManagerParameter* wmMqttServer;
IntParameter* wmMqttPort;
UnsignedIntParameter* wmMqttPort;
WiFiManagerParameter* wmMqttUser;
WiFiManagerParameter* wmMqttPassword;
WiFiManagerParameter* wmMqttPrefix;
IntParameter* wmMqttPublishInterval;
IntParameter* wmOtInPin;
IntParameter* wmOtOutPin;
IntParameter* wmOtMemberIdCode;
UnsignedIntParameter* wmMqttPublishInterval;
UnsignedIntParameter* wmOtInPin;
UnsignedIntParameter* wmOtOutPin;
UnsignedIntParameter* wmOtMemberIdCode;
CheckboxParameter* wmOtDHWPresent;
IntParameter* wmOutdoorSensorPin;
IntParameter* wmIndoorSensorPin;
UnsignedIntParameter* wmOutdoorSensorPin;
UnsignedIntParameter* wmIndoorSensorPin;
SeparatorParameter* wmSep1;
SeparatorParameter* wmSep2;
class WifiManagerTask : public Task {
public:
WifiManagerTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
protected:
bool connected = false;
unsigned long lastArpGratuitous = 0;
const char* getTaskName() {
return "WifiManager";
}
int getTaskCore() {
return 0;
}
void setup() {
//WiFi.mode(WIFI_STA);
wm.setDebugOutput(settings.debug);
//wm.setDebugOutput(settings.debug, WM_DEBUG_VERBOSE);
wm.setTitle("OpenTherm Gateway");
wm.setCustomMenuHTML(PSTR(
"<style>.wrap h1 {display: none;} .wrap h3 {display: none;} .nh {margin: 0 0 1em 0;} .nh .logo {font-size: 1.8em; margin: 0.5em; text-align: center;} .nh .links {text-align: center;}</style>"
"<div class=\"nh\">"
"<div class=\"logo\">OpenTherm Gateway</div>"
"<div class=\"links\"><a href=\"" OT_GATEWAY_REPO "\" target=\"_blank\">Repo</a> | <a href=\"" OT_GATEWAY_REPO "/issues\" target=\"_blank\">Issues</a> | <a href=\"" OT_GATEWAY_REPO "/releases\" target=\"_blank\">Releases</a> | <small>v" OT_GATEWAY_VERSION " (" __DATE__ ")</small></div>"
"</div>"
));
std::vector<const char *> menu = {"custom", "wifi", "param", "sep", "info", "update", "restart"};
wm.setMenu(menu);
wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80);
wm.addParameter(wmHostname);
@@ -35,7 +61,7 @@ protected:
wmMqttServer = new WiFiManagerParameter("mqtt_server", "MQTT server", settings.mqtt.server, 80);
wm.addParameter(wmMqttServer);
wmMqttPort = new IntParameter("mqtt_port", "MQTT port", settings.mqtt.port, 6);
wmMqttPort = new UnsignedIntParameter("mqtt_port", "MQTT port", settings.mqtt.port, 6);
wm.addParameter(wmMqttPort);
wmMqttUser = new WiFiManagerParameter("mqtt_user", "MQTT username", settings.mqtt.user, 32);
@@ -47,19 +73,19 @@ protected:
wmMqttPrefix = new WiFiManagerParameter("mqtt_prefix", "MQTT prefix", settings.mqtt.prefix, 32);
wm.addParameter(wmMqttPrefix);
wmMqttPublishInterval = new IntParameter("mqtt_publish_interval", "MQTT publish interval", settings.mqtt.interval, 5);
wmMqttPublishInterval = new UnsignedIntParameter("mqtt_publish_interval", "MQTT publish interval", settings.mqtt.interval, 5);
wm.addParameter(wmMqttPublishInterval);
wmSep1 = new SeparatorParameter();
wm.addParameter(wmSep1);
wmOtInPin = new IntParameter("ot_in_pin", "Opentherm pin IN", settings.opentherm.inPin, 2);
wmOtInPin = new UnsignedIntParameter("ot_in_pin", "Opentherm pin IN", settings.opentherm.inPin, 2);
wm.addParameter(wmOtInPin);
wmOtOutPin = new IntParameter("ot_out_pin", "Opentherm pin OUT", settings.opentherm.outPin, 2);
wmOtOutPin = new UnsignedIntParameter("ot_out_pin", "Opentherm pin OUT", settings.opentherm.outPin, 2);
wm.addParameter(wmOtOutPin);
wmOtMemberIdCode = new IntParameter("ot_member_id_code", "Opentherm member id", settings.opentherm.memberIdCode, 5);
wmOtMemberIdCode = new UnsignedIntParameter("ot_member_id_code", "Opentherm member id", settings.opentherm.memberIdCode, 5);
wm.addParameter(wmOtMemberIdCode);
wmOtDHWPresent = new CheckboxParameter("ot_dhw_present", "Opentherm DHW present", settings.opentherm.dhwPresent);
@@ -68,10 +94,10 @@ protected:
wmSep2 = new SeparatorParameter();
wm.addParameter(wmSep2);
wmOutdoorSensorPin = new IntParameter("outdoor_sensor_pin", "Outdoor sensor pin", settings.sensors.outdoor.pin, 2);
wmOutdoorSensorPin = new UnsignedIntParameter("outdoor_sensor_pin", "Outdoor sensor pin", settings.sensors.outdoor.pin, 2);
wm.addParameter(wmOutdoorSensorPin);
wmIndoorSensorPin = new IntParameter("indoor_sensor_pin", "Indoor sensor pin", settings.sensors.indoor.pin, 2);
wmIndoorSensorPin = new UnsignedIntParameter("indoor_sensor_pin", "Indoor sensor pin", settings.sensors.indoor.pin, 2);
wm.addParameter(wmIndoorSensorPin);
//wm.setCleanConnect(true);
@@ -82,52 +108,150 @@ protected:
wm.setAPClientCheck(true);
wm.setConfigPortalBlocking(false);
wm.setSaveParamsCallback(saveParamsCallback);
wm.setConfigPortalTimeout(180);
//wm.setDisableConfigPortal(false);
wm.setConfigPortalTimeout(wm.getWiFiIsSaved() ? 180 : 0);
wm.setDisableConfigPortal(false);
wm.autoConnect(AP_SSID, AP_PASSWORD);
}
void loop() {
/*if (WiFi.status() != WL_CONNECTED && !wm.getWebPortalActive() && !wm.getConfigPortalActive()) {
wm.autoConnect(AP_SSID);
}*/
if (connected && WiFi.status() != WL_CONNECTED) {
connected = false;
if (wm.getWebPortalActive()) {
wm.stopWebPortal();
}
#ifdef USE_TELNET
TelnetStream.stop();
#endif
INFO("[wifi] Disconnected");
} else if (!connected && WiFi.status() == WL_CONNECTED) {
connected = true;
wm.setConfigPortalTimeout(180);
if (wm.getConfigPortalActive()) {
wm.stopConfigPortal();
}
if (!wm.getWebPortalActive()) {
wm.startWebPortal();
}
#ifdef USE_TELNET
TelnetStream.begin();
#endif
INFO_F("[wifi] Connected. IP address: %s, RSSI: %d\n", WiFi.localIP().toString().c_str(), WiFi.RSSI());
}
if (WiFi.status() == WL_CONNECTED && !wm.getWebPortalActive() && !wm.getConfigPortalActive()) {
wm.startWebPortal();
#if defined(ESP8266)
if (connected && millis() - lastArpGratuitous > 60000) {
arpGratuitous();
lastArpGratuitous = millis();
}
#endif
wm.process();
}
void static saveParamsCallback() {
strcpy(settings.hostname, wmHostname->getValue());
strcpy(settings.mqtt.server, wmMqttServer->getValue());
settings.mqtt.port = wmMqttPort->getValue();
strcpy(settings.mqtt.user, wmMqttUser->getValue());
strcpy(settings.mqtt.password, wmMqttPassword->getValue());
strcpy(settings.mqtt.prefix, wmMqttPrefix->getValue());
settings.mqtt.interval = wmMqttPublishInterval->getValue();
settings.opentherm.inPin = wmOtInPin->getValue();
settings.opentherm.outPin = wmOtOutPin->getValue();
settings.opentherm.memberIdCode = wmOtMemberIdCode->getValue();
settings.opentherm.dhwPresent = wmOtDHWPresent->getCheckboxValue();
settings.sensors.outdoor.pin = wmOutdoorSensorPin->getValue();
settings.sensors.indoor.pin = wmIndoorSensorPin->getValue();
static void saveParamsCallback() {
bool changed = false;
bool needRestart = false;
if (strcmp(wmHostname->getValue(), settings.hostname) != 0) {
changed = true;
needRestart = true;
strcpy(settings.hostname, wmHostname->getValue());
}
if (strcmp(wmMqttServer->getValue(), settings.mqtt.server) != 0) {
changed = true;
strcpy(settings.mqtt.server, wmMqttServer->getValue());
}
if (wmMqttPort->getValue() != settings.mqtt.port) {
changed = true;
settings.mqtt.port = wmMqttPort->getValue();
}
if (strcmp(wmMqttUser->getValue(), settings.mqtt.user) != 0) {
changed = true;
strcpy(settings.mqtt.user, wmMqttUser->getValue());
}
if (strcmp(wmMqttPassword->getValue(), settings.mqtt.password) != 0) {
changed = true;
strcpy(settings.mqtt.password, wmMqttPassword->getValue());
}
if (strcmp(wmMqttPrefix->getValue(), settings.mqtt.prefix) != 0) {
changed = true;
strcpy(settings.mqtt.prefix, wmMqttPrefix->getValue());
}
if (wmMqttPublishInterval->getValue() != settings.mqtt.interval) {
changed = true;
settings.mqtt.interval = wmMqttPublishInterval->getValue();
}
if (wmOtInPin->getValue() != settings.opentherm.inPin) {
changed = true;
needRestart = true;
settings.opentherm.inPin = wmOtInPin->getValue();
}
if (wmOtOutPin->getValue() != settings.opentherm.outPin) {
changed = true;
needRestart = true;
settings.opentherm.outPin = wmOtOutPin->getValue();
}
if (wmOtMemberIdCode->getValue() != settings.opentherm.memberIdCode) {
changed = true;
settings.opentherm.memberIdCode = wmOtMemberIdCode->getValue();
}
if (wmOtDHWPresent->getCheckboxValue() != settings.opentherm.dhwPresent) {
changed = true;
settings.opentherm.dhwPresent = wmOtDHWPresent->getCheckboxValue();
}
if (wmOutdoorSensorPin->getValue() != settings.sensors.outdoor.pin) {
changed = true;
needRestart = true;
settings.sensors.outdoor.pin = wmOutdoorSensorPin->getValue();
}
if (wmIndoorSensorPin->getValue() != settings.sensors.indoor.pin) {
changed = true;
needRestart = true;
settings.sensors.indoor.pin = wmIndoorSensorPin->getValue();
}
if (!changed) {
return;
}
if (needRestart) {
vars.parameters.restartAfterTime = 5000;
vars.parameters.restartSignalTime = millis();
}
INFO_F(
"New settings:\r\n"
@@ -157,9 +281,16 @@ protected:
settings.sensors.outdoor.pin,
settings.sensors.indoor.pin
);
eeSettings.updateNow();
INFO(F("Settings saved"));
}
bool connected = false;
static void arpGratuitous() {
struct netif* netif = netif_list;
while (netif) {
etharp_gratuitous(netif);
netif = netif->next;
}
}
};

View File

@@ -1,4 +1,5 @@
#define OT_GATEWAY_VERSION "1.3.1"
#define OT_GATEWAY_VERSION "1.3.3"
#define OT_GATEWAY_REPO "https://github.com/Laxilef/OTGateway"
#define AP_SSID "OpenTherm Gateway"
#define AP_PASSWORD "otgateway123456"
#define USE_TELNET
@@ -11,16 +12,28 @@
#define EXT_SENSORS_INTERVAL 5000
#define EXT_SENSORS_FILTER_K 0.15
#define DS_CHECK_CRC true
#define DS_CRC_USE_TABLE true
#define LED_STATUS_PIN 13
#define LED_OT_RX_PIN 15
#define CONFIG_URL "http://%s/"
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
#ifndef OT_IN_PIN_DEFAULT
#define OT_IN_PIN_DEFAULT 0
#endif
#ifndef OT_OUT_PIN_DEFAULT
#define OT_OUT_PIN_DEFAULT 0
#endif
#ifndef SENSOR_OUTDOOR_PIN_DEFAULT
#define SENSOR_OUTDOOR_PIN_DEFAULT 0
#endif
#ifndef SENSOR_INDOOR_PIN_DEFAULT
#define SENSOR_INDOOR_PIN_DEFAULT 0
#endif
#ifdef USE_TELNET
#define INFO_STREAM TelnetStream
#define WARN_STREAM TelnetStream
@@ -43,4 +56,4 @@
#define DEBUG(...) DEBUG_STREAM.print("\r[DEBUG] "); DEBUG_STREAM.println(__VA_ARGS__);
#define DEBUG_F(...) DEBUG_STREAM.print("\r[DEBUG] "); DEBUG_STREAM.printf(__VA_ARGS__);
char buffer[120];
char buffer[255];

View File

@@ -3,13 +3,22 @@
#include <ArduinoJson.h>
#include <TelnetStream.h>
#include <EEManager.h>
#include <Scheduler.h>
#include <Task.h>
#include <LeanTask.h>
#include "Settings.h"
EEManager eeSettings(settings, 30000);
#if defined(ESP32)
#include <ESP32Scheduler.h>
#include <Task.h>
#include <LeanTask.h>
#elif defined(ESP8266)
#include <Scheduler.h>
#include <Task.h>
#include <LeanTask.h>
#elif
#error Wrong board. Supported boards: esp8266, esp32
#endif
#include "WifiManagerTask.h"
#include "MqttTask.h"
#include "OpenThermTask.h"
@@ -27,20 +36,17 @@ MainTask* tMain;
void setup() {
#ifdef USE_TELNET
TelnetStream.begin();
delay(1000);
#else
Serial.begin(115200);
Serial.println("\n\n");
#endif
#ifndef USE_TELNET
Serial.begin(115200);
Serial.println("\n\n");
#endif
EEPROM.begin(eeSettings.blockSize());
uint8_t eeSettingsResult = eeSettings.begin(0, 's');
if (eeSettingsResult == 0) {
INFO("Settings loaded");
if ( strcmp(SETTINGS_VALID_VALUE, settings.validationValue) != 0 ) {
if (strcmp(SETTINGS_VALID_VALUE, settings.validationValue) != 0) {
INFO("Settings not valid, reset and restart...");
eeSettings.reset();
delay(1000);
@@ -69,10 +75,14 @@ void setup() {
tRegulator = new RegulatorTask(true, 10000);
Scheduler.start(tRegulator);
tMain = new MainTask(true);
tMain = new MainTask(true, 50);
Scheduler.start(tMain);
Scheduler.begin();
}
void loop() {}
void loop() {
#if defined(ESP32)
vTaskDelete(NULL);
#endif
}

21
tools/build.py Normal file
View File

@@ -0,0 +1,21 @@
import shutil
import os
Import("env")
def post_build(source, target, env):
if os.path.exists(os.path.join(env["PROJECT_DIR"], "build")) == False:
return
files = {
env.subst("$BUILD_DIR/${PROGNAME}.bin"): "firmware_%s_%s.bin" % (env["PIOENV"], env.GetProjectOption("version")),
env.subst("$BUILD_DIR/${PROGNAME}.factory.bin"): "firmware_%s_%s.factory.bin" % (env["PIOENV"], env.GetProjectOption("version")),
}
for src in files:
if os.path.exists(src):
dest = os.path.join(env["PROJECT_DIR"], "build", files[src])
print("Copying '%s' to '%s'" % (src, dest))
shutil.copy(src, dest)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", post_build)

79
tools/esp32.py Normal file
View File

@@ -0,0 +1,79 @@
# Source: https://raw.githubusercontent.com/letscontrolit/ESPEasy/mega/tools/pio/post_esp32.py
# Part of ESPEasy build toolchain.
#
# Combines separate bin files with their respective offsets into a single file
# This single file must then be flashed to an ESP32 node with 0 offset.
#
# Original implementation: Bartłomiej Zimoń (@uzi18)
# Maintainer: Gijs Noorlander (@TD-er)
#
# Special thanks to @Jason2866 (Tasmota) for helping debug flashing to >4MB flash
# Thanks @jesserockz (esphome) for adapting to use esptool.py with merge_bin
#
# Typical layout of the generated file:
# Offset | File
# - 0x1000 | ~\.platformio\packages\framework-arduinoespressif32\tools\sdk\esp32\bin\bootloader_dout_40m.bin
# - 0x8000 | ~\ESPEasy\.pio\build\<env name>\partitions.bin
# - 0xe000 | ~\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin
# - 0x10000 | ~\ESPEasy\.pio\build\<env name>/<built binary>.bin
Import("env")
platform = env.PioPlatform()
import sys
from os.path import join
sys.path.append(join(platform.get_package_dir("tool-esptoolpy")))
import esptool
def esp32_create_combined_bin(source, target, env):
print("Generating combined binary for serial flashing")
# The offset from begin of the file where the app0 partition starts
# This is defined in the partition .csv file
app_offset = 0x10000
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}.factory.bin")
sections = env.subst(env.get("FLASH_EXTRA_IMAGES"))
firmware_name = env.subst("$BUILD_DIR/${PROGNAME}.bin")
chip = env.get("BOARD_MCU")
flash_size = env.BoardConfig().get("upload.flash_size")
flash_freq = env.BoardConfig().get("build.f_flash", '40m')
flash_freq = flash_freq.replace('000000L', 'm')
flash_mode = env.BoardConfig().get("build.flash_mode", "dio")
memory_type = env.BoardConfig().get("build.arduino.memory_type", "qio_qspi")
if flash_mode == "qio" or flash_mode == "qout":
flash_mode = "dio"
if memory_type == "opi_opi" or memory_type == "opi_qspi":
flash_mode = "dout"
cmd = [
"--chip",
chip,
"merge_bin",
"-o",
new_file_name,
"--flash_mode",
flash_mode,
"--flash_freq",
flash_freq,
"--flash_size",
flash_size,
]
print(" Offset | File")
for section in sections:
sect_adr, sect_file = section.split(" ", 1)
print(f" - {sect_adr} | {sect_file}")
cmd += [sect_adr, sect_file]
print(f" - {hex(app_offset)} | {firmware_name}")
cmd += [hex(app_offset), firmware_name]
print('Using esptool.py arguments: %s' % ' '.join(cmd))
esptool.main(cmd)
env.AddPostAction("$BUILD_DIR/${PROGNAME}.bin", esp32_create_combined_bin)