NodeMCU v3 - Flash-Speicher vorhanden?

Hallo

ich habe einen NodeMCU im Einsatz, mir ist aber unklar, ob der einen onboard Flashspeicher hat, auf denen ich meine Konstanten speichern könnte, damit sie einen (täglichen) Reboot überleben.

Den Umweg über RTC-Memory habe ich noch gar nicht probiert, aber ein Freund meinte, die Dinger hätten evtl. einen onboard Speicher mit.

Auf ebay finde ich auch Hinweise darauf, dort lese ich dann beim NodeMCU v3 zB "NodeMCU V3 Lua WIFI Module ESP8266+32M Extra Memory Flash USB-Serial CH340O'yg" (354203513650) aber eindeutig ist das irgendwie nicht.

Ich finde aber auch keine Programmierbeispiele, wie man dann so einen Speicher schreibt und liest.

Weiß da jemand mehr?
Danke für Tipps.

franc

auf dem ESP8266 gibt es eine EEPROM Emulation.
Die Daten landen dann im hinter dem Filesystem.
Die API ist aber sehr ähnlich (!) wie Arduino Uno EEPROM. Einfach mit .put() und .commit() schreiben, mit .get() zurücklesen.
Für ein paar Variablen reicht das lange.

Beispiele sollten in der IDE zu finden sein.

1 Like

Oder du verwendest Spiffs bzw. neuerdings LittleFS. Da kannst du auch deinen Variableninhalt abspeichern.

Eher nicht, da die EEPROM-Emulation auch ohne SPIFFS/LittleFS funktioniert.
Die landen in einem Flash-Bereich.

Gruß Tommy

Ahh, danke!
Stichwort war EEPROM :slight_smile:

In dieser Beschreibung siehst du wie sich der Flash aufteilt und wo die EEPROM Emulation liegt:
https://arduino-esp8266.readthedocs.io/en/latest/filesystem.html

hier hast du die Beschreibung der EEPROM Emulation des Arduino Cores:
https://arduino-esp8266.readthedocs.io/en/latest/libraries.html#eeprom

1 Like

Es gibt einige RTC mit RAM das Batterigebuffert ist:

DS1307 hat 56 Byte
DS3232 hat 236 Byte

Module mit DS3232 sind schwer zu finden bzw sehr viel teurer als Module mit dem DS3231welches kein RAM hat.

Module mit dem DS3231 haben normalerweise ein EEPROM montiert. (weiß eigentlich nicht wieso).

Grüße Uwe

1 Like

Den kann man problemlos (wenn man etwas löten kann) gegen pinkompatible FRAM austauschen. Z.B FM24C64B (64 kBit = 8 kByte) oder FM24C256 Nachfolger: FM24W256 (256 kBit = 32 kByte)

Gruß Tommy

1 Like

Was ist denn jetzt dann der Unterschied zwischen diesem emulierten EEPROM und dem RTC-Memory Trick?
Was ist da besser?
Ich brauche nur wenige Variablen, allerdings müssen es (kurze) Strings sein.
Mir scheint, aber so richtig begriffen habe ich es noch nicht, dass dann wohl der EEPROM Trick besser wäre, oder?

EEprom bzw Flash haben einen begrenzte Schreibanzahl da die Speicherstelle mit jedem Schreibzyklus degradieren.
SRAM mit Batteriebufferung hat keine Begrenzung der Lese oderSchreibzyklen.
FRAM hat begrenzte Lese/Schreibzyklen, die sind aber so groß (10^14) daß das als unbegrenzt angenommen werden kann.

Schreibst Du oft in die Speicherstelle ist SRAM oder FRAM besser.

Strings sind ja nur eine Abfolge von Zeichen die einzeln in die Speicherstellen geschrieben werden.

Das eine (Batteriebufferung) hat mit dem anderen (Reset) nichts zu tun.
Soweit mir bekannt ist löcht kein Reset ( ob Software Hardware Unterspannungs oder Einschalt-Reset) automatisch irgendeinen externen Speicher.

Grüße Uwe

1 Like

EEPROM überlebt einen Stromverlust.
RTC Memory am ESP wäre bei Stromverlust weg.

Es liegt an dir zu definieren, wie wichtig dir die Daten sind.
Oben hast du geschrieben es wären "Konstanten" ... wundert mich jetzt, dass sich die häufig ändern sollen.

