Skip to main content

Knowledgebase

Search knowledgebase

Known Issues
Electric Imp mobile app may not trigger BlinkUp correctly under iOS [FIXED]

With iOS 9.3.5, iOS 10.x and iOS 11.x, the Electric Imp app and apps based on the iOS BlinkUp SDK versions 19.5.0 and 19.6.0 may fail to run BlinkUp properly:

  • BlinkUp does not show
  • BlinkUp skips frames

The cause appears to be high background CPU activity triggered by other apps and/or iOS.

Fixed The latest Electric Imp mobile app (4.4.5) and iOS BlinkUp SDK 19.7.0 contain fixes for this issue.

Re-configuring the pulse counter causes it to miscount pulses

Re-configuring the pulse counter (using pin.configure()) to use a different sample duration causes the pulse counter to miss count the pulses received during the specified duration.

This behavior is still seen even if you reconfigure the pin as DIGITAL_IN between pulse-counter configuration call.

Expected Fix This issue is scheduled to be addressed in a future impOS release.

The Electric Imp mobile app may not correctly log in under Android [FIXED]

After upgrading to version 4.4.2 of the Android Electric Imp mobile app, users may not be able to log in. The app will present the error “Unexpected server response”.

Fix Update to version 4.4.3, now available from the Google Play Store

Squirrel string length in device code

Device-side Squirrel does not deal correctly with strings longer than 65,535 bytes. No warnings or errors are currently generated, either at compile-time (for literals) or run-time (for constructed strings), but oversize strings behave as if their lengths were reduced modulo 65,536.

Workaround Check the lengths of strings likely to contain 65,535 Ascii characters (fewer if you use unicode) and segment into multiple strings as necessary.

impOS features not yet implemented on the imp005

As of impOS 36, the following standard impOS features are not yet enabled on the imp005:

USB limitations on the imp005
  • USB isochronous endpoints are not supported.
  • In impOS 34, only bulk and control endpoints are supported.
  • impOS 36 adds interrupt-in endpoints.
imp005 Ethernet and WiFi Connectivity under impOS 34 [FIXED]

Under impOS 34, the imp005 will always attempt to connect via Ethernet even if there is no PHY present. This can delay a WiFi connection by several seconds.

  • Fix impOS 36 enables PHY detection to optimize connection times on wireless-only products.

Under impOS 34, the imp005 will always attempt to connect via WiFi, if it has WiFi credentials (ie. an SSID) stored and even if there is no antenna attached.

imp005 does not support WiFi login via WPS

BlinkUp™ can transmit a WPS (WiFi Protected Setup) code to a device. Currently, this mode of configuration is not supported on imp005-based devices. For this reason, imp.net.configurewps() is not available on the imp005.

Workaround For WiFi access, provide the device with the network’s SSID and password, not a WPS code.

Expected Fix impOS 38 will provide WPS support on imp005-based devices.

imp005 does not support deep-sleep

imp005 devices are not capable of deep sleep, so the imp API deep-sleep methods imp.deepsleepfor(), imp.deepsleepuntil(), server.sleepfor() and server.sleepuntil() have been removed from the imp005. Customers using those calls to cause a device reset will now find that their use causes a Squirrel runtime error instead.

Expected Fix imp005 is currently expected to gain deep-sleep support in a future impOS release.

Changes to deep-sleep support on imp003 in impOS 34

May require Squirrel changes

From impOS 34, the imp API deep-sleep methods imp.deepsleepfor(), imp.deepsleepuntil(), server.sleepfor() and server.sleepuntil() are no longer made available on imp003-based designs which lack the 32kHz crystal required for low-power mode. They continue to be available on imp003-based designs which do include a 32kHz crystal.

The presence of these methods can be used to check whether or not a crystal has been detected, for instance in factory firmware, using constructs such as:

if ("sleepfor" in server) { ... }
FAQs
How large can my Squirrel code get?

The Electric Imp IDE, and the impCentral API, put a limit of 4MB on your Squirrel agent code. The same limit is also applied to your device code. Neither agent nor device code can be larger than 4MB.

What happens if I use the BlinkUp API key from a different account in my app?

