esp8266 / Arduino

ESP8266 core for Arduino
GNU Lesser General Public License v2.1
15.99k stars 13.34k forks source link

10 ms ack delay #3316

Closed Jorgegarve closed 7 years ago

Jorgegarve commented 7 years ago

Yesterday I sniffed the communication between my raspberry server (sending TCP packets with 1460 bytes data, so a fully complete TCP packet) and my ESP8266 which receives them and I saw that every ack the ESP8266 send is delayed more or less 10 ms (time from the last TCP packet received). Can someone explain me why or how to fix it? I need it to be inmediate ACK.

The attached file shows what I am talking about. In delta time column you can see these around 10 ms of delay, and you can also see a delay of 100 ms aprox which I don't really know why it is there...

Thanks for all.

captura

igrr commented 7 years ago

Could you post the code you have on the ESP8266 side?

Jorgegarve commented 7 years ago

It is difficult to post the code because I am working with things related to patents and it is a huge code. Which part would you like to see?

igrr commented 7 years ago

Well, since you are reporting an issue I was wondering whether you have an mcve to provide along with the report... Since the issue is about receiving TCP data, i suppose that the example would contain some loop in which you receive data using WiFiClient.

WiFiClient instance will not acknowledge incoming packets until the application calls its read (or readBytes) method to extract the received data. Once the data is taken by the application, WiFiClient informs TCP stack that given amount of data has been received and the TCP stack can now send an ACK. Depending on what the application does in between the reads, there may be some delay before the next chunk of data is 'read' by the application.

Also take a look at espasynctcp library which allows you to acknowledge data reception explicitly.

Jorgegarve commented 7 years ago

Ok, I understand, so the first fast fix to this issue should be to use espasyntcp, am I wrong? How does the asynctcp manage acks? In my code I use a WiFiClient for all the communications and the loop starts with:

void loop() {

if ( client.available() ) { c = client.read(); //actions to do

Thanks in advance for being so fast!

igrr commented 7 years ago

I'm pretty sure that if you read 1 byte at a time, it will take a fairly long time to consume the whole 1460 byte segment (only then will the ACK be sent). Using int WiFiClient::read(uint8_t *buf, size_t size) to read the whole segment would be more efficient. I suggest you do some benchmarking of your code, to see how much time it takes your loop to consume 1460 bytes from client.

Jorgegarve commented 7 years ago

So, for example, in order to read the fully 1460 bytes tcp packet data stored only once, I can do:

c = client.read(buffer, 1460);

Thanks for all the support, and thanks for this amazing work.

Jorgegarve commented 7 years ago

So, igrr, is there a way to force checking for a complete received TCP packet without losing the information in order to send ack? I mean, when you use read() if the TCP packet is completely received, you told me that you will send an ack. But this c = client.read() will take the byte received, and you will be forced to use it somewhere. I am looking towards something that force the ACK to be sent or just checked.

igrr commented 7 years ago

If you want to send an ACK before consuming (processing) the data, that's fine — you can buffer it by doing client.read(buffer, 1460). That places data into a buffer owned by the application, and tells the TCP stack: "data has been passed to the application layer; application layer is now responsible for whatever happens to the data". At this point TCP stack can acknowledge delivery by sending an ACK and increasing the receive window. We can't let the stack increase receive window before data is passed to the application, as that would defeat TCP flow control and allow the remote end to flood the ESP until it runs out of RX buffers.

Jorgegarve commented 7 years ago

Thanks for the answer, it is very self-explanatory! I'm gonna try it and I will tell you something! I'm just afraid of consuming 1460 bytes in order to get a full TCP data packet when the full TCP packet is not received completely yet... Could this happen?

Jorgegarve commented 7 years ago

Also, for a better adaptability, should I store before the return from howManyBytes = client.available() and use it in client.read(buffer, howManyBytes)?

igrr commented 7 years ago

You can query available() first, sure. Also if you request to read certain number of bytes but there are less bytes available, the bytes which are actually available will be put into the buffer and the number of bytes will be returned by read function.

size_t bytes_read = client.read(buffer, TCP_MSS);
if (bytes_read > 0) {
    // do something with the contents of buffer
}
Jorgegarve commented 7 years ago

Now I want to check if I receive what I should receive, but I get instead this error:

Exception (29): epc1=0x4000dfd9 epc2=0x00000000 epc3=0x00000000 excvaddr=0x00000000 depc=0x00000000

ctx: cont sp: 3fff0680 end: 3fff0890 offset: 01a0

stack>>> 3fff0820: 401059a8 001614fb 3ffef6e4 00000000
3fff0830: 4020722d 3ffef6e4 3ffef870 3ffef6e4
3fff0840: 00000000 3ffef860 4020a734 3ffef870
3fff0850: 402010ae 000003e8 000003e8 3ffef860
3fff0860: 3fffdad0 3ffef2d0 3ffef330 402067fe
3fff0870: 3fffdad0 00000000 3ffef858 4020a780
3fff0880: feefeffe feefeffe 3ffef870 40100718
<<<stack<<<

My code is the following: `void loop() {

if (client.available() > 0) { client.read(rxBuffer, sizeBuffer); Serial.println((char)* rxBuffer); }

delay(1000);

}`

igrr commented 7 years ago

Could you please decode the stack dump and paste the result here? Thanks. https://arduino-esp8266.readthedocs.io/en/latest/Troubleshooting/stack_dump.html#decode

Also please check if the data you receive actually contains a null-terminated string. client.read will not add a null character at the end of the buffer for you.

Jorgegarve commented 7 years ago

The data I receive does not contain a null at the end, it is just a huge amount of pictures compressed in JPEG, so the TCP data will contain "random data", but never terminated in null. I though when you call client.read(buffer, size) it will read an amount of size bytes from the TCP buffer.

igrr commented 7 years ago

client.read is no problem, but what do you expect Serial.println to do when you pass it a buffer full of binary data?

Jorgegarve commented 7 years ago

I expect to check if I receive a 0xffd8 (beginning of the JPEG file).

Jorgegarve commented 7 years ago

Decoding 7 results 0x4020a580: esp_yield at C:\Users\Pc\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/core_esp8266_main.cpp line 56 0x402071fd: ClientContext::read(char, unsigned int) at C:\Users\Pc\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\ESP8266WiFi\src/WiFiClient.cpp line 149 : (inlined by) WiFiClient::read(unsigned char, unsigned int) at C:\Users\Pc\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\libraries\ESP8266WiFi\src/WiFiClient.cpp line 230 0x4020a5f8: __yield at C:\Users\Pc\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/core_esp8266_main.cpp line 56 0x4020a638: optimistic_yield at C:\Users\Pc\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/core_esp8266_main.cpp line 56 0x402067ef: loop at C:\Users\Pc\Dropbox\TFM_JorgeGarciaVega\Proyecto\HUDmjpegViewer_readModified/HUDmjpegViewer_readModified.ino line 252 0x4020a5cc: loop_wrapper at C:\Users\Pc\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/core_esp8266_main.cpp line 56 0x40100718: cont_norm at C:\Users\Pc\AppData\Local\Arduino15\packages\esp8266\hardware\esp8266\2.3.0\cores\esp8266/cont.S line 109

igrr commented 7 years ago

I expect to check if I receive a 0xffd8 (beginning of the JPEG file).

No, it doesn't work this way. Serial.println of char* will interpret the argument as a zero-terminated string. If you wish to print the first two bytes as a hexadecimal number, you need to do something different (such as Serial.println(((uint16_t*) buffer)[0], HEX)).

However based on the stack trace it appears that the issue happens before println. Please post the full sketch which exhibits the issue.

Jorgegarve commented 7 years ago
#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP.h>
#include <SPI.h>
#include <SSD_13XX.h>
#include <JPEGDecoder.h>

//Pin definitions for display
#define __CS1  2  //(D4)
#define __DC  5   //(D1)
#define TFT_BLACK 0
#define MAXSIZE_ARRAYJPEG  20000
// Return the minimum of two values a and b
#define minimum(a,b)     (((a) < (b)) ? (a) : (b))

// WiFi information
const char WIFI_SSID[] = "******";
const char WIFI_PSK[] = "*******";
const char http_site[] = "******";
const int http_port = 8080;

// Pin definitions
const int LED_PIN = 2;

// Global variables
WiFiClient client;
SSD_13XX tft = SSD_13XX(__CS1, __DC);
uint32_t tTime, tTimeImage;
String requestPageCommand;
//Debugging
int flag = 0;
uint8_t arrayJpeg[MAXSIZE_ARRAYJPEG];
uint32_t arrayJpeg_currentSize;
uint8_t* rxBuffer;
size_t sizeBuffer = 1460;
boolean decoded;
int state=0;   //  0: init,    1: 0xFF received   2: 0xFF 0xD8 received

//Functions

//====================================================================================
//   Plots information about the image received
//====================================================================================

void jpegInfo() {
}

//====================================================================================
//   Decode and render the Jpeg image onto the TFT screen
//====================================================================================
void jpegRender(int xpos, int ypos) {

  // retrieve infomration about the image
  uint16_t  *pImg;
  uint16_t mcu_w = JpegDec.MCUWidth;
  uint16_t mcu_h = JpegDec.MCUHeight;
  uint32_t max_x = JpegDec.width;
  uint32_t max_y = JpegDec.height;

  // Jpeg images are draw as a set of image block (tiles) called Minimum Coding Units (MCUs)
  // Typically these MCUs are 16x16 pixel blocks
  // Determine the width and height of the right and bottom edge image blocks
  uint32_t min_w = minimum(mcu_w, max_x % mcu_w);
  uint32_t min_h = minimum(mcu_h, max_y % mcu_h);

  // save the current image block size
  uint32_t win_w = mcu_w;
  uint32_t win_h = mcu_h;

  // record the current time so we can measure how long it takes to draw an image
  uint32_t drawTime = millis();

  // save the coordinate of the right and bottom edges to assist image cropping
  // to the screen size
  max_x += xpos;
  max_y += ypos;

  // read each MCU block until there are no more
#ifdef USE_SPI_BUFFER
  while( JpegDec.readSwappedBytes()){ // Swap byte order so the SPI buffer can be used
#else
  while ( JpegDec.read()) { // Normal byte order read
#endif
    // save a pointer to the image block
    pImg = JpegDec.pImage;

    // calculate where the image block should be drawn on the screen
    int mcu_x = JpegDec.MCUx * mcu_w + xpos;
    int mcu_y = JpegDec.MCUy * mcu_h + ypos;

    // check if the image block size needs to be changed for the right and bottom edges
    if (mcu_x + mcu_w <= max_x) win_w = mcu_w;
    else win_w = min_w;
    if (mcu_y + mcu_h <= max_y) win_h = mcu_h;
    else win_h = min_h;

    // calculate how many pixels must be drawn
    uint32_t mcu_pixels = win_w * win_h;

    // draw image MCU block only if it will fit on the screen
    if ( ( mcu_x + win_w) <= tft.width() && ( mcu_y + win_h) <= tft.height())
  {

/*
#ifdef USE_SPI_BUFFER
      // Now set a MCU bounding window on the TFT to push pixels into (x, y, x + width - 1, y + height - 1)
      tft.setWindow(mcu_x, mcu_y, mcu_x + win_w - 1, mcu_y + win_h - 1);
      // Write all MCU pixels to the TFT window
      uint8_t *pImg8 = (uint8_t*)pImg;     // Convert 16 bit pointer to an 8 bit pointer
      tft.pushColors(pImg8, mcu_pixels*2); // Send bytes via 64 byte SPI port buffer
#else
      // Now set a MCU bounding window on the TFT to push pixels into (x, y, x + width - 1, y + height - 1)
      tft.setAddrWindow(mcu_x, mcu_y, mcu_x + win_w - 1, mcu_y + win_h - 1);
      // Write all MCU pixels to the TFT window
      while (mcu_pixels--) tft.pushColor(*pImg++);
#endif

*/
      tft.startPushData((uint16_t)mcu_x, (uint16_t)mcu_y, (uint16_t)(mcu_x + win_w - 1), (uint16_t)(mcu_y + win_h - 1));
      while (mcu_pixels--) tft.pushData(*pImg++);

//jorge comenta abajo y descomenta arriba. Hecho.

      //tft.pushDataPacket(pImg, mcu_pixels);      
      tft.endPushData();
    }

    else if ( ( mcu_y + win_h) >= tft.height()) JpegDec.abort();

  }

  // calculate how long it took to draw the image
  drawTime = millis() - drawTime; // Calculate the time it took

  // print the results to the serial port
  Serial.print  ("Total render time was    : "); Serial.print(drawTime); Serial.println(" ms");
  Serial.println("=====================================");

}

//====================================================================================
//   Attempt to connect to WiFi
//====================================================================================

void connectWiFi() {

  byte led_status = 0;

  // Set WiFi mode to station (client)
  WiFi.mode(WIFI_STA);

  // Initiate connection with SSID and PSK
  WiFi.begin(WIFI_SSID, WIFI_PSK);

  // Blink LED while we wait for WiFi connection
  while ( WiFi.status() != WL_CONNECTED ) {
    digitalWrite(LED_PIN, led_status);
    led_status ^= 0x01;
    delay(100);
  }  
  delay(2000);
  // Turn LED on when we are connected
  digitalWrite(LED_PIN, HIGH);
}

//====================================================================================
//   Makes a HTTP request
//====================================================================================

bool requestPage() {

  // Make an HTTP GET request

// Connect to server
  tTime = millis();
  while(!client.connect(http_site, http_port)){
    Serial.println("Trying to conncect to server");
  }
  tTime = millis() - tTime;
  Serial.print("conncected to server "); Serial.print(http_site);Serial.print(" after "); Serial.print(tTime); Serial.println(" ms");

  //Testing things with delay
  client.setNoDelay(true); 
  client.setTimeout(50); 

  tTime = millis();
  client.print(requestPageCommand);
  tTime = millis() - tTime;
  Serial.print("Page requested in "); Serial.print(tTime); Serial.println(" ms");
  return true;
}

void setup() {
  Serial.begin(57600);

  Serial.println("===============");
  Serial.println("Hola Setup JPEG ");
  Serial.println("===============");

  tft.begin();
  tft.setRotation(0);  // 0 & 2 Portrait. 1 & 3 landscape
  tft.fillScreen(TFT_BLACK);

  arrayJpeg_currentSize = 0;

  // Set up LED for debugging
  pinMode(LED_PIN, OUTPUT);

  // Connect to WiFi
  tTime = millis();
  connectWiFi();
  tTime = millis() - tTime;
  //wdt_enable();     // deshabilitar el watchdog de reinicio del esp8266

  Serial.print("Connected to wifi "); Serial.println(WIFI_SSID);Serial.print("   after "); Serial.print(tTime); Serial.println(" ms");

  // get the first page

  requestPageCommand = String("GET /?action=stream HTTP/1.1\r\nHost: ") + http_site + "\r\n" +
               "Connection: keep-alive\r\n\r\n";

  requestPage();

  //requestPageCommand = String("GET /xxx1.jpg HTTP/1.1\r\nHost: ") + http_site + "\r\n";

  Serial.println("First page requested");

} // End of setup()

void loop() {

if (client.available() > 0) {
client.read(rxBuffer, sizeBuffer);
Serial.println((char)* rxBuffer);
}

delay(1000);

}

We are not using the full code, just trying to check if we receive data correctly...

igrr commented 7 years ago

It seems that you aren't in fact initializing rxBuffer to point to a valid buffer before calling client.read.

Jorgegarve commented 7 years ago

I am currently at work, so I will try to solve it this afternoon. Again, thanks for the amazing support and work you do, and sorry about my insufficient knowledge...

Jorgegarve commented 7 years ago

I am sorry but I am not able to make it work. I don't find any example over the internet in order to have some examples to work around with. I just want to read the full TCP packet and save it in a variable and make things with these bytes, but I just can't do it.

EDITED: Nevermind, I think I have it.

Jorgegarve commented 7 years ago

I finally did it yesterday. It works like a charm... My NodeMCU is able to manage the packets so fast that I could even increase the frame rate of my project. Thanks for the support, igrr, and if someone needs some help about using client.read(buffer, size); just say it.

Keep strong!

john-- commented 5 years ago

Hey @Jorgegarve, I know it's been a while, but do happen to have a recent copy of that script ? I think I may be doing the exact same thing as you.