Was hast du genau vor?

1 Like

Was hast du genau vor?

Ich hab einen Telegram Bot im Einsatz, der mir die Haustür öffnet. Ich will dort eine Telegram ID speichern, die einen Reset überlebt. Resetten muss ich mind. täglich, damit das Programm nicht wg. Speicherfragmentierung abstürzt oder schlimmer: anfangt zu spinnen (s. im Thread des Links).

In meinem Telegram Bot Code habe ich nämlich eine Freigabe (eine reicht auch), wo ich halbautomatisiert jemand für die Tür freischalten kann.
Das muss ich derzeit täglich nach dem Reboot erneuern.

Zudem würde ich gerne eine Uhrzeit speichern, zu der der Reboot erfolgen soll (Reboot durch Reset). Das hätte ich gerne nicht hartcodiert.

Aber im Moment teste ich gerade das Speichern mit EEPROM und es geht ja gar nicht.
Da wird nichts dauerhaft gespeichert, ich habe das Beispiel nachprogrammiert:
EEPROM read and write string
Es speichert nicht mal, wenn ich gar keinen Reset mache oder den ESP vom Strom nehme.

Dann solltest Du Deinen Code einem Review unterziehen und das Modell anpassen.
Es soll ESP's geben, die mit mehr als nur nem Bot über Jahre ohne reset auskommen.

gib mir mal Beispielsdaten und Beispielsvariabeln der Daten die du speichern möchtest.

Code cleanup wurde eh schon angesprochen ... machen ! Einer meiner ESP läuft aktuell:
4 Wochen 1 Tage 7 Stunden 18 Minuten 32 Sekunden

Für "Uhrzeit" am ESP würde sich NTP anbieten.
Das geht eigentlich mit 3 Zeilen Code.
habe ich hier beschrieben:
https://werner.rothschopf.net/201802_arduino_esp8266_ntp.htm

Ich hab den Code aus dem anderen Thread mal befreit.
Deine ganzen String Variablen brechen Dir das Genick, das wurde Dir da schon geschrieben...

#include <ESP8266WiFi.h>
#include <WiFiClientSecure.h>
#include <UniversalTelegramBot.h>

// Wifi network station credentials
#define WIFI_SSID "accesspoint"
#define WIFI_PASSWORD "wifi-password"
#define BOT_TOKEN "1234567890:ABCDEF-123abcd3141239ajdfafwj93blabla"


// 2022-04-10: fuer Debug Ausgaben im Serial Monitor (Tools >) debug auf true setzen
bool debug = false;

const unsigned long BOT_MTBS = 1000; // mean time between scan messages

X509List cert(TELEGRAM_CERTIFICATE_ROOT);
WiFiClientSecure secured_client;
UniversalTelegramBot bot(BOT_TOKEN, secured_client);
unsigned long bot_lasttime; // last time messages' scan has been done

// 2022-05-11: LED Ansteuerung deaktiviert, braucht man wohl nicht mehr
// const int ledPin = LED_BUILTIN;
// int ledStatus = 0;

// relaisDelay (open_time) kann per Kommando veraendert werden (bis Reboot) aber nicht laenger als relaisMaxDelay ms sein
int relaisDelay = 3000;
int relaisMaxDelay = 10000;

// falscher from_name oder chat_id ergibt eine Pause von nagDelay ms fuer das naechste Kommando
int nagDelay = 10000;

// Anmerkung: 2022-04-06: von: https://www.smarthome-tricks.de/esp8266/relais-schalten/
// (GPIO 5)
int pinRelais1 = D1;
// 2022-05-12: siehe: https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/
// (GPIO 4)
int pinKlingel = D2;
int relaisStatus = 0;

// Anmerkung: 2022-04-09: trusted_chat_ids und temp_trusted_chat_ids
// ToDo: hier ein String-Array fuer dauerhaft erlaubte und temporaer erlaubte chat_id anlegen, die man im laufenden ESP hinzufuegen kann.
String trusted_chat_id1 = "1234567890";
String trusted_chat_id2 = "0987654321";
// mit trusted_chat_ids.length() dann ein loop o.ae. durchlaufen

// temp. chat_id default auf Anna gesetzt, zur Laufzeit ueberschreibbar durch Befehl
String temp_trusted_chat_id = "1234567890";
String temp_trusted_from_name = "123beda6725423548245093854f19cd9";