The BlinkUp API key you use to make calls to the BlinkUp SDK’s methods must be the one associated with your account. If this is not the case — for example, it is used with devices blessed to application firmware that is associated with a different account than yours — then your end-users will not be able to perform BlinkUp with your app successfully and they will find their devices are unresponsive.

How do I customize the text in a BlinkUp interface?

Almost all of the text in the BlinkUp process can be customized for your application. The Android BlinkUp SDK allows you to set these programmatically; the iOS BlinkUp SDK includes a file of strings which can be edited. Both SDKs also include default BlinkUp strings.

What is the difference between standard and custom BlinkUp?

Standard BlinkUp presents a user interface generated by the BlinkUp SDK. It allows the user to select which network they wish to connect to, and then perform a BlinkUp. You and your app developer can customize much of the text presented throughout the process.

If you feel you want more control over the look and feel of the BlinkUp process, you can do a custom BlinkUp. This integrates BlinkUp into your own network-selection UI. More information on this process can be found in the documentation accompanying the BlinkUp SDK.

Can I have the same agent URL for a device no matter what?

This is not recommended and should be avoided. Each user has their own plan ID to ensure proper security. Please spend some time architecting your use and management of agent URLs in your app and on your server. Develop a proper plan for the agent URL before launch.

Why does a device’s agent URL keep changing?

The agent URL is derived from both the device ID and the plan ID. If either of these tokens change, a new URL will be generated and the previous agent URL associated with the device ID will be invalidated.

The device ID never changes. The plan ID can be changed — by requesting a new one from the Electric Imp impCloud™ — but this should be avoided in order to prevent the loss of a user’s agent and device settings. Using a new plan ID, even for the same device, will always generate a new agent.

A pre-existing plan ID must be passed to a BlinkUpController object before the BlinkUp process is initiated, otherwise a new plan ID will be requested by the BlinkUp SDK automatically. If the same device is set up with the same plan ID, the agent URL returned will be the same as before.

The plan ID should be unique per user. If you have a single user — ‘George’, for example — you can use the plan ID generated for George each time he uses BlinkUp on a device. It is your job to keep track of the association between plan ID and users. If you need to keep track of unique devices, the device ID should be used.

Why does it sometimes take so long to get an agent URL?

If an issue occurs during the BlinkUp process, the agent URL may never be returned. In this case, a delegate method (iOS) or callback method (Android) will be called with an error after one minute. The BlinkUp may fail for a number of reasons, such as incorrect WiFi settings, obstructions between the phone’s screen and the device’s photosensor, and the device’s on-board imp being unable to connect to the Electric Imp impCloud™. It is possible to change the default timeout.

What is an agent URL?

The agent URL is the Internet address of the unique agent assigned to the device being configured by BlinkUp. When the BlinkUp process completes successfully, the agent URL is returned to the app performing the operation. It should be recorded: the app will need to use it to interact with the agent in future, for instance to issue commands to the device or to request information from the device.

What is a device ID?

The device ID — previously termed the impee ID and still referred to by the SDK as such — is an imp’s authentication ID. It is used to identify which devices are tied to your developer account and which production devices should get the application firmware they were assigned. You can view a developer device’s ID in the IDE, at the top of the device code pane. A device’s ID never changes.

What is a plan ID?

The plan ID is a token that is used to identify a device’s user. New users should be given a new plan ID, which your app should store and use in future BlinkUp operations by setting the planID property of the BlinkUpController object instanced in your app code. A user’s plan ID should be retained and used this way to allow them to re-configure the device’s network access without the loss of agent and device settings. Using a new plan ID, even for the same device, will always generate a new agent.

Hints & Tips
How to Display the WiFi Signal Strength Where an imp is Placed

You can log the strength of the WiFi signal an imp is picking up very easily with the following code:

function reportRSSI() {
    local rssi = imp.rssi();
    local bars = 5;

    if (rssi < -87) {
        bars = 0;
    } else if (rssi < -82) {
        bars = 1;
    } else if (rssi < -77) {
        bars = 2;
    } else if (rssi < -72) {
        bars = 3;
    } else if (rssi < -67) {
        bars = 4;
    }

    if (bars == 1) {
        server.log("Signal Strength: " + rssi + "dBm (1 bar)");
    } else {
        server.log("Signal Strength: " + rssi + "dBm (" + bars + " bars)");
    }
}
How to Signal a Successful Application Firmware Update

