mirror of
https://github.com/Laxilef/OTGateway.git
synced 2025-12-11 18:54:28 +05:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4b9ebeaa40 | ||
|
|
76eaec10ea | ||
|
|
21de692888 | ||
|
|
ce318b8cde | ||
|
|
77b0859cc8 | ||
|
|
3fcb17f2c3 | ||
|
|
6204b46c17 | ||
|
|
b5760eb314 | ||
|
|
eedbd7b80a | ||
|
|
e9bf4c4bd5 | ||
|
|
a255dda8dd | ||
|
|
6de6f7d138 | ||
|
|
5e8916b254 | ||
|
|
7db49350a2 | ||
|
|
8e2a70ec04 | ||
|
|
b2ff71c59a | ||
|
|
3660b89f7c |
34
README.md
34
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!
|
||||
@@ -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)
|
||||
|
||||
|
||||
14
build.py
14
build.py
@@ -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)
|
||||
@@ -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);
|
||||
|
||||
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,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";
|
||||
|
||||
130
platformio.ini
130
platformio.ini
@@ -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
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
133
src/MainTask.h
133
src/MainTask.h
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -22,7 +22,10 @@ protected:
|
||||
bool emptyIndoorTemp = true;
|
||||
|
||||
|
||||
void setup() {}
|
||||
const char* getTaskName() {
|
||||
return "Sensors";
|
||||
}
|
||||
|
||||
void loop() {
|
||||
if (settings.sensors.outdoor.type == 2) {
|
||||
outdoorTemperatureSensor();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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.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];
|
||||
34
src/main.cpp
34
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,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
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