mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-11 18:54:28 +05:00
Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b9ebeaa40 | ||
|
|
76eaec10ea | ||
|
|
21de692888 | ||
|
|
ce318b8cde | ||
|
|
77b0859cc8 | ||
|
|
3fcb17f2c3 | ||
|
|
6204b46c17 | ||
|
|
b5760eb314 | ||
|
|
eedbd7b80a | ||
|
|
e9bf4c4bd5 | ||
|
|
a255dda8dd | ||
|
|
6de6f7d138 | ||
|
|
5e8916b254 | ||
|
|
7db49350a2 | ||
|
|
8e2a70ec04 | ||
|
|
b2ff71c59a | ||
|
|
3660b89f7c | ||
|
|
5c0dfc544e | ||
|
|
5fba94312b | ||
|
|
62bea87f8a | ||
|
|
f52aa8e889 | ||
|
|
df8354866f | ||
|
|
6242db7a29 | ||
|
|
dc00fdcdb6 | ||
|
|
2615e9106e | ||
|
|
0f60a07a71 | ||
|
|
f8750373d4 | ||
|
|
d5a92c47c7 | ||
|
|
bc91168bbf | ||
|
|
96c1a187cd |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.pio
|
||||
.vscode
|
||||
.vscode
|
||||
build/*
|
||||
36
README.md
36
README.md
@@ -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)
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
89
lib/HomeAssistantHelper/HomeAssistantHelper.h
Normal file
89
lib/HomeAssistantHelper/HomeAssistantHelper.h
Normal 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 = "";
|
||||
};
|
||||
37
lib/HomeAssistantHelper/strings.h
Normal file
37
lib/HomeAssistantHelper/strings.h
Normal 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";
|
||||
@@ -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>") {}
|
||||
};
|
||||
140
platformio.ini
140
platformio.ini
@@ -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
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
141
src/MainTask.h
141
src/MainTask.h
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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];
|
||||
36
src/main.cpp
36
src/main.cpp
@@ -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
21
tools/build.py
Normal 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
79
tools/esp32.py
Normal 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)
|
||||
Reference in New Issue
Block a user