To make an imp’s LED flash green when a new application firmware version has been successfully installed on the device and run — after you hit the ‘Build and Run’ button in the IDE — add the following lines to the start of your device code:

imp.enableblinkup(true);
imp.wakeup(30.0, function(){
    imp.enableblinkup(false);
});

This will blink the imp’s light for 30 seconds whenever it starts up, including power-cycles and waking from deep sleep. Set this for a decent duration to give you time to re-BlinkUp while the last WiFi network is still ‘visible’.

How to Scan an I2C Bus for Devices

The following code snippet provides a very simple way of determining the addresses of devices connected to any one of an imp’s I²C buses. The code here assumes the bus in use is the imp001’s hardware.i2c89, but can easily be changed to, for example, hardware.i2cAB or hardware.i2cFG on the imp003, or one of the imp004m’s or imp005’s I²C buses:

i2c <- hardware.i2c89;   // Set to desired I2C bus
i2c.configure(CLOCK_SPEED_100_KHZ);

for (local i = 2 ; i < 256 ; i+=2) {
    if (i2c.read(i, "", 1) != null) server.log(format("Device at address: 0x%02X", i));
}
How to Identify Your Device’s Type of imp

impOS™ 36 will add a new imp API method, imp.info(), which will provide a string indicating the type of imp your code is running on. When impOS 36 is released to production, this page will be updated to make use of that method.

How to Load More Than 512KB of Data Using HTTP

Agents can currently fetch no more than 512KB at any one time using HTTP. This is not a serious limitation in most circumstances, and is more data than the device can sensibly deal with in any case. But for the rare situations in which fetching a larger file is needed, there is a workaround: the file can be streamed, by fetching it in sections using the HTTP ‘Range’ header.

The following code streams the Electric Imp homepage in 1KB lumps. The page is actually much smaller than 512KB, so could have been fetched in one go, but it nonetheless demonstrates the technique well.

function fetch() {
    offset <- 0;
    const LUMP = 1024;
    local URL = "http://electricimp.com";
    local header = {Range = format("bytes = %u - %u", offset, offset + LUMP)};

    do {
        response <- http.get(URL, header).sendsync();

        server.log("Status code: " + response.statuscode);
        server.log("Headers:");

        foreach(key, value in response.headers) {
            server.log(key + " = " + value);
        }

        got <- response.body.len() - 1;
        offset += got;
    }
    while (response.statuscode == 206 && got == LUMP);

    server.log("Got " + offset + " bytes total");
}

To determine the size of the entity you wish to fetch before you fetch it, use http.request() and make an HTTP/1.1 HEAD request:

local httpreq = http.request("HEAD", URL, "", "");
local resp = httpreq.sendsync();
local resource_size = resp.headers.rawget("content-length");
How to Acknowledge Receipt of a Message

The following code provides a very simple — if inelegant — way of acknowledging receipt of a message. The code here assumes the message has been sent from agent to device, but the same technique can be used for messages travelling in the opposite direction: just flip agent.send() to device.send() and agent.on() to device.on().

agent.on("message.from.agent", function(data) {
    // Send acknowledgement
    agent.send("ack", true);

    // Process message's data payload as normal (code omitted)
});

If you are looking for a more sophisticated approach to managing agent-device communications, please take a look at our Bullwinkle library. It allows the basic imp messaging API to be extended in a number of interesting and useful ways.

How to Use Regular Expressions to Parse Incoming XML Data

Many web services return data in JSON format, and the imp API has a number of useful tools for converting JSON data into more readily usable forms. However, some web services send data as XML, and this can be more tricky to extract, especially if the order of the XML tags is not constant. Fortunately, impOS™ provides a regular expression object, and this can be used to examine the incoming XML for specific tags.

For example, your agent may receive some XML like this:

<NotifyReturn>
    <ResponseCode>0</ResponseCode>
    <CallAnswered>false</CallAnswered>
    <QueueID>646253102</QueueID>
    <TryCount>0</TryCount> <Duration>13</Duration>
    <CallComplete>false</CallComplete>
</NotifyReturn>

