Compatible with ESP32

This commit is contained in:
Yurii
2023-11-11 05:01:36 +03:00
parent 6de6f7d138
commit a255dda8dd
13 changed files with 264 additions and 66 deletions

View File

@@ -1,17 +0,0 @@
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
src = target[0].get_abspath()
dest = os.path.join(env["PROJECT_DIR"], "build", "firmware_%s_%s.bin" % (env.GetProjectOption("board"), env.GetProjectOption("version")))
#print("dest:"+dest)
#print("source:"+src)
shutil.copy(src, dest)
env.AddPostAction("$BUILD_DIR/firmware.bin", post_build)

View File

@@ -9,10 +9,8 @@
; https://docs.platformio.org/page/projectconf.html ; https://docs.platformio.org/page/projectconf.html
[env] [env]
platform = espressif8266
framework = arduino framework = arduino
lib_deps = lib_deps =
nrwiersma/ESP8266Scheduler@^1.0
arduino-libraries/NTPClient@^3.2.1 arduino-libraries/NTPClient@^3.2.1
bblanchon/ArduinoJson@^6.20.0 bblanchon/ArduinoJson@^6.20.0
ihormelnyk/OpenTherm Library@^1.1.4 ihormelnyk/OpenTherm Library@^1.1.4
@@ -22,18 +20,72 @@ lib_deps =
gyverlibs/GyverPID@^3.3 gyverlibs/GyverPID@^3.3
milesburton/DallasTemperature@^3.11.0 milesburton/DallasTemperature@^3.11.0
https://github.com/Laxilef/WiFiManager/archive/refs/heads/patch-1.zip https://github.com/Laxilef/WiFiManager/archive/refs/heads/patch-1.zip
; https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2 ;https://github.com/tzapu/WiFiManager.git#v2.0.16-rc.2
build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH build_flags = -D PIO_FRAMEWORK_ARDUINO_LWIP2_HIGHER_BANDWIDTH -mtext-section-literals
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
extra_scripts = post:build.py version = 1.3.3
version = 1.3.2
; Defaults
[esp8266_defaults]
platform = espressif8266
lib_deps =
${env.lib_deps}
nrwiersma/ESP8266Scheduler@^1.0
lib_ignore =
extra_scripts =
post:tools/build.py
[esp32_defaults]
platform = espressif32
lib_deps =
${env.lib_deps}
laxilef/ESP32Scheduler@^1.0
lib_ignore =
extra_scripts =
post:tools/esp32.py
post:tools/build.py
; Boards
[env:d1_mini] [env:d1_mini]
platform = ${esp8266_defaults.platform}
board = d1_mini board = d1_mini
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
[env:d1_mini_lite] [env:d1_mini_lite]
platform = ${esp8266_defaults.platform}
board = d1_mini_lite board = d1_mini_lite
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
[env:d1_mini_pro] [env:d1_mini_pro]
platform = ${esp8266_defaults.platform}
board = d1_mini_pro board = d1_mini_pro
lib_deps = ${esp8266_defaults.lib_deps}
lib_ignore = ${esp8266_defaults.lib_ignore}
extra_scripts = ${esp8266_defaults.extra_scripts}
[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}
[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}
[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}

View File

@@ -7,9 +7,12 @@ public:
MainTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {} MainTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
protected: protected:
const char* taskName = "Main task";
const int taskCore = 2;
unsigned long lastHeapInfo = 0; unsigned long lastHeapInfo = 0;
unsigned long firstFailConnect = 0; unsigned long firstFailConnect = 0;
unsigned short minFreeHeapSize = 65535; unsigned int minFreeHeapSize = RAM_SIZE;
void setup() { void setup() {
pinMode(LED_STATUS_PIN, OUTPUT); pinMode(LED_STATUS_PIN, OUTPUT);
@@ -21,7 +24,7 @@ protected:
} }
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
if (!tMqtt->isEnabled()) { if (!tMqtt->isEnabled() && strlen(settings.mqtt.server) > 0) {
tMqtt->enable(); tMqtt->enable();
} }
@@ -65,15 +68,16 @@ protected:
#endif #endif
if (settings.debug) { if (settings.debug) {
unsigned short freeHeapSize = ESP.getFreeHeap(); unsigned int freeHeapSize = ESP.getFreeHeap();
unsigned short minFreeHeapSizeDiff = 0; unsigned int minFreeHeapSizeDiff = 0;
if (freeHeapSize < minFreeHeapSize) { if (freeHeapSize < minFreeHeapSize) {
minFreeHeapSizeDiff = minFreeHeapSize - freeHeapSize; minFreeHeapSizeDiff = minFreeHeapSize - freeHeapSize;
minFreeHeapSize = freeHeapSize; minFreeHeapSize = freeHeapSize;
} }
if (millis() - lastHeapInfo > 10000 || minFreeHeapSizeDiff > 0) { 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, RAM_SIZE, minFreeHeapSize, minFreeHeapSizeDiff);
lastHeapInfo = millis(); lastHeapInfo = millis();
} }
} }

