[Solved] ESP8266 DTIM Modem-Sleep

I think I finally have figured out how to get an ESP8266 to automatically Modem-Sleep.
Not to be confused with the user-enabled Light-Sleep or Deep-Sleep modes that are sometimes also called "modemsleep".
All you have to do is set the ESP to station mode, in setup(), and add a "delay(1);" somewhere in or at the end of loop().
After about 10 seconds you see the average current draw of the ESP drop.
Ofcourse somewhat dependent on the WiFi DTIM setting in the home router.
Edit: The ESP seems to have it's own heartbeat of ~40 brief full current pulses per minute, independent of any setting.

I tried to implement this in my home lighting system, that should also be able to run 24/7 on battery power.
It currently consists of one WeMos D1 mini as master in AP_STA mode, with several (to be expanded) bare ESP-12 slaves.
STA mode to communicate with the home router, and AP mode to broadcast with UDP to the slaves that control the lights.
Didn't expect that the main AP_STA ESP8266 could put all of it's AP clients into auto Modem-Sleep, but it does.

Here's the problem.
The slaves don't miss any UDP commands if Modem-Sleep on the clients is not enabled,
nor in that 10-second after bootup/connection of a slave, and the client is not into Modem-Sleep yet.
When the clients are into Modem-Sleep, UDP data transfer is hit and miss.