Regular expressions can be used to find specified characters no matter where they appear in the data. In this example, we want to extract the QueueID value when it contains two or more numerical characters. To use regular expressions in Squirrel, we first instantiate a regexp2 object using impOS’ new regexp2() function. You can see this in the code below, in the first non-comment line of the function. We pass the string we’re searching for, including both specific characters (the XML tags we’re interested in) and between them the regular expression code to be matched against the incoming data. Here we use:

([0-9]+)

which says match against any number of characters 0 through 9. The square brackets delimit the range of characters we’re interested in; the plus symbol says ‘match against one or more characters’.

With the regexp2 object instantiated, we call its capture() method, passing the data we want to match the regular expression to. This method returns an array of tables, one table for each match. Each table contains two keys, begin and end, which are the positions within the passed string data of a sub-string matching the regular expression.

We check that we have at least two matches and if so use the second of the two tables in the results array. We use the begin and end values to extract the number embedded in the XML and return it as a string. The first entry in the results array includes the XML tags, so we ignore it.

function getQueueID(xmlString) {
    // Set the regexp object to the string we're searching for
    // <QueueID>xyz</QueueID> where xyz contains arbitrary digits

    local rexp = regexp2(@"<QueueID>([0-9]+)</QueueID>");
    local results = rexp.capture(xmlString);
    local queueID = "";

    if (results && results.len() > 1) {
        queueID = xmlString.slice(results[1].begin, results[1].end);
    }

    return queueID;
}

If we were interested in the CallAnswered tag, we could match against alphabet characters rather than numbers:

([a-z]+)

and using the example XML above, we’d get false returned. A small piece of Squirrel converts this to Boolean values:

if (queueID == "false") {
    return false;
} else {
    return true;
}

Regular expressions can be used to detect complex patterns of characters as well as the simple ones used in the example above. Take a look at the the documentation on the regexp2 object for more information on how to assemble regular expressions for string matching.

The regexp2 object makes use of the Google re2 regular expression engine, which is a considerable improvement upon the standard Squirrel regular expression evaluation functionality provided by regexp. We recommended new code uses regexp2 in preference to the standard functionality. However, re2 memory usage issues mean that regexp2 is currently only available in agent code, a regexp2 regular expression is limited to 256 characters, and each agent is only allowed to have up to ten regexp2 objects simultaneously. The standard functionality will continue to be offered for device code and to maintain compatibility with older agent code.

How to Make Your Squirrel Code More Compact

Here are some general tips for reducing the size of your device or agent code. For a more in-depth discussion of some of the techniques you can use to mare Squirrel run more efficiently to boost performance and reduce the code’s memory footprint, see the Developer Guide ‘Writing Efficient Squirrel’.

  • Keep your variable names short — though you should make sure it’s still clear what information they hold.
  • Comment out variables you have added but not used. You can always ‘un-comment’ them later.
  • Do you use all the methods defined by a custom class? If not, comment out the ones you don’t need. Use /* and */ to block out large sections of code. Again, you can always ‘un-comment’ them later.
How to Convert an Object’s Name to a Reference

The following function takes a string containing an object’s name and parentage — using standard dot notation — and returns a reference to the object in memory.

For example, if the string passed to the function is "myclass.mytable.myelement", the function will return a reference to the object myelement, provided it is a valid object.

function resolveReference(pathString) {
    local pathArray = split(pathString, ".");
    local object = this;

    foreach (element in pathArray)  {
        if (element in object) {
            object = object[element];
        } else {
            return null;
        }
    }

    return object;
}
How to Convert a Hexadecimal String to a Value... and Back Again

Hexadecimal strings of the form 0x12AB are are commonly used part of the Electric Imp API. Use the following code to convert to and from these strings:

function hexStringToInt(hexString) {
    // Does the string start with '0x'? If so, remove it
    if (hexString.slice(0, 2) == "0x") hexString = hexString.slice(2);

    // Get the integer value of the remaining string
    local intValue = 0;

    foreach (character in hexString) {
        local nibble = character - '0';
        if (nibble > 9) nibble = ((nibble & 0x1F) - 7);
        intValue = (intValue << 4) + nibble;
    }

    return intValue;
}
function intToHexString(intValue) {
    // '02' ensures single-digit value are presented with two digits
    return format("0x%02X", intValue);
}
Technotes
Troubleshooting Device-to-Agent Data Transfers

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.
Calling Functions vs Registering Functions