// Befehlsbuttons global hier
String keyboardJson = "[[\"/help\", \"/open\"],[\"/open_in_10\", \"/open1\"]]";

// function to check if a string is a valid number (for relaisDelay setting by command)
boolean isNumber(String str)
{
  for (byte i = 0; i < str.length(); i++)
  {
    if (!isDigit(str.charAt(i)))
    {
      return false;
    }
  }
  return true;
}

void handleNewMessages(int numNewMessages)
{
  if (debug)
  {
    Serial.print("handleNewMessages ");
  }
  if (debug)
  {
    Serial.println(numNewMessages);
  }
  for (int i = 0; i < numNewMessages; i++)
  {
    String chat_id = bot.messages[i].chat_id;
    String text = bot.messages[i].text;
    String from_name = bot.messages[i].from_name;
    if (from_name == "")
    {
      from_name = "Guest";
    }
    // die Eigenschaften der Struktur telegramMessage des Objekts UniversalTelegramBot ausgeben
    // von: https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot/blob/master/src/UniversalTelegramBot.h
    // debug auf true zum Auslesen
    if (debug)
    {
      Serial.print("text: ");
      Serial.println(text);
      Serial.print("chat_id: ");
      Serial.println(chat_id);
      String chat_title = bot.messages[i].chat_title;
      Serial.print("chat_title: ");
      Serial.println(chat_title);
      String from_id = bot.messages[i].from_id;
      Serial.print("from_id: ");
      Serial.println(from_id);
      Serial.print("from_name: ");
      Serial.println(from_name);
      String date = bot.messages[i].date;
      Serial.print("date: ");
      Serial.println(date);
      String type = bot.messages[i].type;
      Serial.print("type: ");
      Serial.println(type);
      String file_caption = bot.messages[i].file_caption;
      Serial.print("file_caption: ");
      Serial.println(file_caption);
      String file_path = bot.messages[i].file_path;
      Serial.print("file_path: ");
      Serial.println(file_path);
      String file_name = bot.messages[i].file_name;
      Serial.print("file_name: ");
      Serial.println(file_name);
      //    Bool hasDocument = bot.messages[i].hasDocument;
      //    Serial.print("hasDocument: ");
      //    Serial.println(String(hasDocument));
      int file_size = bot.messages[i].file_size;
      Serial.print("file_size: ");
      Serial.println(String(file_size));
      Serial.print("trusted_chat_id1: ");
      Serial.println(trusted_chat_id1);
      Serial.print("trusted_chat_id2: ");
      Serial.println(trusted_chat_id2);
      Serial.print("temp_trusted_chat_id: ");
      Serial.println(temp_trusted_chat_id);
      Serial.print("temp_trusted_from_name: ");
      Serial.println(temp_trusted_from_name);
    }
    /*

      die paar fehlen noch:

      struct telegramMessage {
      ...
      float longitude;
      float latitude;
      int update_id;
      int message_id;

      int reply_to_message_id;
      String reply_to_text;
      String query_id;
      };
    */
    // Anna (1234567890 bzw trusted_chat_id1) oder Berta (0987654321 bzw trusted_chat_id2) oder temp. chat_id (initial Anna) oder tem_trusted_from_name (initial md5(Anna))
    if ((chat_id == trusted_chat_id1) || (chat_id == trusted_chat_id2) || (chat_id == temp_trusted_chat_id) || ((from_name == temp_trusted_from_name) && (from_name != "")))
    {
      if (debug)
      {
        Serial.print("chat_id OK - Befehlsabfrage...\n");
      }
      if ((text == "/info") || (text == "/help"))
      {
        String help = "Hallo " + from_name + ".\n\n";
        help += "/info oder /help: Diesen Text zeigen\n";
        help += "/debug : Die seriellen Ausgaben in der IDE umschalten\n(Standard: aus. Code: sets debug = !debug)\n";
        help += "/open_time:<ms> : Setzt (bis zum nächsten Reboot) die Öffnungszeit in Millisekunden (max. 10000)\n";
        help += "/chat_id:<Chat-ID> : Berechtigt (bis zum nächsten Reboot) zusätzlich diese Chat-ID für Kommandos\n";
        help += "/from_name:<Telegram-Name> : Berechtigt (bis zum nächsten Reboot) zusätzlich diesen Telegram-Namen für Kommandos (leer=nicht erlaubt)\n";
        help += "/open : Tür öffnen für " + String(relaisDelay) + " Millisekunden\n";
        help += "/open1 : Tür eine Sekunden lang öffnen\n";
        help += "/open_in_10 : Tür nach 10 Sekunden öffnen für " + String(relaisDelay) + " Millisekunden\n";
        help += "/open:<ms> : Tür für <ms> Millisekunden öffnen (max 10000)\n";
        // welcome += "chat_id: " + chat_id;
        if (debug)
        {
          Serial.print("help: \n");
        }
        if (debug)
        {
          Serial.println(help);
        }
        bot.sendMessage(chat_id, help, "");
      }
      if (text == "/open")
      {
        String command = "Tür öffnen für " + String(relaisDelay) + " ms...";
        bot.sendMessage(chat_id, command, "");
        digitalWrite(pinRelais1, LOW); // turn the Relais on
        delay(relaisDelay);
        digitalWrite(pinRelais1, HIGH); // turn the Relais off again
        // command = "Relais was ON for " + String(relaisDelay) + " ms\n\nTo open press: \n/open";
        // bot.sendMessage(chat_id, command, "");
        // String keyboardJson = "[[\"/help\", \"/open\"],[\"/open_in_10\", \"/open1\"]]";
        bot.sendMessageWithReplyKeyboard(chat_id, "Für alle Befehle auf /help tippen...", "", keyboardJson, true);
      }
      if (text == "/open1")
      {
        String command = "Tür eine Sekunde öffnen ...";
        bot.sendMessage(chat_id, command, "");
        digitalWrite(pinRelais1, LOW); // turn the Relais on
        delay(1000);
        digitalWrite(pinRelais1, HIGH); // turn the Relais off again
        // command = "Relais was ON for " + String(relaisDelay) + " ms\n\nTo open press: \n/open";
        // bot.sendMessage(chat_id, command, "");
        // String keyboardJson = "[[\"/help\", \"/open\"],[\"/open_in_10\", \"/open1\"]]";
        bot.sendMessageWithReplyKeyboard(chat_id, "Für alle Befehle auf /help tippen...", "", keyboardJson, true);
      }
      // ToDo: 2022-04-14: noch fertig stellen und testen:
      if (text.substring(0, 6) == "/open:")
      {
        int length = text.length();
        String str_open_length = text.substring(6, length);
        if (isNumber(str_open_length))
        {
          int open_length = str_open_length.toInt();
          // nicht kleiner als 100 ms und nicht groesser als 10000 ms
          if (open_length < 100 || open_length > 10000)
          {
            open_length = relaisDelay;
          }
          String command = "Tür " + String(open_length) + " Millisekunden öffnen ...";
          bot.sendMessage(chat_id, command, "");
          digitalWrite(pinRelais1, LOW); // turn the Relais on
          delay(open_length);
          digitalWrite(pinRelais1, HIGH); // turn the Relais off again
          // command = "Relais was ON for " + String(relaisDelay) + " ms\n\nTo open press: \n/open";
          // bot.sendMessage(chat_id, command, "");
          // String keyboardJson = "[[\"/help\", \"/open\"],[\"/open_in_10\", \"/open1\"]]";
          bot.sendMessageWithReplyKeyboard(chat_id, "Für alle Befehle auf /help tippen...", "", keyboardJson, true);
        }
        else
        {
          // String keyboardJson = "[[\"/help\", \"/open\"],[\"/open_in_10\", \"/open1\"]]";
          bot.sendMessageWithReplyKeyboard(chat_id, "/open:<ms> - <ms> muss eine Zahl sein!", "", keyboardJson, true);
        }
      }
      if (text == "/open_in_10")
      {
        String command = "Tür in 10 Sekunden für " + String(relaisDelay) + " Millisekunden öffnen...";
        bot.sendMessage(chat_id, command, "");
        // 10 Sek. warten
        delay(10000);
        digitalWrite(pinRelais1, LOW); // turn the Relais on
        delay(relaisDelay);
        digitalWrite(pinRelais1, HIGH); // turn the Relais off again
        // command = "Relais was ON for " + String(relaisDelay) + " ms\n\nTo open press: \n/open";
        // bot.sendMessage(chat_id, command, "");
        // String keyboardJson = "[[\"/help\", \"/open\"],[\"/open_in_10\", \"/open1\"]]";
        bot.sendMessageWithReplyKeyboard(chat_id, "Für alle Befehle auf /help tippen...", "", keyboardJson, true);
      }
      // temporaere chat_id zulassen
      if (text.substring(0, 9) == "/chat_id:")
      {
        if (debug)
        {
          Serial.print("text.substring(0,9): ");
        }
        if (debug)
        {
          Serial.println(text.substring(0, 9));
        }
        int length = text.length();
        temp_trusted_chat_id = text.substring(9, length);
        if (debug)
        {
          Serial.print("length: ");
        }
        if (debug)
        {
          Serial.println(String(length));
        }
        if (debug)
        {
          Serial.print("temp_trusted_chat_id: ");
        }
        if (debug)
        {
          Serial.println(temp_trusted_chat_id);
        }
        String command = "Allow commands from chat_id: " + temp_trusted_chat_id;
        bot.sendMessage(chat_id, command, "");
      }
      if (text.substring(0, 11) == "/from_name:")
      {
        int length = text.length();
        // wenn leer, also length = 11 dann wird der temp. from_name (temp_trusted_from_name) geleert (initial der md5 von Anna)
        String command = "";
        if (length == 11)
        {
          temp_trusted_from_name = "";
          String command = "from_name geleert, from_name nicht mehr aktiv";
        }
        else
        {
          temp_trusted_from_name = text.substring(11, length);
          String command = "Befehle (bis Reboot) erlauben von Telegram-Name: " + temp_trusted_from_name;
        }
        if (debug)
        {
          Serial.print("length: ");
        }
        if (debug)
        {
          Serial.println(String(length));
        }
        if (debug)
        {
          Serial.print("temp_trusted_from_name: ");
        }
        if (debug)
        {
          Serial.println(temp_trusted_from_name);
        }
        bot.sendMessage(chat_id, command, "");
      }
      if (text.substring(0, 11) == "/open_time:")
      {
        String command = "";
        int length = text.length();
        String temp_open_time = text.substring(11, length);
        if (debug)
        {
          Serial.print("length: ");
        }
        if (debug)
        {
          Serial.println(String(length));
        }
        if (debug)
        {
          Serial.print("temp_open_time: ");
        }
        if (debug)
        {
          Serial.println(temp_open_time);
        }
        if (isNumber(temp_open_time))
        {
          int tempRelaisDelay = temp_open_time.toInt();
          if (tempRelaisDelay > relaisMaxDelay)
          {
            relaisDelay = relaisMaxDelay;  // max delay
          }
          else if (tempRelaisDelay < 100)
          {
            relaisDelay = relaisDelay;  // unchanged if less 100 ms
          }
          else
          {
            relaisDelay = tempRelaisDelay;
          }
          command = "open_time now (till next reboot): " + String(relaisDelay);
          if (debug)
          {
            Serial.print(command);
          }
        }
        else
        {
          command = "open_time must be a number!";
          if (debug)
          {
            Serial.print("temp_open_time is not a number! command: " + command);
          }
        }
        bot.sendMessage(chat_id, command, "");
      }
      if (text == "/debug")
      {
        debug = !debug;
        if (debug)
        {
          bot.sendMessage(chat_id, "Debug Nachrichten zum Seriellen Monitor aktiviert (debug = true)", "");
          Serial.print("Debug enabled");
        }
        else
        {
          bot.sendMessage(chat_id, "Debug Nachrichten zum Seriellen Monitor deaktiviert (debug = false)", "");
        }
      }
      // Sendet Debug Nachrichten (natuerlich nur an Anna)
      if (debug)
      {
        String debugMessage = "debug:\n";
        debugMessage += "from_name: " + from_name;
        String from_id = bot.messages[i].from_id;
        debugMessage += "\nfrom_id: " + from_id;
        debugMessage += "\nchat_id: " + chat_id;
        debugMessage += "\ntemp_trusted_from_name: " + temp_trusted_from_name;
        debugMessage += "\ntemp_trusted_chat_id: " + temp_trusted_chat_id;
        debugMessage += "\ntrusted_chat_id1: " + trusted_chat_id1;
        debugMessage += "\ntrusted_chat_id2: " + trusted_chat_id2;
        debugMessage += "\ntext: " + text;
        bot.sendMessage(trusted_chat_id1, debugMessage, "");
      }
    }
    else
    {
      // wrong chat_id or from_name makes delay
      if (debug)
      {
        Serial.print("from_name: ");
      }
      if (debug)
      {
        Serial.println(from_name);
      }
      if (debug)
      {
        Serial.print("chat_id: ");
      }
      if (debug)
      {
        Serial.println(chat_id);
      }
      if (debug)
      {
        Serial.print("wrong chat_id / from_name - delay...");
      }
      if (debug)
      {
        String debugMessage = "debug\nwrong chat_id / from_name - delay\n";
        debugMessage += "from_name: " + from_name;
        String from_id = bot.messages[i].from_id;
        debugMessage += "\nfrom_id: " + from_id;
        debugMessage += "\nchat_id: " + chat_id;
        debugMessage += "\ntemp_trusted_from_name: " + temp_trusted_from_name;
        debugMessage += "\ntemp_trusted_chat_id: " + temp_trusted_chat_id;
        debugMessage += "\ntrusted_chat_id1: " + trusted_chat_id1;
        debugMessage += "\ntrusted_chat_id2: " + trusted_chat_id2;
        debugMessage += "\text: " + text;
        bot.sendMessage(trusted_chat_id1, debugMessage, "");
      }
      delay(nagDelay);
    }
  }
}
// 2022-05-12: noch ganz primitiv implementiert, wenn HIGH auf dem D2 Pin (GPIO4)
// ToDo: noch genauer differenzieren an wen geschickt wird, evtl. mit Parameter von klingel()
// evtl. noch fuer Klingel Code die Tuer oeffnen, dann aber die Klingel unterbrechen, also per Relais als weiteren Ausgang
void klingel()
{
  // Nachricht an Anna (hartcodiert, noch)
  bot.sendMessage("1234567890", "Es hat geklingelt!", "");
}
void bot_setup()
{
  // 2022-05-12: von SetMyCommands inspiriert, siehe:
  // https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot/blob/master/examples/ESP8266/SetMyCommands/SetMyCommands.ino
  // die Befehle werden beim Bot selbst hinterlegt, daher kann man sie auch per anderem ESP8266 hochladen und sie wirken auch
  // fuer das NodeMCU Tueroeffner, das man gar nicht damit aktualisieren muss.
  // Links in der Eingabezeile sieht man dann ein Menue zum Hochklappen mit den Befehlen
  const String commands = F("["
                            "{\"command\":\"/help\",  \"description\":\"Alle Befehle mit Erklärung anzeigen\"},"
                            "{\"command\":\"/debug\",  \"description\":\"Serielle Ausgabe an/aus und Debug an Anna\"},"
                            "{\"command\":\"/open_in_10\",  \"description\":\"Tür in 10s öffnen (3s)\"},"
                            "{\"command\":\"/open1\", \"description\":\"Tür öffnen (1s)\"},"
                            "{\"command\":\"/open\",\"description\":\"Tür öffnen (3s)\"}" // no comma on last command
                            "]");
  bot.setMyCommands(commands);
}

