13

Please move/close this if the question isn't relevant.

Core: Cortex-M4

Microprocessor: TI TM4C1294NCPDT.

IP Stack: lwIP 1.4.1

I am using this microprocessor to do some data logging, and I want to send some information to a separate web server via a HTTP request in the form of:

http://123.456.789.012:8800/process.php?data1=foo&data2=bar&time=1234568789

and I want the processor to be able to see the response header (i.e if it was 200 OK or something went wrong) - it does not have to do display/recieve the actual content.

lwIP has a http server for the microprocessor, but I'm after the opposite (microprocessor is the client).

I am not sure how packets correlate to request/response headers, so I'm not sure how I'm meant to actually send/recieve information.

tgun926
  • 1,573
  • 4
  • 21
  • 37

3 Answers3

23

This ended up being pretty simple to implement, forgot to update this question.

I pretty much followed the instructions given on this site, which is the Raw/TCP 'documentation'.

Basically, The HTTP request is encoded in TCP packets, so to send data to my PHP server, I sent an HTTP request using TCP packets (lwIP does all the work).

The HTTP packet I want to send looks like this:

HEAD /process.php?data1=12&data2=5 HTTP/1.0

Host: mywebsite.com

To "translate" this to text which is understood by an HTTP server, you have to add "\r\n" carriage return/newline in your code. So it looks like this:

char *string = "HEAD /process.php?data1=12&data2=5 HTTP/1.0\r\nHost: mywebsite.com\r\n\r\n ";

Note that the end has two lots of "\r\n"

You can use GET or HEAD, but because I didn't care about HTML site my PHP server returned, I used HEAD (it returns a 200 OK on success, or a different code on failure).

The lwIP raw/tcp works on callbacks. You basically set up all the callback functions, then push the data you want to a TCP buffer (in this case, the TCP string specified above), and then you tell lwIP to send the packet.

Function to set up a TCP connection (this function is directly called by my application every time I want to send a TCP packet):

void tcp_setup(void)
{
    uint32_t data = 0xdeadbeef;

    /* create an ip */
    struct ip_addr ip;
    IP4_ADDR(&ip, 110,777,888,999);    //IP of my PHP server

    /* create the control block */
    testpcb = tcp_new();    //testpcb is a global struct tcp_pcb
                            // as defined by lwIP


    /* dummy data to pass to callbacks*/

    tcp_arg(testpcb, &data);

    /* register callbacks with the pcb */

    tcp_err(testpcb, tcpErrorHandler);
    tcp_recv(testpcb, tcpRecvCallback);
    tcp_sent(testpcb, tcpSendCallback);

    /* now connect */
    tcp_connect(testpcb, &ip, 80, connectCallback);

}

Once a connection to my PHP server is established, the 'connectCallback' function is called by lwIP:

/* connection established callback, err is unused and only return 0 */
err_t connectCallback(void *arg, struct tcp_pcb *tpcb, err_t err)
{
    UARTprintf("Connection Established.\n");
    UARTprintf("Now sending a packet\n");
    tcp_send_packet();
    return 0;
}

This function calls the actual function tcp_send_packet() which sends the HTTP request, as follows:

uint32_t tcp_send_packet(void)
{
    char *string = "HEAD /process.php?data1=12&data2=5 HTTP/1.0\r\nHost: mywebsite.com\r\n\r\n ";
    uint32_t len = strlen(string);

    /* push to buffer */
        error = tcp_write(testpcb, string, strlen(string), TCP_WRITE_FLAG_COPY);

    if (error) {
        UARTprintf("ERROR: Code: %d (tcp_send_packet :: tcp_write)\n", error);
        return 1;
    }

    /* now send */
    error = tcp_output(testpcb);
    if (error) {
        UARTprintf("ERROR: Code: %d (tcp_send_packet :: tcp_output)\n", error);
        return 1;
    }
    return 0;
}

Once the TCP packet has been sent (this is all need if you want to "hope for the best" and don't care if the data actually sent), the PHP server return a TCP packet (with a 200 OK, etc. and the HTML code if you used GET instead of HEAD). This code can be read and verified in the following code:

err_t tcpRecvCallback(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
    UARTprintf("Data recieved.\n");
    if (p == NULL) {
        UARTprintf("The remote host closed the connection.\n");
        UARTprintf("Now I'm closing the connection.\n");
        tcp_close_con();
        return ERR_ABRT;
    } else {
        UARTprintf("Number of pbufs %d\n", pbuf_clen(p));
        UARTprintf("Contents of pbuf %s\n", (char *)p->payload);
    }

    return 0;
}

p->payload contains the actual "200 OK", etc. information. Hopefully this helps someone.

I have left out some error checking in my code above to simplify the answer.

Community
  • 1
  • 1
tgun926
  • 1,573
  • 4
  • 21
  • 37
  • 3
    Great! thanks a lot for your help. lwIP documentation lacks a basic exemple like this. Be careful though, I guess the user code must call pbuf_free(p) manually in tcpRecvCallback, otherwise the memory is not freed from PBUF_POOL. I had some trouble with tcp_close(pcb) as well, it returns ERR_OK but the pcb is never freed and stays in CLOSE_WAIT state indefinitely. I had to set tcp_input_pcb = NULL before calling tcp_close, as a workaround.... – Motla Nov 09 '18 at 14:51
  • This works for me. Thanks @tgun926. One query is that, i am using it for throwing data to server at every 5 mins. it stucks at segmentation part while tcp_connect calls. Can you help me with this? I jus want to use this functions for sending data to sever. and i have multiple servers. So i have to connect tcp with different IP every time. – Jay Katira Mar 09 '20 at 06:35
  • I've tried using your code inside the main loop after sys_init() and lwip_init() have been called, but I can't seem to make the HTTP request unless I explicitly provide (and bind to) my local IP address. Do you have an idea why this is the case? In the documentation link you provided, it says, that calling tcp_bind() is optional (which it is not in my case). – DEls Oct 29 '20 at 17:39
1

Take a look at the HTTP example in Wikipedia. The client will send the GET and HOST lines. The server will respond with many lines for a response. The first line will have the response code.

Robert Deml
  • 12,390
  • 20
  • 65
  • 92
  • Yeah, I've looked at that many times. I was after a more specific answer regarding doing it via the lwIP library. I think I've almost figured it out via the raw/tcp api - will test it out soon. – tgun926 Oct 06 '14 at 21:47
0

I managed to create an HTTP client for raspberry pi Pico W using the example here.

It uses the httpc_get_file or httpc_get_file_dns functions from the sdk.

However, that example is incomplete since it has a memory leak. You will need to free the memory taken by the struct pbuf *hdr in the headers function and struct pbuf *p in the body function with respectively pbuf_free(hdr); and pbuf_free(p);

Without those modifications, it will stop working after about 20 calls (probably depends on the size of the response).

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Darkness
  • 11
  • 1