Deal With WiFi Loss And Planned Disconnections
Internet of Things devices based on imps are usually intended to remain connected to the Internet at all times, but this is not always possible — or even, for some applications, desirable.
Users’ broadband connections go down. WiFi routers crash. Businesses may have redundancy on these resources, but consumers rarely do. In these instances, the developer will need to decide how their imp-based product will respond to such circumstances: should it continue to operate, if only to inform the user that the connection is down, or sleep until it can connect once again?
In fact, the developer may even decide that it is advantageous if the device disconnects itself, perhaps in order to minimize its power consumption in order to preserve battery charge, or because it is relaying sensor data that doesn’t need to be updated continuously.
A new imp-enabled device will not be able to connect to the Internet until it has been activated using BlinkUp™. Yet a developer may want their product to be able to work as soon as the end-user first powers it up. Again, the imp will need to work offline, if only to prompt the user for WiFi credentials.
Additionally, there are cases where a developer may choose to use the imp as the microcontroller within a product for which Internet connectivity is optional. Here the imp will not only need to be able to operate without a connection, but also it may never connect to the Internet and simply run the firmware installed in the factory.
In each of these situations, the imp API provides the developer with tools to manage how the imp operates offline.
You specify how the imp reacts to outages by calling the imp API method server.setsendtimeoutpolicy(). This is always called implicitly with default values, but by including the call in your device code you can control how the imp deals with the loss of its Internet connection.
The imp’s default behavior upon an unintended disconnection is to attempt to reconnect immediately. It will try to do so for 60 seconds. If it fails to connect within that time, it suspends operation, disables its WiFi radio (if it has one) and goes into a low-power state: for example, the imp003 module will draw just 4µA. Under normal operation, it draws between 60mA and 80mA.
An imp remains in this ‘snooze’ state for nine minutes. At the end of this period, the imp will wake automatically and perform a warm start. It will begin running its Squirrel code and remain offline until that code makes an attempt to contact the server, such as server.log() or agent.send(). At this point, impOS™ powers up the WiFi radio (it if has one) and then spends 60 seconds trying to connect to the Electric Imp impCloud™. If it succeeds, Squirrel continues to run, and, for development devices, you will see the message
[Exit Code] imp restarted, reason: wifi outage in the log. If reconnection fails, impOS puts the device back into the snooze state.
Waking from snooze involves restarting your Squirrel code. Upon disconnection, the execution of your code is paused and will only be resumed if the imp reconnects within the initial 60-second period. If this doesn’t happen, the execution of your code stops. When the imp eventually reconnects, your program is restarted. State data and the contents of variables are not retained, though some data can be preserved in the non-volatile nv table if it has been set up by the Squirrel code.
How does impOS know it has lost contact with the impCloud? Firstly, it periodically pings the server. It also assumes that when an attempt to send data to the server fails (ie. the send operation has timed out) that it has lost network connectivity. What happens at this point is determined by the imp’s network error mode, which is set using the imp API method server.setsendtimeoutpolicy(). This call has three parameters, only the first of which is of interest here; for details of the others, please see the method’s documentation. The method’s first parameter, onError, takes one of three possible values, SUSPEND_ON_ERROR, RETURN_ON_ERROR or RETURN_ON_ERROR_NO_DISCONNECT, which specify the network error mode to apply.
SUSPEND_ON_ERROR is the default and specifies the mode of operation outlined above. RETURN_ON_ERROR and RETURN_ON_ERROR_NO_DISCONNECT both tell the imp to continue running as normal when its network connection is lost: Squirrel continues largely uninterrupted and the imp does not go to sleep. Unlike RETURN_ON_ERROR, RETURN_ON_ERROR_NO_DISCONNECT, which is available with impOS 38 and up, allows your application to better deal with certain data-transmission situations; we’ll explore this in a later section.
Which of these behaviors you choose will depend on your application. A device that simply relays data from sensors to its agent — and from there to a web page or a mobile app — will not need to continue processing and relaying sensor data if there is nowhere for that data to be sent; the device should suspend operation until it is once again able to contact its agent. Reconnection will take place automatically.
However, a device that isn’t reliant on an Internet link may need to continue operating if the network connection is lost. An imp-powered digital clock, for instance, needs to continue updating its display even if it is temporarily out of reach of the app that adjusts its settings or the web service from which it gets the weather outlook icon it displays.
At the end of the initial 60-second re-connection period, an imp set to RETURN_ON_ERROR or RETURN_ON_ERROR_NO_DISCONNECT will automatically call any function that has been registered using the API method server.onunexpecteddisconnect(). This callback function provides a way to prepare the imp for a period of operating without an Internet connection: perhaps to present a ‘disconnected’ icon on an attached LCD screen, for example. The nominated function must have a single parameter: in integer into which a code representing the reason for the disconnection, if known, is placed.
The two methods, server.setsendtimeoutpolicy() and server.onunexpecteddisconnect(), are typically called together, at the start of the device code. The default network error mode, SUSPEND_ON_ERROR, remains in force until it is changed using server.setsendtimeoutpolicy() — before then a disconnection event can’t be trapped using the function registered using server.onunexpecteddisconnect(). Your mode selection is not retained after a power-cycle or warm restart. These methods are demonstrated in the code snippet below:
RETURN_ON_ERROR_NO_DISCONNECT is a special case of RETURN_ON_ERROR and is intended to be used by applications which transmit large quantities of data. Unlike RETURN_ON_ERROR, under RETURN_ON_ERROR_NO_DISCONNECT impOS checks whenever agent.send() is called that the data to be sent will fit into the imp’s send buffer (which may already contain data to send). Adding more data that there is room in the send buffer will cause the Squirrel to block while the buffer empties sufficiently for the pending data to be added, and if the send operation does not take place within the timeout period, then impOS will assume that the imp has lost connectivity.
If RETURN_ON_ERROR_NO_DISCONNECT’s checks confirm that the amount of data you want to send would cause Squirrel to block, it informs your code that this is the case. It does so through agent.send()’s return value: look for the constant SEND_ERROR_WOULDBLOCK. In addition, it does not mark the imp as disconnected, though other, subsequent checks (see above) may indicate that it is.
This allows your code to decide whether to wait until the buffer is clear, to send it in chunks, or to extend the buffer size and then re-attempt the send.
It is strongly recommended that you conduct thorough testing of your device’s data-handling code to determine that you have optimal timeout and buffer size settings for your specific application.
Any imp set to RETURN_ON_ERROR or RETURN_ON_ERROR_NO_DISCONNECT will no longer automatically attempt to reconnect to the Internet periodically. Getting back in touch with the agent becomes the responsibility of the device code, which sooner or later must call the API method server.connect() to do so.
This method has two parameters: into the first you pass a function that will be called whatever the outcome of the attempt to connect; into the second you pass a float value specifying the length of time in seconds that the imp will persevere in that attempt. The connection attempt occurs asynchronously; it takes place alongside whatever other code the imp is running.
The callback function specified in server.connect() should have a single parameter of its own: a variable into which an integer code will be passed. The code will be SERVER_CONNECTED if the imp is back online, or one of a number of others values — for example, NO_WIFI, NO_LINK, NO_IP_ADDRESS, NO_SERVER, NOT_RESOLVED, NO_PROXY or NOT_AUTHORISED — if the attempt to connect failed in some way. These are the codes issued when an unexpected disconnection takes place, and for this reason, it is common practice to use the disconnection handler listed in the code above as the server.connect() callback, though this is not mandatory. This is shown below:
If the reason code indicates failure, the device software will typically act on this by scheduling another attempt to connect at some point in the future. In the code above, we simply set a flag, which the main program loop reads to see if it needs to begin counting down toward its reconnection attempt. A list of disconnection/connection failure reason codes can be found here.
If imps appear unresponsive when they are disconnected from the network, it is often because they haven’t been set to override the device’s default unexpected disconnection behavior, or have been set to do so too far into the Squirrel code — a common error. If this is unwanted behavior, it can easily be overridden by including a server.setsendtimeoutpolicy() call as soon as possible in the device code, with RETURN_ON_ERROR passed into the onError parameter, as the code above shows.
Some developers who do include server.setsendtimeoutpolicy() to set the policy they require nonetheless neglect to schedule a call to server.connect() in order to try and re-establish the agent connection. Failing to attempt to reconnect will leave the device operational, as confirmed by visual checks, but not able to be contacted by its agent.
Just as a suddenly disconnected imp can attempt to re-establish an Internet link by calling server.connect(), so too a connected imp may deliberately break its connection by calling another API method, server.disconnect(). This also has the effect of turning off the imp’s networking hardware. Whether the Squirrel application continues to run will depend, again, on the network error mode set using server.setsendtimeoutpolicy().
The default mode, SUSPEND_ON_ERROR, will cause the imp to enter the ten-minute snooze-reconnection cycle discussed above. If the imp is set to RETURN_ON_ERROR or RETURN_ON_ERROR_NO_DISCONNECT, however, it will continue to run its code. Because disconnection was deliberate, any callback function registered using server.onunexpecteddisconnect() will not be called.
The following code shows how this might be used:
Here, the device disconnects from the network as soon as the program starts but continues to operate in order to take data samples from connected sensors every two seconds. Every hour, it attempts to connect to its agent; if it is successful, it uploads the data to the agent. It then disconnects for another hour while it continues to take samples.
Important You must allow impOS an opportunity to perform any necessary housekeeping — listen for an impOS and/or Squirrel update, server redirect, or any application messages to the device — before disconnection takes place. The best — and strongly recommended — way to achieve this is to embed the call in a function that will executed when the imp next goes idle, which happens when there is no further Squirrel code in queue and impOS has performed all the tasks it needs to perform. Register this function using imp.onidle().
This is demonstrated in the code above. The onidle function registered in line 53 ensure the disconnection will take place once Squirrel execution comes to an end, ie. after line 60, which registers a function call to take place in two seconds' time, has been executed.
The scenarios considered above show how to keep an imp running despite an unexpected disconnection from the network and how to deliberately disconnect it. But what about devices which have not yet connected, and perhaps have not yet been configured to connect? All of the tools discussed so far apply in this case too, allowing the imp-enabled device to operate right out of the box.
The developer has extra tools to help in this situation. First, the API method imp.enableblinkup() can be used to disable the BlinkUp feature until the user has been prompted to begin the process of configuring the device to connect to the Internet. BlinkUp functionality is always enabled for the first 60 seconds after an imp has been powered on. However, including:
at the start of your code will ensure the imp can’t be configured by BlinkUp after that initial 60-second period has elapsed. You may choose to disable BlinkUp initially if, for instance, the device has its own screen and you plan to use this to issue prompts to the user. Later, the code can call:
to re-enable the BlinkUp facility. It’s unlikely you would know the credentials of the end-user’s WiFi network, though if you did, you could use the method imp.setwificonfiguration() to configure the device to talk to that network. This method has two parameters, both of which take strings: the first is the network’s SSID, the second its password (or an empty string,
"", if there is no password because it is an open network).
Out of the box, new imp-powered products should have no stored WiFi credentials — manufacturers should clear each unit’s saved credentials in the factory. The following code snippet shows how you might code up a device designed to boot (warm or cold) into an offline state:
Again, the code uses the server.setsendtimeoutpolicy() call to set the device to RETURN_ON_ERROR. This ensures the imp code continues to run after impOS detects that the device is not connected to a wireless network.
So far, we’ve covered situations in which the imp-enabled device is kept running and powered while it is disconnected from the network. It is also possible to disconnect and power down the device to a very low level, awaking only when necessary — perhaps to take a sensor reading, or to very briefly go online and transmit collated sensor data. This provides the best power conservation, though there are consequences that you need to be aware of when considering whether to implement this behavior in your device code.
The imp API provides the convenience method server.sleepfor() to put the device into deep sleep for a fixed duration of time:
If you wish to specify the time at which the imp will wake rather than the length of the sleep period, use server.sleepuntil() which take integers that specify the hour and the minute at which the imp will wake; seconds and day-of-the-week values may also be provided for greater precision:
At the specified time, the imp will automatically wake. Alternatively, it can be woken earlier by sending a trigger pulse on the imp’s wake-up pin — pin 1 (imp001 and imp002) or pin W (imp003, imp004m and impC001); the imp005 currently has no deep sleep capability — if this has been set using:
See the pin.configure() documentation for additional details.
Putting the imp into deep sleep clearly saves energy, but it has another consequence: the CPU stops running your device code. When the imp wakes, it performs a warm boot: impOS is started up, it reloads your code and runs it from the start. No state information, such as the contents of variables, will be retained unless you have explicitly saved them to persistent storage.
When the imp wakes, it will not connect to the Internet immediately. If you’ve called server.setsendtimeoutpolicy() with SUSPEND_ON_ERROR (or left it as the default), the first time your code does something that requires Internet access, such as a call to server.log() or agent.send(), the imp will suspend execution of Squirrel code and try to connect. If you’ve chosen to use the RETURN_ON_ERROR or RETURN_ON_ERROR_NO_DISCONNECT network error modes, however, the imp will not connect until specifically instructed to do so using server.connect().
Remember, when the device restarts and begins running its code, the default policy SUSPEND_ON_ERROR is in force until your code calls server.setsendtimeoutpolicy() specifying RETURN_ON_ERROR or RETURN_ON_ERROR_NO_DISCONNECT.
Please take a look at our network state diagram, which presents the program flow arising from various connected and disconnected scenarios.
Data can be preserved during deep sleeps, either by storing it in the imp’s own nv table (note that not all imps support this) or by sending it to the agent before the network connection is broken. The latter, which requires that data be packaged into a single Squirrel table of 64KB or less, has the advantage that the data is preserved even if the imp’s power is cut; data in the nv table does not persist when the imp loses power completely.
Finally, because deep sleep is triggered as soon as server.sleepfor() or server.sleepuntil() are called, it is important to allow impOS an opportunity to perform any necessary housekeeping before this happens. The best and recommended way to achieve this is to embed the calls listed above in a function that is executed when the imp goes idle, which happens when there is no further Squirrel code in queue and impOS has performed all the tasks it needs to perform. Register this function using imp.onidle() as the code above shows.