void setup()
{
  // muss auch wenn debug aus ist aktiviert werden, sonst kann man nicht mit /debug umschalten
  Serial.begin(115200);
  Serial.println();
  // LED auskommentiert, nicht mehr benoetigt
  // pinMode(ledPin, OUTPUT); // initialize digital ledPin as an output.
  // delay(10);
  // digitalWrite(ledPin, HIGH); // initialize pin as off (active LOW)
  // Anmerkung: 2022-04-06: von: https://www.smarthome-tricks.de/esp8266/relais-schalten/
  pinMode(pinRelais1, OUTPUT);
  digitalWrite(pinRelais1, HIGH); // initialize pin as off (active LOW)
  // 2022-05-12: schon mal den Rohbau fuer die Klingelabfrage:
  pinMode(pinKlingel, INPUT);
  // attempt to connect to Wifi network:
  configTime(0, 0, "pool.ntp.org");      // get UTC time via NTP
  secured_client.setTrustAnchors(&cert); // Add root certificate for api.telegram.org
  if (debug)
  {
    Serial.print("Connecting to Wifi SSID ");
  }
  if (debug)
  {
    Serial.print(WIFI_SSID);
  }
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED)
  {
    if (debug)
    {
      Serial.print(".");
    }
    delay(500);
  }
  if (debug)
  {
    Serial.print("\nWiFi connected. IP address: ");
  }
  if (debug)
  {
    Serial.println(WiFi.localIP());
  }
  // Check NTP/Time, usually it is instantaneous and you can delete the code below.
  if (debug)
  {
    Serial.print("Retrieving time: ");
  }
  time_t now = time(nullptr);
  while (now < 24 * 3600)
  {
    if (debug)
    {
      Serial.print(".");
    }
    delay(100);
    now = time(nullptr);
  }
  if (debug)
  {
    Serial.println(now);
  }
  // String keyboardJson = "[[\"/help\", \"/open\"],[\"/open_in_10\", \"/open1\"]]";
  bot.sendMessageWithReplyKeyboard(trusted_chat_id1, "NodeMCU: tueroeffner.ino Start...", "", keyboardJson, true);
  //  String startup = "NodeMCU: tueroeffner.ino - Startup\n\nFor info press:\n/info\n\nTo open press:\n/open";
  //  bot.sendMessage("1234567890", startup, "");
  bot_setup();
}