View File

@@ -1,6 +1,5 @@
#include <WiFiClient.h> #include <WiFiClient.h>
#include <PubSubClient.h> #include <PubSubClient.h>
#include <netif/etharp.h>
#include "HaHelper.h" #include "HaHelper.h"
WiFiClient espClient; WiFiClient espClient;
@@ -13,6 +12,9 @@ public:
MqttTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {} MqttTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
protected: protected:
const char* taskName = "Mqtt task";
const int taskCore = 1;
unsigned long lastReconnectAttempt = 0; unsigned long lastReconnectAttempt = 0;
unsigned long firstFailConnect = 0; unsigned long firstFailConnect = 0;
@@ -20,7 +22,7 @@ protected:
DEBUG("[MQTT] Started"); DEBUG("[MQTT] Started");
client.setCallback(__callback); client.setCallback(__callback);
haHelper.setPrefix(settings.mqtt.prefix); haHelper.setDevicePrefix(settings.mqtt.prefix);
haHelper.setDeviceVersion(OT_GATEWAY_VERSION); haHelper.setDeviceVersion(OT_GATEWAY_VERSION);
haHelper.setDeviceModel("Opentherm Gateway"); haHelper.setDeviceModel("Opentherm Gateway");
haHelper.setDeviceName("Opentherm Gateway"); haHelper.setDeviceName("Opentherm Gateway");
@@ -59,7 +61,6 @@ protected:
} }
} }
forceARP();
lastReconnectAttempt = millis(); lastReconnectAttempt = millis();
} }
} }
@@ -79,14 +80,6 @@ protected:
} }
static void forceARP() {
struct netif* netif = netif_list;
while (netif) {
etharp_gratuitous(netif);
netif = netif->next;
}
}
static bool updateSettings(JsonDocument& doc) { static bool updateSettings(JsonDocument& doc) {
bool flag = false; bool flag = false;
@@ -359,8 +352,7 @@ protected:
} else { } else {
client.publish(getTopicPath("status").c_str(), vars.states.otStatus ? "online" : "offline"); client.publish(getTopicPath("status").c_str(), vars.states.otStatus ? "online" : "offline");
} }
forceARP();
prevPubVars = millis(); prevPubVars = millis();
} }

View File

@@ -7,7 +7,14 @@ class OpenThermTask : public Task {
public: 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: protected:
const char* taskName = "OpenTherm task";
const int taskCore = 2;
void setup() { void setup() {
vars.parameters.heatingMinTemp = settings.heating.minTemp; vars.parameters.heatingMinTemp = settings.heating.minTemp;
vars.parameters.heatingMaxTemp = settings.heating.maxTemp; vars.parameters.heatingMaxTemp = settings.heating.maxTemp;
@@ -16,8 +23,12 @@ protected:
ot = new CustomOpenTherm(settings.opentherm.inPin, settings.opentherm.outPin); ot = new CustomOpenTherm(settings.opentherm.inPin, settings.opentherm.outPin);
ot->begin(handleInterrupt, responseCallback); ot->setHandleSendRequestCallback(this->sendRequestCallback);
ot->setHandleSendRequestCallback(sendRequestCallback); ot->begin(OpenThermTask::handleInterrupt, this->responseCallback);
ot->setYieldCallback([](void* self) {
static_cast<OpenThermTask*>(self)->yield();
}, this);
#ifdef LED_OT_RX_PIN #ifdef LED_OT_RX_PIN
pinMode(LED_OT_RX_PIN, OUTPUT); pinMode(LED_OT_RX_PIN, OUTPUT);
@@ -160,10 +171,6 @@ protected:
} }
} }
void static IRAM_ATTR handleInterrupt() {
ot->handleInterrupt();
}
void static sendRequestCallback(unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) { void static sendRequestCallback(unsigned long request, unsigned long response, OpenThermResponseStatus status, byte attempt) {
printRequestDetail(ot->getDataID(request), status, request, response, attempt); printRequestDetail(ot->getDataID(request), status, request, response, attempt);
} }

