diff --git a/build.py b/build.py deleted file mode 100644 index 8a708c5..0000000 --- a/build.py +++ /dev/null @@ -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) \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 27a2638..921018c 100644 --- a/platformio.ini +++ b/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 @@ -22,18 +20,72 @@ lib_deps = gyverlibs/GyverPID@^3.3 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 + +[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] +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} [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} [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} + +[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} \ No newline at end of file diff --git a/src/MainTask.h b/src/MainTask.h index 7201158..598b0c7 100644 --- a/src/MainTask.h +++ b/src/MainTask.h @@ -7,9 +7,12 @@ public: MainTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {} protected: + const char* taskName = "Main task"; + const int taskCore = 2; + unsigned long lastHeapInfo = 0; unsigned long firstFailConnect = 0; - unsigned short minFreeHeapSize = 65535; + unsigned int minFreeHeapSize = RAM_SIZE; void setup() { pinMode(LED_STATUS_PIN, OUTPUT); @@ -21,7 +24,7 @@ protected: } if (WiFi.status() == WL_CONNECTED) { - if (!tMqtt->isEnabled()) { + if (!tMqtt->isEnabled() && strlen(settings.mqtt.server) > 0) { tMqtt->enable(); } @@ -65,15 +68,16 @@ 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, RAM_SIZE, minFreeHeapSize, minFreeHeapSizeDiff); lastHeapInfo = millis(); } } diff --git a/src/MqttTask.h b/src/MqttTask.h index 3270b3e..9eb16aa 100644 --- a/src/MqttTask.h +++ b/src/MqttTask.h @@ -1,6 +1,5 @@ #include #include -#include #include "HaHelper.h" WiFiClient espClient; @@ -13,6 +12,9 @@ public: MqttTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {} protected: + const char* taskName = "Mqtt task"; + const int taskCore = 1; + unsigned long lastReconnectAttempt = 0; unsigned long firstFailConnect = 0; @@ -20,7 +22,7 @@ protected: 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"); @@ -59,7 +61,6 @@ protected: } } - forceARP(); 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) { bool flag = false; @@ -359,8 +352,7 @@ protected: } else { client.publish(getTopicPath("status").c_str(), vars.states.otStatus ? "online" : "offline"); } - - forceARP(); + prevPubVars = millis(); } diff --git a/src/OpenThermTask.h b/src/OpenThermTask.h index 5695e79..4e1ac5b 100644 --- a/src/OpenThermTask.h +++ b/src/OpenThermTask.h @@ -7,7 +7,14 @@ 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* taskName = "OpenTherm task"; + const int taskCore = 2; + void setup() { vars.parameters.heatingMinTemp = settings.heating.minTemp; vars.parameters.heatingMaxTemp = settings.heating.maxTemp; @@ -16,8 +23,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(self)->yield(); + }, this); #ifdef LED_OT_RX_PIN 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) { printRequestDetail(ot->getDataID(request), status, request, response, attempt); } diff --git a/src/RegulatorTask.h b/src/RegulatorTask.h index 6345b39..b381c02 100644 --- a/src/RegulatorTask.h +++ b/src/RegulatorTask.h @@ -11,6 +11,9 @@ public: RegulatorTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {} protected: + const char* taskName = "Regulator task"; + const int taskCore = 2; + bool tunerInit = false; byte tunerState = 0; byte tunerRegulator = 0; diff --git a/src/SensorsTask.h b/src/SensorsTask.h index 0feb771..93e283f 100644 --- a/src/SensorsTask.h +++ b/src/SensorsTask.h @@ -6,6 +6,9 @@ public: SensorsTask(bool _enabled = false, unsigned long _interval = 0) : LeanTask(_enabled, _interval) {} protected: + const char* taskName = "Sensors task"; + const int taskCore = 2; + OneWire* oneWireOutdoorSensor; OneWire* oneWireIndoorSensor; diff --git a/src/Settings.h b/src/Settings.h index 6f3f777..42e8abb 100644 --- a/src/Settings.h +++ b/src/Settings.h @@ -3,8 +3,8 @@ 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; @@ -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; diff --git a/src/WifiManagerTask.h b/src/WifiManagerTask.h index 490f717..cfbfba6 100644 --- a/src/WifiManagerTask.h +++ b/src/WifiManagerTask.h @@ -1,5 +1,7 @@ +#define WM_MDNS #include #include +#include // Wifimanager WiFiManager wm; @@ -25,8 +27,12 @@ public: WifiManagerTask(bool _enabled = false, unsigned long _interval = 0) : Task(_enabled, _interval) {} protected: + const char* taskName = "WifiManager"; + const int taskCore = 1; + bool connected = false; + unsigned long lastArpGratuitous = 0; + void setup() { - //WiFi.mode(WIFI_STA); wm.setDebugOutput(settings.debug); wmHostname = new WiFiManagerParameter("hostname", "Hostname", settings.hostname, 80); @@ -95,6 +101,11 @@ protected: if (connected && WiFi.status() != WL_CONNECTED) { connected = false; + + #ifdef USE_TELNET + TelnetStream.stop(); + #endif + INFO("[wifi] Disconnected"); } else if (!connected && WiFi.status() == WL_CONNECTED) { @@ -104,6 +115,10 @@ protected: 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()); } @@ -111,10 +126,17 @@ protected: wm.startWebPortal(); } + #if defined(ESP8266) + if ( connected && millis() - lastArpGratuitous > 60000 ) { + arpGratuitous(); + lastArpGratuitous = millis(); + } + #endif + wm.process(); } - void static saveParamsCallback() { + static void saveParamsCallback() { strcpy(settings.hostname, wmHostname->getValue()); strcpy(settings.mqtt.server, wmMqttServer->getValue()); settings.mqtt.port = wmMqttPort->getValue(); @@ -161,5 +183,11 @@ protected: INFO(F("Settings saved")); } - bool connected = false; + static void arpGratuitous() { + struct netif* netif = netif_list; + while (netif) { + etharp_gratuitous(netif); + netif = netif->next; + } + } }; \ No newline at end of file diff --git a/src/defines.h b/src/defines.h index cd8e96b..f2c09a6 100644 --- a/src/defines.h +++ b/src/defines.h @@ -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_PASSWORD "otgateway123456" #define USE_TELNET @@ -18,6 +18,26 @@ #define CONFIG_URL "http://%s/" #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 #define INFO_STREAM TelnetStream diff --git a/src/main.cpp b/src/main.cpp index b6712f3..90dabec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -3,13 +3,22 @@ #include #include #include -#include -#include -#include #include "Settings.h" EEManager eeSettings(settings, 30000); +#if defined(ESP32) + #include + #include + #include +#elif defined(ESP8266) + #include + #include + #include +#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,7 +75,7 @@ void setup() { tRegulator = new RegulatorTask(true, 10000); Scheduler.start(tRegulator); - tMain = new MainTask(true); + tMain = new MainTask(true, 50); Scheduler.start(tMain); Scheduler.begin(); diff --git a/tools/build.py b/tools/build.py new file mode 100644 index 0000000..91466d3 --- /dev/null +++ b/tools/build.py @@ -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) \ No newline at end of file diff --git a/tools/esp32.py b/tools/esp32.py new file mode 100644 index 0000000..21c3d76 --- /dev/null +++ b/tools/esp32.py @@ -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\\partitions.bin +# - 0xe000 | ~\.platformio\packages\framework-arduinoespressif32\tools\partitions\boot_app0.bin +# - 0x10000 | ~\ESPEasy\.pio\build\/.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)