Many methods in the imp API allow you to register a function that will be automatically called when at some time in the future an event takes place. These functions are called ‘callback functions’ or ‘callbacks’.

Callbacks are registered by providing the imp API method with a reference to the function. This essentially tells the method which function it should call. The function being referred to can be declared inline within the method call, or separately:

// Inline callback declaration
device.on("message.from.agent", function(data) {
  server.log("Message received from agent");
  if (data) server.log("Contains " + data.len() + " bytes of data");
});
// Separate callback declaration
function cb(data) {
  server.log("Message received from agent");
  if (data) server.log("Contains " + data.len() + " bytes of data");
}

device.on("message.from.agent", cb);

The use of a separate declaration is particularly useful as it allows a function to call itself:

loopCount <- 0;

function loop() {
  loopCount++;
  server.log("Cycle count: " + loopCount);
  imp.wakeup(1.0, loop);
}

In both examples of the use of a separate callback declaration, the callback is registered by passing its name not by calling it. This often confuses even experienced programmers who are nonetheless new to Squirrel and the imp API. For example, they write:

imp.wakeup(1.0, aCallbackFunction());

When Squirrel encounters this, it parses the line and sees that it must call aCallbackFunction() immediately in order to determine what value should be passed into imp.wakeup()’s second parameter. It does so, and then processes the remainder of the line, which sets up a timer to fire in 1.0 seconds, when it will call the function named in the second parameter. Unfortunately, because it has actually called aCallbackFunction() already and received a return value (or null), this return value is used as the reference to the function that should be called when the timer fires. The result is unpredictable, but will most likely cause a runtime error.

The one time where this syntax will work is when aCallbackFunction() returns a function reference. For example:

function aCallbackFunction() {
  return function() {
    server.log("Callback called");
  }
}

Now, when the imp.wakeup() line is parsed and aCallbackFunction() executed, the timer is cued to execute the inline function. So in one second’s time, the line "Callback Called" will appear in the log.

The rule of thumb, then to execute a function now, add paratemeter brackets to the function’s name; to register the same function for execution later, omit the brackets.

Agents Have a 30-Day Device-less Lifespan

When a device goes offline, its agent will stay online and accessible via its unique URL. However, if the device remains offline for an extended period of time — currently 30 days — the agent will be shut down automatically to conserve server resources. The agent is stopped; it is not deleted.

If a mobile application, web site or third-party server attempts to contact the agent after this point, they will receive the standard ‘resource missing’ HTTP status code, 404, indicating that the agent is offline. End-users may report such errors if, for example, they attempt to configure a device using its mobile app when that device has been offline for more than a month. Whether the app reports this failure to access the agent as a 404 error or some other message will depend on how your app has been written, and whether the app communicates directly with the agent or routes such messages via your server.

End-users experiencing such communications errors should be instructed to ensure their devices are online. If necessary, they should power-cycle the device and confirm visually that it is connecting. This verification can be made by observing the device’s LED for the status codes it presents.

Be aware, however, that a 404 status code is not a guarantee that an agent has been stopped. An agent can itself issue a 404 HTTP status code and this takes place when the agent is online, receives an HTTP request but has no HTTP request handler function as registered using http.onrequest() to deal with it (see ‘HTTP Request Response Codes’).

As and when the device comes back online, the disabled agent will automatically be restarted. Any data previously preserved using server.save() will still be accessible. The rebooted agent will continue to be contactable at its prior URL — it does not gain a new URL.

How to Manage Agent-device Code Mismatches

When new application code is deployed, there may be a significant gap between the new code beginning to run on agent and device.

Agents that are running a given deployment will be updated with new code one after the other. There is a delay of 100ms between one agent restarting and the next one doing so. Devices are also restarted at 100ms intervals. This is intentional: the delay prevents customers’ backend servers from becoming overloaded as they would be if agents were restarted en masse and all attempted to contact the customer’s backend simultaneously.

The order of the agent and device restarts is not currently pre-defined and so a device may restart with the new code before its agent does, and vice versa. Consequently, there may be an arbitrarily long period when a device might be communicating with an agent running an older or newer version of the assigned application firmware.