Looking for ideas to solve this (it it's even possile).

I could disable Modem-Sleep of a client for a set time after a first command is received,
but that relies on the first command actually being received.

Not sure how the DTIM part on the master side works.
Beacon every 100ms? Skipping slave wakeup every x beacons, depending on DTIM number?
Never have seen any settings for DTIM for an ESP8266 AccessPoint.
Is there a way to wake up all the slaves from the AP side, for the first 10 seconds?
Leo..

Another piece of the puzzle.
Tested if packets were missed with Modem-Sleep in an NTP client.
This sketch definitely puts my WeMos D1 mini into auto Modem-Sleep, with delay(1); in line 60.
No UDP packets are missed when DTIM (value=1) is generated by the home router.

So the problem seems to be that the master WeMos AP is not buffering/sending the UDP data during the time that the slaves are awake, but at random times.
While the home router does send the UDP packets in sync with DTIM.
Maybe I should (also) post this problem on the ESP8266 forum.

Attached sketch is a polished version of an NTP client from this page, and (most of) the credits go to PieterP.
See if you get your ESP8266 to Modem-Sleep with this sketch.
Enter your modem/WiFi credentials and preferred NTP server (optional) in line 6-8.
Temporary change the value in line 40 to e.g. 60000 (one minute) to see if packets are missed.
Leo…

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
WiFiUDP UDP;
IPAddress ip(192, 168, 1, 27), gateway(192, 168, 1, 1), subnet(255, 255, 255, 0); // fixed WiFi/LAN IP for faster (re)connect
IPAddress timeServerIP; // holds IP of the time server
const char* WiFi_SSID = "********"; // WiFi/LAN
const char* WiFi_PW = "********"; // password
const char* NTPServerName = "time.nist.gov"; // time server, use one closest to your location ********
const int NTP_PACKET_SIZE = 48; // NTP time stamp is in the first 48 bytes of the message
byte NTPBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming and outgoing packets
unsigned long currentMillis, prevActualTime, timeUNIX, prevNTP, lastNTPResponse = millis();

void setup() {
  Serial.begin(74880); // this baudrate also shows ESP-12 startup data
  Serial.printf("\r\nESP-12 chip ID: %d\r\n", ESP.getChipId());
  WiFi.mode(WIFI_STA); // disable AP for modemsleep
  WiFi.config(ip, gateway, subnet);
  WiFi.begin(WiFi_SSID, WiFi_PW);
  Serial.printf("Trying to connect to %s ", WiFi_SSID);
  byte w8 = 0; // wait ticks while connecting
  while (WiFi.status() != WL_CONNECTED && w8++ < 15) {
    delay(333); // try for 5 seconds
    Serial.print(">");
  }
  Serial.printf("\r\n");
  if (WiFi.status() == WL_CONNECTED) Serial.printf("Connected to %s  IP address %s  strength %d%%\r\n", WiFi_SSID, WiFi.localIP().toString().c_str(), 2 * (WiFi.RSSI() + 100));
  UDP.begin(123); // listen on port 123
  Serial.printf("Starting UDP, port %d\r\n", UDP.localPort());
  if (!WiFi.hostByName(NTPServerName, timeServerIP)) { // Get the IP address of the NTP server
    Serial.println("DNS lookup failed. Rebooting.");
    Serial.flush();
    ESP.reset();
  }
  Serial.printf("Time server: %s  IP: %s\r\n\nSending NTP request\r\n", NTPServerName, timeServerIP.toString().c_str());
  sendNTPpacket(timeServerIP);
}

void loop() {
  currentMillis = millis();
  if (currentMillis - prevNTP > 6000000UL) { // if 100 minutes have passed since last NTP request
    prevNTP = currentMillis;
    Serial.println("Sending NTP request");
    sendNTPpacket(timeServerIP); // Send an NTP request
  }
  uint32_t time = getTime(); // Check if an NTP response has arrived and get the (UNIX) time
  if (time) { // If a new timestamp has been received
    timeUNIX = time;
    Serial.printf("NTP response: %d\r\n", timeUNIX);
    lastNTPResponse = currentMillis;
  } else if ((currentMillis - lastNTPResponse) > 100000000) {
    Serial.println("more than 28 hours since last NTP response, rebooting");
    Serial.flush();
    ESP.reset();
  }
  uint32_t actualTime = timeUNIX + (currentMillis - lastNTPResponse) / 1000;
  if (actualTime != prevActualTime && timeUNIX != 0) { // If a second has passed since last print
    prevActualTime = actualTime;
    Serial.printf("UTC time: %02d:%02d:%02d\r\n", getHours(actualTime), getMinutes(actualTime), getSeconds(actualTime));
  }
  delay(1); // enable DTIM Modem-Sleep
}

uint32_t getTime() {
  if (UDP.parsePacket() == 0) return 0; // if there's no response (yet)
  UDP.read(NTPBuffer, NTP_PACKET_SIZE); // read the packet into the buffer
  uint32_t NTPTime = (NTPBuffer[40] << 24) | (NTPBuffer[41] << 16) | (NTPBuffer[42] << 8) | NTPBuffer[43]; // combine the 4 timestamp bytes
  uint32_t UNIXTime = NTPTime - 2208988800UL; // subtract seventy years
  return UNIXTime;
}

void sendNTPpacket(IPAddress & address) {
  memset(NTPBuffer, 0, NTP_PACKET_SIZE); // set all bytes in the buffer to 0
  NTPBuffer[0] = 0b11100011; // initialize values needed to form NTP request  LI, Version, Mode
  UDP.beginPacket(address, 123); // send a packet requesting a timestamp,  NTP requests are to port 123
  UDP.write(NTPBuffer, NTP_PACKET_SIZE);
  UDP.endPacket();
}
inline int getSeconds(uint32_t UNIXTime) {
  return UNIXTime % 60;
}
inline int getMinutes(uint32_t UNIXTime) {
  return UNIXTime / 60 % 60;
}
inline int getHours(uint32_t UNIXTime) {
  return UNIXTime / 3600 % 24;
}

Another test that points the finger at the AP_STA master.

Random UDP data generated by “Processing” (1-4 bytes).
PC/Processing > Ethernet > home router WiFi > ESP client in Modem-Sleep > NO packets missed.
Used this sketch on the client.

#include <ESP8266WiFi.h>
#include <WiFiUDP.h>
WiFiUDP UDP;

void setup() {
  Serial.begin(74880);
  WiFi.mode(WIFI_STA);
  WiFi.begin("**SSID**", "**PASSWORD**");
  UDP.begin(8888);
}

void loop() {
  delay(1); // enable Modem-Sleep
  int packetSize = UDP.parsePacket();
  if (packetSize) Serial.println(packetSize);
}

Must try to write a simple sketch for this AP_STA master to test.
:frowning: (currently about 1500 lines, plus several html/css pages in SPIFFS).
Leo…

Solved the problem by constantly broadcasting a single character with UDP to the clients a 100ms interval,
during the time that one or more websockets on the master are active.
Leo..