17 Commits
1.3.2 ... 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
20 changed files with 1928 additions and 1903 deletions

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!
@@ -36,7 +36,9 @@
| [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 |
| [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%" />
@@ -44,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)
@@ -159,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>
@@ -191,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)
@@ -203,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,14 +0,0 @@
import shutil
import os
Import("env")
def post_build(source, target, env):
src = target[0].get_abspath()
dest = os.path.join(env["PROJECT_DIR"], "build", "firmware_%s_%s.bin" % (env.GetProjectOption("board"), env.GetProjectOption("version")))
#print("dest:"+dest)
#print("source:"+src)
shutil.copy(src, dest)
env.AddPostAction("$BUILD_DIR/firmware.bin", post_build)

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);

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,6 +9,17 @@ 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";

View File

@@ -9,10 +9,8 @@
; https://docs.platformio.org/page/projectconf.html
[env]
platform = espressif8266
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
@@ -20,20 +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
extra_scripts = post:build.py
version = 1.3.2
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,3 +1,5 @@
#include <Blinker.h>
extern MqttTask* tMqtt;
extern SensorsTask* tSensors;
extern OpenThermTask* tOt;
@@ -7,12 +9,34 @@ public:
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,8 +44,19 @@ 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();
}
@@ -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,11 +1,10 @@
#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 {
@@ -16,12 +15,22 @@ 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);
@@ -57,7 +66,6 @@ protected:
}
}
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;
@@ -192,21 +192,21 @@ 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;
}
@@ -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;
}
@@ -328,12 +328,8 @@ protected:
}
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();
}

View File

@@ -7,7 +7,19 @@ class OpenThermTask : public Task {
public:
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);
@@ -160,10 +176,6 @@ protected:
}
}
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);
}

View File

@@ -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;

View File

@@ -22,7 +22,10 @@ protected:
bool emptyIndoorTemp = true;
void setup() {}
const char* getTaskName() {
return "Sensors";
}
void loop() {
if (settings.sensors.outdoor.type == 2) {
outdoorTemperatureSensor();

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,6 +106,8 @@ struct Variables {
} temperatures;
struct {
unsigned long restartSignalTime = 0;
unsigned int restartAfterTime = 0;
bool heatingEnabled = false;
byte heatingMinTemp = 20;
byte heatingMaxTemp = 90;

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.2"
#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
@@ -12,13 +13,27 @@
#define EXT_SENSORS_INTERVAL 5000
#define EXT_SENSORS_FILTER_K 0.15
#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
@@ -41,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,13 +36,10 @@ 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');
@@ -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)