void loop()
{
  // millis ist die Zeit in ms seit dem letzten Reboot. bot_lasttime wird unten auf millis gesetzt und sobald millis() eine Sekunde (BOT_MTBS) weiter ist, wird das if betreten
  if (millis() - bot_lasttime > BOT_MTBS)
  {
    int numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    while (numNewMessages)
    {
      if (debug)
      {
        Serial.println("got response");
      }
      handleNewMessages(numNewMessages);
      numNewMessages = bot.getUpdates(bot.last_message_received + 1);
    }
    bot_lasttime = millis();
  }
  else
  {
    // 2022-05-12: den Eingang pruefen, wenn auf Masse, dann Funktion klingel() aufrufen
    // ToDo: genauer die Bedingung pruefen und auf dem Board ueber einen Widerstand gg Plus oder Masse eindeutig setzen
    /*
      das funktioniert noch nicht so, muss noch ein Widerstand rein, dass der Eingang auch def. ist, daher erst mal raus
      if (digitalRead(pinKlingel) == LOW)
      {
      klingel();
      }
    */
  }
}

Es soll ESP's geben, die mit mehr als nur nem Bot über Jahre ohne reset auskommen.

Die Bibliothek mit der ich den Bot anspreche ist schon voller Strings. Die Web Bibliotheken seien schon voller Strings.
Mag sein, dass ich die alle raus kriege und durch Char* ersetzen könnte, aber das dauert mit Sicherheit (mir) viel zu lange. Ich könnte es sicher etwas optimieren, aber um einen Reboot werde ich dauerhaft nicht herum kommen. Es hilft dann wenig, wenn der ESP statt einem Tag eine Woche stabil lauft und dann unzuverlässig wird.