This period can become more extensive as the number of agents and devices that are part of a given product increases. For example, if 15,000 agents are running certain application firmware, deploying updated agent code at a rate of ten agents every second (ie. one every 100ms) means that the deployment will take at least 25 minutes to complete. It is therefore possible that a device might be running different code from its agent for a period of up to 25 minutes.

Looking ahead, ‘polite deployment’ provision for application firmware is on the Electric Imp roadmap and when this is implemented, each agent-device pair will be updated simultaneously, followed by the 100ms delay to prevent customers’ backend servers being overloaded. Until that time, we recommend customers implement the following workaround:

  1. Customers should internally version their application firmware and enter that version in both agent and device code.
  2. At start-up, the agent should request the device’s application version number and compare it to its own. If there is a mismatch, the agent can take appropriate action:
    • If the agent is ahead of the device, it can instruct the device to initiate a manual disconnect-reconnect cycle, which should cause the device to acquire the updated code.
    • If the agent is lagging behind the device, it can call server.restart() to trigger a reboot, this time resulting in the agent receiving the latest version of the application code.
    • If the two versions match, take no action.

This is demonstrated by the following agent code:

const AGENT_VERSION = "3.4.5";

function checkVersion() {
  // Register a function to check the incoming device code version
  device.on("send.devicecode.version", function(deviceVersion) {
    // 'deviceVersion' is a string of the form x.y.z
    local dv = deviceVersion.split(".");
    local av = AGENT_VERSION.split(".");

    // Compare version number components left to right
    for (local i = 0 ; i < 3 ; i++) {
      if (av[i].tointeger() > dv[i].tointeger()) {
        // Agent version component is higher than device's
        // so device should restart to update
        device.send("action.device.restart", true);
        imp.wakeup(10, checkVersion);
        break;
      }

      if (av[i].tointeger() < dv[i].tointeger()) {
        // Agent version component is lower than device's
        // so agent should restart to update
        server.restart();
      }
    }
  });

  // Ask the device for its code version
  device.send("request.devicecode.version", true);
}

// START
checkVersion();

And the following device code:

const DEVICE_VERSION = "3.4.5";

// Register a function to handle device code version requests
agent.on("request.devicecode.version", function() {
  // Immediately return the device code version to the agent
  agent.send("send.devicecode.version", DEVICE_VERSION);
});

// Register a function to initiate a restart
agent.on("action.device.restart", function (dummy) {
  imp.onidle(function() {
    // When the imp goes idle, restart after five seconds' deep sleep
    imp.deepsleepfor(5);
  });
});
Squirrel Error Messages

Squirrel has many runtime error types pre-programmed. They are largely self-explanatory. However, not all of the errors defined by the standard version of Squirrel are included in the imp version of the language: errors relating to files, for instance, are absent because the Electric Imp platform doesn’t support files.

