Closed Jorgegarve closed 7 years ago
Could you post the code you have on the ESP8266 side?
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?
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.
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!
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
.
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.
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.
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.
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?
Also, for a better adaptability, should I store before the return from howManyBytes = client.available() and use it in client.read(buffer, howManyBytes)?
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
}
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);
}`
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.
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.
client.read
is no problem, but what do you expect Serial.println
to do when you pass it a buffer full of binary data?
I expect to check if I receive a 0xffd8 (beginning of the JPEG file).
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
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.
#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...
It seems that you aren't in fact initializing rxBuffer
to point to a valid buffer before calling client.read.
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...
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.
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!
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.
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.