gib mir mal Beispielsdaten und Beispielsvariabeln der Daten die du speichern möchtest.

String grantedID = "123456789";
int reset_time = 1200;

NTP verwende ich ja schon, aber es geht darum, dass ich per Bot-Befehl eine Reset-Uhrzeit eingeben kann, die dann nach dem Reset immer noch gilt.
Ich kann klar die Uhrzeit hartcodieren, aber ich will sie ja weich codieren :smirk:

Der Code schaut natürlich längst ganz anders aus, aber prinzipiell hast du recht, ich verwende viele Strings. Allerdings müsste ich vor allem die genutzte Bibliothek UniversalTelegramBot anpassen, dort sind viele Strings drin und das wird jede Sekunde abgefragt (und damit auch Strings angelegt und wieder gelöscht etc.).

Heißt Batteriebufferung auch, dass SRAM (RTC?) bei einem soft-Reset (der ESP bleibt da ja am USB angeschlossen) seinen Wert hält?

Die werden zu einem überwiegenden Teil via refenz angesprochen und nicht neu angelegt. Beachte das & -Zeichen in den Funktionen.

UniversalTelegramBot::UniversalTelegramBot(const String& token, Client &client) {
  updateToken(token);
  this->client = &client;
}

void UniversalTelegramBot::updateToken(const String& token) {
  _token = token;
}

https://github.com/witnessmenow/Universal-Arduino-Telegram-Bot/blob/a1952c459a617dbf5b41e8fcd625f70b86ac3b9d/src/UniversalTelegramBot.cpp#L41

String command und response aber nicht, zB hier.