Here is a list of error messages you may encounter:

  • A closure with free valiables bound cannot be serialized
  • Arith op [operator] on between [variable name] and [variable name]
  • Assertion failed
  • Attempt to call [entity]
  • Attempt to delete a slot from a [variable type]
  • Attempt to negate a [variable type]
  • Attempt to perform a bitwise op on a [variable type]
  • Bitwise op between [variable name] and [variable name]
  • Call failed
  • Cannot apply instanceof between a [entity] and a [entity]
  • Cannot clone blob
  • Cannot convert the string
  • Cannot create blob
  • Cannot create blob with negative size
  • Cannot delete a slot from [table]
  • Cannot deserialize a [variable type] (0x%x)
  • Cannot iterate a generator
  • Cannot iterate [name]
  • Cannot resize stack while in a metamethod
  • Cannot serialize a [variable type]
  • Cannot set property of object of type [variable type]
  • Cannot suspend through native calls/metamethods
  • Cannot use non-cloneable type for default parameter
  • Class instances do not support the new slot operator
  • Clear only works on table and array
  • Cloning a [type]
  • _cmp must return an integer
  • Compare function failed
  • Comparison between [variable type] and [variable type]
  • CRT API failure
  • Delegate cycle
  • Division by zero
  • Division overflow
  • Empty array
  • Empty separators string
  • Float expected for the specified format
  • Format too long
  • Idx out of range
  • Inconsistent compare function
  • Indexing [variable name] with [variable name]
  • Index out of range
  • Integer expected for the specified format
  • Internal error (_nexti) wrong argument type
  • Invalid base type
  • Invalid environment
  • Invalid format
  • Invalid free var index
  • Invalid index type for an array
  • Invalid object type
  • Invalid or corrupted closure stream
  • Invalid origin
  • Invalid param / Invalid parameter
  • Invalid param type
  • Invalid typemask
  • Invalid type tag
  • Invalid type / Inalid type, expected table
  • Modulo by zero
  • Modulo overflow
  • Native closure expected
  • Native stack overflow
  • Negative size
  • New member only works with classes
  • _nexti failed
  • _nexti returned an invalid idx
  • No closure in the calls stack
  • No data left to read
  • Not enough parameters for the given format string
  • Not enough params in the stack
  • Null cannot be used as index
  • Null is not a valid key / Null key
  • Numeric value expected as return value of the compare function
  • Object must be a class
  • Only generators can be resumed
  • Out of memory
  • Parameter [name] has an invalid type [variable type]
  • Precision format too long
  • rawget works only on array/table/instance and class
  • rawset works only on array/table/class and instance
  • remove() failed
  • rename() failed
  • resize failed
  • Resuming active generator
  • Resuming dead generator
  • Size must be a number
  • Slice out of range
  • Stack overflow
  • Stack overflow, cannot resize stack while in a metamethod
  • String expected for the specified format
  • Table expected
  • The blob is invalid
  • The class is locked
  • The index doesn’t exist / The index [name] does not exist
  • The object is not a class
  • The object is not a class instance
  • The object is not a closure / The object is not a native closure
  • The object must be a weakref
  • The property [name] already exists
  • The target is not a closure
  • The type doesn’t have a default delegate
  • This object cannot be cloned
  • top() on a empty array
  • Trying to inherit from a [variable type]
  • Trying to modify a class that has already been instantiated
  • Trying to resume a [entity], only genenerator can be resumed
  • Trying to yield a [entity], only genenerator can be yielded
  • Unknown error
  • Unrecognized encoding
  • Width format too long
  • Wrong argument type
  • Wrong index / Wrong indexes
  • Wrong number of parameters
  • Wrong param / Wrong parameter
  • Wrong type / Wrong type (expected class or instance)
HTTP Request Response Codes

When a web page or a mobile app attempts to communicate with a device’s agent, it does so using the standard HTTP mechanism. Errors arising from this process will be returned as a status code, of which there are are many, pre-defined values. Wikipedia provides a list of these generic codes.

A number of these, however, have particular meanings in the context of agent communications. If your web page or app is having difficulty exchanging data with an agent, a knowledge and understanding of these specific codes may help you determine the cause of the problem, and arrive at a solution to it.

The table below lists standard HTTP response codes which have specific meanings in the context of communications with imp agents. Codes beginning with the number 4 will be automatically generated by the Electric Imp impCloud™; those starting with a 5 will be generated by the web browser or mobile OS’ HTTP sub-system.

If the statuscode is between 0 and 99, there was an error sending the request. Such errors are currently the same as libcurl error messages. For instance, statuscode == 3 means that the URL was malformed, and statuscode == 6means that the hostname wouldn’t resolve. However, this may change in the future, so don’t write agent code that depends on particular values having particular meanings.

The agent may also send these codes, using the imp API method httpresponse.send(). This method takes two parameters: in integer for the status code to be sent, and a string containing an appropriate description of the status. Your code may send unique strings to tune standard HTTP response codes to your application. All HTTP requests sent to your agent should be completed with a call to httpresponse.send() which also closes the connection between app or website and agent.

Please see the Developer Guide ‘Effective Internet-agent-device Communication’ for an in-depth discussion of the best practices to adopt when managing communications between apps, agents and devices.