View File

@@ -11,6 +11,9 @@ public:
RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {} RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
protected: protected:
const char* taskName = "Regulator task";
const int taskCore = 2;
bool tunerInit = false; bool tunerInit = false;
byte tunerState = 0; byte tunerState = 0;
byte tunerRegulator = 0; byte tunerRegulator = 0;

View File

@@ -6,6 +6,9 @@ public:
SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {} SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {}
protected: protected:
const char* taskName = "Sensors task";
const int taskCore = 2;
OneWire* oneWireOutdoorSensor; OneWire* oneWireOutdoorSensor;
OneWire* oneWireIndoorSensor; OneWire* oneWireIndoorSensor;

View File

@@ -3,8 +3,8 @@ struct Settings {
char hostname[80] = "opentherm"; char hostname[80] = "opentherm";
struct { struct {
byte inPin = 4; byte inPin = OT_IN_PIN_DEFAULT;
byte outPin = 5; byte outPin = OT_OUT_PIN_DEFAULT;
unsigned int memberIdCode = 0; unsigned int memberIdCode = 0;
bool dhwPresent = true; bool dhwPresent = true;
} opentherm; } opentherm;
@@ -60,14 +60,14 @@ struct Settings {
struct { struct {
// 0 - boiler, 1 - manual, 2 - ds18b20 // 0 - boiler, 1 - manual, 2 - ds18b20
byte type = 0; byte type = 0;
byte pin = 12; byte pin = SENSOR_OUTDOOR_PIN_DEFAULT;
float offset = 0.0f; float offset = 0.0f;
} outdoor; } outdoor;
struct { struct {
// 1 - manual, 2 - ds18b20 // 1 - manual, 2 - ds18b20
byte type = 1; byte type = 1;
byte pin = 14; byte pin = SENSOR_INDOOR_PIN_DEFAULT;
float offset = 0.0f; float offset = 0.0f;
} indoor; } indoor;
} sensors; } sensors;

View File

@@ -1,5 +1,7 @@
#define WM_MDNS
#include <WiFiManager.h> #include <WiFiManager.h>
#include <WiFiManagerParameters.h> #include <WiFiManagerParameters.h>
#include <netif/etharp.h>
// Wifimanager // Wifimanager
WiFiManager wm; WiFiManager wm;
@@ -25,8 +27,12 @@ public:
WifiManagerTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {} WifiManagerTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {}
protected: protected:
const char* taskName = "WifiManager";
const int taskCore = 1;
bool connected = false;
unsigned long lastArpGratuitous = 0;
void setup() { void setup() {
//WiFi.mode(WIFI_STA);
wm.setDebugOutput(settings.debug); wm.setDebugOutput(settings.debug);
wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80); wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80);
@@ -95,6 +101,11 @@ protected:
if (connected && WiFi.status() != WL_CONNECTED) { if (connected && WiFi.status() != WL_CONNECTED) {
connected = false; connected = false;
#ifdef USE_TELNET
TelnetStream.stop();
#endif
INFO("[wifi] Disconnected"); INFO("[wifi] Disconnected");
} else if (!connected && WiFi.status() == WL_CONNECTED) { } else if (!connected && WiFi.status() == WL_CONNECTED) {
@@ -104,6 +115,10 @@ protected:
wm.stopConfigPortal(); wm.stopConfigPortal();
} }
#ifdef USE_TELNET
TelnetStream.begin();
#endif
INFO_F("[wifi] Connected. IP address: %s, RSSI: %d\n", WiFi.localIP().toString().c_str(), WiFi.RSSI()); INFO_F("[wifi] Connected. IP address: %s, RSSI: %d\n", WiFi.localIP().toString().c_str(), WiFi.RSSI());
} }
@@ -111,10 +126,17 @@ protected:
wm.startWebPortal(); wm.startWebPortal();
} }
#if defined(ESP8266)
if ( connected && millis() - lastArpGratuitous > 60000 ) {
arpGratuitous();
lastArpGratuitous = millis();
}
#endif
wm.process(); wm.process();
} }
void static saveParamsCallback() { static void saveParamsCallback() {
strcpy(settings.hostname, wmHostname->getValue()); strcpy(settings.hostname, wmHostname->getValue());
strcpy(settings.mqtt.server, wmMqttServer->getValue()); strcpy(settings.mqtt.server, wmMqttServer->getValue());
settings.mqtt.port = wmMqttPort->getValue(); settings.mqtt.port = wmMqttPort->getValue();
@@ -161,5 +183,11 @@ protected:
INFO(F("Settings saved")); INFO(F("Settings saved"));
} }
bool connected = false; static void arpGratuitous() {
struct netif* netif = netif_list;
while (netif) {
etharp_gratuitous(netif);
netif = netif->next;
}
}
}; };

