Skip to main content

Troubleshooting Device-to-Agent Data Transfer

How to manage the imp’s TCP send buffer

When you send data from an imp-enabled device to its agent using agent.send(), impOS™ encrypts the data and puts it into a TCP packet ‘first in, first out’ (FIFO) send buffer which empties into the WiFi chip’s internal buffer. The WiFi chip schedules the packet transmissions depending on radio conditions, and will retransmit if no acknowledgement (ACK) is received from the access point. This process continues until all the data in the TCP FIFO has been sent.

FIFO

When the Buffer Fills

If you send a large volume of data or make many agent.send() calls in rapid succession, it is possible to fill impOS’ send buffer. This will prevent subsequent agent.send() calls from adding data to the buffer, and this is also the outcome if WiFi has disconnected: the impOS buffer can’t be emptied because the WiFi sub-system can’t empty its own buffer.

This situation can cause seemingly strange connection and program execution behaviour. For instance, by default agent.send() blocks until its data is written into the buffer (another possibility is discussed below). If the send buffer is full, agent.send() may block until send timeout is reached (30 seconds by default), which can cause the device to appear unresponsive.

Fortunately, this and many other parts of the device-to-agent data transfer process can be configured to minimize the impact of this behavior.

Increase the TCP Send Buffer Size

If you need to send particularly large amounts of data, consider increasing the TCP send buffer’s size using imp.setsendbuffersize(). The default size is 3456 bytes (six packets of 576 bytes each), but this can be increased to 30KB, free memory permitting. It is only possible to increase the buffer’s size; it can only be reduced by restarting the imp, which sets it to the default.

function increaseBuffer(dataToSend) {
  local oldSize = 0;
  local newSize = dataToSend.len();
  if (newSize > 3456) oldSize = imp.setsendbuffersize(newSize);
  server.log("Buffer increased to " + newSize + " bytes from " + oldSize + " bytes");
}

Wait for Buffer Additions or Server Acknowledgements

You can control whether agent.send() returns as soon as the data has been placed in the impOS send buffer or waits until the data has been passed to WiFi, sent and its receipt acknowledged by the imp server. The first of these is recommended for most applications and so is the default behavior, though it can be selected explicitly by passing the constant WAIT_TIL_SENT onto the method server.setsendtimeoutpolicy()’s second parameter.

However, once the packet leaves impOS’ FIFO, impOS can’t tell whether the packet has been sent successfully, so a packet leaving the FIFO does not guarantee delivery: it could be stuck within the WiFi chip, within the WiFi access point, or in an intermediate router between the WiFi network and the imp server. To force the imp to wait until it has received acknowledgement from the server of receipt of the data, call server.setsendtimeoutpolicy() at the start of your device code and pass the constant WAIT_FOR_ACK as its second parameter. WAIT_FOR_ACK allows you to call agent.send() again safe in the knowledge that the impOS buffer is now clear, provided no disconnection has taken place of course (see below).

Note that WAIT_FOR_ACK can decrease throughput noticeably, and it does not guarantee the agent will get the message, only that the server has received it. If the agent is overloaded with messages or in the process of restarting, for example, the data could still be dropped. The only way to be sure is to code the agent to respond to the successful arrival of a message with a message of its own.

server.settimeoutpolicy(SUSPEND_ON_ERROR, WAIT_FOR_ACK, 10);

// Program does some work...

foreach (count, blob in buffer) {
  // Every send will not return until the server has ACK’d
  // Assume no timeout for this example
  local outcome = agent.send(“process.data”, blob);
  server.log(“Blob %u sent to server”, count + 1);
}

Alter the Timeout

Whichever of these approaches you choose, you can specify how long agent.send() waits for the data to be placed in the send buffer or for the server to acknowledge receipt of the data at protocol level. This is done by setting server.setsendtimeoutpolicy()’s third parameter, which is a timeout value in seconds; the default is 30 seconds. agent.send() will block until it returns; setting a shorter timeout ensures the call returns more quickly if the operation times out: it is unable to add the data to the buffer (the buffer is full) or the agent is taking longer than expected to acknowledge (it may have disconnected).

Test for Send Success or Failure

agent.send() always returns a value to inform you the result of its operation: a 0 if it was successful, or a Send Error Code to indicate the cause of failure:

Code Constant Description
SEND_ERROR_NOT_CONNECTED There is no connection to the server
SEND_ERROR_TIMEOUT The timeout expired before all the data was sent to the server
SEND_ERROR_DISCONNECTED The connection was disconnected before all data was sent to the server

Reading this return value allows your code to decide how to proceed. For example, non-critical data may be discarded and important data stored for re-transmission once the connection with the server is back up.

If your code is working to the default disconnection policy, SUSPEND_ON_ERROR, on anything other than a success, the imp will halt Squirrel execution immediately while impOS attempts to reconnect to the server. However, if you have chosen the RETURN_ON_ERROR policy — by passing this constant as server.setsendtimeoutpolicy()’s first parameter — you can adapt your code’s response to an agent.send() failure by examining the call’s return value: again, storing or rejecting data in the case of a disconnection, or attempting to resend the data after a suitable pause in the case of a timeout:

server.settimeoutpolicy(RETURN_ON_ERROR, WAIT_TIL_SENT, 10);

dataToSend <- null;
retryTime <- 600;  // Ten minutes

// Code at some point will place the data it needs to send into dataToSend and then call
// transmitter(), a wrapper for agent.send() and related calls, to send it.
function transmitter() {
  // Make sure we have data to send
  if (dataToSend == null) return;
  if (server.isconnected()) {
    // At this point we have WiFi connected so try to add dataToSend to TCP send buffer
    local outcome = agent.send(“message”, dataToSend);
    if (outcome == 0) {
      // Record successful addition of data to impOS buffer by zero-ing dataToSend
      // This can be used to indicate dataToSend is free for more data
      dataToSend = null;
    } else {
      // Couldn’t add dataToSend to the buffer OR
      // WiFi has been lost OR some other failure
      // RETURN_ON_ERROR in force so we need to re-connect before
      // attempting to add dataToSend to the buffer again

      // Reschedule transmitter()
      imp.wakeup(retryTime, transmitter);
    }
  } else {
    // WiFi is disconnected, so attempt to reconnect as per RETURN_ON_ERROR
    // Whatever the outcome, we come back to transmitter().
    // We can't schedule transmitter() directly as server.connect()'s
    // callback expects a single parameter to take outcome indicator integer
    server.connect(function(reason) { 
      transmitter(); 
    }r);
  }
}

Summary

This chart summarizes the possible outcomes of agent.send():

agent.send() timeline

  1. Squirrel code calls agent.send().
  2. The policy is WAIT_TIL_SENT.
    impOS writes the data to the TCP Send Buffer.
    agent.send() returns the value 0.
  3. The policy is WAIT_TIL_SENT.
    The attempt to write the data to the buffer times out.
    If SUSPEND_ON_ERROR is in force, Squirrel halts.
    If RETURN_ON_ERROR is in force, agent.send() returns SEND_ERROR_TIMEOUT.
  4. The policy is WAIT_FOR_ACK.
    The server ACK to WiFi is relayed to impOS.
    impOS notifies agent.send() which returns the value 0.
  5. The policy is WAIT_FOR_ACK.
    The wait for the server ACK times out.
    If SUSPEND_ON_ERROR is in force, Squirrel halts.
    If RETURN_ON_ERROR is in force, agent.send() returns SEND_ERROR_TIMEOUT.