Code Meaning Description Suggested Solution
403 Agent restarting Generically, an “access forbidden” code, 403 here indicates that the agent is in the process of restarting and so unable to handle incoming HTTP requests as the present time. Wait a moment and try to send the request to the agent again.
404 No HTTP handler/
agent offline
This is the standard HTTP “not found” code for a missing resource. In the case of the imp, it indicates that the agent you are communicating with has no HTTP request handler function, that such a function has not been registered with the agent, or that the agent is no longer running. Agents continue to run when a device goes offline. However, if the device has been offline for a period of more than 30 days, its agent will be shut down.
See the API documentation to learn how to write and register a suitable HTTP request handler. See also Effective Internet-agent-device Communication.
413 Request size too large The Electric Imp impCloud currently imposes a maximum size of 1MB* for standard developer agents, including code, any data the agent requires and incoming HTTP requests. Your app or web page will receive this code if your request exceeds the available storage after the agent has taken as much of the 1MB as it needs. Check the size of the HTTP request your app or web page is making. If necessary, split the data across multiple requests. Consider whether you are sending more data that the agent requires.
 
Commercial users who require more than 1MB of agent space should contact sales@electricimp.com
429 Rate limit exceeded You are making too many HTTP requests to the agent too quickly. The Electric Imp impCloud currently* accepts no more than ten requests each second, adding them to a pool of up to 20 simultaneously open connections. Once there are 20 open connections, no more requests will be accepted until older ones are closed. Consider if your app or web page is making more requests than it needs to, or making them too frequently.
 
Do you have too many open connections in play? Open connections should be closed by the agent using the httpresponse.send() API call. Connections will stay open until httpresponse.send() is called.
504 Agent timeout The agent did not respond in a timely manner. The Electric Imp impCloud automatically closes open connections after ten minutes of inactivity. Your agent code may be spending too long processing data before returning a response: issue a suitable response using the httpresponse.send() API call before processing data, or before forwarding it to the device or to a web service.
 
Connections will stay open until httpresponse.send() is called, so it’s important to include this call in your code.
 
Uncaught errors may also prevent the agent from responding.

*Electric Imp reserves the right to change these limits at any time, in its sole discretion, with or without notice, although Electric Imp will endeavor to provide Customer with notice of any such limitations described in this section.

How to Fix ‘no handler for device.send()’-type Errors

When agent code calls device.send() and there is no equivalent agent.on() in the device code, the device will log a error like this:

2015-07-07 19:54:17 UTC-5  [server.error]  ERROR: no handler for device.send()

This may seem odd, since the device can’t call device.send(), but it’s simply a sign that the device has received a message transmitted by a device.send() from the agent.

The usual way to debug this is to compare all the instances of device.send() in the agent code to ensure they are accompanied by an agent.on(). The first parameter of both calls, a string containing an identifier for the message being sent, must match across the two paired calls. A common source of error is a mis-typed message name string in either of the two calls.

What if you have eliminated such issues as causes of the error? In this case, it may be that you are experiencing a race condition. If, for instance, your agent code calls device.send() as soon as it boots, but the paired agent.on() is near the bottom of your device code, the message can be sent and received before the device’s Squirrel interpreter has been able to register the handler function you assign in the agent.on() call. Because the code hasn’t yet performed this registration when the message arrives, it will throw the above error.

If this is the case, you can apply any of the following techniques:

  • Register the device’s message handlers as early as possible in the code sequence.
  • Delay the device.send() call on the agent side to allow the device time to complete its setup. For example:
imp.wakeup(1, function() {
  // Wait 1 second for the device to boot before
  // sending it the first message
  device.send("my.message", data);
});
  • Have the device signal its readiness to the agent and ensure that no messages are sent from agent to device until this signal has been received. For example:
device.on("ready", function() {
  // Device has signalled its readiness - send over saved settings
  device.send("clock.set.preferences", settingsTable);
});

An equivalent error involves a missing handler for agent.send() posted by the agent. Again, this is caused by either a message from the device arriving before its intended handler function has been registered, or the other causes described above.

An imp Won’t Reconnect After a WiFi Network Password Has Been Cleared

If you configure an imp-enabled device to connect to your password-protected WiFi network but subsequently downgrade the security of the network by removing the password, the on-board imp will no longer connect to the network. To re-connect, the device must be reconfigured with a blank password.

This is by design. It is intended to prevent your device (and other devices on the network) being ‘captured’ by a rogue WiFi access point masquerading as your network router. It can do this by transmitting the same SSID as your network but at a higher signal strength, but without the password, which the assailant does not know.