View File

@@ -1,4 +1,4 @@
#define OT_GATEWAY_VERSION "1.3.2" #define OT_GATEWAY_VERSION "1.3.3"
#define AP_SSID "OpenTherm Gateway" #define AP_SSID "OpenTherm Gateway"
#define AP_PASSWORD "otgateway123456" #define AP_PASSWORD "otgateway123456"
#define USE_TELNET #define USE_TELNET
@@ -18,6 +18,26 @@
#define CONFIG_URL "http://%s/" #define CONFIG_URL "http://%s/"
#define SETTINGS_VALID_VALUE "stvalid" // only 8 chars! #define SETTINGS_VALID_VALUE "stvalid" // only 8 chars!
#if defined(ESP8266)
#define RAM_SIZE 81920
#define OT_IN_PIN_DEFAULT 4
#define OT_OUT_PIN_DEFAULT 5
#define SENSOR_OUTDOOR_PIN_DEFAULT 12
#define SENSOR_INDOOR_PIN_DEFAULT 14
#elif defined(ESP32)
#define RAM_SIZE 327680
#define OT_IN_PIN_DEFAULT 17
#define OT_OUT_PIN_DEFAULT 21
#define SENSOR_OUTDOOR_PIN_DEFAULT 2
#define SENSOR_INDOOR_PIN_DEFAULT 4
#else
#define RAM_SIZE 999999
#define OT_IN_PIN_DEFAULT 0
#define OT_OUT_PIN_DEFAULT 0
#define SENSOR_OUTDOOR_PIN_DEFAULT 0
#define SENSOR_INDOOR_PIN_DEFAULT 0
#endif
#ifdef USE_TELNET #ifdef USE_TELNET
#define INFO_STREAM TelnetStream #define INFO_STREAM TelnetStream

View File

@@ -3,13 +3,22 @@
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <TelnetStream.h> #include <TelnetStream.h>
#include <EEManager.h> #include <EEManager.h>
#include <Scheduler.h>
#include <Task.h>
#include <LeanTask.h>
#include "Settings.h" #include "Settings.h"
EEManager eeSettings(settings, 30000); 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 "WifiManagerTask.h"
#include "MqttTask.h" #include "MqttTask.h"
#include "OpenThermTask.h" #include "OpenThermTask.h"
@@ -27,13 +36,10 @@ MainTask* tMain;
void setup() { void setup() {
#ifdef USE_TELNET #ifndef USE_TELNET
TelnetStream.begin(); Serial.begin(115200);
delay(1000); Serial.println("\n\n");
#else #endif
Serial.begin(115200);
Serial.println("\n\n");
#endif
EEPROM.begin(eeSettings.blockSize()); EEPROM.begin(eeSettings.blockSize());
uint8_t eeSettingsResult = eeSettings.begin(0, 's'); uint8_t eeSettingsResult = eeSettings.begin(0, 's');
@@ -69,7 +75,7 @@ void setup() {
tRegulator = new RegulatorTask(true, 10000); tRegulator = new RegulatorTask(true, 10000);
Scheduler.start(tRegulator); Scheduler.start(tRegulator);
tMain = new MainTask(true); tMain = new MainTask(true, 50);
Scheduler.start(tMain); Scheduler.start(tMain);
Scheduler.begin(); Scheduler.begin();

21
tools/build.py Normal file
View File

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

79
tools/esp32.py Normal file
View File

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